Injury - 3 months ago 21

Android Question

First read Taig's question

Taig said:

When calling Matrix.postScale( sx, sy, px, py ); the matrix gets

scaled and also translated (depending on the given point x, y). That

predestines this method to be used for zooming into images because I

can easily focus one specific point.

The android doc describes the method like this:

`Postconcats the matrix with the specified scale. M' = S(sx, sy, px, py) * M`

At a first glance this seems ridiculous because M is supposed to be a

3x3-Matrix. Digging around I've found out that android uses a

4x4-Matrix for its computations (while only providing 3x3 on its API).

Since this code is written in C I'm having a hard time trying to

understand what is actually happening.

I saw the visual transform at Wolfram

My question is same as Taig

What I actually want to know: How can I apply this kind of scaling

(with a focused point) to the 3x3 Matrix that I can access within my

Java-code?

Who can give me a example and 2d-scaling formula with 4 parameters (sx, sy, px, py) that a 10-year-old-kid would understand?

Answer

Look more closely at the Matrix methods. You will see `getValue()`

and `setValue()`

. The docs say they work with a `float`

array with 9 values. There are also a bunch of constants: `MSCALE_X`

, `MSCALE_Y`

, `MTRANS_X`

, `MTRANS_Y`

etc. etc. Those constants are indices into the `float[9]`

array.

Since we are only working in 2 dimensions, the matrix would actually be a *2x2* matrix. But because this matrix supports *affine transforms*, the matrix is extended to become a 3x3 matrix. 3x3 = 9, which corresponds to the `float[9]`

array. That is, essentially, your 3x3 matrix.

The actual guts of `Matrix`

are written in C++ and accessed through JNI because the operations have to be fast fast fast fast fast. They even use a special non-standard floating point number format ("16.16") that is optimized for calculation speed.

I don't know where you are getting the information about a 4x4 array. Here's a code snippet from the C++ JNI:

```
SkScalar fMat[9];
mutable uint32_t fTypeMask;
void setScaleTranslate(SkScalar sx, SkScalar sy, SkScalar tx, SkScalar ty) {
fMat[kMScaleX] = sx;
fMat[kMSkewX] = 0;
fMat[kMTransX] = tx;
fMat[kMSkewY] = 0;
fMat[kMScaleY] = sy;
fMat[kMTransY] = ty;
fMat[kMPersp0] = 0;
fMat[kMPersp1] = 0;
fMat[kMPersp2] = 1;
unsigned mask = 0;
if (sx != 1 || sy != 1) {
mask |= kScale_Mask;
}
if (tx || ty) {
mask |= kTranslate_Mask;
}
this->setTypeMask(mask | kRectStaysRect_Mask);
}
```

It's a 3x3 matrix for an affine transform.

When you call `matrix.postScale()`

, you are modifying scaleX, scaleY, transX, and transY. (The `pre..()`

and `post...()`

methods preserve any transform that was in your matrix to start with.) The `Matrix`

applies the new transform like this:

X' = X * scaleX + transX Y' = Y * scaleY + transY

That's the simplified version of the entire matrix multiplication. If I have a figure with point (2,2) and I scale it 2x, the new point will be (4,4). To move along the X or Y axis, I just add a constant.

Because `Matrix.postScale()`

actually takes a focus point, the method internally adjusts transX & transY as though you are translating in, scaling, then translating back out. This makes the scaling appear as though the expansion/shrinking is centered around a point px, py.

transX = (1 - scaleX) * px transY = (1 - scaleY) * py

So for the focus point, I move the figure to (px,py) by adding px and py directly to the original x,y values. Then I do the scaling. But to undo the translation, I have to take into account that my original focus point *is now scaled itself*, so instead of subtracting px and py, I have to subtract scaleX * px and scaleY * py.

*Skew* or *Shear* is like scaling but with opposing axes:

X' = Y * skewX Y' = X * skewY

Since you're scaling and translating without warping, skewX and skewY are set to zero. So they're still used in the matrix multiplication, they just don't affect the final outcome.

*Rotation* is done by adding in a little trig:

theta = angle of rotation scaleX = cos(theta) skewX = -sin(theta) skewY = sin(theta) scaleY = cos(theta)

Then there is the `android.graphics.Camera`

(as opposed to `android.hardware.Camera`

) that can take a 2D plane and rotate/translate it in 3D space. This is where `MPERSP_0`

, `MPERSP_1`

, & `MPERSP_2`

come into play. I'm not doing those equations; I'm a programmer, not a mathematician.

But I don't need to be a mathematician. I don't even need to know how `Matrix`

does its calculations. I have been working on an ImageView subclass that supports pinch/zoom. So I use a `ScaleGestureDetector`

to tell me when the user is zooming. It has methods `getScaleFactor()`

, `getFocusX()`

and `getFocusY()`

. I plug those values into `matrix.postScale()`

, and with my `ImageView`

having a scale type set to `MATRIX`

, I call `ImageView.setImageMatrix()`

with my scaled matrix. VoilĂ , the image zooms exactly the way the user expects to see it based on their gestures.

So I don't understand all the angst about grokking how `Matrix`

works under the hood. Still, I hope something I wrote here gives you the answers you are looking for.