CGContextDrawImage disegna l'immagine sottosopra quando viene passato UIImage.CGImage


179

Qualcuno sa perché CGContextDrawImagedovrebbe disegnare la mia immagine sottosopra? Sto caricando un'immagine dalla mia applicazione:

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

E poi semplicemente chiedendo la grafica di base per attirarla nel mio contesto:

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

Viene visualizzato nel posto e nelle dimensioni giusti, ma l'immagine è capovolta. Mi manca qualcosa di veramente ovvio qui?

Risposte:


238

Invece di

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

Uso

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

Nel mezzo dei CGcontextmetodi di inizio / fine .

Questo attirerà l'immagine con l'orientamento corretto nel tuo contesto di immagine attuale - Sono abbastanza sicuro che abbia qualcosa a che fare con il UIImagemantenimento della conoscenza dell'orientamento mentre il CGContextDrawImagemetodo ottiene i dati di immagine grezzi sottostanti senza alcuna comprensione dell'orientamento.


19
Questa soluzione non consente di utilizzare le funzioni CG sull'immagine, come CGContextSetAlpha (), mentre la seconda risposta fa.
samvermette,

2
Un buon punto, anche se per lo più non hai bisogno di livelli alfa personalizzati per disegnare un'immagine (in genere quelli vengono inseriti in immagini in anticipo per le cose che richiedono alfa). Fondamentalmente il mio motto è, usa un po 'di codice come puoi perché più codice significa più possibilità per i bug.
Kendall Helmstetter Gelner,

Ciao, cosa intendi nel mezzo dei metodi CGContext di inizio / fine, puoi darmi un altro codice di esempio. Sto provando a memorizzare il contesto da qualche parte e usare il contesto per disegnare l'immagine
vodkhang

2
Mentre potrebbe funzionare per Rusty, - [UIImage drawInRect:] ha il problema che non è thread-safe. Per la mia particolare applicazione, ecco perché sto usando CGContextDrawImage () in primo luogo.
Olie,

2
Giusto per chiarire: Core Graphics è costruita su OS X in cui il sistema di coordinate è capovolto rispetto a iOS (y a partire dalla parte in basso a sinistra in OS X). Ecco perché Core Graphics disegna l'immagine sottosopra mentre drawInRect lo gestisce per te
borchero,

213

Anche dopo aver applicato tutto ciò che ho menzionato, ho ancora avuto dei drammi con le immagini. Alla fine, ho appena usato Gimp per creare una versione "capovolta verticale" di tutte le mie immagini. Ora non ho bisogno di usare nessuna trasformazione. Spero che questo non causi ulteriori problemi lungo il percorso.

Qualcuno sa perché CGContextDrawImage avrebbe disegnato la mia immagine sottosopra? Sto caricando un'immagine dalla mia applicazione:

Quartz2d utilizza un diverso sistema di coordinate, in cui l'origine è nell'angolo in basso a sinistra. Quindi quando Quartz disegna pixel x [5], y [10] di un'immagine 100 * 100, quel pixel viene disegnato nell'angolo in basso a sinistra invece che in alto a sinistra. Provocando così l'immagine "capovolta".

Il sistema di coordinate x corrisponde, quindi dovrai capovolgere le coordinate y.

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

Ciò significa che abbiamo tradotto l'immagine di 0 unità sull'asse x e dell'altezza delle immagini sull'asse y. Tuttavia, questo da solo significherà che la nostra immagine è ancora sottosopra, appena disegnata "image.size.height" sotto dove vorremmo che fosse disegnata.

La guida alla programmazione di Quartz2D raccomanda l'uso di ScaleCTM e il passaggio di valori negativi per capovolgere l'immagine. Puoi usare il seguente codice per fare questo -

CGContextScaleCTM(context, 1.0, -1.0);

Combina i due appena prima della tua CGContextDrawImagechiamata e dovresti avere l'immagine disegnata correttamente.

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

Fai solo attenzione se le tue coordinate imageRect non corrispondono a quelle della tua immagine, in quanto puoi ottenere risultati indesiderati.

Per riconvertire le coordinate:

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

