wake-up-neo.com

CGContextDrawImage zeichnet das Bild auf den Kopf, wenn UIImage.CGImage übergeben wird

Weiß jemand, warum CGContextDrawImage mein Bild auf den Kopf stellen würde? Ich lade ein Bild aus meiner Anwendung:

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

Und dann einfach Kerngrafiken fragen, um sie in meinen Kontext zu zeichnen:

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

Es wird an der richtigen Stelle und in den Dimensionen wiedergegeben, aber das Bild ist verkehrt herum. Mir muss hier etwas wirklich offensichtliches fehlen?

176
rustyshelf

Anstatt 

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

Benutzen 

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

In der Mitte Ihrer CGcontext-Methoden begin/end.

Dadurch wird das Bild mit der richtigen Ausrichtung in Ihren aktuellen Bildkontext gezeichnet. Ich bin ziemlich sicher, dass dies etwas damit zu tun hat, dass die UIImage das Wissen über die Orientierung behält, während die CGContextDrawImage-Methode die zugrunde liegenden Rohbilddaten ohne Verständnis der Orientierung erhält.

Beachten Sie, dass Sie in vielen Bereichen auf dieses Problem stoßen, aber ein spezielles Beispiel ist der Umgang mit Benutzerbildern des Adressbuchs.

Selbst nachdem ich alles, was ich erwähnt habe, angewendet habe, hatte ich immer noch Dramen mit den Bildern. Am Ende habe ich nur Gimp verwendet, um eine "gespiegelte vertikale" Version aller meiner Bilder zu erstellen. Jetzt brauche ich keine Transformationen. Hoffentlich wird dies keine weiteren Probleme auf der Strecke verursachen.

Weiß jemand warum CGContextDrawImage würde mein .__ zeichnen. Bild verkehrt herum? Ich lade ein Bild in meiner Bewerbung:

Quartz2d verwendet ein anderes Koordinatensystem, wobei sich der Ursprung in der linken unteren Ecke befindet. Wenn also Quartz Pixel x [5], y [10] eines 100 * 100-Bildes zeichnet, wird dieses Pixel in der unteren linken Ecke anstatt in der oberen linken Ecke gezeichnet. Dadurch entsteht das "gespiegelte" Bild.

Das x-Koordinatensystem stimmt überein. Sie müssen also die y-Koordinaten umdrehen.

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

Das heißt, wir haben das Bild auf der X-Achse um 0 Einheiten und auf der Y-Achse um die Bildhöhe übersetzt. Dies allein bedeutet jedoch, dass unser Bild immer noch auf dem Kopf steht. Es wird einfach "image.size.hight" unterhalb der Stelle gezeichnet, an der wir es zeichnen möchten.

Im Quartz2D-Programmierhandbuch wird empfohlen, ScaleCTM zu verwenden und negative Werte zu übergeben, um das Bild zu spiegeln. Sie können den folgenden Code dazu verwenden -

CGContextScaleCTM(context, 1.0, -1.0);

Kombinieren Sie die beiden unmittelbar vor Ihrem Aufruf von CGContextDrawImage. Das Bild sollte korrekt gezeichnet sein.

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

Seien Sie vorsichtig, wenn Ihre imageRect-Koordinaten nicht mit denen Ihres Bildes übereinstimmen, da dies zu unbeabsichtigten Ergebnissen führen kann.

So konvertieren Sie die Koordinaten zurück:

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

Am besten von beiden Welten, verwenden Sie UIImages drawAtPoint: oder drawInRect:, während Sie weiterhin Ihren benutzerdefinierten Kontext angeben:

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

Sie vermeiden auch, Ihren Kontext mit CGContextTranslateCTM oder CGContextScaleCTM zu ändern, was die zweite Antwort tut.

20
Rivera

Relevante Quartz2D-Dokumente: https://developer.Apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//Apple_ref/doc/uid/TP40010156-CH

Spiegeln des Standardkoordinatensystems

Durch Umblättern in einer UIKit-Zeichnung wird der Hintergrund-CALayer so geändert, dass eine Zeichnungsumgebung mit einem LLO-Koordinatensystem an dem Standardkoordinatensystem von UIKit ausgerichtet wird. Wenn Sie nur UIKit-Methoden und -Funktionen zum Zeichnen verwenden, müssen Sie das CTM nicht umdrehen. Wenn Sie jedoch Core Graphics- oder Image I/O-Funktionsaufrufe mit UIKit-Aufrufen mischen, muss das CTM möglicherweise umgedreht werden.

Insbesondere wenn Sie ein Bild oder PDF Dokument durch direkten Aufruf der Core Graphics-Funktionen zeichnen, wird das Objekt im Kontext der Ansicht verkehrt herum gerendert. Sie müssen das CTM drehen, um das Bild und die Seiten korrekt anzuzeigen .

