CGContextDrawImage ดึงภาพกลับหัวเมื่อผ่าน UIImage.CGImage


179

ไม่มีใครรู้ว่าทำไมการCGContextDrawImageวาดภาพของฉันกลับหัวกลับหาง? ฉันกำลังโหลดภาพจากแอปพลิเคชันของฉัน:

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

จากนั้นขอให้กราฟิกหลักวาดตามบริบทของฉัน:

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

มันแสดงผลในสถานที่ที่เหมาะสมและขนาด แต่ภาพกลับหัว ฉันต้องคิดถึงบางสิ่งที่ชัดเจนจริง ๆ ที่นี่หรือไม่

คำตอบ:


238

แทน

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

ใช้

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

ในช่วงกลางของCGcontextวิธีการเริ่มต้น / สิ้นสุด

วิธีนี้จะวาดภาพด้วยการวางแนวที่ถูกต้องในบริบทภาพปัจจุบันของคุณ - ฉันค่อนข้างแน่ใจว่ามันเกี่ยวข้องกับการUIImageจับความรู้เกี่ยวกับการวางแนวในขณะที่CGContextDrawImageวิธีการรับข้อมูลภาพดิบพื้นฐานโดยไม่เข้าใจการวางแนว


19
วิธีนี้ไม่อนุญาตให้คุณใช้ฟังก์ชั่น CG กับภาพของคุณเช่น CGContextSetAlpha () ในขณะที่คำตอบที่สองทำ
samvermette

2
จุดดี แต่ส่วนใหญ่คุณไม่จำเป็นต้องมีระดับอัลฟาที่กำหนดเองสำหรับการวาดภาพ โดยทั่วไปคติของฉันคือใช้รหัสเล็กน้อยเท่าที่จะทำได้เพราะรหัสเพิ่มเติมหมายถึงโอกาสในการเกิดข้อบกพร่องมากขึ้น
Kendall Helmstetter Gelner

สวัสดีคุณหมายถึงอะไรระหว่างการเริ่มต้น / สิ้นสุด CGContext คุณสามารถให้รหัสตัวอย่างเพิ่มเติมกับฉันได้ไหม ฉันพยายามเก็บบริบทไว้ที่ใดที่หนึ่งและใช้บริบทเพื่อวาดภาพใน
vodkhang

2
ในขณะที่อาจใช้งานได้กับ Rusty - [UIImage drawInRect:] มีปัญหาที่ไม่ปลอดภัยสำหรับเธรด สำหรับแอปพลิเคชันเฉพาะของฉันนั่นเป็นเหตุผลที่ฉันใช้ CGContextDrawImage () ตั้งแต่แรก
Olie

2
เพื่ออธิบายอย่างชัดเจน: กราฟิกคอร์ถูกสร้างขึ้นบน OS X ซึ่งระบบพิกัดนั้นกลับหัวกลับหางเมื่อเทียบกับ iOS (เริ่มต้นที่ด้านซ้ายล่างใน OS X) นั่นเป็นเหตุผลหลักกราฟิกวาดภาพกลับหัวในขณะที่ drawInRect จัดการนี้สำหรับคุณ
borchero

212

แม้หลังจากใช้ทุกอย่างที่ฉันได้พูดไปแล้วฉันก็ยังคงมีละครด้วยภาพ ในท้ายที่สุดฉันเพิ่งใช้ Gimp เพื่อสร้างภาพทั้งหมดของฉันในแนวตั้ง ตอนนี้ฉันไม่จำเป็นต้องใช้การแปลงใด ๆ หวังว่านี่จะไม่ทำให้เกิดปัญหาเพิ่มเติมในการติดตาม

ไม่มีใครรู้ว่าทำไม CGContextDrawImage จะวาดภาพของฉันกลับหัวกลับหาง? ฉันกำลังโหลดภาพจากใบสมัครของฉัน:

Quartz2d ใช้ระบบพิกัดที่แตกต่างกันโดยแหล่งกำเนิดอยู่ที่มุมซ้ายล่าง ดังนั้นเมื่อควอตซ์วาดพิกเซล x [5], y [10] ของภาพ 100 * 100 พิกเซลนั้นจะถูกวาดที่มุมซ้ายล่างแทนที่จะเป็นมุมบนซ้าย จึงทำให้เกิดภาพ 'พลิก'

ระบบพิกัด x ตรงกับดังนั้นคุณจะต้องพลิกพิกัด y

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

