CGAffine以用户可读格式转换矩阵

时间:2014-04-24 09:53:02

标签: ios avfoundation transformation cgaffinetransform affinetransform

我应该如何将CoreGraphics CGAffineTransform解释为人类可读,有意义的格式?

我希望采取类似的方式:

NSLog(@"naturalSize %@, appliedSize %@, transformationMatrix %@",
      NSStringFromCGSize(clipVideoTrack.naturalSize),
      NSStringFromCGSize(CGSizeApplyAffineTransform(clipVideoTrack.naturalSize, clipVideoTrack.preferredTransform)),
      NSStringFromCGAffineTransform(clipVideoTrack.preferredTransform));
  

naturalSize {1920,1080},appliedSize {-1080,1920},   transformationMatrix [0,1,-1,0,1080,0]

上述矩阵转换的最终结果是将此Landscape Right Video转换为此Portrait Up Video

我很高兴能够将其分解为人类可读形式的步骤,以便人们可以查看并理解转换实际上在做什么。

喜欢(不确定我是否正确的步骤):

 0. Will use upper left corner for video export of width 1080, height 1920.
 1. Will move the video -1080(left) on the x axis 
 2. Will move the video 1920(down) on the y axis
 3. Will rotate 90deg clockwise from bottom right corner 

我很感激指向执行此操作的代码,或实现或解释。我试图学习并理解AVFoundation上下文中转换矩阵究竟是如何运作的。

2 个答案:

答案 0 :(得分:16)

