I just can't figure out how to draw on a bitmap (with the help of a canvas) so that the result won't be device density dependent.
Here's the code that makes the drawing:
ImageView iv = (ImageView)findViewById(R.id.container);
Bitmap result = BitmapFactory.decodeResource(getResources(), R.drawable.big_picture).copy(Bitmap.Config.ARGB_8888, true);
Canvas canvas = new Canvas(result);
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.small_picture), actualX, actualY, null);
iv.setImageBitmap(result);
I load a big_picture in the Bitmap result and i want to draw a small_picture on that but at a specified position. If i set actualX and actualY it's ok on a device with the same density. However devices with different densities "scale" the canvas.
The interesting part is that only the small_picture is got "scaled" and goes out of the screen but the pig_picture behind it just fits the screen well on any density screen.
Why is that? How can i draw the small_picture on the big_picture densitiy independently?
Thank you!
I've found out what's the case after debugging the Bitmap. Unless you're putting images to drawable-nodpi the image will be re-sized to match the nominal device density. (Which are 120, 160, 240, 320) If you "load" an image to a Bitmap and Canvas they will have one of these densities regardless of the original image's.
So one solution is to put the images to drawable-nodpi. After that they will behave the same on all densities.
Another solution is to multiply the coordinates based on the ratios of the densities above.
...or you can just make custom images and coordinates for all qualifiers.
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.
When I create a Canvas from a bitmap that is generated with the BitmapFactory.decodeResources from a drawable resource that is a png image file of 400 x 400 pixels, the Canvas height and width are not 400 but 600!
In the code below, the drawable resource wind_scale is a png file that I generated with PHP as a 400 x 400 image.
Bitmap workingBitmap=BitmapFactory.decodeResource(getResources(), R.drawable.wind_scale, options);
Bitmap mutableBitmap = workingBitmap.copy(Bitmap.Config.ARGB_8888, true);
Canvas mCanvas = new Canvas(mutableBitmap);
int mHeight = mCanvas.getHeight(); int mWidth = mCanvas.getWidth(); both 600!!!!
Is this behavior expected? If so, why is there a 50% increase?
I am not 100% sure of the problem here but it's similar to another SO question (Getting weird font size after setting it programmatically). The problem there was also a 50% difference in values, and it was due to a comparison of sp units to pixels on the OP's device with a screen density ratio of 1.5.
You might check into BitmapFactory.Options; there are several scale and density flags that may help. My best guess is that the image is being scaled for the device and this is causing a measurement mismatch.
Canvas it's not related to the bitmap size but to the device available screen size. If you try that code on different devices (or virtual devices) you will find different results.
Canvas and Drawables
I'm making a game that is Pixel-based in Android. I have several 8x8 sprites that need to be resized to fit on a 100x100 area. As a test to see if it would work, I tried to just make the image fill the entire canvas. It kind of worked, but it made the 8x8 sprite turn into a 12x12 sprite, making the pixels look really odd and distorted. Here's what I have so far:
Bitmap grass = BitmapFactory.decodeResource(getResources(), R.drawable.small);
Paint configuration = new Paint();
configuration.setDither(false);
configuration.setAntiAlias(false);
Matrix myMatrix = new Matrix();
myMatrix.postScale(canvas.getWidth() / grass.getWidth(), canvas.getWidth() / grass.getHeight());
Bitmap resizedBitmap = Bitmap.createBitmap(grass, 0, 0, grass.getWidth(), grass.getHeight(), myMatrix, false);
canvas.drawBitmap(resizedBitmap, 0, 0, null);
If you work on bitmaps then you simply can't. You'd minimize the distortion by scaling up by non-fractional factor so each pixed would be repeated same number of times (i.e. image 10x10 to 20x20 is scaled by factor of two, but 8x8 to 12x12 is 1,5 so no luck). The solution would be to have all assets in vector form (i.e. SVG) and then render them on run-time according to device density and other specs or prepare separate assets for various type of devices (which would blow application size up a bit)
I have a View-derived class where I draw a 790x500 (yes, this is odd but it has to stay this way) bitmap directly on the canvas in onDraw. This looks ok on my Nexus S, but when I run my app on a device with a smaller screen (e.g. Wildfire) I only see the upper left part of the bitmap. What am I missing here?
This is a game-like app where I draw a number of bitmaps on certain coordinates. I really don't want to have different pixel setups for LDPI, MDPI and HDPI (I have a good reason for this).
Q: How can I properly scale my bitmaps for any screen resolution? (Centering the game screen for large screens may be an option, but is not a must.) E.g. when I draw a 800x600 image on a 320x240 screen, the image is automatically stretched and when I output a pixel at 100x100, it becomes 40x40.
There are many forms of the drawbitmap() method. Choose the one that allows you to specify the source and destination rectangle. The source rectangle will be the size of your bitmap and the destination rectangle will be scaled to fit on the device display. Android will scale your bitmap when it draws it.
Rect rs = new Rect();
Rect rd = new Rect();
rs.left = rs.top = 0;
rs.right = 789;
rs.bottom = 499;
<calculate destination rectangle from device size>
canvas.drawBitmap(myBitmap, rs, rd, null);
You can also scale and translate (shift) the entire canvas
canvas.scale(float scaleX, float scaleY);
canvas.translate(float dx, float dy);
Canvas.scale() does the trick.
If I draw a bitmap on a view canvas using drawBitmap(), the images will be resampled so that 1 pixel in the image will be 1 dip on the screen. On a device I have with high pixel density, that means each image pixel is spread across 1.5 screen pixels, degrading the image. Handy in general, but in some cases I want to carefully select the images I want to draw, then draw them explicitly at their native size, so they won't degrade. How do I do this?
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inScaled = false;
Bitmap mBitmap = BitmapFactory.decodeResource(mResource, R.drawable.resource, opts);
alternatively you could store your resources inside a res/drawable-nodpi folder