Android Paint why is width from getTextBounds for space 0 - android

I have some code I'm using to detect whether a given font supports a set of characters by rendering the character and checking its width. In the process I discovered width for a space character is 0. Can someone tell me why?
val pp = Paint()
val rr = Rect()
pp.getTextBounds(" ", 0, 1, rr)
"$rr"
> Rect(0, 0 - 0, 0)
The full code for the function is here.

Thanks to Mike in the comments. This is normal behaviour for getTextBounds which treats the space as a non-printed character.

Related

Can I generate 1:1 pixel data within an Android application?

I wish to check the Android display is showing the correct tone curve. If I generate a patch with alternate black and white lines surrounded by a 50% grey, and the two grey levels match then the first stop of the tone curve is right. I can then generate a patch with alternate black and 50% grey lines surrounded by a 25% grey, and so on.
I can find the current display size. I can make RGB raster data that fits. If needs be I can make the image with 2x2 pixel replication for things like retina displays. But I cannot see how to get the raster data to the display without risking resizing.
The particular image I have described might be generated using a matte texture, or some other trick. I have other vision test images that more complicated, and I currently generate as raster data in another program. So, I am really looking for something that can take a rectangle of custom RGB data and stick it onto the screen.
Maybe the tool is there in Android Studio, staring me in the face. But I can't see it.
(the following day)
I have found the Bitmap class. This is probably what I wanted.
(several days later)
No. I am still not there. I can generate an ImageView. I would like to generate a region of bitmap data with alternate black and white lines.
My experiments are in Kotlin.
I have found the problems in getting the dimensions of an ImageView in pixels. The layout effectively works the other way: you say define your layout, and the library works out the dimensions and the resize parameters for you. resize and the size cannot usually be calculated until the view has been laid out. See for instance...
Why does setting view dimensions asynchronously not work in Kotlin?
There is a Java solution that uses viewTreeObserver(). I can use this to get the dimensions in pixels in Toast, but I can't get the value out of the observer and into the context.
The exact pixel size is not the issue here. I could make the ImageView 50% of the display height and width, and calculate the number of pixels as a fraction of the screen dimensions. This would be accurate enough for the general layout of the grey border and the block of stripes. I could use the to lay out the canvas, and then force the view to fit that at 1:1 scale.
It feels that these tools are not supposed to work this way. Is there some completely different way of doing this that I am missing?
Postscript:
There was a way to do this...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityToneBinding.inflate(layoutInflater)
setContentView(binding.root)
stripeColour = lastColour
step = resources.displayMetrics.density
// Initialize the ImageView
stripeView = findViewById(R.id.stripeView)
stripeView.doOnLayout {
val myBitmap = Bitmap.createBitmap(
it.measuredWidth,
it.measuredHeight,
Bitmap.Config.ARGB_8888
)
val myCanvas = Canvas(myBitmap)
val xMax = it.measuredWidth.toFloat()
val yMax = it.measuredHeight.toFloat()
val myPaint = Paint()
myPaint.isAntiAlias = false
myPaint.color = Color.rgb(0, 0, 0)
var y = 0.0f
while (y < yMax) {
myCanvas.drawRect(0.0f, y, xMax, y+step, myPaint)
y += 2.0f*step
}
stripeView.setImageBitmap(myBitmap)
}
The black stripes are opaque, and I could update the other stripes by changing the background. The stripes all had nice sharp edges, without the interpolation I had been getting at the edges. This is a rather specific solution for my particular problem, but it seemed to work fine.
If you want to try it yourself, be warned: I am now seeing something I do not understand. It seems the light and dark stripes are on average brighter then they should be, particularly with dark colours. The tone curve for the display seems to fit the sRGB standard well when I measure large patches, but the sum of the light and dark stripes isn't what it should be. So, this is not the test for the tone curve I was hoping for.
Once I found the terms 'Bitmap' and 'Canvas' I found the right sort of tools, but it was hard to see how to string them together to make something that works. Here is a simple exercise that helped me...
https://developer.android.com/codelabs/advanced-android-kotlin-training-canvas#10
Ignore the bit that sets SYSTEM_UI_FLAG_FULLSCREEN. This is no longer supported. There are other ways to do the same thing, but this example works perfectly well without it.
If you want to render something at pixel resolution, do the rendering in a doOnLayout() call. You can then get the view height and width. The ImageView layout width and height is 0dp (match_constraints). I am still not quite there: my double height stripe pattern is still being interpolated.
class MainActivity : Activity() {
// Here are all the objects(instances)
// of classes that we need to do some drawing
lateinit var myImageView: ImageView
var background = Color.rgb(180, 180, 180)
var darkStripe = Color.rgb(0, 0, 0)
var lightStripe = Color.rgb(255, 255, 255)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize the ImageView
myImageView = findViewById(R.id.imageView)
myImageView.doOnLayout {
val myBitmap = Bitmap.createBitmap(
it.measuredWidth,
it.measuredHeight,
Bitmap.Config.ARGB_8888
)
val w = (it.measuredWidth/4).toFloat()
val h = (it.measuredHeight/4).toFloat()
val myCanvas = Canvas(myBitmap)
myCanvas.drawColor(background)
// Toast.makeText(applicationContext, w.toString(), Toast.LENGTH_SHORT).show()
val tex = createBitmap(
intArrayOf(lightStripe, lightStripe, darkStripe, darkStripe),
0, 1, 1, 4, Bitmap.Config.ARGB_8888
)
val shader: Shader = BitmapShader(tex, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
val myPaint = Paint()
myPaint.setAntiAlias(false)
myPaint.shader = shader
myCanvas.drawRect(w, h,w*3.0f,h*3.0f, myPaint)
myImageView.setImageBitmap(myBitmap)
}
}
}
Here's a revised version that gives my sharp stripes without the hardcoded 3.0 value...
step = resources.displayMetrics.density
// Initialize the ImageView
stripeView = findViewById(R.id.stripeView)
stripeView.doOnLayout {
val myBitmap = Bitmap.createBitmap(
it.measuredWidth,
it.measuredHeight,
Bitmap.Config.ARGB_8888
)
val myCanvas = Canvas(myBitmap)
val xMax = it.measuredWidth.toFloat()
val yMax = it.measuredHeight.toFloat()
val myPaint = Paint()
myPaint.isAntiAlias = false
myPaint.color = Color.rgb(0, 0, 0)
var y = 0.0f
while (y < yMax) {
myCanvas.drawRect(0.0f, y, xMax, y+step, myPaint)
y += 2.0f*step
}
stripeView.setImageBitmap(myBitmap)
}
I am drawing the black stripes as opaque rectangles. The background colour gives the other stripes. This means I can update the stripe colour without having to redraw.

