Android bitmap coordinate wierdness - android

I am trying to develop a custom image display application for android. So far I am able to load a bitmap and display it on the screen. I want to center the users view on the center of the image. To do this, I have been using
Bitmap bmp = BitmapFactory.decodeResource(...)
float offsetX = (androidScreenHeight - bitmapFactoryOptions.outHeight) / 2
canvas.drawBitmap(bmp, offsetX, 0, myPaint)
to render it. androidScreenHeight is correct as far as I can tell. I am using a samsung note which has 1280x800 screen which is the value I am getting for that. My image is 1920 pixels wide, so the offset on each side should be 560, with 800 in the middle for the actual screen. See this picture:
!http://imgur.com/a2DGmjG
The value of offsetX is correct at 560. So I know at least that part is working correctly. But instead of the above, what I am getting is this:
!http://imgur.com/jtHw926
(these are not the actual image)
I am not sure what is going on. Are pixels treated differently somehow on my android device than on my computer? I understand that each pixel will take up a different size on each since the dpi is different. but an offset of 560 pixels should give the same offset on each screen, regardless of the size of the individual pixels. Any ideas what is going on here?

(Promoting my comment to proper answer)
BitmapFactory will perform scaling between that folder's dpi (assuming you have one) and the device dpi. Putting the bitmap in 'drawable-nodpi' will disable the autoscaling but be careful that you really want to do this (cos autoscaling is usually useful and desirable).

Related

strange behaviour of live wallpaper in preview [duplicate]

I am learning how to make live wallpapers, but I have a dilemma I'm sure all who start off have as well.
There is so many resolution screen sizes, how can I just make one set of artwork to be rescaled in code for all versions? I know it's been done as I seen the images in the apk's on a lot of them and they get rescaled.
If it was just one image that did not need any positioning that would be easy, but my problem is I have to get the background image rescaled to fit all devices, I also have animations that fit in a certain x and y position on that background image to fit in place so it looks like the whole background is being animated but only parts of it is (my way of staying away from 300 images of frame by frame live wallpapers).
So the background image needs to be rescaled and the animations need to be rescaled as well to the exact percentage as the background image and they need to sit in a specific x and y position.
Any help would be appreciated so I can get this going.
I tired a few things, figured I would make a scaler for everything example: int scaler; then in onSurfaceChanged scaler = width /1024; //if the bigger image is 1024. that will give me a ratio to work with everywhere. then scale accordingly using scaleBitmap by multiplying the scaler by the image height and width, and also use the same scaler for positioning example image x lets say is at 50, scale it using the same thing x = scaler * 50; that should take care of scaling and positioning, just how to translate all this into java is the next lesson, since I'm new to java, I used to program for flash and php but this is a lot different, take some getting used to. Next thing is how to pan the width, when you move your screen from side to side how to make the image show is the next puzzle I have figure out. Right now it just shows the same width no matter what even though the width is double what the surface shows. If you got an answer or somewhere I can find out the info on this one that would be greatly appreciated.
Well, um, all I can say is "Welcome to the real world." You get your screen dimensions passed to you via onSurfaceChanged, and yes, it is your job to figure out how to scale everything based on this data. That's why they pay us the big bucks. :-)
You will want to make sure your resources are large enough to fit the biggest display you intend to support, so you will always be shrinking things (which distorts much less than expanding things).
Suggest starting with "best practices for screen independence" here: http://developer.android.com/guide/practices/screens_support.html
Additional comments in re your request for more help...
You cannot (necessarily) scale your artwork just using the width, because you need to support multiple aspect ratios. If the screen proportions do not match your artwork, you must decide if you want to distort your artwork, leave blank spaces, etc.
I'm not sure how to interpret your trouble passing around the screen dimensions. Most of us put all of our active code within a single engine class, so our methods can share data via private variables. For example, in the Cube wallpaper in the SDK, onSurfaceChanged() sets mCenterX for later use in drawCube(). I suggest beginning with a similar, simple approach.
Handling scrolling takes some "intelligence" and a careful assessment of the data you receive via onOffsetsChanged(). xStep indicates how many screens your launcher supports. Normally xStep will be 0.25, indicating 5 screens (i.e. xOffset = 0, 0.25, 0.5, 0.75, or 1) but it can be any value from 0 to 1; 0.5 would indicate 3 screens. xPixels gives you an indication of how much the launcher "wants" you to shift your imagery based on the screen you're on; normally you should respect this. On my phone, the launcher "desires" a virtual wallpaper with twice the pixels of the physical screen, so each scroll is supposed to shift things only one quarter of one screen's pixels. All this, and more, is documented in http://developer.android.com/reference/android/app/WallpaperManager.html
This is not "easy" coding--apps are easier than wallpaper. :-)
Good luck...George
P.S. I'll throw in one more thing: somewhere along the line you might want to retrieve the "desired minimum width" of the wallpaper desired by the launcher, so you can explicitly understand the virtualization implicit in xPixels. For example, in my engine constructor, I have
mContext = getApplicationContext();
mWM = WallpaperManager.getInstance(mContext);
mDW = mWM.getDesiredMinimumWidth();
My device has 320 pixel width; I get mDW = 640; as I scroll from screen to screen, xPixels changes by 80 each time...because four scrolls (across five screens) is supposed to double the amount of revealed artwork (this effect is called "parallax scrolling"). The rightmost section has xPixels equals 0; the center (of five) sections has xPixels = -160, etc.
I've used this code snippet to scale one image to fit on different screen sizes.
Bitmap image1, pic1;
image1 = BitmapFactory.decodeResource(getResources(), R.drawable.image1);
float xScale = (float) canvas.getWidth() / image1.getWidth();
float yScale = (float) canvas.getHeight() / image1.getHeight();
float scale = Math.max(xScale, yScale); //selects the larger size to grow the images by
//scale = (float) (scale*1.1); //this allows for ensuring the image covers the whole screen.
scaledWidth = scale * image1.getWidth();
scaledHeight = scale * image1.getHeight();
pic1 = Bitmap.createScaledBitmap(image1, (int)scaledWidth, (int)scaledHeight, true);
Make sure that the edges don't contain vital information as it will be scaled out of the picture on some screen ratios.

