CGContextDrawImage menarik gambar terbalik ketika melewati UIImage.CGImage

179

Adakah yang tahu mengapa CGContextDrawImagegambar saya terbalik? Saya memuat gambar dari aplikasi saya:

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

Dan kemudian hanya meminta grafis inti untuk menariknya ke konteks saya:

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

Itu membuat di tempat yang tepat, dan dimensi, tetapi gambar terbalik. Saya harus kehilangan sesuatu yang sangat jelas di sini?

rustyshelf
sumber

Jawaban:

238

Dari pada

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

Menggunakan

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

Di tengah CGcontextmetode awal / akhir Anda .

Ini akan menarik gambar dengan orientasi yang benar ke dalam konteks gambar Anda saat ini - Saya cukup yakin ini ada hubungannya dengan UIImagepengetahuan tentang orientasi sementara CGContextDrawImagemetode mendapatkan data gambar mentah yang mendasarinya tanpa pemahaman orientasi.

Kendall Helmstetter Gelner
sumber
19
Solusi ini tidak memungkinkan Anda untuk menggunakan fungsi CG pada gambar Anda, seperti CGContextSetAlpha (), sedangkan jawaban 2 tidak.
samvermette
2
Poin bagus, meskipun sebagian besar Anda tidak memerlukan level alfa khusus untuk menggambar gambar (biasanya yang dimasukkan ke gambar sebelumnya untuk hal-hal yang membutuhkan alpha). Pada dasarnya moto saya adalah, gunakan sedikit kode yang Anda bisa karena lebih banyak kode berarti lebih banyak peluang untuk bug.
Kendall Helmstetter Gelner
Hai, Apa yang Anda maksud dengan di tengah metode CGContext awal / akhir Anda, dapatkah Anda memberi saya lebih banyak kode sampel. Saya mencoba menyimpan konteks di suatu tempat dan menggunakan konteks untuk menggambarnya
vodkhang
2
Meskipun mungkin berhasil untuk Rusty, - [UIImage drawInRect:] memiliki masalah yang tidak aman untuk thread. Untuk aplikasi khusus saya, itu sebabnya saya menggunakan CGContextDrawImage () di tempat pertama.
Olie
2
Hanya untuk memperjelas: Core Graphics dibangun ke OS X di mana sistem koordinat terbalik dibandingkan dengan iOS (y mulai dari sisi kiri bawah di OS X). Itu sebabnya Core Graphics menarik gambar terbalik sedangkan drawInRect menangani ini untuk Anda
borchero
213

Bahkan setelah menerapkan semua yang saya sebutkan, saya masih punya drama dengan gambar. Pada akhirnya, saya baru saja menggunakan Gimp untuk membuat versi 'flipped vertical' dari semua gambar saya. Sekarang saya tidak perlu menggunakan Transforms. Semoga ini tidak akan menyebabkan masalah lebih lanjut di trek.

Adakah yang tahu mengapa CGContextDrawImage akan membuat gambar saya terbalik? Saya memuat gambar dari aplikasi saya:

Quartz2d menggunakan sistem koordinat yang berbeda, di mana asalnya berada di sudut kiri bawah. Jadi, ketika Kuarsa menarik piksel x [5], y [10] dari gambar 100 * 100, piksel itu sedang digambar di sudut kiri bawah, bukan di kiri atas. Sehingga menyebabkan gambar 'terbalik'.

Sistem x koordinat cocok, jadi Anda perlu membalikkan koordinat y.

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

Ini berarti kami telah menerjemahkan gambar dengan 0 unit pada sumbu x dan oleh tinggi gambar pada sumbu y. Namun, ini saja akan berarti gambar kita masih terbalik, hanya ditarik "image.size.height" di bawah di mana kita ingin gambar itu diambil.

Panduan pemrograman Quartz2D merekomendasikan penggunaan ScaleCTM dan memberikan nilai negatif untuk membalik gambar. Anda dapat menggunakan kode berikut untuk melakukan ini -

CGContextScaleCTM(context, 1.0, -1.0);

Kombinasikan keduanya tepat sebelum CGContextDrawImagepanggilan Anda dan Anda harus memiliki gambar yang digambar dengan benar.

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

Berhati-hatilah jika koordinat imageRect Anda tidak cocok dengan gambar Anda, karena Anda bisa mendapatkan hasil yang tidak diinginkan.

Untuk mengonversi kembali koordinat:

CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -imageRect.size.height);
Cliff Viegas
sumber
ini bagus tetapi saya menemukan bahwa hal-hal lain sedang dipengaruhi oleh terjemahan dan skala bahkan ketika saya mencoba mengatur kembali ke 0,0 dan 1,0,1.0 saya pergi dengan solusi Kendall pada akhirnya tetapi saya menyadari ini yang menggunakan UIImage daripada hal-hal tingkat CGImage yang lebih rendah yang sedang kami kerjakan di sini
PeanutPower
3
Jika Anda menggunakan drawLayer dan ingin menggambar CGImageRef ke dalam konteksnya, teknik ini hanya ingin diperintahkan oleh dokter! Jika memiliki rect untuk gambar, maka CGContextTranslateCTM (konteks, 0, image.size.height + image.origin.y), lalu atur rect.origin.y ke 0 sebelum CGContextDrawImage (). Terima kasih!
David H
Saya pernah mengalami masalah dengan ImageMagick di mana kamera iPhone mengakibatkan orientasi yang salah. Ternyata itu melakukan bendera orientasi dalam file gambar, jadi gambar potret adalah, katakanlah 960px x 640px dengan bendera diatur ke "kiri" - karena di sisi kiri adalah atas (atau sesuatu yang dekat dengan itu). Utas ini mungkin membantu: stackoverflow.com/questions/1260249/…
Hari Karam Singh
8
Mengapa Anda tidak menggunakan CGContextSaveGState()dan CGContextRestoreGState()menyimpan serta memulihkan matriks transformasi?
Ja͢ck
20

Terbaik dari kedua dunia, gunakan UIImage drawAtPoint:atau drawInRect:saat masih menentukan konteks khusus Anda:

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

Anda juga menghindari mengubah konteks Anda dengan CGContextTranslateCTMatau CGContextScaleCTMyang jawaban kedua lakukan.

Rivera
sumber
Ini adalah ide yang bagus, tetapi bagi saya itu menghasilkan gambar yang terbalik dan kiri ke kanan. Saya menduga bahwa hasilnya akan bervariasi dari gambar ke gambar.
Luke Rogers
Mungkin ini berhenti bekerja dengan rilis iOS nanti? Pada saat itu semuanya bekerja dengan baik untuk saya.
Rivera
Saya pikir sebenarnya adalah bagaimana saya menciptakan konteksnya. Begitu saya mulai menggunakan UIGraphicsBeginImageContextWithOptionsalih-alih hanya mencari yang baru CGContext, sepertinya berhasil.
Luke Rogers
12

Dokumen Quartz2D yang relevan: https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010156-14

Membalik Sistem Koordinat Default

Membalikkan gambar UIKit mengubah CALayer dukungan untuk meluruskan lingkungan gambar yang memiliki sistem koordinat LLO dengan sistem koordinat default UIKit. Jika Anda hanya menggunakan metode dan fungsi UIKit untuk menggambar, Anda tidak perlu membalikkan CTM. Namun, jika Anda mencampurkan panggilan gambar inti atau fungsi I / O gambar dengan panggilan UIKit, membalik CTM mungkin diperlukan.

Khususnya, jika Anda menggambar gambar atau dokumen PDF dengan memanggil fungsi Core Graphics secara langsung, objek dirender terbalik dalam konteks tampilan. Anda harus membalik CTM untuk menampilkan gambar dan halaman dengan benar.

Untuk membalik objek yang ditarik ke konteks Grafik Inti sehingga tampak dengan benar saat ditampilkan dalam tampilan UIKit, Anda harus memodifikasi CTM dalam dua langkah. Anda menerjemahkan asal ke sudut kiri atas area gambar, dan kemudian Anda menerapkan terjemahan skala, memodifikasi koordinat y dengan -1. Kode untuk melakukan ini mirip dengan yang berikut:

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

Jika ada yang tertarik pada solusi no-brainer untuk menggambar gambar dalam kotak kustom dalam konteks:

 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)

}

Gambar akan diskalakan untuk mengisi kotak.

ZpaceZombor
sumber
7

Saya tidak yakin UIImage, tetapi perilaku semacam ini biasanya terjadi ketika koordinat dibalik. Sebagian besar sistem koordinat OS X berasal dari sudut kiri bawah, seperti pada Postscript dan PDF. TapiCGImage sistem koordinat berasal dari sudut kiri atas.

Solusi yang mungkin dapat melibatkan isFlippedproperti atau scaleYBy:-1transformasi affine.

mouviciel
sumber
3

UIImagemengandung CGImagesebagai anggota konten utamanya serta faktor penskalaan dan orientasi. Karena CGImagedan berbagai fungsinya berasal dari OSX, ia mengharapkan sistem koordinat yang terbalik dibandingkan dengan iPhone. Saat Anda membuat UIImage, standar untuk orientasi terbalik untuk kompensasi (Anda dapat mengubah ini!). Menggunakan . CGImageproperti untuk mengakses fungsi yang sangat kuat CGImage, tetapi menggambar ke layar iPhone dll. paling baik dilakukan dengan UIImagemetode.

