3

I've got two UIViews, each of which contains a visual representation of an object. I have three known CGPoints in each UIView which correspond to the same locations on that object. I need to apply a transform to one of those views so those three points line up perfectly with the corresponding points in the other view.

I need to deal with scaling, rotation and translation, i.e. an affine transformation, and I know the math for calculating the parameters for such a translation (see this question). What I DON'T understand is how to actually perform those calculations in Obj-C and plug the correct numbers into CGAffineTrasnformMake. It seems like it should be obvious, but for some reason I think I'm just missing some part of the concept here.

So in short form, given CGPoints (X1a,Y1a), (X2a,Y2a), (X3a,Y3a) in UIView A, and (X1b,Y1b), (X2b,Y2b), (X3b,Y3b) in UIView B, what do I do to get myself a CGAffineTransform I can apply to UIView B? so it lines up with UIView A?

My deployment target, by the way, is iOS 5; I do NOT need to support anything earlier.

Thanks!

Community
  • 1
  • 1
DanM
  • 7,037
  • 11
  • 51
  • 86

1 Answers1

1

I need the exact same thing in iOS. Here is what I finally came up with... I know the algebra calculation, but I don't know how to use LAPACK. You need to add Accelerate framework to calculate matrix inverse. Assume you have points p1, p2, p3 in the original UIView, and q1, q2, q3 in the transformed UIView:

CGPoint p1, p2, p3, q1, q2, q3;

// TODO: initialize points

double A[36];

A[ 0] = p1.x; A[ 1] = p1.y; A[ 2] = 0; A[ 3] = 0; A[ 4] = 1; A[ 5] = 0;
A[ 6] = 0; A[ 7] = 0; A[ 8] = p1.x; A[ 9] = p1.y; A[10] = 0; A[11] = 1;
A[12] = p2.x; A[13] = p2.y; A[14] = 0; A[15] = 0; A[16] = 1; A[17] = 0;
A[18] = 0; A[19] = 0; A[20] = p2.x; A[21] = p2.y; A[22] = 0; A[23] = 1;
A[24] = p3.x; A[25] = p3.y; A[26] = 0; A[27] = 0; A[28] = 1; A[29] = 0;
A[30] = 0; A[31] = 0; A[32] = p3.x; A[33] = p3.y; A[34] = 0; A[35] = 1;

int err = matrix_invert(6, A);
assert(err == 0);

double B[6];

B[0] = q1.x; B[1] = q1.y; B[2] = q2.x; B[3] = q2.y; B[4] = q3.x; B[5] = q3.y;

double M[6];

M[0] = A[ 0] * B[0] + A[ 1] * B[1] + A[ 2] * B[2] + A[ 3] * B[3] + A[ 4] * B[4] + A[ 5] * B[5];
M[1] = A[ 6] * B[0] + A[ 7] * B[1] + A[ 8] * B[2] + A[ 9] * B[3] + A[10] * B[4] + A[11] * B[5];
M[2] = A[12] * B[0] + A[13] * B[1] + A[14] * B[2] + A[15] * B[3] + A[16] * B[4] + A[17] * B[5];
M[3] = A[18] * B[0] + A[19] * B[1] + A[20] * B[2] + A[21] * B[3] + A[22] * B[4] + A[23] * B[5];
M[4] = A[24] * B[0] + A[25] * B[1] + A[26] * B[2] + A[27] * B[3] + A[28] * B[4] + A[29] * B[5];
M[5] = A[30] * B[0] + A[31] * B[1] + A[32] * B[2] + A[33] * B[3] + A[34] * B[4] + A[35] * B[5];

NSLog(@"%f, %f, %f, %f, %f, %f", M[0], M[1], M[2], M[3], M[4], M[5]);

CGAffineTransform transform = CGAffineTransformMake(M[0], M[2], M[1], M[3], M[4], M[5]); // Order is correct... 

Here is the definition of matrix_inverse, it's a C function. It's modified from another SO answer:

#import <Accelerate/Accelerate.h>
#include <stdlib.h>

int matrix_invert(long N, double *matrix) {

    long error=0;
    long *pivot = malloc(N*N*sizeof(long));
    double *workspace = malloc(N*sizeof(double));

    dgetrf_(&N, &N, matrix, &N, pivot, &error);

    if (error != 0) {
        // NSLog(@"Error 1");
        return error;
    }

    dgetri_(&N, matrix, &N, pivot, workspace, &N, &error);

    if (error != 0) {
        // NSLog(@"Error 2");
        return error;
    }

    free(pivot);
    free(workspace);
    return error;
}

Here is the source code on github, I might refactor it into a function taking NSArray of CGPoints later.

If you have more than 3 points, you need to solve by least square fit. Matrix inverse can do , but is not numerical stable (results can be off a lot), I might also add this solution into the gist later. The correct way is using Singular Value Decomposition, which I don't know how to do with LAPACK.

Community
  • 1
  • 1
X.Y.
  • 13,726
  • 10
  • 50
  • 63