Android - Adjusting Image View Position on Top of a Zoomable Image View - java

I'm working on image, making a feature which allows user to place tags on images (like Facebook photo tag). I have a zoomable ImageView (using Mike Ortiz's TouchImageView) as a background image, and some ImageViews as image tags.
Each tag has X and Y position, and there is no problem in displaying both background image and image tags in correct position. However, when user triggers pinch zoom, the position of background image is changed. Which means, the position of each image tags must also be changed/updated according to the current background image's zoom level and scroll position.
I'm currently using this method:
float currentZoom = mImageView.getCurrentZoom();
float zoomedX = x / currentZoom;
float zoomedY = y / currentZoom;
But when I tried, it went totally wrong. Can anyone help me finding correct method/equation?
EDIT:
I tried using Matrix.mapPoints(), but I don't understand how this matrix could help me solving my problem. Here is an image for better explanation:

I finally get the answer (thanks to pskink)! To get the exact tag position on image, all we have to do is to use Matrix.mapPoints(). But that alone will not give the new X & Y position after zoom. We have to multiply the current X & Y position with the current zoom level to get the "after zoom position".
In other words:
Matrix matrix = mImageView.getImageMatrix();
float[] pts = {0, 0};
matrix.mapPoints(pts);
float newTagX = (tagX * currentZoom) + pts[0];
float newTagY = (tagY * currentZoom) + pts[1];

Related

Android Button different position on other devices (using setX)

I want to move several buttons, rearranging them in a different position than the original one at a specific moment, while playing the app. I used the setX() method for this purpose, to move the button to the place I want. I know that this method takes pixels as imput (and pixels depend on the density of the device), so I took the density of the device and multiplied it by a certain number (the position in density pixels), so the output is that position in pixels for each device. I thought that would give me the same button position for all devices, but it doesn´t work. The buttons appear displaced on different devices. This is the method I used to convert density pixels to the corresponding pixels for each device:
public void Converter_Dp_to_Px(){
pxX = (int) (dpX * Resources.getSystem().getDisplayMetrics().density); //Pixels in X direction
pxY = (int) (dpY * Resources.getSystem().getDisplayMetrics().density); //Pixels in Y direction
}
Now I set values for dpX and dpY, convert them into pixels for each device, and place the button in that position with setX() and setY() methods:
dpX = 254;
Converter_Dp_to_Px();
dpY = 477;
Converter_Dp_to_Px();
button1.setX(pxX);
button1.setY(pxY);
I also tried not with absolute position, but with one with percentages, as follows:
int maxX = Resources.getSystem().getDisplayMetrics().heightPixels;
int maxY = Resources.getSystem().getDisplayMetrics().widthPixels;
mov_percenX = 0.37f;
mov_percenY = 0.63f;
button1.setX(button1.getX() + maxX * mov_percenX);
button1.setY(button1.getY() + maxY * mov_percenY);
But it doesn´t work anyway. I hope you can help me, thanks in advance.

make x y work the same on all devices android studio

