The RGBA Histogram for a UIImage in Swift 5

Gary Bartos
2 min readFeb 25, 2021

Finding the histogram of a UIImage can be relatively straightforward: extract the raw data from the UIImage, populate your histogram bins by iterating over every 4-byte representation of a pixel, and then reheat your tea in the microwave while you wait for the histogram to be generated.

The straightforward method is slow.

The fast way to generate a histogram is not straightforward.

Sure, once you copy & paste all the magic function calls from some web page into your project the code is understandable enough. It’s just not code many of us would have been able to generate after just an hour’s contemplation.

The code below is presented in plain text for accessibility. Code with color markup can be found in my reply as Rethunk to this StackOverflow post dating back to Swift 3:

The function below mixes what I found in one StackOverflow poster’s reply along with documentation from Apple.

import Accelerate
import UIKit


/// Get histograms for R, G, B, A channels of UIImage
/// https://stackoverflow.com/questions/37818720/swift-2-2-count-black-pixels-in-uiimage
/// https://stackoverflow.com/questions/40562889/drawing-histogram-of-cgimage-in-swift-3
/// https://developer.apple.com/documentation/accelerate/specifying_histograms_with_vimage
func histogram(image: UIImage) -> (red: [UInt], green: [UInt], blue: [UInt], alpha: [UInt]) {
let img: CGImage = CIImage(image: image)!.cgImage!

let imgProvider: CGDataProvider = img.dataProvider!
let imgBitmapData: CFData = imgProvider.data!
var imgBuffer = vImage_Buffer(
data: UnsafeMutableRawPointer(mutating: CFDataGetBytePtr(imgBitmapData)),
height: vImagePixelCount(img.height),
width: vImagePixelCount(img.width),
rowBytes: img.bytesPerRow)

// bins: zero = red, green = one, blue = two, alpha = three
var binZero = [vImagePixelCount](repeating: 0, count: 256)
var binOne = [vImagePixelCount](repeating: 0, count: 256)
var binTwo = [vImagePixelCount](repeating: 0, count: 256)
var binThree = [vImagePixelCount](repeating: 0, count: 256)

binZero.withUnsafeMutableBufferPointer { zeroPtr in
binOne.withUnsafeMutableBufferPointer { onePtr in
binTwo.withUnsafeMutableBufferPointer { twoPtr in
binThree.withUnsafeMutableBufferPointer { threePtr in

var histogramBins = [zeroPtr.baseAddress, onePtr.baseAddress,
twoPtr.baseAddress, threePtr.baseAddress]

histogramBins.withUnsafeMutableBufferPointer {
histogramBinsPtr in
let error = vImageHistogramCalculation_ARGB8888(
&imgBuffer,
histogramBinsPtr.baseAddress!,
vImage_Flags(kvImageNoFlags))

guard error == kvImageNoError else {
fatalError("Error calculating histogram: \(error)")
}
}
}
}
}
}

return (binZero, binOne, binTwo, binThree)
}

Caveat: For my particular needs, using (binZero, binOne, binTwo, binThree) as (red, green, blue, alpha) works. But I have a nagging feeling that some way of defining UIImage with some other color space could order the channels differently.

--

--

Gary Bartos

Founder of Echobatix, developing assistive technology for the blind. echobatix@gmail.com