CGContextDrawImage zeichnet das Bild verkehrt herum, wenn UIImage.CGImage übergeben wird

179

Weiß jemand, warum CGContextDrawImageich mein Bild verkehrt herum zeichnen würde? Ich lade ein Bild aus meiner Anwendung ein:

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

Und dann einfach die Kerngrafiken bitten, sie in meinen Kontext zu zeichnen:

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

Es wird an der richtigen Stelle und in den richtigen Abmessungen gerendert, aber das Bild steht auf dem Kopf. Mir muss hier etwas wirklich Offensichtliches fehlen?

rostig
quelle

Antworten:

238

Anstatt

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

Verwenden

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

In der Mitte Ihrer Anfangs- / CGcontextEndmethoden.

Dadurch wird das Bild mit der richtigen Ausrichtung in Ihren aktuellen Bildkontext gezeichnet. Ich bin mir ziemlich sicher, dass dies etwas mit dem UIImageFesthalten an der Kenntnis der Ausrichtung zu tun hat, während die CGContextDrawImageMethode die zugrunde liegenden Rohbilddaten ohne Verständnis der Ausrichtung erhält.

Kendall Helmstetter Gelner
quelle
19
Mit dieser Lösung können Sie keine CG-Funktionen für Ihr Bild verwenden, z. B. CGContextSetAlpha (), während dies bei der zweiten Antwort der Fall ist.
Samvermette
2
Guter Punkt, obwohl Sie zum Zeichnen eines Bildes meistens keine benutzerdefinierten Alpha-Ebenen benötigen (normalerweise werden diese für Dinge, die Alpha benötigen, vorab in Bilder eingebrannt). Grundsätzlich lautet mein Motto: Verwenden Sie so wenig Code wie möglich, da mehr Code mehr Chancen für Fehler bedeutet.
Kendall Helmstetter Gelner
Hallo, was meinst du mit mitten in deinen Anfang / Ende-CGContext-Methoden? Kannst du mir mehr Beispielcode geben? Ich versuche, den Kontext irgendwo zu speichern und den Kontext zu verwenden, um das Bild in
vodkhang
2
Während es für Rusty funktionieren könnte, hat - [UIImage drawInRect:] das Problem, dass es nicht threadsicher ist. Aus diesem Grund verwende ich für meine spezielle Anwendung in erster Linie CGContextDrawImage ().
Olie
2
Zur Verdeutlichung: Core Graphics basiert auf OS X, bei dem das Koordinatensystem im Vergleich zu iOS auf dem Kopf steht (y beginnt in OS X unten links). Aus diesem Grund zeichnet Core Graphics das Bild verkehrt herum, während drawInRect dies für Sie
erledigt
213

Selbst nachdem ich alles angewendet habe, was ich erwähnt habe, hatte ich immer noch Dramen mit den Bildern. Am Ende habe ich gerade Gimp verwendet, um eine "gespiegelte vertikale" Version aller meiner Bilder zu erstellen. Jetzt muss ich keine Transformationen mehr verwenden. Hoffentlich wird dies später keine weiteren Probleme verursachen.

Weiß jemand, warum CGContextDrawImage mein Bild verkehrt herum zeichnen würde? Ich lade ein Bild aus meiner Anwendung ein:

Quartz2d verwendet ein anderes Koordinatensystem, bei dem der Ursprung in der unteren linken Ecke liegt. Wenn Quarz also 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, daher müssen Sie die y-Koordinaten umdrehen.

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

Dies bedeutet, dass wir das Bild auf der x-Achse um 0 Einheiten und auf der y-Achse um die Bildhöhe verschoben haben. Dies allein bedeutet jedoch, dass unser Bild immer noch auf dem Kopf steht und nur "image.size.height" unten gezeichnet wird, wo wir es zeichnen möchten.

In der Quartz2D-Programmieranleitung wird empfohlen, ScaleCTM zu verwenden und negative Werte zu übergeben, um das Bild umzudrehen. Sie können dazu den folgenden Code verwenden:

CGContextScaleCTM(context, 1.0, -1.0);

Kombinieren Sie die beiden kurz vor Ihrem CGContextDrawImageAnruf und Sie sollten das Bild richtig gezeichnet haben.

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 nur vorsichtig, wenn Ihre imageRect-Koordinaten nicht mit denen Ihres Bildes übereinstimmen, da Sie unbeabsichtigte Ergebnisse erzielen können.

So konvertieren Sie die Koordinaten zurück:

CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -imageRect.size.height);
Cliff Viegas
quelle
Das ist gut, aber ich habe festgestellt, dass andere Dinge durch die Übersetzung und Skalierung beeinflusst wurden, selbst als ich versuchte, auf 0,0 und 1.0,1.0 zurückzusetzen. Am Ende habe ich mich für Kendalls Lösung entschieden, aber mir ist klar, dass UIImage anstelle von UIImage verwendet wird CGImage-Zeug der unteren Ebene, mit dem wir hier arbeiten
PeanutPower
3
Wenn Sie drawLayer verwenden und ein CGImageRef in den Kontext zeichnen möchten, ist diese Technik hier nur der bestellte Arzt! Wenn Sie das Rect für das Bild haben, dann CGContextTranslateCTM (Kontext, 0, image.size.height + image.origin.y), dann setzen Sie das rect.origin.y vor CGContextDrawImage () auf 0. Vielen Dank!
David H
Ich hatte einmal Probleme mit ImageMagick, bei denen die iPhone-Kamera zu einer falschen Ausrichtung führte. Es stellt sich heraus, dass es das Orientierungsflag in der Bilddatei ist, also waren Porträtbilder beispielsweise 960 x 640 Pixel groß, wobei das Flag auf "links" gesetzt war - wie auf der linken Seite oben (oder etwas in der Nähe davon). Dieser Thread könnte helfen: stackoverflow.com/questions/1260249/…
Hari Karam Singh
8
Warum verwenden CGContextSaveGState()und CGContextRestoreGState()speichern Sie die Transformationsmatrix nicht und stellen sie wieder her?
Ja͢ck
20

Verwenden Sie UIImage's drawAtPoint:oder geben Sie drawInRect:Ihren benutzerdefinierten Kontext an:

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

Außerdem vermeiden Sie es, Ihren Kontext zu ändern CGContextTranslateCTModer CGContextScaleCTMwas die zweite Antwort tut.

Rivera
quelle
Das ist eine großartige Idee, aber für mich ergab sich ein Bild, das sowohl verkehrt herum als auch von links nach rechts war. Ich vermute, dass die Ergebnisse von Bild zu Bild variieren werden.
Luke Rogers
Vielleicht funktioniert dies bei späteren iOS-Versionen nicht mehr? Zu der Zeit hat es mit allem für mich geklappt.
Rivera
Ich denke, es lag tatsächlich daran, wie ich den Kontext erstellt habe. Sobald ich anfing, UIGraphicsBeginImageContextWithOptionsein neues zu verwenden CGContext, anstatt es nur zu starten , schien es zu funktionieren.
Luke Rogers
12

Relevante Quartz2D-Dokumente: https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010154-CH14-

Umdrehen des Standardkoordinatensystems

Durch das Umblättern in der UIKit-Zeichnung wird die Hintergrund-CALayer so geändert, dass eine Zeichnungsumgebung mit einem LLO-Koordinatensystem am Standardkoordinatensystem von UIKit ausgerichtet wird. Wenn Sie nur UIKit-Methoden und -Funktionen zum Zeichnen verwenden, sollten Sie die Gemeinschaftsmarke nicht umdrehen müssen. Wenn Sie jedoch Core Graphics- oder Image I / O-Funktionsaufrufe mit UIKit-Aufrufen mischen, ist möglicherweise ein Umdrehen des CTM erforderlich.

Wenn Sie ein Bild oder PDF-Dokument zeichnen, indem Sie die Funktionen von Core Graphics direkt aufrufen, wird das Objekt im Kontext der Ansicht verkehrt herum gerendert. Sie müssen die Gemeinschaftsmarke umdrehen, um das Bild und die Seiten korrekt anzuzeigen.

Um ein Objekt, das in einen Core Graphics-Kontext gezeichnet wurde, so zu spiegeln, dass es in einer UIKit-Ansicht korrekt angezeigt wird, müssen Sie die Gemeinschaftsmarke in zwei Schritten ändern. Sie übersetzen den Ursprung in die obere linke Ecke des Zeichenbereichs und wenden dann eine Skalenübersetzung an, wobei Sie die y-Koordinate um -1 ändern. Der Code dafür sieht folgendermaßen aus:

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

Wenn jemand an einer einfachen Lösung zum Zeichnen eines Bildes in einem benutzerdefinierten Rechteck in einem Kontext interessiert ist:

 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.

ZpaceZombor
quelle
7

Ich bin mir nicht sicher UIImage, aber diese Art von Verhalten tritt normalerweise auf, wenn Koordinaten umgedreht werden. Die meisten OS X-Koordinatensysteme haben ihren Ursprung in der unteren linken Ecke, wie in Postscript und PDF. Das CGImageKoordinatensystem hat jedoch seinen Ursprung in der oberen linken Ecke.

Mögliche Lösungen können eine isFlippedEigenschaft oder eine scaleYBy:-1affine Transformation beinhalten.

Mouviciel
quelle
3

