CGContextDrawImage рисует изображение вверх ногами при передаче UIImage.CGImage
Кто-нибудь знает почему CGContextDrawImage
будет рисовать мое изображение с ног на голову? Я загружаю изображение из моего приложения:
UIImage *image = [UIImage imageNamed:@"testImage.png"];
А затем просто попросите основную графику нарисовать ее в моем контексте:
CGContextDrawImage(context, CGRectMake(0, 0, 145, 15), image.CGImage);
Он рендерит в нужном месте и размеры, но изображение перевернуто. Должно быть, я что-то упускаю из виду?
16 ответов
Вместо
CGContextDrawImage(context, CGRectMake(0, 0, 145, 15), image.CGImage);
использование
[image drawInRect:CGRectMake(0, 0, 145, 15)];
В середине вашего начала / конца CGcontext
методы.
Это позволит нарисовать изображение с правильной ориентацией в текущем контексте изображения - я уверен, что это как-то связано с UIImage
держась за знание ориентации в то время как CGContextDrawImage
Метод получает исходные необработанные данные изображения без понимания ориентации.
Обратите внимание, что вы столкнетесь с этой проблемой во многих областях, но один конкретный пример связан с изображениями пользователей адресной книги.
Даже после применения всего, что я упомянул, у меня все еще были драмы с изображениями. В конце концов, я просто использовал 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);
Лучшее из обоих миров, используйте UIImage drawAtPoint:
или же drawInRect:
при этом указав свой пользовательский контекст:
UIGraphicsPushContext(context);
[image drawAtPoint:CGPointZero]; // UIImage will handle all especial cases!
UIGraphicsPopContext();
Также вы избегаете изменения своего контекста CGContextTranslateCTM
или же CGContextScaleCTM
что делает второй ответ.
Я использую это расширение Swift 5, чистое ядро графики, которое правильно обрабатывает ненулевое происхождение в изображениях:
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)
Соответствующие документы Quartz2D: https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html
Отражение системы координат по умолчанию
Переключение в чертеже 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);
Если кого-то интересует простое решение для рисования изображения в пользовательском прямоугольнике в контексте:
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)
}
Изображение будет масштабировано, чтобы заполнить прямоугольник.
Я не уверен в UIImage
, но этот тип поведения обычно происходит, когда координаты перевернуты. Большинство систем координат OS X имеют свое происхождение в нижнем левом углу, как в Postscript и PDF. Но CGImage
Система координат имеет свое начало в верхнем левом углу.
Возможные решения могут включать isFlipped
собственность или scaleYBy:-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)
UIImage
содержит CGImage
в качестве основного члена контента, а также факторов масштабирования и ориентации. поскольку CGImage
и его различные функции получены из OSX, он ожидает систему координат, которая перевернута по сравнению с iPhone. Когда вы создаете UIImage
, по умолчанию используется перевернутая ориентация для компенсации (вы можете изменить это!). Использовать.CGImage
собственность для доступа к очень мощным CGImage
функции, но рисование на экране iPhone и т. д. лучше всего сделать с UIImage
методы.
Swift 3.0 и 4.0
yourImage.draw(in: CGRect, blendMode: CGBlendMode, alpha: ImageOpacity)
Никаких изменений не требуется
Мы можем решить эту проблему, используя ту же функцию:
UIGraphicsBeginImageContext(image.size);
UIGraphicsPushContext(context);
[image drawInRect:CGRectMake(gestureEndPoint.x,gestureEndPoint.y,350,92)];
UIGraphicsPopContext();
UIGraphicsEndImageContext();
Дополнительный ответ с кодом 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)
}
Решение 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()
}
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];
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)
В ходе моего проекта я перепрыгнул с ответа Кендалла на ответ Клиффа, чтобы решить эту проблему для изображений, загружаемых с самого телефона.
В конце концов я использовал CGImageCreateWithPNGDataProvider
вместо:
NSString* imageFileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"clockdial.png"];
return CGImageCreateWithPNGDataProvider(CGDataProviderCreateWithFilename([imageFileName UTF8String]), NULL, YES, kCGRenderingIntentDefault);
Это не страдает от проблем ориентации, которые вы получите от получения CGImage
из UIImage
и это может быть использовано в качестве содержимого CALayer
Без промедления.
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()
}
}
Вы также можете решить эту проблему, выполнив это:
//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.