questo è buono ma ho scoperto che altre cose erano influenzate dalla traduzione e dalla scala anche quando ho provato a tornare a 0,0 e 1,0,1,0 Alla fine sono andato con la soluzione di Kendall ma mi rendo conto che questo sta usando UIImage piuttosto che il materiale CGImage di livello inferiore con cui stiamo lavorando qui
PeanutPower

3
Se stai usando drawLayer e vuoi disegnare un CGImageRef nel contesto, questa tecnica qui vuole solo che il medico abbia ordinato! Se hai il rettangolo per l'immagine, quindi CGContextTranslateCTM (contesto, 0, image.size.height + image.origin.y), quindi imposta rect.origin.y su 0 prima di CGContextDrawImage (). Grazie!
David H,

Una volta ho avuto problemi con ImageMagick in cui la fotocamera dell'iPhone ha provocato un orientamento errato. Si scopre che fa il flag di orientamento nel file di immagine, quindi le immagini del ritratto erano, diciamo 960px x 640px con il flag impostato su "left" - come nella parte sinistra è in alto (o qualcosa di simile). Questa discussione potrebbe essere di aiuto: stackoverflow.com/questions/1260249/…
Hari Karam Singh,

8
Perché non usi CGContextSaveGState()e CGContextRestoreGState()per salvare e ripristinare la matrice di trasformazione?
Ja͢ck

20

Meglio di entrambi i mondi, usa UIImage drawAtPoint:o drawInRect:mentre specifichi ancora il tuo contesto personalizzato:

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

Inoltre si evita di modificare il contesto con CGContextTranslateCTMo CGContextScaleCTMche la seconda risposta fa.


Questa è un'ottima idea, ma per me ha dato come risultato un'immagine capovolta e da sinistra a destra. Immagino che i risultati varieranno da immagine a immagine.
Luke Rogers,

Forse questo ha smesso di funzionare con le versioni successive di iOS? Al momento ha funzionato con tutto per me.
Rivera

Penso che in realtà dipendesse da come stavo creando il contesto. Una volta che ho iniziato a utilizzare UIGraphicsBeginImageContextWithOptionsinvece di limitarmi a inserire un nuovo CGContext, sembrava funzionare.
Luke Rogers,

12

Documenti relativi a Quartz2D: https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010156

Lanciando il sistema di coordinate predefinito

Lanciando nel disegno UIKit si modifica il CALayer di supporto per allineare un ambiente di disegno con un sistema di coordinate LLO al sistema di coordinate predefinito di UIKit. Se usi solo metodi e funzioni UIKit per disegnare, non dovresti capovolgere il marchio comunitario. Tuttavia, se si combinano le chiamate della funzione Core Graphics o Image I / O con le chiamate UIKit, potrebbe essere necessario capovolgere il marchio comunitario.

In particolare, se si disegna un'immagine o un documento PDF chiamando direttamente le funzioni Core Graphics, l'oggetto viene visualizzato sottosopra nel contesto della vista. È necessario capovolgere il marchio comunitario per visualizzare correttamente l'immagine e le pagine.

Per capovolgere un oggetto disegnato in un contesto di grafica principale in modo che appaia correttamente quando viene visualizzato in una vista UIKit, è necessario modificare il marchio comunitario in due passaggi. Traducete l'origine nell'angolo in alto a sinistra dell'area di disegno, quindi applicate una traslazione in scala, modificando la coordinata y di -1. Il codice per eseguire questa operazione è simile al seguente:

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

10

Se qualcuno è interessato a una soluzione intuitiva per disegnare un'immagine in un rettangolo personalizzato in un contesto:

 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'immagine verrà ridimensionata per riempire il rettangolo.


7

Non ne sono sicuro UIImage, ma questo tipo di comportamento si verifica in genere quando si capovolgono le coordinate. La maggior parte dei sistemi di coordinate OS X ha origine nell'angolo in basso a sinistra, come in Postscript e PDF. Ma il CGImagesistema di coordinate ha origine nell'angolo in alto a sinistra.

Le possibili soluzioni possono comportare una isFlippedproprietà o una scaleYBy:-1trasformazione affine.


3

