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

179

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

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

Y luego simplemente pedirle a los gráficos principales que lo dibujen a mi contexto:

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

Se presenta en el lugar y las dimensiones correctos, pero la imagen está al revés. ¿Me estoy perdiendo algo realmente obvio aquí?

rustyshelf
fuente

Respuestas:

238

En vez de

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

Utilizar

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

En el medio de sus CGcontextmétodos de inicio / finalización .

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 UIImagemantener el conocimiento de la orientación, mientras que el CGContextDrawImagemétodo obtiene los datos de imagen en bruto subyacentes sin comprender la orientación.

Kendall Helmstetter Gelner
fuente
19
Esta solución no le permite usar funciones CG en su imagen, como CGContextSetAlpha (), mientras que la segunda respuesta sí.
samvermette
2
Buen punto, aunque la mayoría de las veces no necesita niveles alfa personalizados para dibujar una imagen (por lo general, estos se hornean en imágenes con anticipación para las cosas que necesitan alfa). Básicamente mi lema es, usa un pequeño código como puedas porque más código significa más posibilidades de errores.
Kendall Helmstetter Gelner
Hola, ¿Qué quieres decir con en medio de tus métodos CGContext de inicio / fin, puedes darme más código de muestra? Estoy tratando de almacenar el contexto en algún lugar y usar el contexto para dibujar la imagen en
vodkhang
2
Si bien podría funcionar para Rusty, - [UIImage drawInRect:] tiene el problema de que no es seguro para subprocesos. Para mi aplicación en particular, por eso estoy usando CGContextDrawImage () en primer lugar.
Olie
2
Solo para aclarar: Core Graphics está integrado en OS X, donde el sistema de coordenadas está al revés en comparación con iOS (y comienza en el lado inferior izquierdo de OS X). Es por eso que Core Graphics dibuja la imagen al revés, mientras que drawInRect lo maneja por usted
borchero
213

Incluso después de aplicar todo lo que he mencionado, todavía he tenido dramas con las imágenes. Al final, acabo de usar Gimp para crear una versión 'vertical invertida' de todas mis imágenes. Ahora no necesito usar ninguna Transformación. Esperemos que esto no cause más problemas en el futuro.

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

Quartz2d utiliza 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 superior izquierda. Causando así la imagen 'volteada'.

El sistema de coordenadas x coincide, por lo que deberá 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 todavía está al revés, simplemente dibujada "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 justo antes de su CGContextDrawImagellamada y debería tener la imagen dibujada 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 volver a convertir las coordenadas:

CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -imageRect.size.height);
Un acantilado
fuente
esto es bueno, pero descubrí que la traducción y la escala estaban influyendo en otras cosas, incluso cuando intenté volver a configurar 0,0 y 1.0,1.0. Al final, utilicé la solución de Kendall, pero me doy cuenta de que esto está usando UIImage en lugar de cosas de CGImage de nivel inferior con las que estamos trabajando aquí
PeanutPower
3
Si está utilizando drawLayer y desea dibujar un CGImageRef en el contexto, esta técnica aquí solo quiere que se ordene al médico. Si tiene el rect para la imagen, entonces CGContextTranslateCTM (context, 0, image.size.height + image.origin.y), luego establezca rect.origin.y en 0 antes de CGContextDrawImage (). ¡Gracias!
David H
Tuve problemas una vez con ImageMagick donde la cámara del iPhone resultó en una orientación incorrecta. Resulta que es la bandera de orientación en el archivo de imagen, por lo que las imágenes de retrato eran, digamos 960px x 640px con la bandera en "izquierda", ya que en el lado izquierdo está la parte superior (o algo parecido a eso). Este hilo podría ayudar: stackoverflow.com/questions/1260249/…
Hari Karam Singh
8
¿Por qué no usa CGContextSaveGState()y CGContextRestoreGState()para guardar y restaurar la matriz de transformación?
Ja͢ck
20

Lo mejor de ambos mundos, use UIImage's drawAtPoint:o drawInRect:mientras aún especifica su contexto personalizado:

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

También evita modificar su contexto con CGContextTranslateCTMo lo CGContextScaleCTMque hace la segunda respuesta.

Rivera
fuente
Esta es una gran idea, pero para mí resultó en una imagen que estaba al revés y de izquierda a derecha. Supongo que los resultados variarán de una imagen a otra.
Luke Rogers,
¿Quizás esto dejó de funcionar con versiones posteriores de iOS? En ese momento funcionó con todo para mí.
Rivera
Creo que en realidad se debía a cómo estaba creando el contexto. Una vez que comencé a usar en UIGraphicsBeginImageContextWithOptionslugar de simplemente iniciar un nuevo CGContext, parecía funcionar.
Luke Rogers
12

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

