ios - CGContextDrawImage dibuja la imagen al revés cuando se pasa UIImage.CGImage
cocoa-touch core-graphics (16)
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);
¿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í?
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.
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 dramas 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 programació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(ctx, 1.0, -1.0);
CGContextTranslateCTM(ctx, 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.
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
.
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();
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.
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.
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
.
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];
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 documentation 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
.
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)
}
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.restoreGState()
}
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()
}
}