CGContextDrawImage рисует изображение вверх дном, когда передается UIImage.CGImage

Кто-нибудь знает, почему CGContextDrawImage будет рисовать мой образ вверх дном? Я загружаю изображение из своего приложения:

UIImage *image = [UIImage imageNamed:@"testImage.png"];

И затем просто попросите основную графику привлечь его к моему контексту:

CGContextDrawImage(context, CGRectMake(0, 0, 145, 15), image.CGImage);

Он отображает нужное место и размеры, но изображение перевернуто. Я должен упустить что-то действительно очевидное здесь?

Ответ 1

Вместо

CGContextDrawImage(context, CGRectMake(0, 0, 145, 15), image.CGImage);

Используйте

[image drawInRect:CGRectMake(0, 0, 145, 15)];

В середине ваших методов начала/конца CGcontext.

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

Ответ 2

Даже после применения всего, что я упомянул, у меня все еще были драмы с изображениями. В конце концов, я просто использовал Gimp для создания "перевернутой вертикали" версии всех моих изображений. Теперь мне не нужно использовать какие-либо преобразования. Надеюсь, это не вызовет дальнейших проблем в будущем.

Кто-нибудь знает почему CGContextDrawImage будет рисовать мой изображение с ног на голову? Я загружаю изображение из моего приложения:

Quartz2d использует другую систему координат, где начало координат находится в левом нижнем углу. Поэтому, когда Кварц рисует пиксель x [5], y [10] изображения 100 * 100, этот пиксель рисуется в левом нижнем углу вместо верхнего левого. Таким образом вызывая "перевернутое" изображение.

Система координат x совпадает, поэтому вам нужно будет перевернуть координаты y.

CGContextTranslateCTM(context, 0, image.size.height);

Это означает, что мы перевели изображение на 0 единиц по оси x и по высоте изображений по оси y. Однако это само по себе будет означать, что наше изображение все еще вверх дном, просто рисуем "image.size.height" ниже, где мы хотим, чтобы оно было нарисовано.

Руководство по программированию Quartz2D рекомендует использовать ScaleCTM и передавать отрицательные значения, чтобы перевернуть изображение. Вы можете использовать следующий код для этого -

CGContextScaleCTM(context, 1.0, -1.0);

Объедините их сразу перед вызовом CGContextDrawImage, и вы должны правильно нарисовать изображение.

UIImage *image = [UIImage imageNamed:@"testImage.png"];    
CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);       

CGContextTranslateCTM(context, 0, image.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

CGContextDrawImage(context, imageRect, image.CGImage);

Просто будьте осторожны, если ваши координаты imageRect не совпадают с координатами вашего изображения, так как вы можете получить непредвиденные результаты.

Чтобы преобразовать обратно координаты:

CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -imageRect.size.height);

Ответ 3

Лучший из обоих миров, используйте UIImage drawAtPoint: или drawInRect:, все еще указывая свой пользовательский контекст:

UIGraphicsPushContext(context);
[image drawAtPoint:CGPointZero]; // UIImage will handle all especial cases!
UIGraphicsPopContext();

Кроме того, вы избегаете изменения своего контекста с помощью CGContextTranslateCTM или CGContextScaleCTM, который делает второй ответ.

Ответ 4

Релевантные документы Quartz2D: https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010156-CH14-SW4

Отражение системы координат по умолчанию

Перемещение в чертеже UIKit модифицирует поддерживающий CALayer, чтобы выровнять среду рисования, имеющую систему координат LLO, с системой координат по умолчанию UIKit. Если вы используете только методы и функции UIKit для рисования, вам не нужно переворачивать CTM. Однако, если вы смешиваете вызовы функций Core Graphics или Image I/O с вызовами UIKit, может потребоваться переключение CTM.

В частности, если вы рисуете изображение или документ PDF, напрямую вызывая функции Core Graphics, объект визуализируется в обратном порядке в контексте представлений. Вы должны перевернуть CTM, чтобы отобразить изображение и страницы правильно.

Чтобы перевернуть объект, нарисованный в контексте основной графики, чтобы он отображался правильно, когда он отображается в представлении UIKit, вы должны изменить CTM в два этапа. Вы переводите начало координат в верхний левый угол области рисования, а затем применяете масштабный перевод, изменяя y-координату на -1. Код для этого выглядит примерно так:

CGContextSaveGState(graphicsContext);
CGContextTranslateCTM(graphicsContext, 0.0, imageHeight);
CGContextScaleCTM(graphicsContext, 1.0, -1.0);
CGContextDrawImage(graphicsContext, image, CGRectMake(0, 0, imageWidth, imageHeight));
CGContextRestoreGState(graphicsContext);