I just began to develop a app with java, and I only got some experience in C. In my code in Activity.java (in android studio) I got things like, just to give some examples:
meteorite1.setX(meteoritePlacementX(meteorite1.getX()));
meteorite1.setY(-2000);
gnome.setX(330);
gnome.setY(800);
meteorite2.setX(meteoritePlacementX(meteorite2.getX()));
meteorite2.setY(meteoritePlacementY(meteorite1.getY()));
meteorite3.setX(meteoritePlacementX(meteorite3.getX()));
meteorite3.setY(meteoritePlacementY(meteorite2.getY()));
meteorite4.setX(meteoritePlacementX(meteorite4.getX()));
meteorite4.setY(meteoritePlacementY(meteorite3.getY()));
meteorite5.setX(meteoritePlacementX(meteorite5.getX()));
meteorite5.setY(meteoritePlacementY(meteorite4.getY()));
meteoritedestruction1.setX(0);
meteoritedestruction1.setY(-2000);
meteoritedestruction2.setX(0);
meteoritedestruction2.setY(-2000);
meteoritedestruction3.setX(0);
meteoritedestruction3.setY(-2000);
meteoritedestruction4.setX(0);
meteoritedestruction4.setY(-2000);
meteoritedestruction5.setX(0);
meteoritedestruction5.setY(-2000);
star1.setX(300);
star2.setX(150);
star3.setX(50);
star4.setX(500);
star5.setX(600);
star6.setX(350);
star7.setX(80);
star8.setX(450);
tinystar1.setX(302);
tinystar2.setX(240);
tinystar3.setX(57);
tinystar4.setX(660);
tinystar5.setX(400);
star1.setY(300);
star2.setY(-300);
star3.setY(-100);
star4.setY(100);
star5.setY(300);
star6.setY(500);
star7.setY(700);
star8.setY(900);
tinystar1.setY(300);
tinystar2.setY(-400);
tinystar3.setY(-200);
tinystar4.setY(150);
tinystar5.setY(30);
and
public float meteoritePlacementX(float X){
float MeteoriteNewX = 0f;
int random = (int )(Math.random() * 480 - 50);
MeteoriteNewX = random;
return MeteoriteNewX;
}
Which workes fine, but just on my phone (720 x 1280 pixels (~294 ppi pixel density)) which I tested my code at. Now I published my app, but on other device, the layout of the app is totally out of sync (which makes sense to me now, cause x and y are different for every screen). Buttons and pictures workes fine, but moving object like
meteorite1.setY(meteorite1.getY() + 20);
where I use x and y are broken on other devices. I use the relative layout.
So long story short; Is there a way to change x and y, so it becomes relative to the screen? Otherwise I need to change the whole code.
In general using placement based on hard coded pixel values is not a good practice. Not only would this break with backwards compatibility but also think about what you would have to do when 2k+ phones come out, you would need an entire refactor. Look at this question and the answer by Guillaume Perrot you can get get the maximum and minimum pixel values relative to the user's phone and use those instead of the 480 - 50 and your star set functions.
For the movement do
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE); // the results will be higher than using the activity context object or the getWindowManager() shortcut
wm.getDefaultDisplay().getMetrics(displayMetrics);
int maxWidth = displayMetrics.widthPixels;
//Make this percentage whatever you want
float movementPercentage = 0.02
//Will move the object 2 percent up the y axis
meteorite1.setY(meteorite1.getY() + maxWidth*movementPercentage);

Get screen position of Image (or actor) in Libgdx Stage

I have this piece of code:
Image myImage = new Image(new Texture("data/file.png"));
How to get myImage position on a screen? I tried myImage.getX() and myImage.getImageX() both always return 0.0. What's wrong?
I believe getX() and getY() on an Actor are relative to their parent container, so you'll need to convert the coordinates to "stage" coordinates, and then from there to "screen" coordinates. (I think there is an easier way to do this, so there may be a better answer out there).
Image myImage = ...;
Vector2 coords = new Vector2(myImage.getX(), myImage.getY());
myImage.localToStageCoordinates(/*in/out*/coords);
myImage.getStage().stageToScreenCoordinates(/*in/out*/coords);
System.out.println("Image X " +myImage.get()+ " maps to screen " +coords.x);
Just a guess!!! - Moving the image & checking for position but getX() returning 0.0 might be because its the camera which moves & produces the movement effect, image may havenot been moved at all & tsayed at the initial position of 0.0 (i think this might be the thing you are missing)

Relative coordinates of an ImageView in Android

I have a square image in an ImageView contained within a FrameLayout that is right aligned (landscape layout). The FrameLayout is set to FillParent width and height and the ImageView is set to Adjust View Bounds so that the resulting ImageView is a perfect square with each side being the length of the height of the landscape layout, right aligned. However, the original image is larger than this.
I need to get the coordinates clicked on the ImageView in relation to itself (ie. the top left hand corner of the ImageView is 0,0 and the bottom right corner would be the displayed width and height coordinates) so that I can work out if the clicked point was past the mid-point of the ImageView.
I initially get the size of the ImageView using:
int myImageViewWidth = imageView.getWidth();
int myImageViewHeight = imageView.getHeight();
I've created an OnTouchListener and within this I'm using event.getX() and event.getY(), but this seems to return the coordinates relative to the image's oiginal size, not the displayed size. I've also tried the following:
Matrix m = imageView.getImageMatrix();
float[] values = new float[9];
m.getValues(values);
float relativeX = (event.getX() - values[2]) / values[0];
float relativeY = (event.getY() - values[5]) / values[4];
But again this seems to return inaccurate coordinates. Can anyone please help?

How to have a "Camera" only show a portion of a loaded area

