Повернуть изображение с помощью 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);

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

4 ответа

Решение

Что нужно помнить о преобразованиях координат - хорошо, одна из вещей, о которых следует помнить - это то, что вы не манипулируете объектами, как команда Transform в графическом редакторе; Вы манипулируете самим пространством.

Представьте, что устройство подвешено к столу с помощью гусиной шеи и зафиксировано на месте. Преобразования координат перемещают стол (перевод), и поворачивают его так или иначе (вращение), и даже раздавливают и растягивают (масштабирование).

Еще одна вещь, которую следует помнить о преобразованиях координат, заключается в том, что преобразование всегда связано с источником. Представьте, что ваш стол начинается с одного из его углов, соответствующих углу вашего обзора, прямо под углом того места, где ваш вид заканчивается на экране. Translating перемещает угол стола, а остальную часть стола с ним, так или иначе сдвигая его под экран. Вращение поворачивает стол за этот угол.

Еще одна вещь: преобразования влияют на будущее, а не на прошлое. Рисование чего-то, а затем попытка трансформировать это не сработает, потому что вы не трансформируете то, что нарисовали, вы трансформируете пространство, в которое собираетесь рисовать. Итак, нам нужно переместить стол, прежде чем мы сможем разместить изображение в нужном месте.

(Я упоминаю эту последнюю часть, потому что у вас есть пара команд преобразования, которые немедленно возвращаются вашим 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]);

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

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

Свифт 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)
    }
}

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

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

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

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

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

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

-(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)
Другие вопросы по тегам