I've been doing some work with Android's Canvas
, specifically trying to determine how its getClipBounds
results are determined. I understand Canvas
internally keeps a transform Matrix
which is updated as I call translate
, scale
, etc. but trying to replicate that Matrix
's results has baffled me.
@Override
public void onDraw(Canvas canvas) {
Rect clipBounds;
RectF viewport;
canvas.save();
canvas.concat(translationMatrix);
//viewportMatrix.preConcat(canvas.getMatrix());
viewportMatrix.set(canvas.getMatrix());
clipBounds = canvas.getClipBounds();
viewport = GetViewport();
Log.d("clipBounds", clipBounds.toString() + " (" + clipBounds.width() + ", " + clipBounds.height() + ")");
Log.d("viewport", viewport.toString() + " (" + viewport.width() + ", " + viewport.height() + ")");
//drawing is done here
canvas.restore();
}
//viewport code modeled after http://stackoverflow.com/a/17142856/2245528
private RectF GetViewport() {
RectF viewport = new RectF();
viewportMatrix.mapRect(viewport, originalViewport);
return viewport;
}
private void Translate(float x, float y) {
translationMatrix.postTranslate(x, y);
invalidate();
}
private void Scale(float scaleFactor, PointF focusPoint) {
if (focusPoint == null) {
translationMatrix.postScale(scaleFactor, scaleFactor);
}
//keep the focus point in focus if possible
else {
translationMatrix.postScale(scaleFactor, scaleFactor, focusPoint.x, focusPoint.y);
}
invalidate();
}
private final Matrix translationMatrix = new Matrix();
private final RectF originalViewport = new RectF();
private final Matrix viewportMatrix = new Matrix();
originalViewport
is set to 0, 0, canvas.getWidth(), canvas.getHeight()
. Translate
and Scale
are called from gesture event handlers, which are working correctly.
The part that confuses me is viewportMatrix
. It doesn't seem to matter whether I do
viewportMatrix.set(canvas.getMatrix());
or
viewportMatrix.preConcat(canvas.getMatrix());
or even a one-time call to
viewportMatrix.set(canvas.getMatrix());
at the beginning followed by side-by-side Translate
/Scale
calls to the two matrixes. I've even tried completely ignoring the Canvas
's built-in Matrix
and rewriting GetViewport
as
//viewport code modeled after http://stackoverflow.com/a/17142856/2245528
private RectF GetViewport() {
RectF viewport = new RectF();
translationMatrix.mapRect(viewport, originalViewport);
return viewport;
}
I can never seem to match getClipBounds()
, and the discrepancies are fairly serious:
with viewportMatrix.set(canvas.getMatrix)
:
clipBounds: Rect(-97, -97 - 602, 452) (699, 549)
viewport: RectF(97.04178, 97.06036, 797.04175, 647.06036) (700.0, 550.0)
with viewportMatrix.preConcat(canvas.getMatrix)
:
clipBounds: Rect(-97, -96 - 602, 453) (699, 549)
viewport: RectF(2708.9663, 2722.2754, 3408.9663, 3272.2754) (700.0, 550.0)
with translationMatrix.mapRect
:
clipBounds: Rect(-96, -96 - 603, 453) (699, 549)
viewport: RectF(96.73213, 96.85794, 796.7321, 646.8579) (700.0, 550.0)
with a one-shot call to viewportMatrix.preConcat(canvas.getMatrix())
followed by side-by-side Translate
/Scale
calls:
clipBounds: Rect(-96, -97 - 603, 452) (699, 549)
viewport: RectF(96.57738, 97.78168, 796.5774, 647.7817) (700.0, 550.0)
with a one-shot call to viewportMatrix.set(canvas.getMatrix())
followed by side-by-side Translate
/Scale
calls:
clipBounds: Rect(-96, -96 - 603, 453) (699, 549)
viewport: RectF(96.40051, 96.88153, 796.4005, 646.88153) (700.0, 550.0)
I can't even check the Canvas
source, as all the Matrix
code vanishes into private native calls whose code isn't shown.
Why are my GetViewport
calls so grossly off, and what's going on behind-the-scenes with getClipBounds
?
I read through this answer on GameDev SE, which uses matrix inversion to swap between screen and world coordinate systems:
To go from screen to world space simply use Vector2.Transform. This is commonly used to get the location of the mouse in the world for object picking.
Vector2.Transform(mouseLocation, Matrix.Invert(Camera.TransformMatrix));
To go from world to screen space simply do the opposite.
Vector2.Transform(mouseLocation, Camera.TransformMatrix);
Inspired by this approach, I tried inverting the transformation matrix like this:
private RectF GetViewport() {
RectF viewport = new RectF();
Matrix viewportMatrix = new Matrix();
translationMatrix.invert(viewportMatrix);
viewportMatrix.mapRect(viewport, originalViewport);
return viewport;
}
This viewport correctly matches the results returned by getClipBounds
.
This matrix tutorial explains what I'd noticed from the results of transform
calls, but hadn't applied to my matrix:
In a computer program the camera doesn’t move at all and in actuality, the world is just moving in the opposite direction and orientation of how you would want the camera to move in reality.
In order to understand this correctly, we must think in terms of two different things:
- The Camera Transformation Matrix: The transformation that places the camera in the correct position and orientation in world space (this is the transformation that you would apply to a 3D model of the camera if you wanted to represent it in the scene).
- The View Matrix: This matrix will transform vertices from world-space to view-space. This matrix is the inverse of the camera’s transformation matrix described above.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With