Best way to resize and position bitmaps in a canvas so they look the same in different devices?

I am developing a game using a surface panel. I've done a lot of research about how to properly scale and position drawables in the canvas for multiple devices and I came up with a solution that is working fine on phones but has some flaws when I try it on tablets. I am aware that I can use different resources for tablets (and i might end up doing that) but let's assume for now that I don't want to do it, I want to use the same resources for every single different phone in the market.
All the resources that I have are located it in the hdpi folder, and they are properly sized for a 480x800 device.
My approach is similar to the one described here, please take a look on the explanation below, and I would like to know if there is a better solution for this problem!
I have a Galaxy S2 for testing my apps. So my first approach was to manually insert position everything directly in the canvas by trying and finding the best position for everything. Taking the first character position as an example:
draw_x = (float) (19);
draw_y = (float) (279);
canvas.drawBitmap(toDrawBitmap, draw_x, draw_y, null);
When I first tested it in different devices, everything as a mess, out of scale. So digging around I thought about using the density for scaling the resources.
// I am dividing by 1.5 because my initial positions are on a high density device
// so when it goes for a medium density it should scale for 0.66 and a small density
// for 0.5 of my positions.
float scale = getResources().getDisplayMetrics().density /1.5;
draw_x = (float) (19) * scale;
draw_y = (float) (279) * scale;
canvas.drawBitmap(toDrawBitmap, draw_x, draw_y, null);
And at first impression this worked like a charm. It all my characters were in the proper positions. But I noticed that if the device has a different scale widht/height compared to the Galaxy S2 that I am using the problems begin. Although everything was properly positioned part of the image was cut out of the screen, the canvas was calculated larger than the phone screen.
Galaxy S2 is 480x800. My background is also 480x800. When I tested it in the emulator on a small screen resolution 320x480 Android didn't scale my background correctly as I expected it to do so. Instead of scaling it for the right resolution it gave me a background larger than my canvas 320x533.
With some simple math we figure that 320x533 / 480x800 = 0.66. So instead of properly scaling the background in the canvas, it just scaled using the density of the devices.
So my workaround for this problem was the simplest I could think of. I know the resolution of my background, I know the resolution of the phone, so I can calculate the proportion I need and force a resize.
//Set the proportions for scaling in multiple devices
public void setProportions(float screenWidth,float ScreenHeight,Bitmap background){
this.heightProportion = ScreenHeight/background.getHeight();
this.widthProportion = screenWidth/background.getWidth();
}
public Bitmap scaleBitmaps(Bitmap bitmap) {
Bitmap out = Bitmap.createScaledBitmap(bitmap, (int) (bitmap.getWidth() * widthProportion),
(int) (bitmap.getHeight()*heightProportion), false);
return out;
}
That worked fine for the sizes of the drawables, so I just needed to do the same for the positions, using the scale and this new Proportion I was able to calculate using a fixed size background image
public float convertX(float x){
return x* scale * widthProportion;
}
public float convertY(float y){
return y* scale * heightProportion;
}
//calculate the positions applying the scale and the proportion
draw_x = convertX((float) (19));
draw_y = convertY((float) (279));
//draw the bitmap with the scaled position
canvas.drawBitmap(toDrawBitmap, draw_x, draw_y, null);
Long story short, to properly position the drawables I manually set the desired position in my device, calculated a scale between the densities and a porportion between the background image size and the screen size.
To re-size the drawables I just used the proportion because android automatically applies the density scale.
I tested in several different phones and tablets and this approach works perfectly for phones. On tablets it gives me some minor mistakes in the re-sizing of the drawables.
Finally after all this, my question is, what is the problem with this solution? Can I make it work on every phone regardless of the size or there is a better solution for this?
Please note that this strict to Canvas. The same background is re-sized correctly for every phone if I use it in the XML layout. If I wasn't clear or I should give more information please let me know!
The first thing you have to know before solve this problem is about device phone running system. Though you suggest the phone will choose either hdpi or other versions, it depends on each phone running system.
A. Size fitting problem
The problem is how do you process the bitmap. Though you re-scale the bitmap with any math formula, the size of original bitmap will have different output for each different phone. To solve this, you have to set inScaled of bitmap to false.
B. Position fitting problem
Thus you have the problem in fitting the size of bitmap, the position of bitmap will synchronize the position depends on your phone screen size. You should define the background object and positioning the object x and y based on the background. For example if you want to put an object in the middle of phone screen no matter what phone we use it, the code must be `
canvas.drawBitmap(toDrawBitmap, background.getwidth()/2, background.getheight()/2, null);
to solve the fitting position problem.
Let me know what happen.

Something going wrong with drawBitmap - with source and dest rectangles

I have a game in which, during one of the levels, sheep can get blown up by mines. The explosion animation is controlled by having a png containing a 4x4 array of explosion images within a 512x515 png file... see below.
I then animate the explosion using the following code:
exp_bitmap_number_to_draw = (int)(time_since_death / 100);
if (exp_bitmap_number_to_draw < 16)
{
explosion_dst_rect.left = b2sx(sheep_x[i]) - sheep_radius * 5;
explosion_dst_rect.right = b2sx(sheep_x[i]) + sheep_radius * 5;
explosion_dst_rect.bottom = b2sy(sheep_y[i]) + sheep_radius * 5;
explosion_dst_rect.top = b2sy(sheep_y[i]) - sheep_radius * 5;
explosion_src_rect.left = 128 * (exp_bitmap_number_to_draw % 4);
explosion_src_rect.top = 128 * (exp_bitmap_number_to_draw / 4);
explosion_src_rect.right = explosion_src_rect.left + 128;
explosion_src_rect.bottom = explosion_src_rect.top + 128;
canvas.drawBitmap(explosion_bitmap, explosion_src_rect, explosion_dst_rect, null);
}
Where explosion_src_rect is a Rect and explosion_dst_rect is a RectF. b2sx() and b2sy() are functions which convert from the absolute coordinates on the sheep on the "playing field" to the coordinates on the screen - its simply adding an offset.
The code works perfectly on several phones I've tried, including a nexus S and a Galaxy S II. But just now a friend tried the code on a samsung galaxy tab 8.9 and found the explosions appeared goofey. He sent me this partial screen grab:
Any idea what could be causing this?
If resources are not available in the correct density, the system loads the default resources and scales them up or down as needed to match the current screen's density.
There are some situations in which you might not want Android to pre-scale a resource. The easiest way to avoid pre-scaling is to put the resource in a resource directory with the nodpi configuration qualifier. For example:
res/drawable-nodpi/icon.png. So you have to put your bitmaps into `res/drawable-nodpi/ folder.
Have a look at: http://developer.android.com/guide/practices/screens_support.html
the width of your destination rectangle, instead of being the size of one of your explosion frames, as you said, is being set to the width of the canvas / 4. Phones are typically of similar size, but the larger tablet has a much larger screen size - As it says in the documentation:
Draw the specified bitmap, scaling/translating automatically to fill the destination rectangle [...]
This function ignores the density associated with the bitmap. This is because the source and destination rectangle coordinate spaces are in their respective densities, so must already have the appropriate scaling factor applied.
So your bitmap, not scaled to the canvas size, is getting drawn starting with the src Rect, but expands beyond the bounds of the src because it's been told to fill the DST, ignoring the densities of the two locations.
As to solution, I'm not sure, exactly - would probably be better to just have images of all sizes raring to go in your /res. Depending on how you load the bitmap, you might be able to get the bitmap to scale, but then your fixed width translation across the bitmap would have to become more dynamic.
Hope that helps somehow :)
I had the same problem which I asked on here and the answer for me was to load my bitmaps like this, rather than the normal way:
getResources().openRawResource(imageId)

