Поворот изображения с помощью CGContextDrawImage

Как я могу повернуть изображение, нарисованное 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);

Я рисую изображение в начале координат и поворачиваю его в центре, а затем перемещаю туда, где оно должно быть нарисовано. Не работает.

Ответ 1

То, что нужно помнить о преобразованиях координат - ну, одна из вещей, которые нужно запомнить, - это то, что вы не манипулируете объектами, такими как команда 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]);

Когда ваш стол так перемещен, центр этого прямоугольника находится прямо под точкой на экране, где вы хотите, чтобы изображение было центрировано, поэтому изображение имеет такую ​​центральную ориентацию.

Ответ 2

Эта функция может рисовать изображение на существующем изображении с углом поворота в радианах.

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

Ответ 3

Вы можете использовать следующий метод для поворота изображения.

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

Ответ 4

Если вы в конечном счете рисуете на экране (в отличие от создания изображения для сохранения в файл), вы можете вообще не использовать CGContext, а скорее помещать изображение в CALayer и вращать слой вокруг его опорная точка:

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

Если изображение само генерируется (в отличие от статического ресурса из вашего пакета или чего-то, что предоставляется пользователем), вы можете подумать о том, чтобы сделать его из слоев и установить sublayerTransform своего родительского/общего-предка слоя:

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

(Могут быть способы сделать это с помощью представлений вместо слоев, я парень Mac, поэтому я не очень глубоко знаю UIView.)