CGContextDrawImage dessine l'image à l'envers lorsqu'elle est transmise à UIImage.CGImage


179

Est-ce que quelqu'un sait pourquoi CGContextDrawImagedessiner mon image à l'envers? Je charge une image depuis mon application:

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

Et puis demander simplement aux graphiques de base de le dessiner dans mon contexte:

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

Il rend au bon endroit et aux bonnes dimensions, mais l'image est à l'envers. Je dois manquer quelque chose de vraiment évident ici?

Réponses:


238

Au lieu de

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

Utilisation

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

Au milieu de vos CGcontextméthodes de début / fin .

Cela dessinera l'image avec l'orientation correcte dans votre contexte d'image actuel - je suis presque sûr que cela a quelque chose à voir avec le UIImagemaintien de la connaissance de l'orientation tandis que la CGContextDrawImageméthode obtient les données d'image brutes sous-jacentes sans aucune compréhension de l'orientation.


19
Cette solution ne vous permet pas d'utiliser des fonctions CG sur votre image, telles que CGContextSetAlpha (), contrairement à la 2ème réponse.
samvermette

2
Bon point, même si la plupart du temps, vous n'avez pas besoin de niveaux alpha personnalisés pour dessiner une image (généralement ceux-ci sont cuits dans les images à l'avance pour les choses qui ont besoin d'alpha). Fondamentalement, ma devise est, utilisez un peu de code comme vous le pouvez, car plus de code signifie plus de risques de bogues.
Kendall Helmstetter Gelner

Salut, Que voulez-vous dire par au milieu de vos méthodes CGContext de début / fin, pouvez-vous me donner plus d'exemple de code. J'essaie de stocker le contexte quelque part et d'utiliser le contexte pour dessiner l'image dans
vodkhang

2
Bien que cela puisse fonctionner pour Rusty, - [UIImage drawInRect:] a le problème qu'il n'est pas thread-safe. Pour mon application particulière, c'est pourquoi j'utilise CGContextDrawImage () en premier lieu.
Olie

2
Juste pour clarifier: Core Graphics est construit sur OS X où le système de coordonnées est à l'envers par rapport à iOS (y commençant en bas à gauche sous OS X). C'est pourquoi Core Graphics dessine l'image à l'envers tandis que drawInRect gère cela pour vous
borchero

212

Même après avoir appliqué tout ce que j'ai mentionné, j'ai encore eu des drames avec les images. En fin de compte, je viens d'utiliser Gimp pour créer une version «verticale inversée» de toutes mes images. Désormais, je n'ai plus besoin d'utiliser de Transforms. Espérons que cela ne causera pas d'autres problèmes sur la piste.

Est-ce que quelqu'un sait pourquoi CGContextDrawImage dessinerait mon image à l'envers? Je charge une image depuis mon application:

Quartz2d utilise un système de coordonnées différent, où l'origine se trouve dans le coin inférieur gauche. Ainsi, lorsque Quartz dessine le pixel x [5], y [10] d'une image 100 * 100, ce pixel est dessiné dans le coin inférieur gauche au lieu du coin supérieur gauche. Ainsi provoquant l'image «retournée».

Le système de coordonnées x correspond, vous devrez donc inverser les coordonnées y.

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

Cela signifie que nous avons traduit l'image par 0 unité sur l'axe x et par la hauteur des images sur l'axe y. Cependant, cela seul signifiera que notre image est toujours à l'envers, étant simplement dessinée "image.size.height" ci-dessous où nous souhaitons qu'elle soit dessinée.

Le guide de programmation Quartz2D recommande d'utiliser ScaleCTM et de passer des valeurs négatives pour inverser l'image. Vous pouvez utiliser le code suivant pour ce faire -

CGContextScaleCTM(context, 1.0, -1.0);

Combinez les deux juste avant votre CGContextDrawImageappel et vous devriez avoir l'image correctement dessinée.

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

Faites juste attention si vos coordonnées imageRect ne correspondent pas à celles de votre image, car vous pouvez obtenir des résultats inattendus.

Pour reconvertir les coordonnées:

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

c'est bien, mais j'ai trouvé que d'autres choses étaient influencées par la traduction et l'échelle, même lorsque j'ai essayé de revenir à 0,0 et 1,0, 1,0 j'ai opté pour la solution de Kendall à la fin mais je me rends compte que c'est en utilisant UIImage plutôt que le CGImage de niveau inférieur avec lequel nous travaillons ici
PeanutPower

