CGContextDrawImage dibuja la imagen al revés cuando se pasa UIImage.CGImage

¿Alguien sabe por qué CGContextDrawImage mi imagen al revés? Estoy cargando una imagen desde mi aplicación:

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

Y luego simplemente pidiendo gráficos centrales para dibujarlo en mi contexto:

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

Se renderiza en el lugar y dimensiones correctos, pero la imagen está boca abajo. Debo estar perdiendo algo realmente obvio aquí?

En lugar de

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

Utilizar

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

En medio de tus métodos de inicio / finalización de CGcontext .

Esto dibujará la imagen con la orientación correcta en su contexto de imagen actual. Estoy bastante seguro de que esto tiene algo que ver con el UIImage conocimiento de la orientación, mientras que el método CGContextDrawImage obtiene los datos de imagen sin procesar subyacentes sin entender la orientación.

Tenga en cuenta que se encontrará con este problema en muchas áreas, pero un ejemplo específico es el de las imágenes de usuario de la libreta de direcciones.

Incluso después de aplicar todo lo que he mencionado, todavía tengo dtwigs con las imágenes. Al final, acabo de usar Gimp para crear una versión ‘volteada vertical’ de todas mis imágenes. Ahora no necesito usar ninguna Transformación. Con suerte, esto no causará más problemas en la pista.

¿Alguien sabe por qué CGContextDrawImage dibujaría mi imagen al revés? Estoy cargando una imagen desde mi aplicación:

Quartz2d usa un sistema de coordenadas diferente, donde el origen está en la esquina inferior izquierda. Entonces, cuando Quartz dibuja el píxel x [5], y [10] de una imagen de 100 * 100, ese píxel se dibuja en la esquina inferior izquierda en lugar de la esquina superior izquierda. Por lo tanto, causando la imagen ‘volteada’.

El sistema de coordenadas x coincide, por lo que necesitarás voltear las coordenadas y.

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

Esto significa que hemos traducido la imagen por 0 unidades en el eje xy por la altura de las imágenes en el eje y. Sin embargo, esto solo significará que nuestra imagen aún está boca abajo, simplemente se dibuja “image.size.height” debajo de donde deseamos que se dibuje.

La guía de progtwigción Quartz2D recomienda usar ScaleCTM y pasar valores negativos para voltear la imagen. Puede usar el siguiente código para hacer esto:

 CGContextScaleCTM(context, 1.0, -1.0); 

Combine los dos momentos antes de su llamada CGContextDrawImage y la imagen debe dibujarse correctamente.

 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); 

Solo tenga cuidado si sus coordenadas imageRect no coinciden con las de su imagen, ya que puede obtener resultados no deseados.

Para convertir de vuelta las coordenadas:

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

Lo mejor de ambos mundos, use drawAtPoint: de UIImage drawAtPoint: o drawInRect: mientras sigue especificando su contexto personalizado:

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

También evitas modificar tu contexto con CGContextTranslateCTM o CGContextScaleCTM que CGContextScaleCTM que hace la segunda respuesta.

Documentos relevantes de Quartz2D: https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010156-CH14-SW4

Voltear el sistema de coordenadas predeterminado

Al voltearse en el dibujo de UIKit, se modifica el CALayer de respaldo para alinear un entorno de dibujo que tenga un sistema de coordenadas LLO con el sistema de coordenadas predeterminado de UIKit. Si solo usa los métodos y funciones de UIKit para dibujar, no debería necesitar voltear el CTM. Sin embargo, si mezcla llamadas de funciones de gráficos de núcleo o de imagen de E / S con llamadas de UIKit, puede ser necesario cambiar el CTM.

Específicamente, si dibuja una imagen o un documento PDF llamando directamente a las funciones de Core Graphics, el objeto se muestra boca abajo en el contexto de la vista. Debe voltear el CTM para mostrar la imagen y las páginas correctamente.

Para voltear un objeto dibujado a un contexto de Core Graphics para que aparezca correctamente cuando se muestra en una vista de UIKit, debe modificar el CTM en dos pasos. Usted traduce el origen a la esquina superior izquierda del área de dibujo, y luego aplica una traducción de escala, modificando la coordenada y por -1. El código para hacer esto es similar al siguiente:

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