Ответ 5

Если кто-то заинтересован в беспроблемном решении для рисования изображения в пользовательском прямоугольнике в контексте:

 func drawImage(image: UIImage, inRect rect: CGRect, context: CGContext!) {

    //flip coords
    let ty: CGFloat = (rect.origin.y + rect.size.height)
    CGContextTranslateCTM(context, 0, ty)
    CGContextScaleCTM(context, 1.0, -1.0)

    //draw image
    let rect__y_zero = CGRect(origin: CGPoint(x: rect.origin.x, y:0), size: rect.size)
    CGContextDrawImage(context, rect__y_zero, image.CGImage)

    //flip back
    CGContextScaleCTM(context, 1.0, -1.0)
    CGContextTranslateCTM(context, 0, -ty)

}

Изображение будет масштабировано для заполнения прямоугольника.

Ответ 6

Я не уверен в UIImage, но такое поведение обычно возникает, когда координаты перевернуты. Большинство систем координат OS X имеют начало в нижнем левом углу, как в Postscript и PDF. Но система координат CGImage имеет начало в верхнем левом углу.

Возможные решения могут включать свойство isFlipped или аффинное преобразование scaleYBy:-1.

Ответ 7

UIImage содержит CGImage в качестве основного элемента контента, а также коэффициенты масштабирования и ориентации. Поскольку CGImage и его различные функции получены из OSX, он ожидает, что система координат будет перевернута вверх по сравнению с iPhone. Когда вы создаете UIImage, по умолчанию используется перевернутая ориентация для компенсации (вы можете это изменить!). Используйте свойство. CGImage для доступа к очень мощным функциям CGImage, но рисование на экране iPhone и т.д. Лучше всего выполнять с помощью методов UIImage.

Ответ 8

Мы можем решить эту проблему, используя ту же функцию:

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

[image drawInRect:CGRectMake(gestureEndPoint.x,gestureEndPoint.y,350,92)];

UIGraphicsPopContext();

UIGraphicsEndImageContext();

Ответ 9

Дополнительный ответ с кодом Swift

Кварцевая 2D-графика использует систему координат с началом в левом нижнем углу, тогда как UIKit в iOS использует систему координат с началом в левом верхнем углу. Все работает нормально, но при выполнении некоторых графических операций вам необходимо самостоятельно изменить систему координат. documentation гласит:

Некоторые технологии настраивают свои графические контексты, используя по умолчанию, чем система, используемая кварцем. Относительно Кварц, такая система координат представляет собой модифицированную систему координат и должны быть компенсированы при выполнении какого-либо кварцевого рисунка операции. Наиболее распространенная модифицированная система координат помещает происхождение в верхнем левом углу контекста и изменяет ось y для указания в нижней части страницы.

Это явление можно увидеть в следующих двух экземплярах пользовательских представлений, которые рисуют изображение в их методах drawRect.

введите описание изображения здесь

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

Верхнее изображение

override func drawRect(rect: CGRect) {

    // image
    let image = UIImage(named: "rocket")!
    let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)

    // context
    let context = UIGraphicsGetCurrentContext()

    // draw image in context
    CGContextDrawImage(context, imageRect, image.CGImage)

}

Измененная система координат

override func drawRect(rect: CGRect) {

    // image
    let image = UIImage(named: "rocket")!
    let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)

    // context
    let context = UIGraphicsGetCurrentContext()

    // save the context so that it can be undone later
    CGContextSaveGState(context)

    // put the origin of the coordinate system at the top left
    CGContextTranslateCTM(context, 0, image.size.height)
    CGContextScaleCTM(context, 1.0, -1.0)

    // draw the image in the context
    CGContextDrawImage(context, imageRect, image.CGImage)

    // undo changes to the context
    CGContextRestoreGState(context)
}

Ответ 10

Swift 3.0 и 4.0

yourImage.draw(in: CGRect, blendMode: CGBlendMode, alpha: ImageOpacity)

Нет необходимости изменения

Ответ 11

drawInRect - это, безусловно, путь. Вот еще одна мелочь, которая пригодится при этом. Обычно изображение и прямоугольник, в который он собирается идти, не соответствуют. В этом случае drawInRect растянет изображение. Здесь быстрый и крутой способ убедиться, что рамочный аспект изображения не изменился, путем изменения преобразования (которое будет соответствовать всему этому):

//Picture and irect don't conform, so there'll be stretching, compensate
    float xf = Picture.size.width/irect.size.width;
    float yf = Picture.size.height/irect.size.height;
    float m = MIN(xf, yf);
    xf /= m;
    yf /= m;
    CGContextScaleCTM(ctx, xf, yf);

    [Picture drawInRect: irect];

