Please see each section below for a description of my problem described in three separate ways. Hopefully should help people to answer.
Problem: How do you find a pair of coordinate expressed in canvas/userspace when you only have the coordinate expressed in terms of a zoomed image, given the original scale point & scale factor?
Problem in practice:
I'm currently trying to replicate the zoom functionality used in apps such as the gallery / maps, when you can pinch to zoom/zoom out with the zoom moving towards the midpoint of the pinch.
On down I save the centre point of the zoom (which is in X,Y coordinates based on the current screen). I then have this function act when a "scale" gesture is detected:
class ImageScaleGestureDetector extends SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
if(mScaleAllowed)
mCustomImageView.scale(detector.getScaleFactor(), mCenterX, mCenterY);
return true;
}
}
The scale function of the CustomImageView look like this:
public boolean scale(float scaleFactor, float focusX, float focusY) {
mScaleFactor *= scaleFactor;
// Don't let the object get too small or too large.
mScaleFactor = Math.max(MINIMUM_SCALE_VALUE, Math.min(mScaleFactor, 5.0f));
mCenterScaleX = focusX;
mCenterScaleY = focusY;
invalidate();
return true;
}
The drawing of the scaled image is achieved by overriding the onDraw method which scales the canvas around the centre ands draw's the image to it.
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(mCenterScaleX, mCenterScaleY);
canvas.scale(mScaleFactor, mScaleFactor);
canvas.translate(-mCenterScaleX, -mCenterScaleY);
mIcon.draw(canvas);
canvas.restore();
}
This all works fine when scaling from ScaleFactor 1, this is because the initial mCenterX and mCenterY are coordinates which are based on the device screen. 10, 10 on the device is 10, 10 on the canvas.
After you have already zoomed however, then next time you click position 10, 10 it will no longer correspond to 10, 10 in the canvas because of the scaling & transforming that has already been performed.
Problem in abstraction:
The image below is an example of a zoom operation around centre point A. Each box represents the position and size of the view when at that scale factor (1, 2, 3, 4, 5).

In the example if you scaled by a factor of 2 around A then you clicked on position B, the X, Y reported as B would be based on the screen position - not on the position relative to 0,0 of the initial canvas.
I need to find a way of getting the absolute position of B.
So, after redrawing the problem I've found the solution I was looking for. It's gone through a few iteration's but here's how I worked it out:

It's pretty easy to find the values for each between A and B (both X and Y) since you can convert known distances by dividing by the scale factor in order to find it's distance in canvas/userspace. This along with the fixed values (in blue) give you the distance between A and it's furthest edge. Using these two values you know where A is in userspace.
public float[] getAbsolutePosition(float Ax, float Ay) {
float toCurrentCenterX = (mCenterScaleX - Ax) / mScaleFactor;
float toEdgeFromCenterX = mOriginalWidth - mCenterScaleX;
float x = mOriginalWidth - toCurrentCenterX - toEdgeFromCenterX;
float toCurrentCenterY = (mCenterScaleY - Ay) / mScaleFactor;
float toEdgeFromCenterY = mOriginalHeight - mCenterScaleY;
float y = mOriginalHeight - toCurrentCenterY - toEdgeFromCenterY;
return new float[] { x, y };
}
The above code and image describe when you're clicking to the top left of the original centre. I drew these to their logical conclusion to get the following code which works perfectly:
public float[] getAbsolutePosition(float Ax, float Ay) {
float x = getAbsolutePosition(mCenterScaleX, Ax, mOriginalWidth);
float y = getAbsolutePosition(mCenterScaleY, Ay, mOriginalHeight);
return new float[] { x, y };
}
private float getAbsolutePosition(float oldCenter, float newCenter, float dimension) {
if(newCenter > oldCenter) {
return oldCenter + ((newCenter - oldCenter) / mScaleFactor);
} else {
return oldCenter - ((oldCenter - newCenter) / mScaleFactor);
}
}