android live wallpaper rescaling

I am learning how to make live wallpapers, but I have a dilemma I'm sure all who start off have as well.
There is so many resolution screen sizes, how can I just make one set of artwork to be rescaled in code for all versions? I know it's been done as I seen the images in the apk's on a lot of them and they get rescaled.
If it was just one image that did not need any positioning that would be easy, but my problem is I have to get the background image rescaled to fit all devices, I also have animations that fit in a certain x and y position on that background image to fit in place so it looks like the whole background is being animated but only parts of it is (my way of staying away from 300 images of frame by frame live wallpapers).
So the background image needs to be rescaled and the animations need to be rescaled as well to the exact percentage as the background image and they need to sit in a specific x and y position.
Any help would be appreciated so I can get this going.
I tired a few things, figured I would make a scaler for everything example: int scaler; then in onSurfaceChanged scaler = width /1024; //if the bigger image is 1024. that will give me a ratio to work with everywhere. then scale accordingly using scaleBitmap by multiplying the scaler by the image height and width, and also use the same scaler for positioning example image x lets say is at 50, scale it using the same thing x = scaler * 50; that should take care of scaling and positioning, just how to translate all this into java is the next lesson, since I'm new to java, I used to program for flash and php but this is a lot different, take some getting used to. Next thing is how to pan the width, when you move your screen from side to side how to make the image show is the next puzzle I have figure out. Right now it just shows the same width no matter what even though the width is double what the surface shows. If you got an answer or somewhere I can find out the info on this one that would be greatly appreciated.
Well, um, all I can say is "Welcome to the real world." You get your screen dimensions passed to you via onSurfaceChanged, and yes, it is your job to figure out how to scale everything based on this data. That's why they pay us the big bucks. :-)
You will want to make sure your resources are large enough to fit the biggest display you intend to support, so you will always be shrinking things (which distorts much less than expanding things).
Suggest starting with "best practices for screen independence" here: http://developer.android.com/guide/practices/screens_support.html
Additional comments in re your request for more help...
You cannot (necessarily) scale your artwork just using the width, because you need to support multiple aspect ratios. If the screen proportions do not match your artwork, you must decide if you want to distort your artwork, leave blank spaces, etc.
I'm not sure how to interpret your trouble passing around the screen dimensions. Most of us put all of our active code within a single engine class, so our methods can share data via private variables. For example, in the Cube wallpaper in the SDK, onSurfaceChanged() sets mCenterX for later use in drawCube(). I suggest beginning with a similar, simple approach.
Handling scrolling takes some "intelligence" and a careful assessment of the data you receive via onOffsetsChanged(). xStep indicates how many screens your launcher supports. Normally xStep will be 0.25, indicating 5 screens (i.e. xOffset = 0, 0.25, 0.5, 0.75, or 1) but it can be any value from 0 to 1; 0.5 would indicate 3 screens. xPixels gives you an indication of how much the launcher "wants" you to shift your imagery based on the screen you're on; normally you should respect this. On my phone, the launcher "desires" a virtual wallpaper with twice the pixels of the physical screen, so each scroll is supposed to shift things only one quarter of one screen's pixels. All this, and more, is documented in http://developer.android.com/reference/android/app/WallpaperManager.html
This is not "easy" coding--apps are easier than wallpaper. :-)
Good luck...George
P.S. I'll throw in one more thing: somewhere along the line you might want to retrieve the "desired minimum width" of the wallpaper desired by the launcher, so you can explicitly understand the virtualization implicit in xPixels. For example, in my engine constructor, I have
mContext = getApplicationContext();
mWM = WallpaperManager.getInstance(mContext);
mDW = mWM.getDesiredMinimumWidth();
My device has 320 pixel width; I get mDW = 640; as I scroll from screen to screen, xPixels changes by 80 each time...because four scrolls (across five screens) is supposed to double the amount of revealed artwork (this effect is called "parallax scrolling"). The rightmost section has xPixels equals 0; the center (of five) sections has xPixels = -160, etc.
I've used this code snippet to scale one image to fit on different screen sizes.
Bitmap image1, pic1;
image1 = BitmapFactory.decodeResource(getResources(), R.drawable.image1);
float xScale = (float) canvas.getWidth() / image1.getWidth();
float yScale = (float) canvas.getHeight() / image1.getHeight();
float scale = Math.max(xScale, yScale); //selects the larger size to grow the images by
//scale = (float) (scale*1.1); //this allows for ensuring the image covers the whole screen.
scaledWidth = scale * image1.getWidth();
scaledHeight = scale * image1.getHeight();
pic1 = Bitmap.createScaledBitmap(image1, (int)scaledWidth, (int)scaledHeight, true);
Make sure that the edges don't contain vital information as it will be scaled out of the picture on some screen ratios.

