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

179

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

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

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

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

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

rustyshelf
источник

Ответы:

238

Вместо того

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

использование

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

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

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

Кендалл Хельмштеттер Гелнер
источник
19
Это решение не позволяет вам использовать функции CG на вашем изображении, такие как CGContextSetAlpha (), тогда как второй ответ делает это.
Самверметт
2
Хорошая мысль, хотя в основном вам не нужны настраиваемые альфа-уровни для рисования изображения (обычно они запекаются в изображениях заранее для вещей, которые нуждаются в альфа-канале). По сути, мой девиз: используйте как можно меньше кода, потому что больше кода означает больше шансов на ошибки.
Кендалл Хельмштеттер Гелнер
Привет, Что вы подразумеваете под серединой ваших начальных / конечных методов CGContext, не могли бы вы дать мне больше примеров кода. Я пытаюсь где-то сохранить контекст и использовать контекст для рисования изображения
vodkhang
2
Хотя это может работать для Rusty, - [UIImage drawInRect:] имеет проблему, заключающуюся в том, что он не является поточно-ориентированным. Для моего конкретного приложения, именно поэтому я использую CGContextDrawImage () в первую очередь.
Оли
2
Просто для пояснения: Core Graphics встроена в OS X, где система координат перевернута вверх по сравнению с iOS (y начинается в нижнем левом углу в OS X). Вот почему Core Graphics рисует изображение с ног на голову, в то время как drawInRect обрабатывает это для вас
borchero
212

Даже после применения всего, что я упомянул, у меня все еще были драмы с изображениями. В конце концов, я просто использовал 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);
Клифф Виегас
источник
это хорошо, но я обнаружил, что на другие вещи влиял перевод и масштабирование, даже когда я попытался установить обратно 0,0 и 1,0,1,0, я в конце концов решил использовать решение Кендалла, но я понимаю, что это использует UIImage, а не CGImage с более низким уровнем, с которым мы здесь работаем
PeanutPower
3
Если вы используете drawLayer и хотите нарисовать CGImageRef в контексте, эту технику просто нужно заказать врачу! Если есть прямоугольник для изображения, то CGContextTranslateCTM (context, 0, image.size.height + image.origin.y), затем установите rect.origin.y в 0 перед CGContextDrawImage (). Спасибо!
Дэвид Х
Однажды у меня возникли проблемы с ImageMagick, когда камера iPhone привела к неправильной ориентации. Оказывается, это делает флаг ориентации в файле изображения, поэтому портретные изображения были, скажем, 960px x 640px с флагом, установленным в «left» - как на левой стороне вверху (или что-то похожее на это). Эта ветка может помочь: stackoverflow.com/questions/1260249/…
Хари Карам Сингх
8
Почему вы не используете CGContextSaveGState()и CGContextRestoreGState()для сохранения и восстановления матрицы преобразования?
Яцк
20

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

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

Также вы избегаете изменения своего контекста CGContextTranslateCTMили того, CGContextScaleCTMчто делает второй ответ.

Rivera
источник
Это отличная идея, но для меня это привело к изображению, которое было и вверх ногами, и слева направо. Я предполагаю, что результаты будут отличаться от изображения к изображению.
Люк Роджерс
Может быть, это перестало работать с более поздними выпусками iOS? В то время у меня все работало.
Ривера
Я думаю, что это на самом деле было связано с тем, как я создавал контекст. Как только я начал использовать UIGraphicsBeginImageContextWithOptionsвместо того, чтобы просто начинать новый CGContext, это, казалось, работало.
Люк Роджерс
12

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

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

Переключение в чертеже 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);
kcmoffat
источник
10

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

 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)

}

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

ZpaceZombor
источник
7

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

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

mouviciel
источник
3

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

Джеймс Л
источник
1
как изменить стандартную ориентацию UIImage по умолчанию?
MegaManX
3

Дополнительный ответ с кодом 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)
}
Suragch
источник
3

Swift 3.0 и 4.0

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

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

Абдул Вахид
источник
2

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

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

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

UIGraphicsPopContext();

UIGraphicsEndImageContext();
Приянка В
источник
1

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];
Марк Мейер
источник
1

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()
}
biomiker
источник
1

Это происходит потому, что 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)
dimpiax
источник
1

Я использую это 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)
Робин Стюарт
источник
0

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

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

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

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

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

Разъем
источник
0
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()
    }
}
neoneye
источник
0

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)
n13
источник
Это почти идеальное решение, но рассмотрите возможность изменения функции рисования (image: UIImage, inRect rect: CGRect) и обработки uiImage.cgimage внутри метода
Mars
-5

Вы также можете решить эту проблему следующим образом:

//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.
Бен Зееман
источник