ซึ่งหมายความว่าเราได้แปลภาพโดย 0 หน่วยบนแกน x และตามความสูงของภาพบนแกน y อย่างไรก็ตามสิ่งนี้เพียงอย่างเดียวจะหมายถึงภาพของเรายังกลับหัวกลับหางเพียงแค่ถูกดึง "image.size.height" ด้านล่างที่เราต้องการวาด

คู่มือการเขียนโปรแกรม Quartz2D แนะนำให้ใช้ ScaleCTM และส่งค่าลบเพื่อพลิกภาพ คุณสามารถใช้รหัสต่อไปนี้เพื่อทำสิ่งนี้ -

CGContextScaleCTM(context, 1.0, -1.0);

รวมสองก่อนการCGContextDrawImageโทรของคุณและคุณควรจะมีภาพที่ถูกต้อง

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

เพียงแค่ระวังถ้าพิกัด imageRect ของคุณไม่ตรงกับภาพของคุณเพราะคุณสามารถได้ผลลัพธ์ที่ไม่ได้ตั้งใจ

ในการแปลงกลับพิกัด:

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

นี่เป็นสิ่งที่ดี แต่ฉันพบว่าสิ่งอื่น ๆ ได้รับอิทธิพลจากการแปลและการปรับขนาดแม้ว่าฉันจะลองตั้งค่ากลับเป็น 0,0 และ 1.0,1.0 ฉันไปกับทางออกของ Kendall ในท้ายที่สุด แต่ฉันก็รู้ว่ากำลังใช้ UIImage มากกว่า CGImage ระดับต่ำกว่าสิ่งที่เรากำลังทำงานกับที่นี่
PeanutPower

3
หากคุณใช้ drawLayer และต้องการวาด CGImageRef ในบริบทเทคนิคนี้ที่นี่เป็นเพียงแค่ต้องการให้แพทย์สั่ง! หากมี rect สำหรับรูปภาพจากนั้น CGContextTranslateCTM (บริบท, 0, image.size.height + image.origin.y) จากนั้นตั้ง rect.origin.y เป็น 0 ก่อน CGContextDrawImage () ขอบคุณ!
David H

ฉันมีปัญหาหนึ่งครั้งกับ ImageMagick ที่กล้อง iPhone ทำให้เกิดการวางแนวที่ไม่ถูกต้อง ปรากฎว่ามันทำเครื่องหมายการวางแนวในไฟล์ภาพดังนั้นรูปแนวตั้งจึงพูด 960px x 640px โดยตั้งค่าสถานะเป็น "ซ้าย" - ในด้านซ้ายเป็นด้านบน (หรือบางอย่างใกล้เคียง) หัวข้อนี้อาจช่วยได้: stackoverflow.com/questions/1260249/…
Hari Karam Singh

8
ทำไมคุณไม่ใช้CGContextSaveGState()และCGContextRestoreGState()บันทึกและกู้คืนเมทริกซ์การแปลง
Ja͢ck

20

ดีที่สุดในโลกทั้งสองใช้ UIImage drawAtPoint:หรือdrawInRect:ในขณะที่ยังระบุบริบทที่กำหนดเองของคุณ:

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

นอกจากนี้คุณหลีกเลี่ยงการแก้ไขบริบทของคุณด้วยCGContextTranslateCTMหรือCGContextScaleCTMที่คำตอบที่สองไม่


นี่เป็นความคิดที่ดี แต่สำหรับฉันแล้วมันทำให้เกิดภาพที่คว่ำและจากซ้ายไปขวา ฉันเดาว่าผลลัพธ์จะแตกต่างกันไปในแต่ละภาพ
ลุคโรเจอร์ส

บางทีนี่อาจจะหยุดทำงานกับ iOS รุ่นที่ใหม่กว่า ตอนนั้นมันใช้ได้กับทุกอย่างสำหรับฉัน
Rivera

ฉันคิดว่ามันเป็นเรื่องจริงที่ฉันจะสร้างบริบท เมื่อฉันเริ่มใช้UIGraphicsBeginImageContextWithOptionsแทนการเริ่มใหม่CGContextก็ดูเหมือนจะทำงาน
Luke Rogers

12

เอกสาร Quartz2D ที่เกี่ยวข้อง: https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010156-1444

การพลิกระบบพิกัดเริ่มต้น

