I have a classic ImageView that is resized to fit its parent with match_parent on both axes and scaleType is fitCenter.
The coordinates I get out of an onTouch event, however, are relative to the bounds of the ImageView. How can I convert them to be relative to the bounds of the image itself?
UPDATE
I ended up doing this manually. This is in Scala, but probably easy to translate into Java :
// Long press on the screen adds a new interaction at that position
screenView onTouch {
(v: View, ev: MotionEvent) => {
// Create the image transformation matrix
val m = screenView.getImageMatrix
val d = screenView.getDrawable
val drawableRect = new RectF(0, 0, d.getIntrinsicWidth, d.getIntrinsicHeight)
val viewRect = new RectF(0, 0, screenView.getWidth, screenView.getHeight)
m.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.CENTER)
m.invert(m)
// Convert the points inside the image
val points = Array(ev.getX, ev.getY)
m mapPoints points
// Compute normalized coordinates
current_x = points(0) / d.getIntrinsicWidth
current_y = points(1) / d.getIntrinsicHeight
// Only continue if we touched inside the image
!(current_x >= 0 && current_x <= 1 &&
current_y >= 0 && current_y <= 1)
}
}
You can calculate it mathematically.
Compute the ratio between height & width of the image, then calculate the actual width of the image as displayed.
Subtract the image's actual width from the imageview's width, and divide by two.
This will give you the width of the grey area left of the image. Simply subtract this number from the X coordinates you got, and you will get the relative X position from the actual image.
Related
I'm trying to vertically center a rotated and scaled View. It doesn't seem to matter if it is rotated or not (if I keep the scale at 1.0 and rotate the view, and change the x position of the view by using setX it works as expected).
I know the center of my container, and I also know the width of the rectangle that holds the View in the container.
I am setting X of the View to be: the middle of the container - view/2.
This works if I don't scale the view up or down, however if I scale the image up the image doesn't appear in the center, it is always off a noticeable amount.
// Get center of container
val rect = Rect()
containerView.getHitRect(rect)
val width = rect.right - rect.left
val centerContainerView = rect.left + width/2
// At this point assume myView is rotated and scaled up (using any one of the many translate, scale, rotate on touch libraries)
val viewRect = Rect()
myView.getHitRect(viewRect)
val viewWidth = viewRect.right - viewRect.left
val newX = centerContainerView - viewWidth/2
// Tap a button and run the below to center myView
myView.x = newX.toFloat()
SetX
In my app I need to let users to check the eyes at some photo.
In OnTouchListener.onTouch(...) I get the coordinates of the ImageView.
How can I convert this coordinates to the point at the bitmap that was touched?
this works for me at least with API 10+:
final float[] getPointerCoords(ImageView view, MotionEvent e)
{
final int index = e.getActionIndex();
final float[] coords = new float[] { e.getX(index), e.getY(index) };
Matrix matrix = new Matrix();
view.getImageMatrix().invert(matrix);
matrix.postTranslate(view.getScrollX(), view.getScrollY());
matrix.mapPoints(coords);
return coords;
}
Okay, so I've not tried this, but giving it a bit of thought, here's what I've got as a suggestion:
ImageView imageView = (ImageView)findViewById(R.id.imageview);
Drawable drawable = imageView.getDrawable();
Rect imageBounds = drawable.getBounds();
//original height and width of the bitmap
int intrinsicHeight = drawable.getIntrinsicHeight();
int intrinsicWidth = drawable.getIntrinsicWidth();
//height and width of the visible (scaled) image
int scaledHeight = imageBounds.height();
int scaledWidth = imageBounds.width();
//Find the ratio of the original image to the scaled image
//Should normally be equal unless a disproportionate scaling
//(e.g. fitXY) is used.
float heightRatio = intrinsicHeight / scaledHeight;
float widthRatio = intrinsicWidth / scaledWidth;
//do whatever magic to get your touch point
//MotionEvent event;
//get the distance from the left and top of the image bounds
int scaledImageOffsetX = event.getX() - imageBounds.left;
int scaledImageOffsetY = event.getY() - imageBounds.top;
//scale these distances according to the ratio of your scaling
//For example, if the original image is 1.5x the size of the scaled
//image, and your offset is (10, 20), your original image offset
//values should be (15, 30).
int originalImageOffsetX = scaledImageOffsetX * widthRatio;
int originalImageOffsetY = scaledImageOffsetY * heightRatio;
Give this idea a try and see if it works for you.
besides considering the offset due to padding (margin is part of the layout, it's space outside the view and doesn't have to be considered), if the image is scaled you can get the image matrix (ImageView.getImageMatrix()) to scale coordinates.
EDIT:
You can get x/y scaling factor and translation amount getting the values array and using respective index constants:
float[] values;
matrix.getValues(values);
float xScale = values[Matrix.MSCALE_X];
note that translation doesn't include padding, you still would have to consider that separately. translation is used for instance in FIT_CENTER scaling when there's some "blank" space.
I'd say you probably need to offset the coordinates from the ImageView with any padding or margins in the layout to get the correct coordinates of the BitMap.
To add to kcoppock's answer, I just want to add that:
//original height and width of the bitmap
int intrinsicHeight = drawable.getIntrinsicHeight();
int intrinsicWidth = drawable.getIntrinsicWidth();
may return an answer you're not expecting. These values depend on the dpi of the drawable folder you load the image from. For instance, you might get a different value if you load the image from /drawable vs /drawable-hdpi vs /drawable-ldpi.
Get floor Width and height
float floorWidth = floorImage.getWidth();
float floorHeight = floorImage.getHeight();
Calculate protionate value
float proportionateWidth = bitmapWidth / floorWidth;
float proportionateHeight = bitmapHeight / floorHeight;
Your X & Y
float x = 315;
float y = 119;
Multiple with PropotionateValue
x = x * proportionateWidth;
y = y * proportionateHeight;
As I came accross this question and tried it out myself, here is my solution.
It seems to work with stretched and centered images.
class MyEditableImageView(context: Context, attrs: AttributeSet) :
androidx.appcompat.widget.AppCompatImageView(context, attrs) {
override fun onTouchEvent(event: MotionEvent): Boolean {
val image = drawable.toBitmap().copy(Bitmap.Config.ARGB_8888, true)
val xp = (event.x - imageMatrix.values()[Matrix.MTRANS_X]) / imageMatrix.values()[Matrix.MSCALE_X]
val yp = (event.y - imageMatrix.values()[Matrix.MTRANS_Y]) / imageMatrix.values()[Matrix.MSCALE_Y]
if (xp >= 0 && xp < image.width && yp >= 0 && yp < image.height) {
doSomethingOnImage(image, xp, yp)
setImageBitmap(image)
}
return super.onTouchEvent(event)
}
...
}
It seems that setScaleX or setScaleY don't actually change left,top,right,bottom properties. getX and getY remain unchanged too.
So if I scale a view whats the easiest way to get 4 corner coordinates of the newly scaled view?
I tried getHitRect but that doesn't give me the right answer. I am trying to avoid manually calculating the new bounds based on existing transformations (rotation and scale with pivots factored in).
After exploring the view api, it looks like there is no direct API method that does this.
However you can easily get the new points by grabbing the transform matrix of the view and using that to get the new bounds.
Something like this:
Matrix m = view.getMatrix();
Rect bbox = new Rect();
view.getDrawingRect(bbox);
m.mapRect(bbox);
If you want to operate on (x,y) coordiantes directly there is a matrix.mapPoints that will achieve the same result.
I believe if you get the width and height and multiply it by the scales, you'll get the scaled width and height.
int scaledWidth = getWidth() * getScaleX();
int scaledHeight = getHeight() * getScaleY();
int newLeft = getLeft() + (scaledWidth / 2);
int newRight = newLeft + scaledWidth;
int newTop = getTop() + (scaledHeight / 2);
int newBottom = newTop + scaledHeight;
This is assuming that you scaled with a pivot x and y at the center of the view. Things gets far more complicated if you have pivots in strange areas.
I am trying to center a 256px X 256px image in LibGDX. When i run the code I'm using it renders the image in the upper right hand corner of the window. For the camera's height and width I use Gdx.graphics.getHeight(); and Gdx.graphcis.getWidth(); . I set the cameras position to the camera's width divided by the two and its height divided by two... this should put it in the middle of the screen right? when I draw the texture, I set it's position to the camera's width and height divided by two -- so it's centered..or so I think. Why doesn't the image draw in the center of the screen, is there something I'm not understanding?
Thanks!
It sounds as if your camera is ok.
If you set the textures position, you set the position of the lower left corner of that texture. It is not centered. Therefore if you set it to the coordinates of the center of the screen, its extends will cover the space to the right and the top of that point. To center it, you need to subtract half of the textures width from the x, and half of the textures height from the y coordinate. Something along these lines:
image.setPosition(Gdx.graphics.getWidth()/2 - image.getWidth()/2,
Gdx.graphics.getHeight()/2 - image.getHeight()/2);
You should draw your texture at the camera position - half the dimensions of the texture...
For example:
class PartialGame extends Game {
int w = 0;
int h = 0;
int tw = 0;
int th = 0;
OrthographicCamera camera = null;
Texture texture = null;
SpriteBatch batch = null;
public void create() {
w = Gdx.graphics.getWidth();
h = Gdx.graphics.getheight();
camera = new OrthographicCamera(w, h);
camera.position.set(w / 2, h / 2, 0); // Change the height --> h
camera.update();
texture = new Texture(Gdx.files.internal("data/texture.png"));
tw = texture.getwidth();
th = texture.getHeight();
batch = new SpriteBatch();
}
public void render() {
batch.begin();
batch.draw(texture, camera.position.x - (tw / 2), camera.position.y - (th / 2));
batch.end();
}
}
I am trying to do hit test on a rotated image object drawn on the canvas in surface view.
When image is not rotate we can directly use left, top , width and height as boundaries to check whether the point lies within image rectangle. But how to do it when the object is rotated to some angle?
I am using: canvas.rotate(angle, pivotX ,pivotY); to draw the rotated image.
I could not get the rotated left and top of the image object. I tried to take original left and top of the image and when i tap on the screen i rotate the touch point back with same angle using:
angledTouchX = (float) (eventX * Math.cos(-objectAngle) - eventY * Math.sin(-objectAngle));
angledTouchY = (float) (eventY * Math.sin(-objectAngle) + eventX * Math.cos(-objectAngle));
It does not work because it rotates the point wrt (0,0), but i want it wrt center of the image object.
Was working on similar problem. In my case, I want to do a hottest between touchpoint and rotated and scaled view. So I transform touch point co-ordinate system according to view's rotation and check if its inside non-rotated view boundary or not.
Step
Get touch point in screen co-ordinate system which received from event.getRawX() and event.getRawY()
Target view location in screen location which received from
int location[] = new int[2];
view.getLocationOnScreen(location);
int viewX = location[0];
int viewY = location[1];
Create vector as if view origin is the space origin
float[] vec = new float[2];
vec[0] = event.getRawX() - viewX;
vec[1] = event.getRawY() - viewY;
Rotate the vector
Matrix matrix = new Matrix();
matrix.setRotate(-1 * view.getRotation());
matrix.mapVectors(vec);
Change rotated vector to screen space
vec[0] = vec[0] + viewX;
vec[1] = vec[1] + viewY;
Check if rotated touch co-ordinate is in view unrotated boundary
if(( vec[0] > viewX
&& vec[0] < (viewX + (view.getWidth() * view.getScaleX()) ))
&&( vec[1] > viewY
&& vec[1] < (viewY + (view.getHeight() * view.getScaleY()) ))){
isPointInTextView = true;
} else {
isPointInTextView = false;
}
You need to work with it mathematically. the best way to do is before you rotating the canvas change the position by width/2, height/2. Then apply rotation, finally move back to the previous location by width/2, height/2. Then your picture will be always rotated from the center. But still after rotating your new image will have a new size which fits as a rectagle align to screen sides.