旋转图像上下文(CGContextRotateCTM)导致其变为空白。为什么?

31
#define radians(degrees) (degrees * M_PI/180)

UIImage *rotate(UIImage *image) {
  CGSize size = image.size;;

  UIGraphicsBeginImageContext(size);
  CGContextRef context = UIGraphicsGetCurrentContext();

  // If this is commented out, image is returned as it is.
  CGContextRotateCTM (context, radians(90));

  [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
  UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();

  return newImage;
}

还可能有其他问题吗?我已经没有更多想法了。


请查看Hardy Macia的简单而棒的代码:http://www.catamount.com/blog/1015/uiimage-extensions-for-cutting-scaling-and-rotating-uiimages/ 感谢Hardy Macia! - Ben Groot
5个回答

53
问题在于您的上下文被围绕(0,0)即左上角旋转。如果您围绕左上角旋转90度,所有的绘图将会超出上下文的边界。您需要进行两步变换来将上下文的原点移动到中心,然后再进行旋转。此外,您需要以移动/旋转后的原点为中心绘制图像,就像这样:
CGContextTranslateCTM( context, 0.5f * size.width, 0.5f * size.height ) ;
CGContextRotateCTM( context, radians( 90 ) ) ;

[ image drawInRect:(CGRect){ { -size.width * 0.5f, -size.height * 0.5f }, size } ] ;

希望能帮到你


没错,它是图像旋转的轴心。但那段代码没有帮助。不管怎样,我找到了正确的方法。很快会发布答案。谢谢你的回答,点个赞!非常感谢! - Gurpartap Singh
嗯...是先旋转再平移吗?有时我会混淆顺序。 - nielsbot
谢谢!这对我有帮助。我在此基础上进行了扩展,通过找到的唯一方法来恢复上下文。让我在新答案中发布它。 - Timo
Timo--你可以通过调用 CGContextSaveGState() 来保存变换矩阵,然后在完成后调用 CGContextRestoreGState() - nielsbot

11

我尝试了下面的代码,它有效,并且是从http://www.catamount.com/blog/1015/uiimage-extensions-for-cutting-scaling-and-rotating-uiimages/获取的。

+ (UIImage *)rotateImage:(UIImage*)src byRadian:(CGFloat)radian
{
    // calculate the size of the rotated view's containing box for our drawing space
    //UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0, src.size.width, src.size.height)];
    //CGAffineTransform t = CGAffineTransformMakeRotation(radian);
    //rotatedViewBox.transform = t;
    //CGSize rotatedSize = rotatedViewBox.frame.size;
    CGRect rect = CGRectApplyAffineTransform(CGRectMake(0,0, src.size.width, src.size.height), CGAffineTransformMakeRotation(radian));
    CGSize rotatedSize = rect.size;

    // Create the bitmap context
    UIGraphicsBeginImageContext(rotatedSize);
    CGContextRef bitmap = UIGraphicsGetCurrentContext();

    // Move the origin to the middle of the image so we will rotate and scale around the center.
    CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);

    //   // Rotate the image context
    CGContextRotateCTM(bitmap, radian);

    // Now, draw the rotated/scaled image into the context
    CGContextScaleCTM(bitmap, 1.0, -1.0);
    CGContextDrawImage(bitmap, CGRectMake(-src.size.width / 2, -src.size.height / 2, src.size.width, src.size.height), [src CGImage]);

    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}

为了获取旋转矩形的边界而创建一个新视图有点疯狂。你可以用以下代码替换前4行:CGSize rotatedSize = CGSizeMake(src.size.height, src.size.width); - Craig McMahon
@CraigMcMahon,你说得对。因为我的重点是展示CTM功能,所以使用最简单但有效的方法来获取边界,当使用你的代码时应该是完美的。 - lbsweek
@CraigMcMahon 正确的使用方法应该是这样的: CGSize rotatedSize = CGRectApplyAffineTransform(CGRectMake(0,0, src.size.width, src.size.height), CGAffineTransformMakeRotation(radian)); - lbsweek

10
如果您不想改变所有的原点和尺寸,可以像下面这样移动中心,旋转并再次将中心点改为角落: CGContextTranslateCTM(context,0.5f * size.width,0.5f * size.height); CGContextRotateCTM(context,radians(90)); CGContextTranslateCTM(context,-0.5f * size.width,-0.5f * size.height); 然后像这样正常绘制: [image drawInRect:CGRectMake(0, 0, size.width, size.height)];

如果您能够用一些图示来说明这是如何实现期望结果的,我将不胜感激。特别是,这是否是因为旋转发生在上下文的中心而需要这样做? - Smart Home

1
扩展Nielsbot的答案,我创建了以下代码片段。请注意,最好将其制作为函数而不是代码片段,以防止“复制/粘贴编程”。我还没有做到这一点,所以请随意调整。
代码片段:
// <Draw in frame with orientation>
UIImage* imageToDrawRotated = <#image#>;
float rotationAngle = <#angle#>;
CGRect drawFrame = CGRectMake(<#CGFloat x#>, <#CGFloat y#>, <#CGFloat width#>, <#CGFloat height#>);
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), floor(drawFrame.origin.x + drawFrame.size.width/2), floor(drawFrame.origin.y + drawFrame.size.height/2));
CGContextRotateCTM(UIGraphicsGetCurrentContext(), rotationAngle);
[imageToDrawRotated drawInRect:CGRectMake(-floor(drawFrame.size.width/2), -floor(drawFrame.size.height/2), drawFrame.size.width, drawFrame.size.height)];
CGContextRotateCTM(UIGraphicsGetCurrentContext(), -rotationAngle);
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), -floor(drawFrame.origin.x + drawFrame.size.width/2), -floor(drawFrame.origin.y + drawFrame.size.height/2));
// </Draw in frame with orientation>

此外,我欢迎一种更好的方法来将CGContext重置为其先前的状态。


更好的方法?您可以直接使用CGContextSaveGState/CGContextRestoreGState。 - nielsbot

1

我喜欢@lbsweek的解决方案,但是我有点担心为了变换而分配UIView的内存,所以我尝试了以下方法

- (UIImage *)rotateImage:(UIImage *)sourceImage byDegrees:(float)degrees
{
    CGFloat radian = degrees * (M_PI/ 180);
    CGRect contextRect = CGRectMake(0, 0, sourceImage.size.width, sourceImage.size.height);
    float newSide = MAX([sourceImage size].width, [sourceImage size].height);
    CGSize newSize =  CGSizeMake(newSide, newSide);
    UIGraphicsBeginImageContext(newSize);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGPoint contextCenter = CGPointMake(CGRectGetMidX(contextRect), CGRectGetMidY(contextRect));
    CGContextTranslateCTM(ctx, contextCenter.x, contextCenter.y);
    CGContextRotateCTM(ctx, radian);
    CGContextTranslateCTM(ctx, -contextCenter.x, -contextCenter.y);
    [sourceImage drawInRect:contextRect];
    UIImage* rotatedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return rotatedImage;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接