使用CGContextDrawImage旋转图像

15

如何在中心点旋转使用CGContextDrawImage()绘制的图片?

drawRect:中进行操作。

CGContextSaveGState(c);

rect = CGRectOffset(rect, -rect.size.width / 2, -rect.size.height / 2);
CGContextRotateCTM(c, angle);
CGContextDrawImage(c, rect, doodad.CGImage);
CGContextRotateCTM(c, -angle);
CGContextTranslateCTM(c, pt.x, pt.y);

prevRect = rect;

CGContextRestoreGState(c);

我在原点绘制图像,然后在其中心旋转,最后将其平移到应该绘制的位置。但不起作用。


您可以通过设置锚点来使绘制视图中的图像居中旋转。 - Venk
@Tony:这假设图像是在一个单独的视图中(比如一个图像视图),并且提问者不打算将旋转后的图像保存在任何地方。(如果后一种假设成立,将其放入图像视图并旋转可能是正确的解决方案。) - Peter Hosey
4个回答

47
记住坐标变换的一件事情——其中之一是你不是在操作对象,就像图形编辑器中的Transform命令那样; 你正在操纵空间本身。想象一下设备被鹅颈臂悬挂在桌子上,并固定在位置上。坐标变换将移动桌子(平移),将其向左或向右旋转,甚至使其变形(缩放)。另一件关于坐标变换的事情要记住的是变换始终相对于原点。想象一下你的桌子从一个角开始——对应于视图的一个角——直接在屏幕上找到你的视图结束的角落。平移移动桌子的角落和桌子的其余部分,将其滑动到屏幕下方的某个地方。旋转将桌子围绕那个角旋转。还有一件事:变换影响未来而不是过去。先绘制某些东西,然后尝试变换它是行不通的,因为你没有变换你所绘制的内容,而是变换了你要绘制的空间。因此,我们需要在将图像放置在正确的位置之前移动桌子。(我提到最后这部分是因为你有几个变换命令立即被CGContextRestoreGState调用撤消。它们之间没有绘图可以影响。)因此,让我们从未旋转的图像矩形开始。
CGRect imageRect = { pointWhereYouWantTheImageCentered, doodad.size };

现在,CGContextDrawImage需要一个矩形,但正如你所知,它将从该矩形的左下角绘制图像,而不是中心。(因此有了这整个操作。)

所以,这就是你要做的事情。在最后,你将不会在上面显示的那个点上绘制图像,而是在零点处 - 也就是桌子的角落。(不是在角落中心,而是将图像的角放在桌子的角落。)

那怎么做呢?这需要一些设置。您需要移动您的桌子。

首先,您需要将桌子向移动,直到桌子的原点角位于所需的中心点:

CGContextTranslateCTM(context, imageRect.origin.x, imageRect.origin.y);

然后你需要旋转(以其原点为中心旋转桌子):

CGContextRotateCTM(context, angleInRadians);

然后将桌子向下和向左移动图像宽度和高度的一半:

CGContextTranslateCTM(context, imageRect.size.width * -0.5, imageRect.size.height * -0.5);

最后,把图片放在桌子的角落。

CGContextDrawImage(context, (CGRect){ CGPointZero, imageRect.size }, [doodad CGImage]);

你的桌子移动后,矩形的中心正好位于屏幕上你想要图像居中的位置下方,因此图像就被居中对齐了。


