我应该如何将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上下文中转换矩阵究竟是如何运作的。
答案 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)