No estoy seguro de UIImage , pero este tipo de comportamiento generalmente ocurre cuando se cambian las coordenadas. La mayoría de los sistemas de coordenadas OS X tienen su origen en la esquina inferior izquierda, como en Postscript y PDF. Pero el sistema de coordenadas CGImage tiene su origen en la esquina superior izquierda.

Las soluciones posibles pueden incluir una propiedad isFlipped o una transformación afín scaleYBy:-1 .

Si alguien está interesado en una solución obvia para dibujar una imagen en un contexto personalizado en un contexto:

  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) } 

La imagen se escalará para llenar el rect.

Podemos resolver este problema usando la misma función:

 UIGraphicsBeginImageContext(image.size); UIGraphicsPushContext(context); [image drawInRect:CGRectMake(gestureEndPoint.x,gestureEndPoint.y,350,92)]; UIGraphicsPopContext(); UIGraphicsEndImageContext(); 

UIImage contiene un CGImage como miembro de contenido principal, así como factores de escala y orientación. Dado que CGImage y sus diversas funciones se derivan de OSX, espera un sistema de coordenadas al revés en comparación con el iPhone. Cuando creas un UIImage , por defecto tiene una orientación al revés para compensar (¡puedes cambiar esto!). Utilizar el . CGImage propiedad CGImage para acceder a las poderosas funciones CGImage , pero dibujar en la pantalla del iPhone, etc. se realiza mejor con los métodos UIImage .

Swift 3.0 y 4.0

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

No se necesitan modificaciones

Respuesta suplementaria con código Swift

Los gráficos 2D de cuarzo usan un sistema de coordenadas con el origen en la parte inferior izquierda, mientras que UIKit en iOS usa un sistema de coordenadas con el origen en la parte superior izquierda. Por lo general, todo funciona bien, pero al realizar algunas operaciones de gráficos, usted mismo debe modificar el sistema de coordenadas. La documentación dice:

Algunas tecnologías configuran sus contextos gráficos usando un sistema de coordenadas predeterminado distinto al utilizado por Quartz. En relación con el Cuarzo, dicho sistema de coordenadas es un sistema de coordenadas modificado y debe ser compensado cuando se realizan algunas operaciones de dibujo de Cuarzo. El sistema de coordenadas modificado más común coloca el origen en la esquina superior izquierda del contexto y cambia el eje y para señalar hacia la parte inferior de la página.

Este fenómeno se puede ver en las siguientes dos instancias de vistas personalizadas que dibujan una imagen en sus métodos drawRect .

enter image description here

En el lado izquierdo, la imagen está al revés y en el lado derecho el sistema de coordenadas ha sido traducido y escalado para que el origen esté en la parte superior izquierda.

Imagen invertida

 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) } 

Sistema de coordenadas modificado

 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) } 

drawInRect es sin duda el camino a seguir. Aquí hay otra pequeña cosa que será útil al hacer esto. Por lo general, la imagen y el rectángulo en el que va a ir no se ajustan. En ese caso, drawInRect la imagen. Aquí hay una manera rápida y genial de asegurarse de que la relación de aspecto de la imagen no se modifique, invirtiendo la transformación (que se ajustará a todo):

 //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]; 

Solución CoreGraphics de Swift 3

Si desea utilizar CG por la razón que sea, en lugar de UIImage, esta construcción de Swift 3 basada en respuestas anteriores me solucionó el problema:

 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.restreGState() } 

Esta es una muestra de adjuntar una imagen de UIView a un MFMailComposeViewController .

Durante el transcurso de mi proyecto salté de la respuesta de Kendall a la respuesta de Cliff para resolver este problema en el caso de las imágenes que se cargan desde el teléfono.

Al final, terminé usando CGImageCreateWithPNGDataProvider en CGImageCreateWithPNGDataProvider lugar:

 NSString* imageFileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"clockdial.png"]; return CGImageCreateWithPNGDataProvider(CGDataProviderCreateWithFilename([imageFileName UTF8String]), NULL, YES, kCGRenderingIntentDefault); 

Esto no tiene los problemas de orientación que obtendría al obtener CGImage de un UIImage y puede usarse como contenido de un CALayer sin problemas.

 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.restreGState() } } 

También puedes resolver este problema haciendo esto:

 //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. 
Intereting Posts