抱歉回复晚了,感谢您提供这么详细的答案。您的代码产生了所需的结果,但有一件事我不明白:为什么桌面原点不在图像中心,而是在其原点?我认为这段代码(我以为会起作用)似乎有些偏差:CGContextTranslateCTM(c, rect.origin.x + rect.size.width * 0.5, rect.origin.y + rect.size.height * 0.5); CGContextRotateCTM(c, angle); CGContextDrawImage(c, (CGRect){CGPointZero, rect.size}, doodad.CGImage); - Morrowless
继续之前的内容。这个翻译将桌面原点带到图像中心,因此旋转是围绕图像中心进行的(或者我是这样认为的)。但显然是错误的。 - Morrowless
@Plenilune:CGContextDrawImage 会用图像填充你提供的矩形(必要时进行拉伸)。该矩形的原点——在我的版本中是零点——就是图像的一个角落。我传递了零点,因为我将其转换为那个角落;另一种选择是将我传递给 translate 的相同数字作为图像矩形的原点传递,这样图像的中心(其原点悬挂在桌子上)就在桌子的角落(原点)上。 - Peter Hosey
关于这个问题:对我们来说,保持桌子的角落不动会带来问题(由于其他依赖项)。有没有一种方法可以移动桌子,执行旋转,然后将桌子移回原位(进行反向平移)以便绘制?我现在正在尝试,但很想知道您对此有什么反馈。 - mayabelle
1
这是一个很好的解决方案。但有没有人注意到旋转后的东西在生成的图像上比原来的尺寸更大?我有多个图层(imageview)叠放在一起,当我从它们中生成一个图像时,所有旋转部分都会变得稍微更大(我猜这是因为当我旋转画布进行绘制时,框架发生了改变)。有没有办法避免这种情况,保持物品的原始大小? - rimes
显示剩余4条评论

7
这个函数可以在一个已有的图片上使用弧度角来绘制图像。
Swift 3:
extension UIImage {
    func putImage(image: UIImage, on rect: CGRect, angle: CGFloat = 0.0) -> UIImage{

        let drawRect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        UIGraphicsBeginImageContextWithOptions(drawRect.size, false, 1.0)

        // Start drawing self
        self.draw(in: drawRect)

        // Drawing new image on top
        let context = UIGraphicsGetCurrentContext()!

        // Get the center of new image
        let center = CGPoint(x: rect.midX, y: rect.midY)

        // Set center of image as context action point, so rotation works right
        context.translateBy(x: center.x, y: center.y)
        context.saveGState()

        // Rotate the context
        context.rotate(by: angle)

        // Context origin is image's center. So should draw image on point on origin
        image.draw(in: CGRect(origin: CGPoint(x: -rect.size.width/2, y: -rect.size.height/2), size: rect.size), blendMode: .normal, alpha:
1.0)

        // Go back to context original state.
        context.restoreGState()

        // Get new image
        let newImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()

        return newImage
    }

}

如果您需要创建一个指定大小和颜色的空白图像,请使用以下代码:
extension UIImage {
    convenience init?(size: CGSize, color: UIColor) {
        let rect = CGRect(origin: .zero, size: size)
        UIGraphicsBeginImageContextWithOptions(rect.size, true, 1.0)
        color.setFill()
        UIRectFill(rect)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        guard let cgImage = image?.cgImage else { return nil }
        self.init(cgImage: cgImage)
    }
}

0
你可以使用以下方法旋转图片。
-(UIImage*)rotateImage:(UIImage*)img forAngle:(CGFloat)radian   {

    UIView *rotatedViewBox = [[UIView alloc]initWithFrame:CGRectMake(0, 0, img.size.width, img.size.height)];
    CGAffineTransform t = CGAffineTransformMakeRotation(radian);
    rotatedViewBox.transform = t;
    CGSize rotatedSize = rotatedViewBox.frame.size;

    UIGraphicsBeginImageContext(rotatedSize);

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextTranslateCTM(context, rotatedSize.width, rotatedSize.height);
    CGContextRotateCTM(context, radian);
    CGContextScaleCTM(context, 1.0, -1.0);

    CGContextDrawImage(context, CGRectMake(-img.size.width, -img.size.height, img.size.width, img.size.height), img.CGImage);
    UIImage *returnImg = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();
    return returnImg;
}

如果你使用角度,那么这里是将角度转换为弧度的宏

#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)

0

如果你最终是要将内容绘制到屏幕上(而不是生成一个图像并保存到文件),那么你可以考虑不使用CGContext,而是将图像放入CALayer中,并围绕锚点旋转该图层:

[layer setValue:@(rotationInRadians) forKeyPath:@"transform.rotation.z"];

如果图像是自动生成的(而不是来自捆绑包中的静态资源或用户提供的内容),您可以考虑将其制作成图层,并设置它们的父/共同祖先层的sublayerTransform

[layerContainingTheConstituentLayers setValue:@(rotationInRadians) forKeyPath:@"sublayerTransform.rotation.z"];

也许可以使用视图而不是层来完成这个任务;我是Mac用户,所以对UIView并不是很了解。


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