CGContextDrawImage vẽ hình ảnh lộn ngược khi vượt qua UIImage.CGImage


179

Có ai biết tại sao CGContextDrawImagesẽ vẽ hình ảnh của tôi lộn ngược? Tôi đang tải một hình ảnh từ ứng dụng của mình:

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

Và sau đó chỉ cần yêu cầu đồ họa cốt lõi để vẽ nó vào bối cảnh của tôi:

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

Nó hiển thị ở đúng vị trí và kích thước, nhưng hình ảnh bị lộn ngược. Tôi phải thiếu một cái gì đó thực sự rõ ràng ở đây?

Câu trả lời:


238

Thay vì

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

Sử dụng

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

Ở giữa các CGcontextphương pháp bắt đầu / kết thúc của bạn .

Điều này sẽ vẽ hình ảnh với hướng chính xác vào bối cảnh hình ảnh hiện tại của bạn - Tôi khá chắc chắn rằng điều này có liên quan đến việc UIImagenắm bắt kiến ​​thức về hướng trong khi CGContextDrawImagephương pháp lấy dữ liệu hình ảnh thô bên dưới mà không hiểu về định hướng.


19
Giải pháp này không cho phép bạn sử dụng các chức năng CG trên hình ảnh của mình, chẳng hạn như CGContextSetAlpha (), trong khi câu trả lời thứ 2 thì có.
samvermette

2
Điểm hay, mặc dù chủ yếu là bạn không cần các mức alpha tùy chỉnh để vẽ một hình ảnh (thường là các mức được đưa vào hình ảnh trước thời hạn cho những thứ cần alpha). Về cơ bản phương châm của tôi là, sử dụng một ít mã như bạn có thể bởi vì nhiều mã hơn có nghĩa là nhiều cơ hội hơn cho các lỗi.
Kendall Helmstetter Gelner

Xin chào, ý của bạn là gì ở giữa các phương thức CGContext bắt đầu / kết thúc của bạn, bạn có thể cho tôi thêm mã mẫu không. Tôi đang cố gắng lưu trữ bối cảnh ở đâu đó và sử dụng bối cảnh để vẽ hình ảnh trong
vodkhang

2
Mặc dù nó có thể hoạt động cho Rusty, - [UIImage drawInRect:] có vấn đề là nó không an toàn cho chuỗi. Đối với ứng dụng cụ thể của tôi, đó là lý do tại sao tôi sử dụng CGContextDrawImage () ở nơi đầu tiên.
Olie

2
Chỉ cần làm rõ: Đồ họa lõi được xây dựng trên OS X nơi hệ thống tọa độ lộn ngược so với iOS (y bắt đầu ở phía dưới bên trái trong OS X). Đó là lý do tại sao Core Graphics vẽ hình ảnh lộn ngược trong khi drawInRect xử lý việc này cho bạn
borchero

213

Ngay cả sau khi áp dụng mọi thứ tôi đã đề cập, tôi vẫn có những bộ phim truyền hình với những hình ảnh. Cuối cùng, tôi vừa sử dụng Gimp để tạo phiên bản 'lật dọc' cho tất cả các hình ảnh của mình. Bây giờ tôi không cần sử dụng bất kỳ Biến đổi nào. Hy vọng rằng điều này sẽ không gây ra vấn đề tiếp theo.

Có ai biết tại sao CGContextDrawImage sẽ vẽ hình ảnh của tôi lộn ngược không? Tôi đang tải một hình ảnh từ ứng dụng của mình:

Quartz2d sử dụng một hệ thống phối hợp khác nhau, trong đó gốc tọa độ ở góc dưới bên trái. Vì vậy, khi Quartz vẽ pixel x [5], y [10] của hình ảnh 100 * 100, pixel đó sẽ được vẽ ở góc dưới bên trái thay vì phía trên bên trái. Do đó gây ra hình ảnh 'lật'.

Hệ thống tọa độ x khớp, vì vậy bạn sẽ cần phải lật tọa độ y.

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

Điều này có nghĩa là chúng tôi đã dịch hình ảnh bằng 0 đơn vị trên trục x và theo chiều cao hình ảnh trên trục y. Tuy nhiên, điều này một mình sẽ có nghĩa là hình ảnh của chúng tôi vẫn bị lộn ngược, chỉ được vẽ "image.size.height" bên dưới nơi chúng tôi muốn nó được vẽ.

Hướng dẫn lập trình Quartz2D khuyên bạn nên sử dụng ScaleCTM và truyền các giá trị âm để lật ảnh. Bạn có thể sử dụng mã sau đây để làm điều này -

CGContextScaleCTM(context, 1.0, -1.0);

Kết hợp cả hai ngay trước CGContextDrawImagecuộc gọi của bạn và bạn sẽ có hình ảnh được vẽ chính xác.

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

Chỉ cần cẩn thận nếu tọa độ imageRect của bạn không khớp với hình ảnh của bạn, vì bạn có thể nhận được kết quả ngoài ý muốn.

Để chuyển đổi lại tọa độ:

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