การพลิกในการวาดภาพ UIKit จะปรับเปลี่ยน CALayer สำรองเพื่อจัดสภาพแวดล้อมการวาดภาพให้มีระบบพิกัด LLO กับระบบพิกัดเริ่มต้นของ UIKit หากคุณใช้วิธีการและฟังก์ชันของ UIKit สำหรับการวาดเท่านั้นคุณไม่จำเป็นต้องพลิก CTM อย่างไรก็ตามหากคุณผสมคอร์กราฟิกหรือการเรียกใช้ฟังก์ชั่นภาพ I / O กับการโทร UIKit การพลิก CTM อาจจำเป็น

โดยเฉพาะถ้าคุณวาดภาพหรือเอกสาร PDF โดยการเรียกฟังก์ชั่นคอร์กราฟิกโดยตรงวัตถุจะถูกแสดงผลกลับหัวในบริบทของมุมมอง คุณต้องพลิก CTM เพื่อแสดงภาพและหน้าอย่างถูกต้อง

ในการพลิกวัตถุที่ถูกดึงไปยังบริบทกราฟิกหลักเพื่อให้ปรากฏอย่างถูกต้องเมื่อแสดงในมุมมอง UIKit คุณต้องแก้ไข CTM ในสองขั้นตอน คุณแปลต้นกำเนิดเป็นมุมซ้ายบนของพื้นที่วาดรูปจากนั้นคุณใช้การแปลระดับปรับเปลี่ยนพิกัด y ด้วย -1 รหัสสำหรับการทำเช่นนี้ดูเหมือนว่าต่อไปนี้:

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

10

หากใครมีความสนใจในวิธีการแก้ปัญหาที่ไม่มีเกมง่ายๆสำหรับการวาดภาพใน rect ที่กำหนดเองในบริบท:

 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)

}

ภาพจะถูกปรับสัดส่วนเพื่อเติมเต็ม rect


7

ฉันไม่แน่ใจUIImageแต่พฤติกรรมแบบนี้มักเกิดขึ้นเมื่อพิกัดถูกพลิก ระบบพิกัด OS X ส่วนใหญ่มีจุดกำเนิดที่มุมซ้ายล่างเช่นเดียวกับ Postscript และ PDF แต่CGImageระบบพิกัดมีจุดกำเนิดที่มุมซ้ายบน

วิธีแก้ไขที่เป็นไปได้อาจเกี่ยวข้องกับisFlippedคุณสมบัติหรือการscaleYBy:-1แปลงเลียนแบบ


3

UIImageมีCGImageสมาชิกเนื้อหาหลักเช่นเดียวกับปัจจัยการปรับขนาดและการวางแนว เนื่องจากCGImageและฟังก์ชั่นต่าง ๆ นั้นมาจาก OSX จึงคาดว่าระบบพิกัดที่จะกลับหัวเมื่อเทียบกับ iPhone เมื่อคุณสร้าง a UIImageมันจะเริ่มต้นที่การวางแนวคว่ำเพื่อชดเชย (คุณสามารถเปลี่ยนได้!) ใช้ . CGImageคุณสมบัติในการเข้าถึงCGImageฟังก์ชั่นที่ทรงพลังแต่การวาดภาพลงบนหน้าจอ iPhone ฯลฯ ทำได้ดีที่สุดด้วยUIImageวิธีการต่างๆ


1
คุณจะเปลี่ยนการวางแนวคว่ำ - ลงเริ่มต้นของ UIImage ได้อย่างไร
MegaManX

3

ตอบเพิ่มเติมด้วยรหัส Swift

กราฟิกควอตซ์ 2 มิติใช้ระบบพิกัดที่มีจุดกำเนิดที่ด้านล่างซ้ายในขณะที่ UIKit ใน iOS ใช้ระบบพิกัดที่มีแหล่งกำเนิดที่ด้านบนซ้าย ทุกอย่างใช้งานได้ปกติ แต่เมื่อใช้งานกราฟิกคุณต้องแก้ไขระบบพิกัดด้วยตัวเอง เอกสารฯ :

เทคโนโลยีบางอย่างตั้งค่าบริบทของกราฟิกโดยใช้ระบบพิกัดเริ่มต้นที่แตกต่างจากที่ใช้โดยควอตซ์ สัมพันธ์กับควอตซ์ระบบพิกัดดังกล่าวเป็นระบบพิกัดที่ปรับเปลี่ยนและต้องได้รับการชดเชยเมื่อดำเนินการวาดภาพควอตซ์บางส่วน ระบบพิกัดที่แก้ไขบ่อยที่สุดวางต้นกำเนิดที่มุมซ้ายบนของบริบทและเปลี่ยนแกน y ให้ชี้ไปที่ด้านล่างของหน้า

