0

I have a bunch of CIFilters that finally scale & crop the large image (from iPhone camera) to an 1080x1920 CIImage.

I then want to save the image as a JPG:

var outputFilter: CIFilter?
...
if let ciImage = outputFilter?.outputImage {
    let outputImage = UIImage(ciImage: ciImage)
    let data = outputImage?.jpegData(compressionQuality: 0.8)
    ...
}

The ciImage.extent is 1080x1920, outputImage.size is also 1080x1920, outputImage.scale is 1.0.

The image saved to disk however is 3x as large: 3240x5760.

What am I missing?

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
meaning-matters
  • 21,929
  • 10
  • 82
  • 142
  • We may need to see more of your code (or, at least, a simple working example) but... try adding a `scale` value to your *ci to ui* line: `let outputImage = UIImage(ciImage: outputImage, scale: 1.0, orientation: .up)` – DonMag Feb 28 '21 at 14:41
  • 1
    @DonMag No. If you pass 1.0 it won't make a difference. You need to pass the screen scale. – Leo Dabus Feb 28 '21 at 14:53
  • @LeoDabus - hmmm... testing with a `360x360` image, if I use, for example, `.setValue(2.0, forKey: kCIInputScaleKey)` with a "CILanczosScaleTransform" filter, and then `scale: UIScreen.main.scale`, I get a `360x360` image as a result, whereas `scale: 1.0` gives me a `720x720` image (which I would expect)? – DonMag Feb 28 '21 at 15:00
  • @LeoDabus - my quick test: https://pastebin.com/52Lacqzz ... ( I'm fully aware that I could be misunderstanding things :) – DonMag Feb 28 '21 at 15:09
  • You are probably using playground or an iPad which screen scales are 2x – Leo Dabus Feb 28 '21 at 15:09

2 Answers2

3

This will return an image based on your screen scale. If you check your screen scale it will result in 3X. What you need is to initialize your uiimage with the screen scale:

let outputImage = UIImage(ciImage: ciImage, scale: UIScreen.main.scale, orientation: .up)

To render the image you can use UIGraphicsImageRenderer:

extension CIImage {
    var rendered: UIImage {
        let cgImage = CIContext(options: nil).createCGImage(self, from: extent)!
        let size = extent.size
        let format = UIGraphicsImageRendererFormat.default()
        format.opaque = false
        return UIGraphicsImageRenderer(size: size, format: format).image { ctx in
            var transform = CGAffineTransform(scaleX: 1, y: -1)
            transform = transform.translatedBy(x: 0, y: -size.height)
            ctx.cgContext.concatenate(transform)
            ctx.cgContext.draw(cgImage, in: CGRect(origin: .zero, size: size))
        }
    }
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • Thanks Leo, amazing that worked. But could you please shed some light or share a link, that will help me understand what is going on? – meaning-matters Feb 28 '21 at 14:50
  • The resulting image has jagged edges. When zooming in, a vague 3x3 raster of pixels appears (3 is my screen's scale). How can this prevented? – meaning-matters Mar 02 '21 at 11:24
  • @meaning-matters you were complaining about the increased resolution. Just leave it at its default value – Leo Dabus Mar 02 '21 at 12:27
  • It's not just resolution. There's a rendering issue. And the default value creates a too large image. – meaning-matters Mar 02 '21 at 12:42
  • The only way to add the scale info to the image is adding @3x to its name – Leo Dabus Mar 02 '21 at 12:43
  • I want the pixel data of the image to be correctly rendered and I simply want a 1080x1920 JPG that can be shared online. Adding @3x doesn't do anything and doesn't have any meaning outside iOS. – meaning-matters Mar 02 '21 at 12:47
  • So just create a new image context and render your image there – Leo Dabus Mar 02 '21 at 12:49
  • Would you be willing to share a few lines of code on how to render my `CIImage` in your answer? – meaning-matters Mar 02 '21 at 12:56
  • @meaning-matters I can show you how to render the image but as I already said once you save it as a jpeg there is no way to add the UIImage scale info into it. It will be 2 or 3 times as bigger than the original image depending on the screen scale. All I can show is how to have your image at the proper size to be displayed at the device with the proper scale – Leo Dabus Mar 02 '21 at 13:16
  • Thanks! Could you explain why the transform is needed? – meaning-matters Mar 02 '21 at 13:54
  • @meaning-matters https://stackoverflow.com/questions/64536888/using-cgcontext-of-uigraphicimagerenderer-to-redraw-an-image-flipped-the-image/64538344#comment114130165_64538344 – Leo Dabus Mar 02 '21 at 14:06
0

I needed to do the following to both get the correct size and avoid bad rendering (as discussed in the comments of Leo Dabus' answer).

private func renderImage(ciImage: CIImage) -> UIImage?
{
    var outputImage: UIImage?

    UIGraphicsBeginImageContext(CGSize(width: 1080, height: 1920))
    if let context = UIGraphicsGetCurrentContext()
    {
        context.interpolationQuality = .high
        context.setShouldAntialias(true)

        let inputImage = UIImage(ciImage: ciImage)
        inputImage.draw(in: CGRect(x: 0, y: 0, width: 1080, height: 1920))

        outputImage = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()
    }

    return outputImage
}
meaning-matters
  • 21,929
  • 10
  • 82
  • 142