CGContextDrawImage rysuje obraz do góry nogami po przekazaniu UIImage.CGImage

179

Czy ktoś wie, dlaczego CGContextDrawImagemiałbym rysować mój obraz do góry nogami? Ładuję obraz z mojej aplikacji:

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

A potem po prostu poproś podstawową grafikę, aby narysowała ją w moim kontekście:

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

Renderuje się we właściwym miejscu i wymiarach, ale obraz jest odwrócony. Muszę tu przegapić coś naprawdę oczywistego?

rustyshelf
źródło

Odpowiedzi:

238

Zamiast

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

Posługiwać się

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

W trakcie CGcontextmetod początkowych / końcowych .

Spowoduje to narysowanie obrazu z prawidłową orientacją do bieżącego kontekstu obrazu - jestem prawie pewien, że ma to coś wspólnego z UIImageutrzymywaniem wiedzy o orientacji, podczas gdy CGContextDrawImagemetoda pobiera podstawowe surowe dane obrazu bez zrozumienia orientacji.

Kendall Helmstetter Gelner
źródło
19
To rozwiązanie nie pozwala używać funkcji CG na obrazie, takich jak CGContextSetAlpha (), podczas gdy druga odpowiedź tak.
samvermette
2
Dobra uwaga, chociaż przeważnie nie potrzebujesz niestandardowych poziomów alfa do rysowania obrazu (zwykle są one upieczone w obrazach przed czasem dla rzeczy, które wymagają alfa). Zasadniczo moim mottem jest użycie małego kodu, ponieważ możesz, ponieważ więcej kodu oznacza większe szanse na błędy.
Kendall Helmstetter Gelner
Cześć, Co masz na myśli mówiąc o środkowych metodach CGContext na początku / na końcu, czy możesz podać mi więcej przykładowego kodu? Próbuję
zapisać
2
Chociaż może to działać dla Rusty, - [UIImage drawInRect:] ma problem z tym, że nie jest bezpieczny dla wątków. W mojej konkretnej aplikacji właśnie dlatego używam CGContextDrawImage ().
Olie,
2
Dla wyjaśnienia: Core Graphics jest wbudowany w OS X, w którym układ współrzędnych jest odwrócony do góry nogami w porównaniu do iOS (y zaczynając od lewej dolnej części w OS X). Właśnie dlatego Core Graphics rysuje obraz do góry nogami, a drawInRect obsługuje to za Ciebie
borchero
213

Nawet po zastosowaniu wszystkiego, o czym wspomniałem, wciąż miałem dramaty z obrazami. Ostatecznie właśnie użyłem Gimpa do stworzenia wersji „odwróconej pionowej” wszystkich moich zdjęć. Teraz nie muszę używać żadnych transformacji. Mamy nadzieję, że nie spowoduje to dalszych problemów na późniejszym etapie.

Czy ktoś wie, dlaczego CGContextDrawImage rysuje mój obraz do góry nogami? Ładuję obraz z mojej aplikacji:

Quartz2d używa innego układu współrzędnych, którego pochodzenie znajduje się w lewym dolnym rogu. Kiedy więc Kwarc rysuje piksel x [5], y [10] obrazu 100 * 100, piksel ten jest rysowany w lewym dolnym rogu zamiast w lewym górnym rogu. Powodując w ten sposób „odwrócony” obraz.

Układ współrzędnych x pasuje, więc trzeba odwrócić współrzędne y.

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

Oznacza to, że przetłumaczyliśmy obraz o 0 jednostek na osi x i wysokość obrazów na osi y. Jednak to samo będzie oznaczać, że nasz obraz jest wciąż do góry nogami, po prostu zostanie narysowany „image.size.height” poniżej, gdzie chcemy go narysować.

Przewodnik po programowaniu Quartz2D zaleca stosowanie ScaleCTM i przekazywanie wartości ujemnych w celu odwrócenia obrazu. Aby to zrobić, możesz użyć następującego kodu -

CGContextScaleCTM(context, 1.0, -1.0);

Połącz dwa tuż przed CGContextDrawImagerozmową, a obraz powinien być narysowany poprawnie.

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

Należy zachować ostrożność, jeśli współrzędne imageRect nie pasują do współrzędnych obrazu, ponieważ można uzyskać niezamierzone wyniki.

Aby przekonwertować współrzędne z powrotem:

CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -imageRect.size.height);
Cliff Viegas
źródło
to dobrze, ale zauważyłem, że tłumaczenie i skalowanie wpłynęły na inne rzeczy, nawet gdy próbowałem ustawić z powrotem na 0,0 i 1,0,1,0. W końcu poszedłem z rozwiązaniem Kendall, ale zdaję sobie sprawę, że używa UIImage zamiast rzeczy na niższym poziomie CGImage, z którymi tutaj
pracujemy
3
Jeśli używasz drawLayer i chcesz narysować CGImageRef w kontekście, ta technika tutaj jest po prostu chcesz, żeby lekarz zlecił! Jeśli masz poprawkę do obrazu, to CGContextTranslateCTM (kontekst, 0, image.size.height + image.origin.y), a następnie ustaw rect.origin.y na 0 przed CGContextDrawImage (). Dzięki!
David H
Miałem kiedyś problemy z ImageMagick, w wyniku których kamera iPhone'a spowodowała nieprawidłową orientację. Okazuje się, że robi to flagę orientacji w pliku obrazu, więc obrazy portretowe miały, powiedzmy, 960px x 640px z flagą ustawioną na „lewą” - tak jak po lewej stronie jest góra (lub coś blisko tego). Ten wątek może pomóc: stackoverflow.com/questions/1260249/…
Hari Karam Singh
8
Dlaczego nie używasz CGContextSaveGState()i CGContextRestoreGState()nie zapisujesz i nie przywracasz macierzy transformacji?
Ja͢ck
20

Najlepsze z obu światów, użyj UIImage drawAtPoint:lub drawInRect:wciąż określając swój niestandardowy kontekst:

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

Ponadto unikasz modyfikowania swojego kontekstu CGContextTranslateCTMlub tego, CGContextScaleCTMco robi druga odpowiedź.

Rivera
źródło
To świetny pomysł, ale dla mnie zaowocował obrazem odwróconym do góry nogami i od lewej do prawej. Zgaduję, że wyniki będą się różnić w zależności od obrazu.
Luke Rogers
Może to przestało działać w późniejszych wersjach iOS? W tym czasie dla mnie wszystko działało.
Rivera
Myślę, że tak naprawdę wynikało to z tego, jak tworzyłem kontekst. Kiedy zacząłem używaćUIGraphicsBeginImageContextWithOptions zamiast inicjować nowy CGContext, wydawało się, że działa.
Luke Rogers
12

Odpowiednie dokumenty Quartz2D: https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP400S15W-CH14

Odwracanie domyślnego układu współrzędnych

Odwrócenie w rysunku UIKit modyfikuje podkładkę CALayer, aby wyrównać środowisko rysunkowe posiadające układ współrzędnych LLO z domyślnym układem współrzędnych UIKit. Jeśli używasz tylko metod i funkcji UIKit do rysowania, nie musisz przerzucać CTM. Jeśli jednak łączysz wywołania funkcji Core Graphics lub Image I / O z wywołaniami UIKit, przerzucenie CTM może być konieczne.

W szczególności, jeśli narysujesz obraz lub dokument PDF, wywołując funkcje Core Graphics bezpośrednio, obiekt jest renderowany do góry nogami w kontekście widoku. Musisz odwrócić CTM, aby poprawnie wyświetlić obraz i strony.

Aby odwrócić obiekt narysowany w kontekście Core Graphics, aby wyświetlał się poprawnie, gdy jest wyświetlany w widoku UIKit, należy zmodyfikować CTM w dwóch krokach. Tłumaczysz początek w lewym górnym rogu obszaru rysunku, a następnie stosujesz translację skali, modyfikując współrzędną y o -1. Kod do wykonania tej czynności wygląda podobnie do poniższej:

CGContextSaveGState(graphicsContext);
CGContextTranslateCTM(graphicsContext, 0.0, imageHeight);
CGContextScaleCTM(graphicsContext, 1.0, -1.0);
CGContextDrawImage(graphicsContext, image, CGRectMake(0, 0, imageWidth, imageHeight));
CGContextRestoreGState(graphicsContext);
kcmoffat
źródło
10

Jeśli ktoś jest zainteresowany bezmyślnym rozwiązaniem do rysowania obrazu w niestandardowym odbiciu w kontekście:

 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)

}

Obraz zostanie przeskalowany, aby wypełnić prostokąt.

ZpaceZombor
źródło
7

Nie jestem pewien UIImage, ale takie zachowanie zwykle występuje, gdy współrzędne są odwracane. Większość układów współrzędnych OS X ma swój początek w lewym dolnym rogu, jak w Postscript i PDF. AleCGImage układ współrzędnych ma swój początek w lewym górnym rogu.

Możliwe rozwiązania mogą obejmować isFlippedwłaściwość lub scaleYBy:-1transformację afiniczną.

mouviciel
źródło
3

UIImagezawiera CGImagejako główny element treści, a także czynniki skalowania i orientacji. Ponieważ CGImagei jego różne funkcje pochodzą z OSX, oczekuje on układu współrzędnych, który jest do góry nogami w porównaniu do iPhone'a. Kiedy tworzysz UIImage, domyślnie orientacja jest odwrócona, aby to zrekompensować (możesz to zmienić!). Użyj . CGImage, aby uzyskać dostęp do bardzo zaawansowanych CGImagefunkcji, ale rysowanie na ekranie iPhone'a itp. najlepiej wykonywać UIImagemetodami.