ปรากฏการณ์นี้สามารถเห็นได้ในสองอินสแตนซ์ต่อไปนี้ของมุมมองแบบกำหนดเองที่วาดรูปในdrawRectวิธีการของพวกเขา

ป้อนคำอธิบายรูปภาพที่นี่

ทางด้านซ้ายของภาพจะกลับหัวกลับด้านและทางด้านขวาของระบบพิกัดได้รับการแปลและปรับขนาดเพื่อให้จุดกำเนิดอยู่ด้านซ้ายบน

ภาพกลับหัว

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)

}

ระบบพิกัดที่ดัดแปลง

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 และ 4.0

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

ไม่มีการเปลี่ยนแปลงที่จำเป็น


2

เราสามารถแก้ปัญหานี้ได้โดยใช้ฟังก์ชั่นเดียวกัน:

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

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

UIGraphicsPopContext();

UIGraphicsEndImageContext();

1

drawInRectเป็นวิธีที่จะไปอย่างแน่นอน นี่เป็นอีกสิ่งเล็ก ๆ น้อย ๆ ที่จะมีประโยชน์เมื่อทำสิ่งนี้ โดยปกติรูปภาพและสี่เหลี่ยมผืนผ้าที่กำลังจะไปไม่สอดคล้องกัน ในกรณีdrawInRectนั้นจะยืดภาพ ต่อไปนี้เป็นวิธีที่รวดเร็วและยอดเยี่ยมที่จะทำให้แน่ใจว่าสัดส่วนของภาพจะไม่เปลี่ยนแปลงโดยการย้อนกลับการเปลี่ยนแปลง (ซึ่งจะเหมาะกับทุกสิ่ง)

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

หากคุณต้องการใช้ CG ไม่ว่าจะด้วยเหตุผลใดก็ตามแทนที่จะเป็น UIImage โครงสร้าง Swift 3 ที่อิงจากคำตอบก่อนหน้านี้ช่วยแก้ปัญหาให้ฉันได้:

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

มันเกิดขึ้นเพราะ QuartzCore มีระบบพิกัด "ล่างซ้าย" ในขณะที่ UIKit - "บนซ้าย"

ในกรณีที่คุณสามารถขยาย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

ฉันใช้ส่วนขยายกราฟิกหลักของSwift 5 ที่บริสุทธิ์ซึ่งจัดการต้นกำเนิดที่ไม่เป็นศูนย์ในรูปสี่เหลี่ยมผืนผ้าได้อย่างถูกต้อง:

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

คุณสามารถใช้มันเหมือนวิธีการCGContextปกติdraw(: in:):

ctx.drawFlipped(myImage, in: myRect)

0

ในระหว่างหลักสูตรของโครงการของฉันที่ฉันกระโดดลงมาจากคำตอบของเคนดอลที่จะตอบคลิฟของการแก้ปัญหานี้สำหรับภาพที่โหลดจากโทรศัพท์มือถือของตัวเอง

ในที่สุดฉันก็ใช้CGImageCreateWithPNGDataProviderแทน:

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

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

สิ่งนี้ไม่ได้รับผลกระทบจากปัญหาการวางแนวที่คุณจะได้รับจากการใช้CGImagea UIImageและสามารถใช้เป็นเนื้อหาของ a CALayerโดยไม่ต้องผูกปม


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

Swift 5 คำตอบตามคำตอบที่ยอดเยี่ยมของ @ZpaceZombor

หากคุณมี UIImage เพียงใช้

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

หากคุณมี CGImage ใช้หมวดหมู่ของฉันด้านล่าง

หมายเหตุ: ต่างจากคำตอบอื่น ๆ คำตอบนี้คำนึงถึงว่า rect ที่คุณต้องการวาดอาจมี y! = 0 คำตอบที่ไม่นำมาพิจารณานั้นไม่ถูกต้องและจะไม่ทำงานในกรณีทั่วไป

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)

    }
} 

ใช้แบบนี้:

let imageFrame: CGRect = ...
let context: CGContext = ....
let img: CGImage = ..... 
context.drawImage(image: img, inRect: imageFrame)

นี่เป็นวิธีการแก้ปัญหาที่สมบูรณ์แบบ แต่ให้พิจารณาเปลี่ยนฟังก์ชั่นเพื่อวาด (ภาพ: UIImage, inRect rect: CGRect) และจัดการ uiImage.cgimage ภายในวิธีนี้
ดาวอังคาร

-5

คุณสามารถแก้ปัญหานี้ได้โดยทำดังนี้

//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.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.