I want to draw on canvas month's text vertical along screen height.
Paint init:
this.paint = new Paint();
this.paint.setAntiAlias(true);
this.paint.setDither(true);
this.paint.setSubpixelText(true);
this.paint.setColor(color_text_dark);
this.paint.setTextAlign(Align.RIGHT);
Drawing:
// Set the scale to the widest month
float scale = getHeight() / this.max_month_width;
String month_string = FULL_MONTH_NAME_FORMATTER.
format(active_month_calendar.getTime());
canvas.save();
canvas.translate(getWidth(), 0);
canvas.rotate(-90);
canvas.scale(scale, scale);
canvas.drawText(month_string, 0, 0, this.paint);
canvas.restore();
Result looks good on hdpi screen, but very ugly and pixelated on xhdpi one.
I did more test on various devices, and understood what result depends on Android version, not screen density and resolution.
Code works fine on 2.x platform, but doesn't work on 4.0.3+. Suppose, Android draw implementation was changed here.
Full code you can see here.
hdpi version 2.3.5 (also tested 2.2)
xhdpi version 4.2 (also tested 4.1, 4.0.3)
Trying different variations for paint antialias, subpixel text has no effect. How can I fix this issue?
The problem is that you're drawing text at one size and scaling the result up. Once you've determined how wide you want the text to be, you should use calls to Paint.measureText(), adjusting the size via Paint.setTextSize() accordingly. Once it measures correctly, then you do your call to Canvas.drawText().
An alternative would be to not measure the text at all and just immediately call:
paint.setTextSize(paint.getSize() * scale)
There's no guarantee the text will fit in this case, though.
None of your other transform calls should result in interpolation, so it should give you very sharp lines.
Edit
Here is a code sample and comparison screenshot:
canvas.save();
canvas.scale(10, 10);
canvas.drawText("Hello", 0, 10, mTextPaint);
canvas.restore();
float textSize = mTextPaint.getTextSize();
mTextPaint.setTextSize(textSize * 10);
canvas.drawText("Hello", 0, 300, mTextPaint);
mTextPaint.setTextSize(textSize);
I don't have enough reputation to comment on Krylez's excellent answer, but I'd like to reply to mcfly soft's comment/question about paths.
The idea is the same for paths as text. Instead of scaling and translating the canvas a path is drawn on, put the same scaling and translation into a matrix and pass that to Path.transform:
// instead of this:
canvas.scale(sX, sY);
canvas.translate(trX, trY);
canvas.drawPath(path);
// do this:
matrix.postScale(sX, sY);
matrix.postTranslate(trX, trY);
path.transform(matrix);
canvas.drawPath(path);
Related
I created some test assets for ldpi up to xxxhdpi and put them in there respective drawable folders. If I draw things at the corners of the screen say position
0,0 or 0+canvas.getWidth()-bitmap.getWidth()
or things like that they align properly across all device screens.
I'd like to be able to draw my bitmaps anywhere on screen and have them scale adjusted properly across all device sizes but I am just not getting it.
Here's a sample screen shot same code running on two emulators.
The drawing code is the same. I was able to get the Paint to scale the line properly by adding a scale. The bitmaps also scale relative to their screen sizes but how do I draw at 50units in code and have it be 50units on all devices?
init function
Paint p = new Paint();
p.setColor(Color.RED);
p.setStyle(Paint.Style.FILL_AND_STROKE);
float scale = context.getResources().getDisplayMetrics().density;
p.setStrokeWidth(40*scale);
Bitmap ball = BitmapFactory.decodeResource(context.getResources(), R.drawable.ball);
render function
canvas.drawLine(0, 0, 400, 400, p);
canvas.drawBitmap(ball, 50, 50, null);
canvas.drawBitmap(ball2, 150, 150, null);
from the above screenshot you can see two things wrong. The length of the line, it's 400px long. Now that's an just a number I chose but it goes way too far on the smaller phone.
You also see how they are really far apart on one device but literally on top of each other on the second device.
What can to get past this issue?
Looks like your images are not scaled properly in the drawable folders.
According to this post xxhdpi images must be 4 times bigger than ldpi images i.e. 400x400px (xxhdpi) and 100x100px (ldpi).
The line goes from (0,0) all the way to (400, 400). The ldpi screen is only 240x320px, so the bottom right corner coordinate would be (240, 320). What you see is perfectly normal.
My App have a feature that let's the user capture photo and add drawing on that photo.
All the photo's are re-sized to exactly 900 x 900.
To allow the user add drawing to the image. I keep a transparent image over the original image and do the drawing on the transparent image. Drawing is done using canvas.
But when drawing in a device that has 720 x 480 (height x width). If i create a 900 x 900 transparent image and draw a line from 0,0 to 900,900, canvas only draws line from 0,0 to 480,480.
Below is the respective portion of the code:
Preparing Canvas:
holder = getHolder();
if (holder.getSurface().isValid()) {
Canvas canvas = holder.lockCanvas();
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
/* original image is 900 x 900 */
overlayBitmap = Bitmap.createBitmap(originalImage.getWidth(), originalImage.getHeight(), originalImage.getConfig());
canvas.setBitmap(overlayBitmap);
}
Drawing Line:
canvas.drawLine(0, 0, 900, 900, paint);
I have no idea why i am having this issue. It is because of using canvas?? Is there any work around? Any help is highly appreciated :-)
After some more reading about canvas and also help from this post i was able to fix the issue.
The issue was in the canvas clip rectangle. It was (0,0,480,480) by default as device display was 720 x 480 i guess? So whatever was on the bitmap was always clipped down to 480 x 480.
Later i modified my code like this:
holder = getHolder();
if (holder.getSurface().isValid()) {
Canvas canvas = holder.lockCanvas();
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
/* original image is 900 x 900 */
overlayBitmap = Bitmap.createBitmap(originalImage.getWidth(), originalImage.getHeight(), originalImage.getConfig());
canvas.setBitmap(overlayBitmap);
/* set extended clip rectangle for the larger target bitmap */
Rect clipRect = canvas.getClipBounds();
clipRect.set(0, 0, image.getWidth(), image.getHeight());
canvas.clipRect(clipRect, Region.Op.REPLACE);
}
After replacing clip rectangle size with image size everything worked just fine.
Just because a photo is 900x900, that doesn't mean that it's exactly those many pixels when displayed on the device screen. Device screens can have very different sizes, and when an image (or any view for that matter) is told to expand its with to fit the screen, or measured in dp (device independent pixels), the actual number of device pixels will vary depending on the device screen.
Your code needs to be sensitive to these differences. When you do your drawing, don't assume the size of the Canvas of a view. Instead, ask the view how big it is, and perform all the drawing based on the actual measured size.
I've created a simple custom control in android and on it I place a background image. I'm having problems when the control is placed on a layout at different sizes (i.e. when it is stretched), specifically:
I wish to overlay a rectangle at a specific position and size, which I know the pixel position for the original image. How can I do this with my control. I suspect something like this is impossible given it's a 9-patch. Is my best bet to work out the percentage from the top/left on the original or is that pointless given some parts stretch and some don't?
In the custom control I set the image like this in the constructor:
setBackgroundResource(R.drawable.buttonbt);
Which is working just fine, however I wanted to originally draw it in the onDraw event as I might want to change it depending on property changes, e.g.
Bitmap b=BitmapFactory.decodeResource(getResources(), R.drawable.buttonbt);
canvas.drawBitmap(b, 0, 0, null);
But this does not resize according to the size of its bounding box, it is simply trying to show it at it's original size without scaling to fit. How would you do this (whether the former method is better or not).
thanks.
You can create a scaled bitmap as below
Bitmap bitmap = Bitmap.createScaledBitmap(b, width, height, true);
Hope it will work for you. Please let me know!
ok when your View is say 100x100 px and your Bitmap is 300x300 you can try the following (pseudo code here) in inDraw method:
# src is a Bitmap 300x300
# dst is a View 100x100
mMatrix.setRectToRect(RectF src, RectF dst, Matrix.ScaleToFit stf)
canvas.save()
canvas.concat(mMatrix)
canvas.drawBitmap(mBitmap, 0, 0, null)
// actually it will draw a rect with left/top edge at (10, 10) and right/bottom at (20, 20)
canvas.drawRect(30, 30, 60, 60, paint)
canvas.restore()
I am trying to make an app using canvas and a surfaceview, and I am worrying that in the future I would have many problems with it because I am not sure if the canvas is proportional with every device. currently I can only use my emulator since my phone's usb cable doesn't work(I know.. I have to get a new one..).
anyways, i would like to know if the canvas would transfer my coordinates and make everything proportional, what I mean by that is that if i have something in point a, lets say (10, 10) on a device that the screen of it is 100 X 100 (this is just an example for easy calculation) it would be on point (1, 1) on a 10 X 10 device.
This is really bothering me...
Thanks!
No, this wouldn't be the case. If you have a coordinate (10,10), it would be the same on all devices. I'd suggest you scale your drawings.
To scale your drawings you simply define a bitmap (that will stay the same) you'd like to draw to (when screen sizes change, that bitmap will be stretched).
Define a constant bitmap:
Bitmap gameScreen = Bitmap.createBitmap(getGameScreenWidth(),
getGameScreenHeight(), Config.RGB_565);
Get the scale for both x and y
width = game.getWindowManager().getDefaultDisplay().getWidth();
height = game.getWindowManager().getDefaultDisplay().getHeight();
scaleXFromVirtualToReal = (float) width/this.gameScreenWidth;
scaleYFromVirtualToreal = (float) height/this.gameScreenHeight;
Define a canvas object based on the bitmap you defined earlier on (allowing you to draw to it eg. canvas.drawRect() [...]):
Canvas canvasGameScreen = new Canvas(gameScreen);
In your rendering Thread you'll have to have a Canvas called frameBuffer, which will render the virtual framebuffer:
frameBuffer.drawBitmap(this.gameScreen, null, new Rect(0, 0, width,
height), null);
No, the unit on the screen (whether you are using canvas or OpenGL) is a pixel. You can get the size of your canvas using Canvas.getWidth() and Canvas.getHeight() if you need relative coordinates, but your Canvas drawing methods are also in Pixels, so I guess you will need to convert coordinates in OpenGL only and not while using Canvas.
For an experiment I want to draw pixelated text on a canvas.
This is what I have so far:
Paint text = new Paint();
text.setAntiAlias(false);
text.setFilterBitmap(false);
text.setDither(false);
text.setFakeBoldText(false);
text.setLinearText(false);
text.setTextSize(10);
// Scale the canvas we draw on
matrix = c.getMatrix();
matrix.reset();
matrix.postTranslate(0, 0);
matrix.postScale(10,10);
c.setMatrix(matrix);
c.drawText("ABCabc", 0, 10, text);
This achieves the size I want, but the text is antialiased and dithered, and this is not what I want.
The closest I've gotten is by doing this:
text.setTextScaleX(5);
which scales the text the way I want horizontally, but unfortunately there is no text.setTextScaleY-functuion...
Any ideas?
Can you use text.setTextSize(n) and scale up the font instead?
I know that this is a really old question but here's a simple solution if anyone's still looking for one:
textView.setTextScaleX(scaleX);
textView.setScaleY(scaleY);
So by letting scaleX == scaleY, the font will be scaled uniformly as expected.