Search code examples
ioscore-imagecifilter

Average color of a CIImage ... a faster method


I have a CIImage that is 258x258. I apply a filter CIAreaAverage to it, in order to get the green value of the average color of that image.

I am using this function to get that...

// channel 0,1,2 = red, green, blue
func averageColorOfImage (_ inputImage: CIImage, _ channel: Int) -> Double {

  let extentVector = CIVector(x: inputImage.extent.origin.x, y: inputImage.extent.origin.y, z: inputImage.extent.size.width, w: inputImage.extent.size.height)

  guard let filter = CIFilter(name: "CIAreaAverage", parameters: [kCIInputImageKey: inputImage, kCIInputExtentKey: extentVector]) else { return 0 }
  guard let outputImage = Static.filter!.outputImage else { return 0 }

  var bitmap = [UInt8](repeating: 0, count: 4)
  let context = CIContext(options: [.workingColorSpace: kCFNull])
  context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: .RGBA8, colorSpace: nil)

  return Double(bitmap[channel]) / Double(255)
}

This runs relatively fast but I am trying to see if there is a way to get that color fast.

I remember that Core Image has this custom "core filter" stuff.

Is there a way to do get the green value of the average color of an image using custom core filter?


Solution

  • Here's what I'd do. (And actually do in one app to create a constant color CIImage of a specific size.

    (1) Use CIAreaAverage to get you signal pixel value just like you are doing. But instead of using the output of the green channel to create a bitmap (which I believe is the real issue you are facing)....

    (2) Use if as input for CIConstantColorGenerator, which will create an infinite sized color. Then....

    (3) Use that output as input to CICrop and crop it to the dimensions you want.

    Essentially you end up with a "sized" CIImage of a constant color of whatever you wish. In the code below, replace my input for inputColor with the green channel output of CIAreaAverage and you'll get a 640x640 CIImage of it.

    func createNewPalette() {
        let filter = CIFilter(name: "CIConstantColorGenerator")
        filter?.setValue(CIColor(red: 0.7, green: 0.4, blue: 0.4), forKey: "inputColor")
        ciPalette = filter?.outputImage
        let crop = CIFilter(name: "CICrop")
        crop?.setValue(ciPalette, forKey: "inputImage")
        crop?.setValue(CIVector(x: 0, y: 0, z: 640, w: 640), forKey: "inputRectangle")
        ciPaletteOriginal = crop?.outputImage
    }