UIImageenthält ein CGImageals Hauptinhaltselement sowie Skalierungs- und Orientierungsfaktoren. Da CGImageund seine verschiedenen Funktionen von OSX abgeleitet sind, erwartet es ein Koordinatensystem, das im Vergleich zum iPhone auf dem Kopf steht. Wenn Sie ein erstellen UIImage, wird standardmäßig eine verkehrte Ausrichtung verwendet, um dies zu kompensieren (Sie können dies ändern!). Verwenden Sie die. CGImageEigenschaft, um auf die sehr leistungsfähigen CGImageFunktionen zuzugreifen , aber das Zeichnen auf den iPhone-Bildschirm usw. erfolgt am besten mit den UIImageMethoden.

James L.
quelle
1
Wie können Sie die Standardausrichtung von UIImage auf den Kopf stellen?
MegaManX
3

Ergänzende Antwort mit Swift-Code

Quarz-2D-Grafiken verwenden ein Koordinatensystem mit dem Ursprung unten links, während UIKit in iOS ein Koordinatensystem mit dem Ursprung oben links verwendet. Normalerweise funktioniert alles einwandfrei, aber bei einigen Grafikoperationen müssen Sie das Koordinatensystem selbst ändern. In der Dokumentation heißt es:

Einige Technologien richten ihre Grafikkontexte mit einem anderen Standardkoordinatensystem als dem von Quartz ein. Im Vergleich zu Quarz ist ein solches Koordinatensystem ein modifiziertes Koordinatensystem und muss bei der Ausführung einiger Quarzzeichnungsvorgänge kompensiert werden. Das am häufigsten geänderte Koordinatensystem platziert den Ursprung in der oberen linken Ecke des Kontexts und ändert die y-Achse so, dass sie zum unteren Rand der Seite zeigt.

Dieses Phänomen tritt in den folgenden zwei Fällen von benutzerdefinierten Ansichten auf, die in ihren drawRectMethoden ein Bild zeichnen .

Geben Sie hier die Bildbeschreibung ein

Auf der linken Seite steht das Bild auf dem Kopf und auf der rechten Seite wurde das Koordinatensystem übersetzt und skaliert, sodass der Ursprung oben links liegt.

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

}

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

Swift 3.0 & 4.0

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

Keine Änderung erforderlich

Abdul Waheed
quelle
2

Wir können dieses Problem mit derselben Funktion lösen:

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

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

UIGraphicsPopContext();

UIGraphicsEndImageContext();
Priyanka V.
quelle
1

drawInRectist sicherlich der richtige Weg. Hier ist eine weitere Kleinigkeit, die dabei nützlich sein wird. Normalerweise stimmen das Bild und das Rechteck, in das es gehen soll, nicht überein. In diesem Fall drawInRectwird das Bild gedehnt. Hier ist eine schnelle und coole Möglichkeit, um sicherzustellen, dass das Seitenverhältnis des Bildes nicht geändert wird, indem Sie die Transformation umkehren (die das Ganze einfügt):

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

Swift 3 CoreGraphics-Lösung

Wenn Sie CG aus irgendeinem Grund anstelle von UIImage verwenden möchten, hat diese Swift 3-Konstruktion, die auf früheren 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()
}
Biomiker
quelle
1

Dies geschieht, weil QuartzCore das Koordinatensystem "unten links" und UIKit "oben links" hat.

In dem Fall können Sie CGContext erweitern :

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

Ich verwende diese Swift 5 , eine reine Core Graphics- Erweiterung, die Nicht-Null-Ursprünge in Bildbereichen korrekt behandelt:

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

Sie können es genau wie CGContextdie reguläre draw(: in:)Methode verwenden:

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

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 habe ich CGImageCreateWithPNGDataProviderstattdessen verwendet:

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

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

Dies leidet nicht unter den Orientierungsproblemen, die Sie durch das Abrufen CGImagevon a erhalten würden, UIImageund es kann problemlos als Inhalt von a verwendet CALayerwerden.

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

Schnelle 5-Antwort basierend auf der hervorragenden Antwort von @ ZpaceZombor

Wenn Sie ein UIImage haben, verwenden Sie einfach

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

Wenn Sie ein CGImage haben, verwenden Sie meine Kategorie unten

Hinweis: Im Gegensatz zu einigen anderen Antworten wird bei dieser Antwort berücksichtigt, dass das zu zeichnende Rechteck möglicherweise y! = 0 hat. Die Antworten, die dies nicht berücksichtigen, sind falsch und funktionieren im allgemeinen Fall nicht.

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)

    }
} 

Verwenden Sie wie folgt:

let imageFrame: CGRect = ...
let context: CGContext = ....
let img: CGImage = ..... 
context.drawImage(image: img, inRect: imageFrame)
n13
quelle
Dies ist fast eine perfekte Lösung, aber erwägen Sie, die Funktion zum Zeichnen (Bild: UIImage, inRect rect: CGRect) und zum Behandeln von uiImage.cgimage innerhalb der Methode zu ändern
Mars
-5

Sie können dieses Problem auch folgendermaßen lösen:

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