How to scale bitmaps on View.Canvas in Android? - android

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.

Related

android drawing to canvas and setting dpi

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.

How to convert coordinates on Bitmap to real coordiates on Image View displayed on screen

I have an Image View which displays an image (e.g 2000x1000 pixels) and I have a coordinate (X,Y) on that image (not the image view). The canvas of my Image View is 600x800 for example. How can I convert the point (X,Y) to screen coordinate so that I can draw a path with them on the OnDraw(...) method of Image View. Any help is appreciated! Thank you.
Update: If I use matrix to draw the path between coordinates, it works but the path and objects i draw become really small. Here is the code i used.
final Matrix matrix = canvas.getMatrix();
matrix.preConcat( _view.getImageMatrix() );
matrix.preScale( 1.0f /_inSampleSize, 1.0f / _inSampleSize);
canvas.setMatrix( matrix );
//I draw the path here
Update: I add a picture to show the effect when using matrix to draw the path. I would like to have the 4 line and the 4 corner balls to be in normal size. The red color is the boundary of the Image View which holds the picture.
I think that might depend on how exactly you are displaying your image. Your ImageView (600x800) is not the same aspect ratio as your bitmap (2000x1000).
You are keeping the bitmap's aspect ratio stable as you scale it down? If so, which part (height or width) takes up the full screen and which has black (or whatever else) as padding? This will help you determine your scale factor.
scale_factor = goal_height/height1; //if height is what you are scaling by
scale_factor = goal_width/width1; //if width is what you are scaling by.
I would try:
x_goal = x1 * scale_factor;
y_goal = y1 * scale_factor;
That is, if you have a point (1333, 900) in your image, and your image takes up the full width, you would multiply both x and y by 600/2000 to get (399.9, 270). (you might want to round that decimal).
If you are NOT keeping the bitmaps aspect ratio stable (that is, you're squeezing it to fit), then you'd have a height_scale_factor and a width_scale factor. So you'd take (1333,900) and multiply x by 600/2000 and y by 800/1000 to get (399.9,720).

How do I scale a 8x8 pixel art Bitmap to be 100 pixels width and height without any distortion in Android?

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)

is canvas in android proportional on different devices

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.

bitmap + canvas density dependency

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.

Categories

Resources