OpenCV Android Green Color Detection

currently I'm making an app where user will detect green colors. I use this photo for testing:
My problem is that I can not detect any green pixel. Before I worked with blue color and everything worked fine. Now I can't detect anything though I tried different combinations of RGB. I wanted to know whether it's problem with green or my detection range, so I made an image in paint using (0, 255, 0) and it worked. Why it can't see this circle then? I use this code for detection:
Core.inRange(hsv_image, new Scalar([I change this value]), new Scalar(60, 255, 255), ultimate_blue);
It could have been that I set wrong Range, but I use Photoshop to get color of one of green pixels and convert RGB value of it into HSV. Yet it doesn't work. It don't detect even pixel that I've sampled. What's wrong? Thanks in advance.
Using Miki's answer:
Green color is HSV space has H = 120 and it's in range [0, 360].
OpenCV halves the H values to fit the range [0,255], so H value instead of being in range [0, 360], is in range [0, 180].
S and V are still in range [0, 255].
As a consequence, the value of H for green is 60 = 120 / 2.
You upper and lower bound should be:
// sensitivity is a int, typically set to 15 - 20
[60 - sensitivity, 100, 100]
[60 + sensitivity, 255, 255]
UPDATE
Since your image is quite dark, you need to use a lower bound for V. With these values:
sensitivity = 15;
[60 - sensitivity, 100, 50] // lower bound
[60 + sensitivity, 255, 255] // upper bound
the resulting mask would be like:
You can refer to this answer for the details.

Custom shaped textview, Android