I'm having a little problem with figuring something out (Obviously).
I'm creating a 2D Top-down mmorpg, and in this game I wish the player to move around a tiled map similar to the way the game Pokemon worked, if anyone has ever played it.
If you have not, picture this: I need to load various areas, constructing them from tiles which contain an image and a location (x, y) and objects (players, items) but the player can only see a portion of it at a time, namely a 20 by 15 tile-wide area, which can be 100s of tiles tall/wide. I want the "camera" to follow the player, keeping him in the center, unless the player reaches the edge of the loaded area.
I don't need code necessarily, just a design plan. I have no idea how to go about this kind of thing.
I was thinking of possibly splitting up the entire loaded area into 10x10 tile pieces, called "Blocks" and loading them, but I'm still not sure how to load pieces off screen and only show them when the player is in range.
The picture should describe it:
Any ideas?
My solution:
The way I solved this problem was through the wonderful world of JScrollPanes and JPanels.
I added a 3x3 block of JPanels inside of a JScrollPane, added a couple scrolling and "goto" methods for centering/moving the JScrollPane around, and voila, I had my camera.
While the answer I chose was a little more generic to people wanting to do 2d camera stuff, the way I did it actually helped me visualize what I was doing a little better since I actually had a physical "Camera" (JScrollPane) to move around my "World" (3x3 Grid of JPanels)
Just thought I would post this here in case anyone was googling for an answer and this came up. :)
For a 2D game, it's quite easy to figure out which tiles fall within a view rectangle, if the tiles are rectangular. Basically, picture a "viewport" rectangle inside the larger world rectangle. By dividing the view offsets by the tile sizes you can easily determine the starting tile, and then just render the tiles in that fit inside the view.
First off, you're working in three coordinate systems: view, world, and map. The view coordinates are essentially mouse offsets from the upper left corner of the view. World coordinates are pixels distances from the upper left corner of tile 0, 0. I'm assuming your world starts in the upper left corner. And map cooridnates are x, y indices into the map array.
You'll need to convert between these in order to do "fancy" things like scrolling, figuring out which tile is under the mouse, and drawing world objects at the correct coordinates in the view. So, you'll need some functions to convert between these systems:
// I haven't touched Java in years, but JavaScript should be easy enough to convey the point
var TileWidth = 40,
TileHeight = 40;
function View() {
this.viewOrigin = [0, 0]; // scroll offset
this.viewSize = [600, 400];
this.map = null;
this.worldSize = [0, 0];
}
View.prototype.viewToWorld = function(v, w) {
w[0] = v[0] + this.viewOrigin[0];
w[1] = v[1] + this.viewOrigin[1];
};
View.prototype.worldToMap = function(w, m) {
m[0] = Math.floor(w[0] / TileWidth);
m[1] = Math.floor(w[1] / TileHeight);
}
View.prototype.mapToWorld = function(m, w) {
w[0] = m[0] * TileWidth;
w[1] = m[1] * TileHeight;
};
View.prototype.worldToView = function(w, v) {
v[0] = w[0] - this.viewOrigin[0];
v[1] = w[1] - this.viewOrigin[1];
}
Armed with these functions we can now render the visible portion of the map...
View.prototype.draw = function() {
var mapStartPos = [0, 0],
worldStartPos = [0, 0],
viewStartPos = [0, 0];
mx, my, // map coordinates of current tile
vx, vy; // view coordinates of current tile
this.worldToMap(this.viewOrigin, mapStartPos); // which tile is closest to the view origin?
this.mapToWorld(mapStartPos, worldStartPos); // round world position to tile corner...
this.worldToView(worldStartPos, viewStartPos); // ... and then convert to view coordinates. this allows per-pixel scrolling
mx = mapStartPos[0];
my = mapStartPos[y];
for (vy = viewStartPos[1]; vy < this.viewSize[1]; vy += TileHeight) {
for (vx = viewStartPos[0]; vx < this.viewSize[0]; vy += TileWidth) {
var tile = this.map.get(mx++, my);
this.drawTile(tile, vx, vy);
}
mx = mapStartPos[0];
my++;
vy += TileHeight;
}
};
That should work. I didn't have time to put together a working demo webpage, but I hope you get the idea.
By changing viewOrigin you can scroll around. To get the world, and map coordinates under the mouse, use the viewToWorld and worldToMap functions.
If you're planning on an isometric view i.e. Diablo, then things get considerably trickier.
Good luck!
The way I would do such a thing is to keep a variable called cameraPosition or something. Then, in the draw method of all objects, use cameraPosition to offset the locations of everything.
For example: A rock is at [100,50], while the camera is at [75,75]. This means the rock should be drawn at [25,-25] (the result of [100,50] - [75,75]).
You might have to tweak this a bit to make it work (for example maybe you have to compensate for window size). Note that you should also do a bit of culling - if something wants to be drawn at [2460,-830], you probably don't want to bother drawing it.
One approach is along the lines of double buffering ( Java Double Buffering ) and blitting ( http://download.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html ). There is even a design pattern associated with it ( http://www.javalobby.org/forums/thread.jspa?threadID=16867&tstart=0 ).

Categories