Um ein in einen Core Graphics-Kontext gezeichnetes Objekt so zu kippen, dass es in einer UIKit-Ansicht korrekt angezeigt wird, müssen Sie das CTM in zwei Schritten ändern. Sie verschieben den Ursprung in die linke obere Ecke des Zeichenbereichs und wenden dann eine Skalierungsübersetzung an, wobei Sie die y-Koordinate um -1 ändern. Der Code dafür sieht ungefähr so ​​aus:

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

Ich bin nicht sicher für UIImage, aber dieses Verhalten tritt normalerweise auf, wenn Koordinaten umgedreht werden. Die meisten OS X-Koordinatensysteme haben ihren Ursprung in der linken unteren Ecke, wie in Postscript und PDF. Das CGImage-Koordinatensystem hat jedoch seinen Ursprung in der oberen linken Ecke.

Mögliche Lösungen können eine isFlipped-Eigenschaft oder eine scaleYBy:-1-affine Transformation umfassen.

7
mouviciel

Wenn sich jemand für eine unkomplizierte Lösung für das Zeichnen eines Bildes in einem benutzerdefinierten Bericht in einem Kontext interessiert:

 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)

}

Das Bild wird skaliert, um das Rechteck zu füllen.

5
ZpaceZombor

Wir können dieses Problem mit der gleichen Funktion lösen:

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

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

UIGraphicsPopContext();

UIGraphicsEndImageContext();
3
Priyanka V

Swift 3.0 & 4.0

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

Keine Änderung erforderlich

3
Abdul Waheed

UIImage enthält eine CGImage als Hauptinhaltselement sowie Skalierungs- und Orientierungsfaktoren. Da CGImage und seine verschiedenen Funktionen von OSX abgeleitet sind, wird ein gegenüber dem iPhone verkehrtes Koordinatensystem erwartet. Wenn Sie eine UIImage erstellen, wird standardmäßig eine umgekehrte Ausrichtung verwendet, um dies zu kompensieren (Sie können dies ändern!). Verwenden Sie die .CGImage-Eigenschaft, um auf die sehr leistungsfähigen CGImage-Funktionen zuzugreifen. Das Zeichnen auf den iPhone-Bildschirm usw. erfolgt am besten mit den UIImage-Methoden.

3
James L

Ergänzende Antwort mit Swift-Code

2D-Quarzgrafiken verwenden ein Koordinatensystem mit dem Ursprung links unten, während UIKit in iOS ein Koordinatensystem mit dem Ursprung oben links verwendet. Normalerweise funktioniert alles gut, aber wenn Sie einige Grafikoperationen ausführen, müssen Sie das Koordinatensystem selbst ändern. Die Dokumentation besagt:

Einige Technologien richten ihren Grafikkontext mit einem anderen .__ ein. Standardkoordinatensystem als das von Quartz verwendete. Relativ zu Quarz, ein solches Koordinatensystem ist ein modifiziertes Koordinatensystem und muss bei der Ausführung einiger Quarzzeichnungen ausgeglichen werden Operationen. Das am häufigsten verwendete modifizierte Koordinatensystem platziert das Ursprung in der oberen linken Ecke des Kontexts und ändert die Y-Achse zum Ende der Seite zeigen.

Dieses Phänomen ist in den folgenden zwei Fällen von benutzerdefinierten Ansichten zu sehen, die ein Bild in ihren drawRect-Methoden zeichnen.

 enter image description here

Auf der linken Seite befindet sich das Bild auf dem Kopf und auf der rechten Seite wurde das Koordinatensystem so verschoben und skaliert, dass der Ursprung oben links angezeigt wird.

Auf dem Kopf stehendes Bild

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)

}

Geändertes Koordinatensystem

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

Swift 3 CoreGraphics-Lösung

Wenn Sie CG aus irgendeinem Grund anstelle von UIImage verwenden möchten, hat diese Swift-3-Konstruktion, die auf vorherigen Antworten basiert, das Problem für mich gelöst:

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
biomiker

drawInRect ist sicherlich der Weg zu gehen. Hier ist eine weitere Kleinigkeit, die sich dabei als nützlich erweisen wird. Normalerweise passen das Bild und das Rechteck, in das es eingefügt werden soll, nicht zusammen. In diesem Fall streckt drawInRect das Bild. Hier ist eine schnelle und coole Methode, um sicherzustellen, dass das Seitenverhältnis des Bildes nicht geändert wird, indem die Transformation umgekehrt wird (was in das Ganze hineinpassen soll):

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

Im Verlauf meines Projekts bin ich von Kendalls Antwort zu Cliffs Antwort gesprungen, um dieses Problem für Bilder zu lösen, die vom Telefon selbst geladen werden.

Am Ende benutzte ich stattdessen CGImageCreateWithPNGDataProvider :

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

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

Dies leidet nicht unter den Orientierungsproblemen, die sich ergeben, wenn Sie die CGImage von einer UIImage erhalten, und sie kann ohne Probleme als Inhalt einer CALayer verwendet werden.

0
Ja͢ck
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
neoneye