Ответ 12

Swift 3 CoreGraphics Solution

Если вы хотите использовать CG по любой причине, вместо UIImage, эта конструкция Swift 3, основанная на предыдущих ответах, решила проблему для меня:

if let cgImage = uiImage.cgImage {
    cgContext.saveGState()
    cgContext.translateBy(x: 0.0, y: cgRect.size.height)
    cgContext.scaleBy(x: 1.0, y: -1.0)
    cgContext.draw(cgImage, in: cgRect)
    cgContext.restoreGState()
}

Ответ 14

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

В конце концов я использовал CGImageCreateWithPNGDataProvider:

NSString* imageFileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"clockdial.png"];

return CGImageCreateWithPNGDataProvider(CGDataProviderCreateWithFilename([imageFileName UTF8String]), NULL, YES, kCGRenderingIntentDefault);

Это не страдает от проблем с ориентацией, которые вы получите от получения CGImage от UIImage, и его можно использовать как содержимое CALayer без заминки.

Ответ 15

func renderImage(size: CGSize) -> UIImage {
    return UIGraphicsImageRenderer(size: size).image { rendererContext in
        // flip y axis
        rendererContext.cgContext.translateBy(x: 0, y: size.height)
        rendererContext.cgContext.scaleBy(x: 1, y: -1)

        // draw image rotated/offsetted
        rendererContext.cgContext.saveGState()
        rendererContext.cgContext.translateBy(x: translate.x, y: translate.y)
        rendererContext.cgContext.rotate(by: rotateRadians)
        rendererContext.cgContext.draw(cgImage, in: drawRect)
        rendererContext.cgContext.restoreGState()
    }
}

Ответ 16

Ответ Swift 5 на основе превосходного ответа @ZpaceZombor

Если у вас есть UIImage, просто используйте

var image: UIImage = .... 
image.draw(in: CGRect)

Если у вас есть CGImage, используйте мою категорию ниже

Примечание: в отличие от некоторых других ответов, этот учитывает, что прямоangularьник, который вы хотите нарисовать, может иметь y! = 0. Те ответы, которые не учитывают это, являются неправильными и не будут работать в общем случае.

extension CGContext {
    final func drawImage(image: CGImage, inRect rect: CGRect) {

        //flip coords
        let ty: CGFloat = (rect.origin.y + rect.size.height)
        translateBy(x: 0, y: ty)
        scaleBy(x: 1.0, y: -1.0)

        //draw image
        let rect__y_zero = CGRect(x: rect.origin.x, y: 0, width: rect.width, height: rect.height)
        draw(image, in: rect__y_zero)

        //flip back
        scaleBy(x: 1.0, y: -1.0)
        translateBy(x: 0, y: -ty)

    }
} 

Используйте вот так:

let imageFrame: CGRect = ...
let context: CGContext = ....
let img: CGImage = ..... 
context.drawImage(image: img, inRect: imageFrame)

Ответ 17

Это происходит потому, что QuartzCore имеет систему координат "левый нижний", а UIKit - "верхний левый".

В этом случае вы можете расширить CGContext:

extension CGContext {
  func changeToTopLeftCoordinateSystem() {
    translateBy(x: 0, y: boundingBoxOfClipPath.size.height)
    scaleBy(x: 1, y: -1)
  }
}

// somewhere in render 
ctx.saveGState()
ctx.changeToTopLeftCoordinateSystem()
ctx.draw(cgImage!, in: frame)

Ответ 18

Я использую это Swift 5, чистое расширение Core Graphics, которое правильно обрабатывает ненулевое происхождение в ректах изображений:

extension CGContext {

    /// Draw 'image' flipped vertically, positioned and scaled inside 'rect'.
    public func drawFlipped(_ image: CGImage, in rect: CGRect) {
        self.saveGState()
        self.translateBy(x: 0, y: rect.origin.y + rect.height)
        self.scaleBy(x: 1.0, y: -1.0)
        self.draw(image, in: CGRect(origin: CGPoint(x: rect.origin.x, y: 0), size: rect.size))
        self.restoreGState()
    }
}

Вы можете использовать его точно так же, как CGContext обычный метод draw(: in:):

ctx.drawFlipped(myImage, in: myRect)

Ответ 19

Вы также можете решить эту проблему, выполнив следующие действия:

//Using an Image as a mask by directly inserting UIImageObject.CGImage causes
//the same inverted display problem. This is solved by saving it to a CGImageRef first.

//CGImageRef image = [UImageObject CGImage];

//CGContextDrawImage(context, boundsRect, image);

Nevermind... Stupid caching.