Điều này là tốt nhưng tôi thấy rằng những thứ khác đã bị ảnh hưởng bởi bản dịch và tỷ lệ ngay cả khi tôi cố gắng thiết lập lại 0,0 và 1,0,1.0 Cuối cùng tôi đã đi đến giải pháp của Kendall nhưng tôi nhận ra rằng đó là sử dụng UIImage thay vì công cụ CGImage cấp thấp hơn mà chúng tôi đang làm việc ở đây
PeanutPower

3
Nếu bạn đang sử dụng drawLayer và muốn vẽ CGImageRef vào bối cảnh, kỹ thuật này ở đây chỉ là muốn bác sĩ ra lệnh! Nếu có chỉnh lưu cho hình ảnh, thì CGContextTranslateCTM (bối cảnh, 0, image.size.height + image.origin.y), sau đó đặt orth.origin.y thành 0 trước CGContextDrawImage (). Cảm ơn!
David H

Tôi đã gặp sự cố một lần với ImageMagick khi camera iPhone dẫn đến định hướng không chính xác. Hóa ra đó là cờ định hướng trong tệp hình ảnh, vì vậy, hình ảnh chân dung là, có nghĩa là 960px x 640px với cờ được đặt thành "bên trái" - vì ở phía bên trái là trên cùng (hoặc một cái gì đó gần với nó). Chủ đề này có thể giúp: stackoverflow.com/questions/1260249/ Mạnh
Hari Karam Singh

8
Tại sao bạn không sử dụng CGContextSaveGState()CGContextRestoreGState()để lưu và khôi phục ma trận biến đổi?
Ja͢ck

20

Tốt nhất trong cả hai thế giới, hãy sử dụng UIImage drawAtPoint:hoặc drawInRect:trong khi vẫn chỉ định bối cảnh tùy chỉnh của bạn:

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

Ngoài ra, bạn tránh sửa đổi bối cảnh của bạn với CGContextTranslateCTMhoặc CGContextScaleCTMcâu trả lời thứ hai làm.


Đây là một ý tưởng tuyệt vời, nhưng đối với tôi nó dẫn đến một hình ảnh bị lộn ngược và từ trái sang phải. Tôi đoán rằng kết quả sẽ thay đổi từ hình ảnh này sang hình ảnh khác.
Luke Rogers

Có lẽ điều này đã ngừng hoạt động với các bản phát hành iOS sau này? Tại thời điểm nó làm việc với tất cả mọi thứ cho tôi.
Rivera

Tôi nghĩ rằng nó thực sự phụ thuộc vào cách tôi tạo ra bối cảnh. Khi tôi bắt đầu sử dụng UIGraphicsBeginImageContextWithOptionsthay vì chỉ sử dụng một cái mới CGContext, nó dường như hoạt động.
Luke Rogers

12

Các tài liệu Quartz2D có liên quan: https://developer.apple.com/l Library / ios / document / 2Dabinging

Lật hệ thống tọa độ mặc định

Việc lật trong bản vẽ UIKit sẽ sửa đổi CALayer sao lưu để căn chỉnh môi trường bản vẽ có hệ tọa độ LLO với hệ tọa độ mặc định của UIKit. Nếu bạn chỉ sử dụng các phương thức và chức năng của UIKit để vẽ, bạn không cần phải lật CTM. Tuy nhiên, nếu bạn kết hợp các cuộc gọi chức năng Core Graphics hoặc Image I / O với các cuộc gọi UIKit, việc lật CTM có thể là cần thiết.

Cụ thể, nếu bạn vẽ một hình ảnh hoặc tài liệu PDF bằng cách gọi trực tiếp các chức năng Đồ họa lõi, đối tượng sẽ bị đảo lộn trong bối cảnh của chế độ xem. Bạn phải lật CTM để hiển thị hình ảnh và trang chính xác.

Để lật một đối tượng được vẽ vào bối cảnh Đồ họa lõi để nó xuất hiện chính xác khi được hiển thị trong chế độ xem UIKit, bạn phải sửa đổi CTM theo hai bước. Bạn dịch điểm gốc sang góc trên bên trái của khu vực vẽ và sau đó bạn áp dụng bản dịch tỷ lệ, sửa đổi tọa độ y theo -1. Mã để làm điều này trông tương tự như sau:

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

10

Nếu bất cứ ai quan tâm đến một giải pháp không có trí tuệ để vẽ một hình ảnh trong một chỉnh lưu tùy chỉnh trong một bối cảnh:

 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)

}

Hình ảnh sẽ được thu nhỏ để điền vào trực tràng.


7

Tôi không chắc chắn UIImage, nhưng loại hành vi này thường xảy ra khi lật tọa độ. Hầu hết các hệ tọa độ OS X đều có nguồn gốc ở góc dưới bên trái, như trong Postcript và PDF. Nhưng CGImagehệ tọa độ có nguồn gốc ở góc trên bên trái.

Các giải pháp có thể có thể liên quan đến một isFlippedtài sản hoặc một scaleYBy:-1biến đổi affine.


3

