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?

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.


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

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ź.


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

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.


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ą.


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.


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

3

Swift 3.0 i 4.0

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

Żadna zmiana nie jest wymagana


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

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

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

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)

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)

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 .


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

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)

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.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.