Rotate image using CGContextDrawImage

2020-02-05 19:14发布

问题:

How can I rotate an image drawn by CGContextDrawImage() at its center?

In 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);

I draw the image at the origin, and rotate it at its center, then translate to where it should be drawn. Not working.

回答1:

The thing to remember about coordinate transformations—well, one of the things to remember—is that you are not manipulating objects, like the Transform command in a graphics editor; you are manipulating space itself.

Imagine that the device is suspended over a desk by a gooseneck arm, and is fixed in position. Coordinate transformations move the desk around (translation), and turn it one way or the other (rotation), and even squash and stretch it (scaling).

Another of the things to remember about coordinate transformations is that transformation is always relative to the origin. Imagine that your desk starts out with one of its corners—corresponding to a corner of your view—directly underneath the corner of where your view ends up on the screen. Translating moves the corner of the desk, and the rest of the desk with it, sliding it one way or another underneath the screen. Rotating turns the desk around that corner.

One more thing: Transformations affect the future, not the past. Drawing something and then trying to transform it won't work, because you don't transform what you've drawn, you transform the space you're going to draw into. So, we need to move the desk before we can place the image in the right spot.

(I mention that last part because you have a couple of transformation commands that are immediately reverted by your CGContextRestoreGState call. There is no drawing in between for them to affect.)

So, let's start with the unrotated rectangle of the image.

CGRect imageRect = { pointWhereYouWantTheImageCentered, doodad.size };

Now, CGContextDrawImage takes a rectangle, but, as you know, it's going to draw the image from the lower-left corner of that rectangle, not the center. (Hence this whole exercise.)

So, here's what you're going to do. At the end of this, you're going to draw the image not at that point shown above, but at the zero point—that is, on the very corner of the desk. (Not centered on the corner, but with the image's corner on the desk's corner.)

How will that work? It takes some set-up. You're going to have to move your desk.

First, you need to move your desk up and right until the origin corner of your desk is at the desired center point:

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

Then you do the rotation (turning the desk around its origin corner):

CGContextRotateCTM(context, angleInRadians);

Then you move the desk back down and left by half of the image's width and height:

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

And then, finally, place the image on the corner of the desk.

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

With your desk so moved, the center of that rectangle lies directly under the point on the screen where you want the image centered, so the image is so centered.



回答2:

This function can draw image on an existing image with rotation angle in radians.

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
    }

}

If you need to create and empty image with size and color, use:

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)
    }
}


回答3:

You can use the following method for rotating image.

-(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;
}

If you use angle, then here is the macro for converting degree to radian

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


回答4:

If you're ultimately drawing to the screen (as opposed to generating an image to save to a file), you might consider not using a CGContext at all, but rather putting the image into a CALayer and rotating the layer about its anchor point:

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

If the image is itself generated (as opposed to a static resource from your bundle or something user-supplied), you might consider making it out of layers and setting the sublayerTransform of their parent/common-ancestor layer:

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

(There may be ways to do this with views instead of layers; I'm a Mac guy, so I don't know UIView very deeply.)