事实证明,在大多数情况下,你可以对仿射变换做出正确的描述,因为它非常有限。为3D变换做同样的事情很多更难:(

请注意,我仍然无法告诉您连接的不同变换矩阵,只能告诉您最终结果。我也忽略了剪切,因为没有提供创建这种变换的功能。


我编写了一个函数,可以很好地确定仿射变换的作用。

您会在很多地方看到我写if (fabs(foo - bar) < FLT_EPSILON)而不只是if (foo == bar)。这是为了保护自己在比较中的浮点(im)精度。

另一个值得注意的事情是我正在计算旋转角度的方式。对于纯旋转我可以使用asin(b),但如果变换也被缩放,那么该结果将是不正确的。相反,我将b除以a并使用arctan来计算角度。

代码中有相当多的评论,所以你应该能够跟进,主要是通过阅读它。

NSString *affineTransformDescription(CGAffineTransform transform)
{
    // check if it's simply the identity matrix
    if (CGAffineTransformIsIdentity(transform)) {
        return @"Is the identity transform";
    }
    // the above does't catch things like a 720° rotation so also check it manually
    if (fabs(transform.a  - 1.0) < FLT_EPSILON &&
        fabs(transform.b  - 0.0) < FLT_EPSILON &&
        fabs(transform.c  - 0.0) < FLT_EPSILON &&
        fabs(transform.d  - 1.0) < FLT_EPSILON &&
        fabs(transform.tx - 0.0) < FLT_EPSILON &&
        fabs(transform.ty - 0.0) < FLT_EPSILON) {
        return @"Is the identity transform";
    }

    // The affine transforms is built up like this:

    // a b tx
    // c d ty
    // 0 0 1

    // An array to hold all the different descirptions, charasteristics of the transform.
    NSMutableArray *descriptions = [NSMutableArray array];

    // Checking for a translation
    if (fabs(transform.tx) > FLT_EPSILON) { // translation along X
        [descriptions addObject:[NSString stringWithFormat:@"Will move %.2f along the X axis",
                                 transform.tx]];
    }
    if (fabs(transform.ty) > FLT_EPSILON) { // translation along Y
        [descriptions addObject:[NSString stringWithFormat:@"Will move %.2f along the Y axis",
                                 transform.ty]];
    }


    // Checking for a rotation
    CGFloat angle = atan2(transform.b, transform.a); // get the angle of the rotation. Note this assumes no shearing!
    if (fabs(angle) < FLT_EPSILON || fabs(angle - M_PI) < FLT_EPSILON) {
        // there is a change that there is a 180° rotation, in that case, A and D will and be negative.
        BOOL bothAreNegative  = transform.a < 0.0 && transform.d < 0.0;

        if (bothAreNegative) {
            angle = M_PI;
        } else {
            angle = 0.0; // this is not considered a rotation, but a negative scale along one axis.
        }
    }

    // add the rotation description if there was an angle
    if (fabs(angle) > FLT_EPSILON) {
        [descriptions addObject:[NSString stringWithFormat:@"Will rotate %.1f° degrees",
                                 angle*180.0/M_PI]];
    }


    // Checking for a scale (and account for the possible rotation as well)
    CGFloat scaleX = transform.a/cos(angle);
    CGFloat scaleY = transform.d/cos(angle);


    if (fabs(scaleX - scaleY) < FLT_EPSILON && fabs(scaleX - 1.0) > FLT_EPSILON) {
        // if both are the same then we can format things a little bit nicer
        [descriptions addObject:[NSString stringWithFormat:@"Will scale by %.2f along both X and Y",
                                 scaleX]];
    } else {
        // otherwise we look at X and Y scale separately
        if (fabs(scaleX - 1.0) > FLT_EPSILON) { // scale along X
            [descriptions addObject:[NSString stringWithFormat:@"Will scale by %.2f along the X axis",
                                     scaleX]];
        }

        if (fabs(scaleY - 1.0) > FLT_EPSILON) { // scale along Y
            [descriptions addObject:[NSString stringWithFormat:@"Will scale by %.2f along the Y axis",
                                     scaleY]];
        }
    }

    // Return something else when there is nothing to say about the transform matrix
    if (descriptions.count == 0) {
        return @"Can't easilly be described.";
    }

    // join all the descriptions on their own line
    return [descriptions componentsJoinedByString:@",\n"];
}

为了尝试一下,我测试了许多不同变换的输出。这是我用来测试它的代码:

// identity
CGAffineTransform t = CGAffineTransformIdentity;
NSLog(@"identity: \n%@", affineTransformDescription(t));


// translation
t = CGAffineTransformMakeTranslation(10, 0);
NSLog(@"translate(10, 0): \n%@", affineTransformDescription(t));

t = CGAffineTransformMakeTranslation(0, 20);
NSLog(@"translate(0, 20): \n%@", affineTransformDescription(t));

t = CGAffineTransformMakeTranslation(2, -3);
NSLog(@"translate(2, -3): \n%@", affineTransformDescription(t));


// scale
t = CGAffineTransformMakeScale(2, 2);
NSLog(@"scale(2, 2): \n%@", affineTransformDescription(t));


t = CGAffineTransformMakeScale(-1, 3);
NSLog(@"scale(-1, 3): \n%@", affineTransformDescription(t));



// rotation
t = CGAffineTransformMakeRotation(M_PI/3.0);
NSLog(@"rotate(60 deg): \n%@", affineTransformDescription(t));

t = CGAffineTransformMakeRotation(M_PI);
NSLog(@"rotate(180 deg): \n%@", affineTransformDescription(t));

t = CGAffineTransformMakeRotation(4.0*M_PI);
NSLog(@"rotate(720 deg): \n%@", affineTransformDescription(t));

t = CGAffineTransformMakeRotation(3.0*M_PI);
NSLog(@"rotate(540 deg): \n%@", affineTransformDescription(t));



// concatenated transforms
// rotate & translate
t = CGAffineTransformMakeRotation(M_PI/3.0);
t = CGAffineTransformTranslate(t, 10, 20);
NSLog(@"rotate(60 deg), translate(10, 20): \n%@", affineTransformDescription(t));

t = CGAffineTransformMakeTranslation(10, 20);
t = CGAffineTransformRotate(t, M_PI/3.0);
NSLog(@"translate(10, 20), rotate(60 deg): \n%@", affineTransformDescription(t));

// rotate & scale
t = CGAffineTransformMakeRotation(M_PI/3.0);
t = CGAffineTransformScale(t, 2, 2);
NSLog(@"rotate(60 deg), scale(2, 2): \n%@", affineTransformDescription(t));

t = CGAffineTransformMakeScale(2, 2);
t = CGAffineTransformRotate(t, M_PI/3.0);
NSLog(@"scale(2, 2), rotate(60 deg): \n%@", affineTransformDescription(t));

// translate & scale
t = CGAffineTransformMakeTranslation(10, 20);
t = CGAffineTransformScale(t, 2, 2);
NSLog(@"translate(10, 20), scale(2, 2): \n%@", affineTransformDescription(t));

t = CGAffineTransformMakeScale(2, 2);
t = CGAffineTransformTranslate(t, 10, 20);
NSLog(@"scale(2, 2), translate(10, 20): \n%@", affineTransformDescription(t));

和该测试的输出:

identity: 
  Is the identity transform
translate(10, 0): 
  Will move 10.00 along the X axis
translate(0, 20): 
  Will move 20.00 along the Y axis
translate(2, -3): 
  Will move 2.00 along the X axis,
  Will move -3.00 along the Y axis
scale(2, 2): 
  Will scale by 2.00 along both X and Y
scale(-1, 3): 
  Will scale by -1.00 along the X axis,
  Will scale by 3.00 along the Y axis
rotate(60 deg): 
  Will rotate 60.0° degrees
rotate(180 deg): 
  Will rotate 180.0° degrees
rotate(720 deg): 
  Is the identity transform
rotate(540 deg): 
  Will rotate 180.0° degrees
rotate(60 deg), translate(10, 20): 
  Will move -12.32 along the X axis,
  Will move 18.66 along the Y axis,
  Will rotate 60.0° degrees
translate(10, 20), rotate(60 deg): 
  Will move 10.00 along the X axis,
  Will move 20.00 along the Y axis,
  Will rotate 60.0° degrees
rotate(60 deg), scale(2, 2): 
  Will rotate 60.0° degrees,
  Will scale by 2.00 along both X and Y
scale(2, 2), rotate(60 deg): 
  Will rotate 60.0° degrees,
  Will scale by 2.00 along both X and Y
translate(10, 20), scale(2, 2): 
  Will move 10.00 along the X axis,
  Will move 20.00 along the Y axis,
  Will scale by 2.00 along both X and Y
scale(2, 2), translate(10, 20): 
  Will move 20.00 along the X axis,
  Will move 40.00 along the Y axis,
  Will scale by 2.00 along both X and Y

答案 1 :(得分:2)

要了解仿射变换矩阵是如何工作的,您可以阅读this优秀文章。

正如评论中所提到的,一旦将一些变换加在一起,就很难计算出各个组件。我在GitHub上有一个小应用程序,它允许您叠加变换以查看单个效果。这适用于3D变换,但原理是相同的。