James L.
sumber
1
bagaimana Anda mengubah orientasi UIImage terbalik terbalik default?
MegaManX
3

Jawaban tambahan dengan kode Swift

Grafik kuarsa 2D menggunakan sistem koordinat dengan asal di kiri bawah sedangkan UIKit di iOS menggunakan sistem koordinat dengan asal di kiri atas. Semuanya biasanya berfungsi dengan baik tetapi ketika melakukan beberapa operasi grafis, Anda harus memodifikasi sendiri sistem koordinat. The dokumentasi menyatakan:

Beberapa teknologi mengatur konteks grafik mereka menggunakan sistem koordinat default yang berbeda dari yang digunakan oleh Quartz. Relatif terhadap Kuarsa, sistem koordinat seperti itu adalah sistem koordinat yang dimodifikasi dan harus dikompensasi ketika melakukan beberapa operasi menggambar Quartz. Sistem koordinat yang dimodifikasi yang paling umum menempatkan asal di sudut kiri atas konteks dan mengubah sumbu y untuk menunjuk ke bagian bawah halaman.

Fenomena ini dapat dilihat dalam dua contoh tampilan kustom yang menarik gambar dalam drawRectmetode mereka .

masukkan deskripsi gambar di sini

Di sisi kiri, gambar terbalik dan di sisi kanan sistem koordinat telah diterjemahkan dan diskalakan sehingga asal berada di kiri atas.

Gambar terbalik

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)

}

Sistem koordinat yang dimodifikasi

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
sumber
3

Swift 3.0 & 4.0

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

Tidak Perlu Mengubah

Abdul Waheed
sumber
2

Kami dapat memecahkan masalah ini menggunakan fungsi yang sama:

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

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

UIGraphicsPopContext();

UIGraphicsEndImageContext();
Priyanka V
sumber
1

drawInRecttentu cara untuk pergi. Inilah hal kecil lain yang akan berguna saat melakukan hal ini. Biasanya gambar dan persegi panjang yang akan dituju tidak sesuai. Dalam hal ini drawInRectakan meregangkan gambar. Berikut cara cepat dan keren untuk memastikan bahwa rasio aspek gambar tidak berubah, dengan membalik transformasi (yang akan cocok dengan semuanya):

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

Solusi Swift 3 CoreGraphics

Jika Anda ingin menggunakan CG untuk alasan apa pun, alih-alih UIImage, konstruksi Swift 3 ini berdasarkan jawaban sebelumnya memang memecahkan masalah bagi saya:

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

Itu terjadi karena QuartzCore memiliki sistem koordinat "kiri bawah", sedangkan UIKit - "kiri atas".

Dalam hal ini Anda dapat memperluas 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
sumber
1

Saya menggunakan Swift 5 ini , ekstensi Core Graphics murni yang dengan benar menangani asal bukan nol dalam rect gambar:

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

Anda dapat menggunakannya persis seperti metode CGContextbiasa draw(: in:):

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

Selama proyek saya, saya melompat dari jawaban Kendall ke jawaban Cliff untuk memecahkan masalah ini untuk gambar yang diambil dari telepon itu sendiri.

Pada akhirnya saya CGImageCreateWithPNGDataProvidermalah menggunakan :

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

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

Ini tidak mengalami masalah orientasi yang akan Anda dapatkan dari mendapatkan CGImagedari UIImagedan itu dapat digunakan sebagai konten CALayertanpa hambatan.

Mendongkrak
sumber
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
sumber
0

Swift 5 menjawab berdasarkan @ ZpaceZombor

Jika Anda memiliki UIImage, gunakan saja

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

Jika Anda memiliki CGImage gunakan kategori saya di bawah ini

Catatan: Tidak seperti beberapa jawaban lain, jawaban ini memperhitungkan bahwa rect yang ingin Anda tarik mungkin memiliki y! = 0. Jawaban yang tidak memperhitungkannya salah dan tidak akan berfungsi dalam kasus umum.

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)

    }
} 

Gunakan seperti ini:

let imageFrame: CGRect = ...
let context: CGContext = ....
let img: CGImage = ..... 
context.drawImage(image: img, inRect: imageFrame)
n13
sumber
Ini hampir merupakan solusi yang sempurna, tetapi pertimbangkan untuk mengubah fungsi menggambar (gambar: UIImage, inRect rect: CGRect) dan menangani uiImage.cgimage di dalam metode
Mars
-5

Anda juga dapat mengatasi masalah ini dengan melakukan ini:

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