I'm looking for a way of making custom shaped text input control on Android, like showed on screenshot.
What is need is to have some predefined places(gray rects) that will not be covered by text. So when user typing - text will lays out with some offsets from left side, or right side, depending on place of gray rect.
It is easy to do on iOS, just a couple lines of code and that is all. But I can not find a way of doing this on Android. Note that gray rects may not be a part of text input component. On iOS I simply put two UIImageView over the UITextView, and set up rects for excluding from rendering text:
CGFloat margin = 8;
CGRect firstPathRect = CGRectMake(0, 80, 160 + margin, 90 + margin);
CGRect secondPathRect = CGRectMake([UIScreen mainScreen].bounds.size.width - 160 - 2 * margin, 280, 160, 90 + margin);
UIBezierPath *path1 = [UIBezierPath bezierPathWithRect:firstPathRect];
UIBezierPath *path2 = [UIBezierPath bezierPathWithRect:secondPathRect];
self.textView.textContainer.exclusionPaths = #[path1, path2];
I hope somebody can help me with this task. Thanks in advance!
Check out this library for flowing text:
https://github.com/deano2390/FlowTextView

Android: Measure Text Height on a Canvas

I am currently working on rendering a Bitmap, that I then want to send to a mobile printer. However, I am struggling with measuring the height of my text, so I can advance the y position appropriately.
My basic bitmap/canvas/paint configuration is this (Font Size is 16 and the dimensions of the bitmap are 200x400 (width x height):
public MyRenderer() {
// Initialize bitmap
bitmap = Bitmap.createBitmap(200, 400, Bitmap.Config.ARGB_8888);
// Initialize canvas
canvas = new Canvas(bitmap);
// Initialize brush (Paint instance)
brush = new Paint();
brush.setTextSize(16);
brush.setTypeface(Typeface.SANS_SERIF);
brush.setColor(Color.BLACK);
brush.setStyle(Paint.Style.FILL);
brush.setAntiAlias(true);
brush.setTextAlign(Align.LEFT);
}
So far so good, now what I want to do is: If I use the Paint's method drawText I need to supply the x and y coordinates. As for x that's zero (assuming left aligned text) but as for y, I'd have to calculate the height of each text I print and add it up, so I can keep track of my current y position.
And this is where it gets odd: I am using the following method to determine the height of a text (using the Paint objected that I initialized previously - it's called "brush"):
public int measureHeight(String text) {
Rect result = new Rect();
// Measure the text rectangle to get the height
brush.getTextBounds(text, 0, text.length(), result);
return result.height();
}
The above method returns the following values for the following texts:
"Hello World" returns a height of 12
"A camera instance can be used to compute 3D transformations and generate a matrix." returns a height of 16
"Introducing Android Design: The place to learn about principles, building blocks, and patterns for creating world-class Android user interfaces. Whether you're a UI professional or a developer playing that role, these docs show you how to make good design decisions, big and small." returns a height of 16
It makes sense to me, that number 2 and 3 return a greater height than number 1 but if one line has a height of 12 (as number one does) - it makes no sense, that multiple lines have a height of 16 ?
Am I missing something here? There is a convenience method for measuring the width of a text (using an instance of paint and call measureText("myText") which works perfectly, however I am quite at a loss, when it comes to the height, as the above given results don't make any sense to me.
EDIT
I am aware, that getTextBounds probably does no auto-wrapping of multi-lined text, and that's ok, I already wrote a method for splitting text, but even if it just measures one line, the above given length values still seem unlikely.
I think it is because the "p" in "compute" extends below the baseline whereas "Hello World" only contains letters that are above the baseline.
Since the line distance should not depend on what specific letters your text happens to consist of you are probably looking for Paint.FontMetrics which can be obtained via Paint.getFontMetrics(). Compute descent - ascent + leading to get the recommended baseline distance (because ascent has a negative value).
There is a small error in the accepted answer. If you want the text height, you should use
Paint.FontMetrics fm = mTextPaint.getFontMetrics();
float textHeight = fm.descent - fm.ascent;
And if you want the line height, you should use
float lineHeight = fm.bottom - fm.top + fm.leading;
Leading is optional interline spacing, so if you need to get the line hight you can include it. But if you just want the text height, then you can leave it off.
Note
I've never actually seen leading be anything else than 0, and as far as I can tell it even seems to be ignored in the TextView source code (and its associated Layout, StaticLayout, etc.). Please correct me if I'm wrong. So it is probably safe it leave it out of line hight calulations, but I'm not completely sure about that.
See also
Getting text height from getTextBounds vs FontMetrics vs StaticLayout
Meaning of top, ascent, baseline, descent, bottom, and leading in Android's FontMetrics

Draw text fitting in some specified Rectangle

Using a canvas, I want to draw some short label text (1-2 characters) which fits into some specified rectangle.
For some other reasons, the scaling I use is such that the dimensions of this recangle are small, i.e. about 1.
The problem I'm facing is to calculate the optimal (as large as possible so that the text still fits) text size to use with Paint.setTextSize prior to drawing the text (which I do using Canva.drawText()).
For that I can either use the Paint.Fontmetrics object to get some general font dimensions as floats or getTextBounds(String text, int start, int end, Rect bounds) to get the bounding box of the text as an integer rectangle. Due to the scaling I use, the integer bounding box from the latter is to imprecise to calculate the optimal text size for my purpose.
What I would need is some method to get the bounding box of the text with higher precision (i.e. like getStringBounds(String str, Graphics context) in java.awt.FontMetrics), but I found no suitable method.
I now was also busy with this problem.
Unfortunately I didn't manage to install the native source yet, but I'm pretty sure that text measurements (both getTextBounds() and measureText()) differ heftily from the real text output, especially for small font sizes (as to be seen in Moritz' screenshot).
I suppose that the measurement methods use a float width for a char, and the real text output uses an int (presumably due to performance). This will of course fail, if you need an absolutely exact size (e.g for autosize).
I experimented a bit with a monospace font. This Image shows some of these experiments:
.
The text scale in these experiments was set by auto scaling it.
In the top row, I created boxes with an integer increment. These match the android text output. You see, the last cipher doesn't match the screen!
In the 2nd row, the boxes were created with a float increment. The boxes match the screen better, but they don't match the android text output!
In the last row, the boxes were incremented by a float increment and the text was output char by char by the same increment. Both boxes and chars fit perfect.
In my conclusion, it is currently not possible to set an Android text output to an exact width. The problem seems not to be the measurement methods, which are exact enough. The problem seems to be, that you can not set an exact text width by setTextSize().
Try the following to reproduce this:
float width = 100; // define a width which should be achieved
m_textPaint.setTextSize( 100 ); // set a text size surely big enough
Rect r = new Rect();
String s = new String("This is a test text");
m_textPaint.getTextBounds( s, 0, s.length(), r ); // measure the text with a random size
float fac = width / r.width(); // compute the factor, which will scale the text to our target width
Log.i( "MyTestOutput", "current text width:" + r.width() );
Log.i( "MyTestOutput", "wanted text width:" + width );
Log.i( "MyTestOutput", "factor:" + fac );
Log.i( "MyTestOutput", "expected result:" + (float) r.width() * fac );
m_textPaint.setTextSize( m_textPaint.getTextSize() * fac );
m_textPaint.getTextBounds( s, 0, s.length(), r ); // now final measurement: whats the real width?
Log.i( "MyTestOutput", "real result:" + r.width() );
I get an output of:
05-26 12:18:18.420: I/MyTestOutput(23607): current text width:1125
05-26 12:18:18.425: I/MyTestOutput(23607): wanted text width:100.0
05-26 12:18:18.425: I/MyTestOutput(23607): factor:0.08888889
05-26 12:18:18.425: I/MyTestOutput(23607): expected result:100.0
05-26 12:18:18.430: I/MyTestOutput(23607): real result:94
So in my opinion, the only way to achieve very exactly autoscaled text, is to
a) autoscale it by measuring and setting text size
b) output it char by char by a self computed increment.
Unfortunately, this works only for monospaced fonts. For other fonts, it will be more complicated, but also possible.
I did that just some days ago: See my blog.
You would use a StaticLayout:
// bmp1 is a source bitmap (in that case 44x44px big).
// density is the screen density, you will need to retrieve it yourself as well.
canvas.drawBitmap(BitmapFactory.decodeResource(context.getResources(), R.drawable.overlay_44x44), new Matrix(), null);
// Initialize using a simple Paint object.
final TextPaint tp = new TextPaint(WHITE);
// Save the canvas state, it might be re-used later.
canvas.save();
// Create a padding to the left & top.
canvas.translate(4*density, 4*density);
// Clip the bitmap now.
canvas.clipRect(new Rect(0, 0, bmp1.getWidth(),(int) (bmp1.getHeight() - (6*density))));
// Basic StaticLayout with mostly default values
StaticLayout sl = new StaticLayout(message, tp, bmp1.getWidth(), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
sl.draw(canvas);
// Restore canvas.
canvas.restore();

Categories

Resources