3
Si vous utilisez drawLayer et que vous souhaitez dessiner un CGImageRef dans le contexte, cette technique ici est juste que le médecin l'ordonne! Si vous avez le droit de l'image, puis CGContextTranslateCTM (contexte, 0, image.size.height + image.origin.y), puis définissez le rect.origin.y sur 0 avant CGContextDrawImage (). Merci!
David H

J'ai eu des problèmes une fois avec ImageMagick où la caméra de l'iPhone entraînait une orientation incorrecte. Il s'avère que c'est le drapeau d'orientation dans le fichier image, donc les images de portrait étaient, disons 960px x 640px avec le drapeau réglé sur "gauche" - comme dans le côté gauche se trouve le haut (ou quelque chose de proche de cela). Ce fil pourrait vous aider: stackoverflow.com/questions/1260249/…
Hari Karam Singh

8
Pourquoi n'utilisez-vous pas CGContextSaveGState()et CGContextRestoreGState()pour enregistrer et restaurer la matrice de transformation?
Ja͢ck

20

Le meilleur des deux mondes, utilisez UIImage drawAtPoint:ou drawInRect:tout en spécifiant votre contexte personnalisé:

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

Vous évitez également de modifier votre contexte avec CGContextTranslateCTMou CGContextScaleCTMce que fait la deuxième réponse.


C'est une excellente idée, mais pour moi, cela a abouti à une image à la fois à l'envers et de gauche à droite. Je suppose que les résultats varieront d'une image à l'autre.
Luke Rogers

Peut-être que cela a cessé de fonctionner avec les versions ultérieures d'iOS? À l'époque, cela fonctionnait avec tout pour moi.
Rivera

Je pense que c'était en fait à la façon dont je créais le contexte. Une fois que j'ai commencé à utiliser UIGraphicsBeginImageContextWithOptionsau lieu de simplement en lancer un nouveau CGContext, cela semblait fonctionner.
Luke Rogers

12

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

Inverser le système de coordonnées par défaut

Le retournement dans le dessin UIKit modifie le calque de support pour aligner un environnement de dessin ayant un système de coordonnées LLO avec le système de coordonnées par défaut d'UIKit. Si vous n'utilisez que des méthodes et des fonctions UIKit pour dessiner, vous ne devriez pas avoir besoin de retourner le CTM. Cependant, si vous mélangez des appels de fonction Core Graphics ou Image I / O avec des appels UIKit, il peut être nécessaire de retourner le CTM.

Plus précisément, si vous dessinez une image ou un document PDF en appelant directement les fonctions Core Graphics, l'objet est rendu à l'envers dans le contexte de la vue. Vous devez retourner le CTM pour afficher correctement l'image et les pages.

Pour retourner un objet dessiné dans un contexte Core Graphics afin qu'il apparaisse correctement lorsqu'il est affiché dans une vue UIKit, vous devez modifier le CTM en deux étapes. Vous traduisez l'origine vers le coin supérieur gauche de la zone de dessin, puis vous appliquez une translation d'échelle, en modifiant la coordonnée y par -1. Le code pour cela ressemble à ce qui suit:

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

10

Si quelqu'un est intéressé par une solution simple pour dessiner une image dans un rect personnalisé dans un contexte:

 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)

}

L'image sera mise à l'échelle pour remplir le rect.


7

Je n'en suis pas sûr UIImage, mais ce genre de comportement se produit généralement lorsque les coordonnées sont inversées. La plupart des systèmes de coordonnées OS X ont leur origine dans le coin inférieur gauche, comme dans Postscript et PDF. Mais le CGImagesystème de coordonnées a son origine dans le coin supérieur gauche.

Les solutions possibles peuvent impliquer une isFlippedpropriété ou une scaleYBy:-1transformation affine.


3

UIImagecontient un CGImagecomme membre de contenu principal ainsi que des facteurs d'échelle et d'orientation. Puisque CGImageses différentes fonctions sont dérivées d'OSX, il s'attend à un système de coordonnées à l'envers par rapport à l'iPhone. Lorsque vous créez un UIImage, il utilise par défaut une orientation à l'envers pour compenser (vous pouvez changer cela!). Utilisez le . CGImagepropriété pour accéder aux CGImagefonctions très puissantes , mais dessiner sur l'écran de l'iPhone, etc. est mieux fait avec les UIImageméthodes.