Voltear en el dibujo UIKit modifica el CALayer de respaldo para alinear un entorno de dibujo que tiene un sistema de coordenadas LLO con el sistema de coordenadas predeterminado de UIKit. Si solo utiliza los métodos y la función UIKit para dibujar, no debería tener que voltear la marca comunitaria. Sin embargo, si combina llamadas de función Core Graphics o Image I / O con llamadas UIKit, puede ser necesario voltear el CTM.

Específicamente, si dibuja una imagen o un documento PDF llamando directamente a las funciones de Core Graphics, el objeto se muestra al revés en el contexto de la vista. Debe voltear la marca comunitaria 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 UIKit, debe modificar la CTM en dos pasos. 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);
kcmoffat
fuente
10

Si alguien está interesado en una solución obvia para dibujar una imagen en un rectángulo 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.

ZpaceZombor
fuente
7

No estoy seguro 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 CGImagesistema de coordenadas tiene su origen en la esquina superior izquierda.

Las posibles soluciones pueden involucrar una isFlippedpropiedad o una scaleYBy:-1transformación afín.

Mouviciel
fuente
3

UIImagecontiene a CGImagecomo su principal miembro de contenido, así como factores de escala y orientación. Dado que CGImagey sus diversas funciones se derivan de OSX, espera un sistema de coordenadas al revés en comparación con el iPhone. Cuando crea un UIImage, el valor predeterminado es una orientación invertida para compensar (¡puede cambiar esto!). Utilizar el . CGImagepropiedad para acceder a las CGImagefunciones muy potentes , pero dibujar en la pantalla del iPhone, etc., se realiza mejor con los UIImagemétodos.

James L
fuente
1
¿Cómo se cambia la orientación al revés predeterminada de UIImage?
MegaManX
3

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 gráficas, debe modificar el sistema de coordenadas usted mismo. La documentación dice:

Algunas tecnologías configuran sus contextos gráficos utilizando un sistema de coordenadas predeterminado diferente al utilizado por Quartz. En relación con Quartz, dicho sistema de coordenadas es un sistema de coordenadas modificado y debe compensarse al realizar algunas operaciones de dibujo de Quartz. 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 apuntar 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 drawRectmétodos.

ingrese la descripción de la imagen aquí

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

Imagen al revés

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)
}
Suragch
fuente
3

Swift 3.0 y 4.0

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

No se necesita alteración

Abdul Waheed
fuente
2

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();
Priyanka V
fuente
1

drawInRectes sin duda el camino a seguir. Aquí hay otra pequeña cosa que será útil cuando haga esto. Por lo general, la imagen y el rectángulo en el que va a ir no se ajustan. En ese caso drawInRectse estirará la imagen. Aquí hay una forma rápida y genial de asegurarse de que la relación de aspecto de la imagen no cambie, invirtiendo la transformación (que será para encajar 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];
Marc Meyer
fuente
1

Solución Swift 3 CoreGraphics

Si desea utilizar CG por cualquiera que sea su razón, en lugar de UIImage, esta construcción de Swift 3 basada en respuestas anteriores resolvió el problema por mí:

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
fuente
1

Ocurre porque QuartzCore tiene el sistema de coordenadas "abajo a la izquierda", mientras que UIKit - "arriba a la izquierda".

En el caso, puede extender 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
fuente
1

Utilizo esta extensión Swift 5 , pura Core Graphics que maneja correctamente los orígenes distintos de cero en los rectificadores de imagen:

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

Puede usarlo exactamente como CGContextel draw(: in:)método habitual :

ctx.drawFlipped(myImage, in: myRect)
Robin Stewart
fuente
0

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

Al final terminé usando en su CGImageCreateWithPNGDataProviderlugar:

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

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

Esto no sufre los problemas de orientación que obtendría al obtener CGImagede a UIImagey se puede usar como el contenido de a CALayersin problemas.

Jack
fuente
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
fuente
0

Respuesta rápida 5 basada en la excelente respuesta de @ZpaceZombor

Si tienes un UIImage, solo usa

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

Si tienes una CGImage usa mi categoría a continuación

Nota: a diferencia de otras respuestas, esta toma en cuenta que el rectángulo que desea dibujar puede tener y! = 0. Esas respuestas que no toman en cuenta eso son incorrectas y no funcionarán en el caso general.

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)

    }
} 

Usar así:

let imageFrame: CGRect = ...
let context: CGContext = ....
let img: CGImage = ..... 
context.drawImage(image: img, inRect: imageFrame)
n13
fuente
Esta es una solución casi perfecta, pero considere cambiar la función para dibujar (imagen: UIImage, inRect rect: CGRect) y manejar uiImage.cgimage dentro del método
Marte
-5

También puede 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.
Ben Zeeman
fuente