Используя 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)
        }
Другие вопросы по тегам