1
comment changer l'orientation par défaut à l'envers de UIImage?
MegaManX

3

Réponse supplémentaire avec code Swift

Les graphiques Quartz 2D utilisent un système de coordonnées avec l'origine en bas à gauche tandis que UIKit sous iOS utilise un système de coordonnées avec l'origine en haut à gauche. Tout fonctionne généralement bien, mais lorsque vous effectuez certaines opérations graphiques, vous devez modifier vous-même le système de coordonnées. La documentation déclare:

Certaines technologies configurent leurs contextes graphiques en utilisant un système de coordonnées par défaut différent de celui utilisé par Quartz. Par rapport à Quartz, un tel système de coordonnées est un système de coordonnées modifié et doit être compensé lors de l'exécution de certaines opérations de dessin Quartz. Le système de coordonnées modifié le plus courant place l'origine dans le coin supérieur gauche du contexte et modifie l'axe y pour qu'il pointe vers le bas de la page.

Ce phénomène peut être observé dans les deux instances suivantes de vues personnalisées qui dessinent une image dans leurs drawRectméthodes.

entrez la description de l'image ici

Sur le côté gauche, l'image est à l'envers et sur le côté droit le système de coordonnées a été traduit et mis à l'échelle de sorte que l'origine soit en haut à gauche.

Image à l'envers

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)

}

Système de coordonnées modifié

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

3

Swift 3.0 et 4.0

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

Aucune modification nécessaire


2

Nous pouvons résoudre ce problème en utilisant la même fonction:

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

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

UIGraphicsPopContext();

UIGraphicsEndImageContext();

1

drawInRectest certainement la voie à suivre. Voici une autre petite chose qui vous sera utile lors de cette opération. Habituellement, l'image et le rectangle dans lesquels il va aller ne sont pas conformes. Dans ce cas, drawInRectcela étendra l'image. Voici un moyen rapide et cool de vous assurer que le rapport hauteur / largeur de l'image n'est pas modifié, en inversant la transformation (ce qui sera pour adapter le tout):

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

Solution Swift 3 CoreGraphics

Si vous souhaitez utiliser CG pour quelque raison que ce soit, au lieu de UIImage, cette construction Swift 3 basée sur les réponses précédentes a résolu le problème pour moi:

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

1

Cela se produit parce que QuartzCore a le système de coordonnées "en bas à gauche", tandis que UIKit - "en haut à gauche".

Dans le cas où vous pouvez étendre 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)

1

J'utilise ce Swift 5 extension , pure Core Graphics qui gère correctement les origines non nulles dans les rects d'image:

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

Vous pouvez l'utiliser exactement comme CGContextla draw(: in:)méthode habituelle :

ctx.drawFlipped(myImage, in: myRect)

0

Au cours de mon projet, j'ai sauté de la réponse de Kendall à la réponse de Cliff pour résoudre ce problème pour les images chargées à partir du téléphone lui-même.

Au final, j'ai fini par utiliser à la CGImageCreateWithPNGDataProviderplace:

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

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

Cela ne souffre pas des problèmes d'orientation que vous obtiendriez en obtenant le CGImagedepuis a UIImageet il peut être utilisé comme contenu d'un CALayersans accroc.


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

0

Réponse Swift 5 basée sur l'excellente réponse de @ ZpaceZombor

Si vous avez une UIImage, utilisez simplement

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

Si vous avez une CGImage, utilisez ma catégorie ci-dessous

Remarque: contrairement à d'autres réponses, celle-ci prend en compte le fait que le rect que vous voulez dessiner pourrait avoir y! = 0. Les réponses qui ne prennent pas cela en compte sont incorrectes et ne fonctionneront pas dans le cas général.

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)

    }
} 

Utilisez comme ceci:

let imageFrame: CGRect = ...
let context: CGContext = ....
let img: CGImage = ..... 
context.drawImage(image: img, inRect: imageFrame)

C'est presque une solution parfaite, mais envisagez de changer la fonction pour dessiner (image: UIImage, inRect rect: CGRect) et de gérer uiImage.cgimage dans la méthode
Mars

-5

Vous pouvez également résoudre ce problème en faisant ceci:

//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.
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.