Solve for 2D translation, rotation and scale given two touch point movements For a phone app I need to let users translate, rotate and scale a rectangle on the 2D plane using "pinch" gesture.
Suppose the rectangle already has TRS = $t, \theta, s $; and the user has their fingers on two touch points $p_1, p_2$.  The user then moves the touch points to  $q_1, q_2$. What is the new TRS that will map $p_1 \rightarrow q_1, p_2 \rightarrow q_2$.  In other words, the user should feel that their fingers are "stuck" to the rectangle. The scale is always the same for $x$ and $y$.
The scale is easy. Let $p=p_1 - p_2$ and  $q=q_1 - q_2$, then  $s' = s \cdot  \dfrac{||q||}{||p||}  $
I think rotation is can use the arcSine of the cross product. Let $q_\bot = (-q_y, q_x)$ and
$\Delta\theta = arcSine \dfrac{q_\bot\cdot p}{||q||\cdot||p||}$, then $\theta' = \theta + \Delta\theta$
I think the translation should preserve the midpoint. So: $t' = t +  \frac{1}{2}((q_1 - p_1)+(q_2 - p_2))$.
But when I implement this, it doesn't work very well. The rotation is not around the correct point so the rectangle seems to slide away strangely.  Any advice please?
 A: For those who are interested in a less rigorous, but more practical answer, here is the source code that I used with the android.graphics.Canvas.
First I made a class called Transform as follows (Vec2 is just two floats)
public class Transform
{
    Vec2 translation;
    Vec2 center;
    float rotation;
    float scale;

    Matrix getMatrix()
    {
        Matrix m = new Matrix();
        m.setTranslate(center.x, center.y);
        m.preRotate(degrees(rotation));
        m.preScale(scale, scale);
        m.preTranslate(translation.x - center.x, translation.y - center.y);
        return m;
    }
}

Then I made an OnTouchListener that captures two vectors, p and q at the start and end of the pinch gesture.

The method OnTouchListener.getTransform returns a Transform with translation (the green vector), center (the red vector), rotation (the angle from p to q), scale (length of q over p). Both vectors are in device coordinates (blue).
In the MainView.onDraw(Canvas) method I apply the transform as follows:
    transform = touchListener.getTransform();
    canvas.save();
    canvas.translate(transform.center.x, transform.center.y);
    canvas.rotate(degrees(transform.rotation));
    canvas.scale(transform.scale, transform.scale);
    canvas.translate(transform.translation.x - transform.center.x,
                     transform.translation.y - transform.center.y);
    mainWorld.onDraw(canvas);
    canvas.restore();

If I want, I can instead use the matrix as follows,
    transform = touchListener.getTransform();
    currentMatrix = transform.getMatrix();
    canvas.save();
    canvas.setMatrix(currentMatrix);
    mainWorld.onDraw(canvas);
    canvas.restore();

During the gesture, I may want to keep the current matrix separate from the previous matrix. In that case I would apply both of them as follows,
    canvas.save();
    canvas.setMatrix(currentMatrix);
    canvas.concat(previousMatrix);
    mainWorld.onDraw(canvas);
    canvas.restore();

At the end of the gesture, I merge the current into the previous matrix,
    previousMatrix.postConcat(currentMatrix);

Here are some utilities to extract the transform from a matrix.
public static float getAngleFromMatrix(Matrix matrix)
{
    float[] m = new float[9];
    matrix.getValues(m);
    float c = m[0] + m[4];
    float s = m[3] - m[1];
    return MathUtils.atan2(s,c);
}

public static float getScaleFromMatrix(Matrix matrix)
{
    float[] m = new float[9];
    matrix.getValues(m);
    return 0.5f * (MathUtils.sqrt(m[0]*m[0] + m[1]*m[1]) + MathUtils.sqrt(m[3]*m[3] + m[4]*m[4]));
}

public static Vec2 getTranslationFromMatrix(Matrix matrix)
{
    float[] m = new float[9];
    matrix.getValues(m);
    return new Vec2(m[2], m[5]);
}

