Используя CGContext UIGraphicImageRenderer для перерисовки изображения, перевернуло изображение. Как предотвратить флип?
Учитывая изображение, когда я хочу преобразовать его в стандартный sRGB, я могу CGContext помочь нарисовать его, как показано ниже.
Учитывая исходное изображение
Когда я использую
CGContext
напрямую, он перерисовывается правильно
extension UIImage {
func toSRGB() -> UIImage {
guard let cgImage = cgImage,
let colorSpace = CGColorSpace(name: CGColorSpace.sRGB),
let cgContext = CGContext(
data: nil,
width: Int(size.width),
height: Int(size.height),
bitsPerComponent: 8,
bytesPerRow: 0,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
else { return self }
cgContext.draw(cgImage, in: CGRect(origin: .zero, size: size))
guard let newCGImage = cgContext.makeImage()
else { return self }
return UIImage.init(cgImage: newCGImage)
}
}
Однако, если я использую
CGContext
из
UIGraphicImageRenderer
extension UIImage {
func toSRGB() -> UIImage {
guard let cgImage = cgImage else { return self }
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image { ctx in
ctx.cgContext.draw(cgImage, in: CGRect(origin: .zero, size: size))
}
}
}
Изображение было перевернуто, как показано ниже. Почему он перевернулся и как его избежать?
3 ответа
Нет необходимости получать cgImage. вы можете просто нарисовать UIImage:
extension UIImage {
func toSRGB() -> UIImage {
UIGraphicsImageRenderer(size: size).image { _ in
draw(in: CGRect(origin: .zero, size: size))
}
}
}
Причина, по которой ваше изображение перевернулось, - это разница между системой координат в UIKit и Quartz. Вертикальное начало UIKit находится вверху, а вертикальное начало Quartz - внизу. Самый простой способ исправить это - применить CGAffineTransform, масштабируя его на минус один, а затем переводя расстояние по высоте назад:
extension CGAffineTransform {
static func flippingVerticaly(_ height: CGFloat) -> CGAffineTransform {
var transform = CGAffineTransform(scaleX: 1, y: -1)
transform = transform.translatedBy(x: 0, y: -height)
return transform
}
}
extension UIImage {
func toSRGB() -> UIImage {
guard let cgImage = cgImage else { return self }
return UIGraphicsImageRenderer(size: size).image { ctx in
ctx.cgContext.concatenate(.flippingVerticaly(size.height))
ctx.cgContext.draw(cgImage, in: CGRect(origin: .zero, size: size))
}
}
}
изменить / обновить:
Чтобы сохранить масштаб равным 1x, вы можете передать формат средства визуализации изображения:
extension UIImage {
func toSRGB() -> UIImage {
guard let cgImage = cgImage else { return self }
let format = imageRendererFormat
format.scale = 1
return UIGraphicsImageRenderer(size: size, format: format).image { ctx in
ctx.cgContext.concatenate(.flippingVerticaly(size.height))
ctx.cgContext.draw(cgImage, in: CGRect(origin: .zero, size: size))
}
}
}
После изучения, просто чтобы добавить пояснения к тому, что я заметил в их различиях, как показано ниже.
Исходное изображение 5х3 пикселей
При прямом использовании CGContext
extension UIImage {
func toSRGB() -> UIImage {
guard let cgImage = cgImage,
let colorSpace = CGColorSpace(name: CGColorSpace.sRGB),
let cgContext = CGContext(
data: nil,
width: Int(size.width),
height: Int(size.height),
bitsPerComponent: 8,
bytesPerRow: 0,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
else { return self }
cgContext.draw(cgImage, in: CGRect(origin: .zero, size: size))
guard let newCGImage = cgContext.makeImage()
else { return self }
return UIImage.init(cgImage: newCGImage)
}
}
Мы сохранили тот же Pixel (5x3). Это преимущество этого решения, но оно требует доступа к CGContext и непосредственного управления им.
Второй подход
UIGraphicsImageRenderer
и доступ к внутреннему
ctx
extension UIImage {
func toSRGB() -> UIImage {
guard let cgImage = cgImage else { return self }
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image { ctx in
ctx.cgContext.draw(cgImage, in: CGRect(origin: .zero, size: size))
}
}
}
Используя этот подход, он
- смотрит вверх дном.
- Пиксель увеличился вдвое и по ширине, и по высоте (10x6), чтобы сгладить цветовой край.
Чтобы решить перевернутый, нам нужно перевернуть его в соответствии с ответом @LeoDabus, используя
extension CGAffineTransform {
static func flippingVerticaly(_ height: CGFloat) -> CGAffineTransform {
var transform = CGAffineTransform(scaleX: 1, y: -1)
transform = transform.translatedBy(x: 0, y: -height)
return transform
}
}
extension UIImage {
func toSRGB() -> UIImage {
guard let cgImage = cgImage else { return self }
return UIGraphicsImageRenderer(size: size).image { ctx in
ctx.cgContext.concatenate(.flippingVerticaly(size.height))
ctx.cgContext.draw(cgImage, in: CGRect(origin: .zero, size: size))
}
}
}
Теперь изображение имеет правильную ориентацию, но по-прежнему удваивается по ширине и высоте (10x6).
Перевернутый результат такой же, как и упрощенный ответ, предоставленный @LeoDabus
extension UIImage {
func toSRGB() -> UIImage {
UIGraphicsImageRenderer(size: size).image { _ in
draw(in: CGRect(origin: .zero, size: size))
}
}
}
Вот техника, примененная непосредственно (без расширения) кUIGraphicsImageRenderer
, в которойCTFontDrawGlyphs()
делает рисунок.
// Assume the preceding code (omitted for this example) does this:
//
// - Loads a CTFont for a specified font size
// - calls CTFontGetGlyphsForCharacters() to cvt UniChar to CGGlyph
// - calls CTFontGetBoundingRectsForGlyphs() to get their bbox's
.
.
.
// This code concatenates an CGAffineTransform() to the context
// prior to calling CTFontDrawGlyphs(), causing the image
// to flip 180° so that it looks right in UIKit.
let points = [CGPoint(x: 0, y: 0)]
let rect = CGRect(x:0, y:0, width: bbox.size.width, height: bbox.size.height)
let renderer = UIGraphicsImageRenderer(bounds: rect, format: UIGraphicsImageRendererFormat.default())
let image = renderer.image { renderCtx in
let ctx = renderCtx.cgContext
ctx.concatenate(CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -rect.size.height))
ctx.setFillColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1.0)
CTFontDrawGlyphs(ctFont, glyphs, points, glyphs.count, ctx)
}