Android - determine specific locations (X,Y coordinates) on a Bitmap on different resolutions?

My app that I am trying to create is a board game. It will have one bitmap as the board and pieces that will move to different locations on the board. The general design of the board is square, has a certain number of columns and rows and has a border for looks. Think of a chess board or scrabble board.
Before using bitmaps, I first created the board and boarder by manually drawing it - drawLine & drawRect. I decided how many pixels in width the border would be based on the screen width and height passed in on "onSizeChanged". The remaining screen I divided by the number of columns or rows I needed.
For examples sake, let's say the screen dimensions are 102 x 102.
I may have chosen to set the border at 1 and set the number of rows & columns at 10. That would leave 100 x 100 left (reduced by two to account for the top & bottom border, as well as left/right border). Then with columns and rows set to 10, that would leave 10 pixels left for both height and width.
No matter what screen size is passed in, I store exactly how many pixels in width the boarder is and the height & width of each square on the board. I know exactly what location on the screen to move the pieces to based on a simple formula and I know exactly what cell a user touched to make a move.
Now how does that work with bitmaps? Meaning, if I create 3 different background bitmaps, once for each density, won't they still be resized to fit each devices screen resolution, because from what I read there were not just 3 screen resolutions, but 5 and now with tablets - even more. If I or Android scales the bitmaps up or down to fit the current devices screen size, how will I know how wide the border is scaled to and the dimensions of each square in order to figure out where to move a piece or calculate where a player touched. So far the examples I have looked at just show how to scale the overall bitmap and get the overall bitmaps width and height. But, I don't see how to tell how many pixels wide or tall each part of the board would be after it was scaled. When I draw each line and rectangle myself based in the screen dimensions from onSizeChanged, I always know these dimensions.
If anyone has any sample code or a URL to point me to that I can a read about this with bitmaps, I would appreciate it.
BTW, here is some sample code (very simplified) on how I know the dimensions of my game board (border and squares) no matter the screen size. Now I just need to know how to do this with the board as a bitmap that gets scaled to any screen size.
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
intScreenWidth = w;
intScreenHeight = h;
// Set Border width - my real code changes this value based on the dimensions of w
// and h that are passed in. In other words bigger screens get a slightly larger
// border.
intOuterBorder = 1;
/** Reserve part of the board for the boardgame and part for player controls & score
My real code forces this to be square, but this is good enough to get the point
across.
**/
floatBoardHeight = intScreenHeight / 4 * 3;
// My real code actually causes floatCellWidth and floatCellHeight to
// be equal (Square).
floatCellWidth = (intScreenWidth - intOuterBorder * 2 ) / intNumColumns;
floatCellHeight = (floatBoardHeight - intOuterBorder * 2) / intNumRows;
super.onSizeChanged(w, h, oldw, oldh);
}
I think I found the answer. I might not be able to find the exact width/height and location of each playable square within a single scaled bitmap, but by looking at the Snake example in the SDK, I see it doesn't create 1 bitmap for the entire board and scale it based on the screen dimensions - instead it creates a bitmap for each tile and then scales the tile based on the screen resolution and the number of tiles wanted on the screen - just like I do when I draw the board manually. With this method, I should be able find the exact pixel boundaries for all of the playable squares on the board. I just have to break the board into multiple bitmaps for each square. I probably will have to do a similar approach for the borders, so I can detect their width/height as well after scaling.
Now I will test it to verify, but I expect it to work based on what I saw in the Snake SDK example.
--Mike
I tested a way to do what I was asking and it seems to work. Here is what I did:
I created a 320 x 320 bitmap for a board. It was made up of a border and squares (like a chess board). The border was 10 pixels in width all the way around the board. The squares were 20 x 20 pixels.
I detected the width and height of the screen through onSizeChanged. On a 480 x 800 display, I would set the new width for the board to be 480 x 480 and use the following code to scale the whole thing:
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
floatBoardWidth = w;
floatBoardHeight = floatBoardWidth;
bitmapScaledBoard = Bitmap.createScaledBitmap(bitmapBoard, (int)floatBoardWidth, (int)floatBoardHeight, true);
super.onSizeChanged(w, h, oldw, oldh);
}
Now in order to detect how many pixels wide the border was scaled to and how many pixels in height & width the squares were scaled to, I first calculated how much the over all image was scaled. I knew the bitmap was 320 x 320, since I created it. I used the following formula to calculate how much the image was scaled:
floatBoardScale = floatScreenWidth / 320;
In the case of a 480 width screen, floatBoardScale equals: 1.5. Then to calculate what my border within the full bitmap was scaled to, I did:
floatBorderWidth = 10 * floatBoardScale;
10 was the original border width in my 320 x 320 bitmap. In my final code I won't hardcode values, I will use variables. Anyway, in the case of this formula, the new calculated border width should be: 15
When I multiplied the same scale factor to the board squares (that were 20 x 20 in the original bitmap) I got new values of 30 x 30. When I used those values in my formulas to calculate what square a person touched, it worked. I touched every corner of the squares and in the center and it always calculated the right location. Which is important, so no matter what the screen resolution, I know where the user wanted to move a piece and visually it shows up in the right location.
I hope this helps anyone who may have had the same question. Also, if anyone has a better method of accomplishing the same thing, please post it.
A couple things. First, start reading about how to support multiple screens. Pay close attention to learning about dips and how they work.
Next, watch this video (at least the first 15-20 minutes of it).
This subject isn't a cakewalk to grasp. I found it best to start playing around inside my code. I would suggest creating a surfaceview and start messing around with some bitmaps, different emulators (screen sizes and densities), and the different types of drawable folders.
Unfortunately, there is more to this topic than I think Google wants to admit, and while it's definitely do-able is isn't simple to get started on it for some types of applications.
Finally, you should consider boiling down your question to be more straight forward if you aren't looking for an abstract answer (like this one).
Good luck!

Categories

Resources