UIImagecontiene un CGImagemembro del contenuto principale, nonché fattori di ridimensionamento e orientamento. Dal momento che CGImagee le sue varie funzioni derivano da OSX, si aspetta un sistema di coordinate capovolto rispetto all'iPhone. Quando crei un UIImage, per impostazione predefinita viene impostato un orientamento capovolto per compensare (puoi cambiarlo!). Utilizzare il . CGImageproprietà per accedere alle CGImagefunzioni molto potenti , ma attingendo allo schermo dell'iPhone ecc. è meglio farlo con i UIImagemetodi.


1
come si modifica l'orientamento capovolto predefinito di UIImage?
MegaManX,

3

Risposta supplementare con codice Swift

La grafica 2D al quarzo utilizza un sistema di coordinate con l'origine in basso a sinistra, mentre UIKit in iOS utilizza un sistema di coordinate con l'origine in alto a sinistra. Di solito tutto funziona bene, ma quando si eseguono alcune operazioni grafiche, è necessario modificare il sistema di coordinate da soli. La documentazione afferma:

Alcune tecnologie impostano i loro contesti grafici utilizzando un sistema di coordinate predefinito diverso da quello utilizzato da Quartz. Rispetto al quarzo, tale sistema di coordinate è un sistema di coordinate modificato e deve essere compensato quando si eseguono alcune operazioni di disegno al quarzo. Il sistema di coordinate modificato più comune posiziona l'origine nell'angolo in alto a sinistra del contesto e cambia l'asse y in modo che punti verso il fondo della pagina.

Questo fenomeno può essere visto nelle seguenti due istanze di viste personalizzate che disegnano un'immagine nei loro drawRectmetodi.

inserisci qui la descrizione dell'immagine

Sul lato sinistro, l'immagine è capovolta e sul lato destro il sistema di coordinate è stato tradotto e ridimensionato in modo che l'origine sia in alto a sinistra.

Immagine capovolta

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 di coordinate modificato

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 e 4.0

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

Nessuna modifica necessaria


2

Possiamo risolvere questo problema usando la stessa funzione:

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

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

UIGraphicsPopContext();

UIGraphicsEndImageContext();

1

drawInRectè sicuramente la strada da percorrere. Ecco un'altra piccola cosa che ti tornerà utile quando lo fai. Di solito l'immagine e il rettangolo in cui sta andando non sono conformi. In tal caso drawInRectallungherà l'immagine. Ecco un modo rapido e interessante per assicurarti che il rapporto di aspetto dell'immagine non sia cambiato, invertendo la trasformazione (che si adatterà al tutto):

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

Soluzione Swift 3 CoreGraphics

Se vuoi usare CG per qualsiasi motivo, al posto di UIImage, questa costruzione di Swift 3 basata su risposte precedenti mi ha risolto il 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()
}

1

Succede perché QuartzCore ha il sistema di coordinate "in basso a sinistra", mentre UIKit - "in alto a sinistra".

Nel caso in cui puoi estendere 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

Uso questa estensione Swift 5 , pura Core Graphics che gestisce correttamente origini diverse da zero nei rects delle immagini:

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

Puoi usarlo esattamente come CGContextil normale draw(: in:)metodo:

ctx.drawFlipped(myImage, in: myRect)

0

Nel corso del mio progetto sono passato dalla risposta di Kendall alla risposta di Cliff per risolvere questo problema per le immagini caricate dal telefono stesso.

Alla fine ho usato CGImageCreateWithPNGDataProviderinvece:

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

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

Questo non soffre dei problemi di orientamento che potresti ottenere dall'a ottenere CGImageda UIImagee può essere usato come contenuto di a CALayersenza intoppi.


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

Swift 5 risposta basata sulla risposta eccellente di @ ZpaceZombor

Se hai un UIImage, basta usare

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

Se hai un CGImage usa la mia categoria qui sotto

Nota: a differenza di altre risposte, questa tiene conto del fatto che il rettangolo che vuoi disegnare potrebbe avere y! = 0. Quelle risposte che non lo tengono in considerazione sono errate e non funzioneranno nel caso generale.

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)

    }
} 

Usa così:

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

Questa è quasi una soluzione perfetta, ma considera di cambiare la funzione per disegnare (image: UIImage, inRect rect: CGRect) e gestire uiImage.cgimage all'interno del metodo
Marte

-5

Puoi anche risolvere questo problema facendo questo:

//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.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.