James L.
źródło
1
Jak zmienić domyślną orientację UIImage do góry nogami?
MegaManX,
3

Dodatkowa odpowiedź z kodem Swift

Kwarcowa grafika 2D używa układu współrzędnych z punktem początkowym w lewym dolnym rogu, podczas gdy UIKit w iOS używa układu współrzędnych z początkiem w lewym górnym rogu. Zazwyczaj wszystko działa dobrze, ale wykonując niektóre operacje graficzne, musisz samodzielnie zmodyfikować układ współrzędnych. W dokumentacji czytamy:

Niektóre technologie konfigurują konteksty graficzne przy użyciu innego domyślnego układu współrzędnych niż ten używany przez Quartz. W odniesieniu do kwarcu taki układ współrzędnych jest zmodyfikowanym układem współrzędnych i musi być kompensowany podczas wykonywania niektórych operacji rysowania kwarcu. Najczęstszy zmodyfikowany układ współrzędnych umieszcza punkt początkowy w lewym górnym rogu kontekstu i zmienia oś y tak, aby wskazywała na dół strony.

Zjawisko to można zaobserwować w następujących dwóch przykładach niestandardowych widoków, które rysują obraz w swoich drawRectmetodach.

wprowadź opis zdjęcia tutaj

Po lewej stronie obraz jest odwrócony, a po prawej stronie układ współrzędnych został przetłumaczony i przeskalowany, tak aby początek znajdował się w lewym górnym rogu.

Obraz do góry nogami

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)

}

Zmodyfikowany układ współrzędnych

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
źródło
3

Swift 3.0 i 4.0

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

Żadna zmiana nie jest wymagana

Abdul Waheed
źródło
2

Możemy rozwiązać ten problem za pomocą tej samej funkcji:

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

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

UIGraphicsPopContext();

UIGraphicsEndImageContext();
Priyanka V.
źródło
1

drawInRectjest z pewnością właściwą drogą. Oto kolejna drobna rzecz, która będzie bardzo przydatna podczas tej czynności. Zwykle obraz i prostokąt, do którego ma się dostać, nie są zgodne. W takim przypadku drawInRectobraz zostanie rozciągnięty. Oto szybki i fajny sposób, aby upewnić się, że proporcje obrazu nie zostaną zmienione, poprzez odwrócenie transformacji (która będzie pasować do całości):

//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
źródło
1

Swift 3 CoreGraphics Solution

Jeśli chcesz używać CG z dowolnego powodu, zamiast UIImage, ta konstrukcja Swift 3 oparta na poprzednich odpowiedziach rozwiązała dla mnie problem:

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
źródło
1

Dzieje się tak, ponieważ QuartzCore ma układ współrzędnych „lewy dolny”, a UIKit - „lewy górny”.

W przypadku, gdy możesz rozszerzyć 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
źródło
1

Używam tego Swift 5 , czystego rozszerzenia Core Graphics, które poprawnie obsługuje niezerowe początki w prostokątach obrazu:

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

Możesz użyć go dokładnie tak jak CGContextzwykłej draw(: in:)metody:

ctx.drawFlipped(myImage, in: myRect)
Robin Stewart
źródło
0

W trakcie mojego projektu Skoczyłem z odpowiedzią Kendalla do odpowiedzi Cliffa aby rozwiązać ten problem w przypadku obrazów, które są ładowane z samego telefonu.

W końcu CGImageCreateWithPNGDataProviderzamiast tego użyłem :

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

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

Nie ma to problemów z orientacją, które można uzyskać od CGImagea, UIImagei może być użyte jako zawartość CALayerbez żadnych problemów .

Jacek
źródło
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
źródło
0

Szybka odpowiedź 5 oparta na doskonałej odpowiedzi @ ZpaceZombor

Jeśli masz UIImage, po prostu użyj

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

Jeśli masz CGImage, skorzystaj z mojej kategorii poniżej

Uwaga: W przeciwieństwie do niektórych innych odpowiedzi, ta bierze pod uwagę, że prostokąt, który chcesz narysować, może mieć y! = 0. Te odpowiedzi, które nie biorą tego pod uwagę, są niepoprawne i nie będą działać w ogólnym przypadku.

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)

    }
} 

Użyj w ten sposób:

let imageFrame: CGRect = ...
let context: CGContext = ....
let img: CGImage = ..... 
context.drawImage(image: img, inRect: imageFrame)
n13
źródło
Jest to prawie idealne rozwiązanie, ale rozważ zmianę funkcji rysowania (image: UIImage, inRect rect: CGRect) i obsługiwać uiImage.cgimage wewnątrz metody
Mars
-5

Możesz również rozwiązać ten problem, wykonując następujące czynności:

//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
źródło