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

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

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

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

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

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


person rustyshelf    schedule 03.02.2009    source источник


Ответы (17)


Вместо

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

Использовать

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

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

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

person Kendall Helmstetter Gelner    schedule 06.02.2009
comment
Это решение не позволяет вам использовать функции CG для вашего изображения, такие как CGContextSetAlpha (), тогда как второй ответ позволяет. - person samvermette; 18.05.2010
comment
Хороший момент, хотя в большинстве случаев вам не нужны настраиваемые альфа-уровни для рисования изображения (обычно они заранее запекаются в изображения для вещей, которым требуется альфа). По сути, мой девиз: используйте как можно меньше кода, потому что чем больше кода, тем больше шансов на ошибку. - person Kendall Helmstetter Gelner; 21.05.2010
comment
Привет, что вы имеете в виду в середине ваших методов begin / end CGContext, не могли бы вы дать мне больше примеров кода. Я пытаюсь где-то сохранить контекст и использовать контекст для рисования изображения в - person vodkhang; 22.09.2010
comment
Вы не должны хранить контекст, используйте UIGraphicsGetCurrentContext (), чтобы получить контекст, который вы можете передать этому методу. Также существует пара методов UIGraphicsBeginImageContext и UIGraphicsEndImageContext, которые вы используете при рисовании в настраиваемое изображение вместо текущего контекста. - person Kendall Helmstetter Gelner; 22.09.2010
comment
Хотя это может сработать для Расти, - [UIImage drawInRect:] имеет проблему, заключающуюся в том, что он не является потокобезопасным. Именно поэтому для моего конкретного приложения я в первую очередь использую CGContextDrawImage (). - person Olie; 16.11.2010
comment
Оли, не могли бы вы подробнее рассказать / дать ссылку на отсутствие потоковобезопасности в drawInRect? - person David Moles; 18.02.2011
comment
Apple заявляет в документации UIKit, что вы должны вызывать методы пользовательского интерфейса в основном потоке, который включает методы рисования ... - person Kendall Helmstetter Gelner; 18.02.2011
comment
Просто чтобы уточнить: Core Graphics построена на OS X, где система координат перевернута по сравнению с iOS (y начинается с нижнего левого угла в OS X). Вот почему Core Graphics рисует изображение вверх ногами, тогда как drawInRect делает это за вас. - person borchero; 21.09.2015

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

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

Quartz2d использует другую систему координат, где начало координат находится в нижнем левом углу. Поэтому, когда Quartz рисует пиксель 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);
person Cliff Viegas    schedule 04.02.2009
comment
это хорошо, но я обнаружил, что перевод и масштабирование влияют на другие вещи, даже когда я пытался вернуться к 0,0 и 1.0,1.0. В конце концов я пошел с решением Кендалла, но я понимаю, что это использует UIImage, а не материал CGImage нижнего уровня, с которым мы работаем здесь - person acheo; 23.01.2010
comment
Если вы используете drawLayer и хотите нарисовать CGImageRef в контексте, этот метод здесь просто нужен врачу! Если есть прямоугольник для изображения, тогда CGContextTranslateCTM (context, 0, image.size.height + image.origin.y), затем установите для rect.origin.y значение 0 перед CGContextDrawImage (). Спасибо! - person David H; 20.02.2012
comment
Однажды у меня были проблемы с ImageMagick, когда камера iPhone приводила к неправильной ориентации. Оказывается, это делает флаг ориентации в файле изображения, поэтому портретные изображения были, скажем, 960 x 640 пикселей с флагом, установленным влево - так как в левой части находится верхняя часть (или что-то близкое к этому). Эта тема может помочь: stackoverflow.com/questions/1260249/ - person Hari Honor; 17.08.2013
comment
Почему бы вам не использовать CGContextSaveGState() и CGContextRestoreGState() для сохранения и восстановления матрицы преобразования? - person Ja͢ck; 11.03.2014

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

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

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

person Rivera    schedule 07.08.2013
comment
Это отличная идея, но для меня она привела к тому, что изображение было перевернуто слева направо. Я предполагаю, что результаты будут отличаться от изображения к изображению. - person Luke Rogers; 08.05.2017
comment
Может быть, это перестало работать с более поздними версиями iOS? В то время у меня все работало. - person Rivera; 08.05.2017
comment
Думаю, на самом деле все сводилось к тому, как я создавал контекст. Как только я начал использовать UIGraphicsBeginImageContextWithOptions вместо того, чтобы просто создавать новый CGContext, казалось, что это сработало. - person Luke Rogers; 09.05.2017

Соответствующие документы Quartz2D: https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/1410/156-TP400/.

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

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

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

Чтобы перевернуть объект, нарисованный в контексте Core Graphics, чтобы он правильно отображался при отображении в представлении 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);
person kcmoffat    schedule 08.04.2016

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

 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)

}

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

person ZpaceZombor    schedule 18.02.2016

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

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

person mouviciel    schedule 03.02.2009

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

person James L    schedule 31.07.2010
comment
как изменить ориентацию UIImage по умолчанию вверх ногами? - person MegaManX; 20.02.2013

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

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

Некоторые технологии настраивают свои графические контексты, используя систему координат по умолчанию, отличную от той, которая используется Quartz. По сравнению с Кварцем такая система координат является модифицированной системой координат, и ее необходимо компенсировать при выполнении некоторых операций рисования Кварца. Наиболее распространенная модифицированная система координат помещает начало координат в верхний левый угол контекста, а ось 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)
}
person Suragch    schedule 22.12.2015

Я использую это 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()
    }
}

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

ctx.drawFlipped(myImage, in: myRect)
person Robin Stewart    schedule 20.10.2019

Swift 3.0 и 4.0

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

Никаких изменений не требуется

person Abdul Waheed    schedule 04.08.2017

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];
person marc meyer    schedule 22.02.2010

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

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

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

UIGraphicsPopContext();

UIGraphicsEndImageContext();
person Priyanka V    schedule 25.08.2011

Решение Swift 3 CoreGraphics

Если вы хотите использовать 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()
}
person biomiker    schedule 31.08.2017

Это происходит потому, что 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)
person dimpiax    schedule 30.09.2019

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

В конце концов я использовал _ 1_ вместо этого:

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

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

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

person Ja͢ck    schedule 15.03.2014

Ответ Swift 5 на основе отличного ответа @ ZpaceZombor

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

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

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

Примечание. В отличие от некоторых других ответов, этот учитывает, что прямоугольник, который вы хотите нарисовать, может иметь 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)
person n13    schedule 20.09.2019
comment
Это почти идеальное решение, но рассмотрите возможность изменения функции рисования (image: UIImage, inRect rect: CGRect) и обработки uiImage.cgimage внутри метода. - person Mars; 30.05.2020

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

//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.
person Ben Zeeman    schedule 04.04.2012