UIImagechứa một CGImagethành viên nội dung chính của nó cũng như các yếu tố tỷ lệ và định hướng. Vì CGImagevà các chức năng khác nhau của nó có nguồn gốc từ OSX, nó mong đợi một hệ thống tọa độ lộn ngược so với iPhone. Khi bạn tạo một UIImage, nó mặc định theo hướng lộn ngược để bù lại (bạn có thể thay đổi điều này!). Sử dụng . CGImagethuộc tính để truy cập các CGImagechức năng rất mạnh , nhưng vẽ lên màn hình iPhone, vv được thực hiện tốt nhất với các UIImagephương thức.


1
Làm thế nào để bạn thay đổi hướng lộn ngược mặc định của UIImage?
MegaManX

3

Câu trả lời bổ sung với mã Swift

Đồ họa Quartz 2D sử dụng hệ thống tọa độ có gốc ở phía dưới bên trái trong khi UIKit trong iOS sử dụng hệ thống tọa độ có gốc ở phía trên bên trái. Mọi thứ thường hoạt động tốt nhưng khi thực hiện một số thao tác đồ họa, bạn phải tự sửa đổi hệ tọa độ. Các tài liệu nêu:

Một số công nghệ thiết lập bối cảnh đồ họa của chúng bằng hệ thống tọa độ mặc định khác với hệ thống tọa độ được sử dụng bởi Quartz. Liên quan đến Quartz, một hệ tọa độ như vậy là một hệ tọa độ được sửa đổi và phải được bù khi thực hiện một số thao tác vẽ Quartz. Hệ tọa độ được sửa đổi phổ biến nhất đặt gốc tọa độ ở góc trên bên trái của bối cảnh và thay đổi trục y để chỉ về phía dưới cùng của trang.

Hiện tượng này có thể được nhìn thấy trong hai trường hợp sau đây của chế độ xem tùy chỉnh vẽ hình ảnh trong drawRectphương thức của họ .

nhập mô tả hình ảnh ở đây

Ở phía bên trái, hình ảnh lộn ngược và ở phía bên phải, hệ tọa độ đã được dịch và thu nhỏ sao cho điểm gốc nằm ở phía trên bên trái.

Hình ảnh lộn ngược

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)

}

Hệ thống tọa độ sửa đổi

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)

Không cần thay đổi


2

Chúng ta có thể giải quyết vấn đề này bằng cách sử dụng chức năng tương tự:

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

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

UIGraphicsPopContext();

UIGraphicsEndImageContext();

1

drawInRectchắc chắn là con đường để đi Đây là một điều nhỏ sẽ có ích khi làm điều này. Thông thường hình ảnh và hình chữ nhật mà nó sẽ đi không phù hợp. Trong trường hợp đó drawInRectsẽ kéo dài hình ảnh. Đây là một cách nhanh chóng và thú vị để đảm bảo rằng khẩu phần của hình ảnh không bị thay đổi, bằng cách đảo ngược sự biến đổi (sẽ phù hợp với toàn bộ nội dung):

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

Giải pháp Swift 3 CoreGraphics

Nếu bạn muốn sử dụng CG cho bất kỳ lý do gì của bạn, thay vì UIImage, việc xây dựng Swift 3 này dựa trên các câu trả lời trước đó đã giải quyết vấn đề cho tôi:

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

Điều này xảy ra vì QuartzCore có hệ tọa độ "dưới cùng bên trái", trong khi UIKit - "trên cùng bên trái".

Trong trường hợp bạn có thể mở rộng 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

Tôi sử dụng Swift 5 , tiện ích mở rộng Core Graphics thuần túy này xử lý chính xác nguồn gốc khác không trong các hình ảnh:

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

Bạn có thể sử dụng chính xác như phương pháp CGContextthông thường draw(: in:):

ctx.drawFlipped(myImage, in: myRect)

0

Trong quá trình thực hiện dự án của mình, tôi đã nhảy từ câu trả lời của Kendall sang câu trả lời của Cliff để giải quyết vấn đề này cho những hình ảnh được tải từ chính điện thoại.

Cuối cùng, tôi đã sử dụng CGImageCreateWithPNGDataProviderthay thế:

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

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

Điều này không gặp phải các vấn đề định hướng mà bạn sẽ nhận được từ việc lấy CGImagetừ a UIImagevà nó có thể được sử dụng làm nội dung của một CALayercú hích.


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

Câu trả lời Swift 5 dựa trên câu trả lời xuất sắc của @ ZpaceZombor

Nếu bạn có UIImage, chỉ cần sử dụng

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

Nếu bạn có CGImage, hãy sử dụng danh mục của tôi dưới đây

Lưu ý: Không giống như một số câu trả lời khác, câu trả lời này có tính đến việc người bạn muốn rút ra có thể có y! = 0. Những câu trả lời không tính đến điều đó là không chính xác và sẽ không hoạt động trong trường hợp chung.

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)

    }
} 

Sử dụng như thế này:

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

Đây gần như là một giải pháp hoàn hảo, nhưng hãy xem xét thay đổi chức năng để vẽ (hình ảnh: UIImage, inRect orth: CGRect) và xử lý uiImage.cgimage bên trong phương thức
Mars

-5

Bạn cũng có thể giải quyết vấn đề này bằng cách làm điều này:

//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.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.