Sample Code

Converting Color Images to Grayscale

Convert a color image to grayscale using matrix multiplication.

Download

Overview

In this sample code project, you’ll convert a 3-channel 8-bit ARGB image to a 1-channel grayscale image by using a matrix of coefficients that define the color conversion.

vImage provides matrix multiplication operations that offer functionality analogous to the dot product (that is, returning the sum of the products of the corresponding elements in two vectors). vImageMatrixMultiply_ARGB8888ToPlanar8(_:_:_:_:_:_:_:) and vImageMatrixMultiply_ARGBFFFFToPlanarF(_:_:_:_:_:_:) multiply each channel of an interleaved image with a value in a matrix and return the sum of the multiplications to a planar image.

The following shows how vImageMatrixMultiply_ARGB8888ToPlanar8 calculates the result for each pixel:

let p = (sourcePixels[index].a + preBias.a) * matrix.a +
        (sourcePixels[index].r + preBias.r) * matrix.r +
        (sourcePixels[index].g + preBias.g) * matrix.g +
        (sourcePixels[index].b + preBias.b) * matrix.b

let destinationPixels[index] = (p + postBias) / divisor

Define the Coefficient Values

Luma coefficients model an eye’s response to red, green, and blue light. The following formula shows the Rec. 709 luma coefficients for the color-to-grayscale conversion, and it calculates luminance. In the formula, Y represents the luminance of the red, green, and blue values:

Rec. 709 color to luma formula

The following shows how you declare the luma coefficients:

let redCoefficient: Float = 0.2126
let greenCoefficient: Float = 0.7152
let blueCoefficient: Float = 0.0722

Define the Coefficients Matrix

vImageMatrixMultiply_ARGB8888ToPlanar8 accepts integer values for the matrix; multiply each fractional coefficient in the matrix by divisor:

let divisor: Int32 = 0x1000
let fDivisor = Float(divisor)

var coefficientsMatrix = [
    Int16(redCoefficient * fDivisor),
    Int16(greenCoefficient * fDivisor),
    Int16(blueCoefficient * fDivisor)
]

The matrix multiply function requires an Int32 divisor, but the coefficients are Float values. To simplify the matrix initialization, declare and use fDivisor to multiply each coefficients by the divisor.

Perform the Matrix Multiply

The color-to-grayscale calculation is the sum of the products of each color value and its corresponding coefficient, so define pre- and post-bias as zero. Call vImageMatrixMultiply_ARGB8888ToPlanar8 to perform the matrix multiplication:

let preBias: [Int16] = [0, 0, 0, 0]
let postBias: Int32 = 0

vImageMatrixMultiply_ARGB8888ToPlanar8(&sourceBuffer,
                                       &destinationBuffer,
                                       &coefficientsMatrix,
                                       divisor,
                                       preBias,
                                       postBias,
                                       vImage_Flags(kvImageNoFlags))

On return, the destinationBuffer contains a grayscale representation of your original image.

Create a Grayscale Core Graphics Image

Finally, you can create a 1-channel grayscale CGImage instance from the destination buffer. The image format contains 8 bits per component and 8 bits per pixel. A single channel format has the same number of bits per pixel as bits per component.

guard let monoFormat = vImage_CGImageFormat(
    bitsPerComponent: 8,
    bitsPerPixel: 8,
    colorSpace: CGColorSpaceCreateDeviceGray(),
    bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue),
    renderingIntent: .defaultIntent) else {
        return
}

To create the image, pass the destination buffer and grayscale format to createCGImage(format:flags:):

let result = try? destinationBuffer.createCGImage(format: monoFormat)

On return, result contains a single-channel image that you can, for example, use to instantiate a UIImage instance and display onscreen:

if let result = result {
    imageView.image = UIImage(cgImage: result)
}

Grayscale photograph.

See Also

vImage Operations

Adjusting the Brightness and Contrast of an Image

Use a gamma function to apply a linear or exponential curve.

Adjusting Saturation and Applying Tone Mapping

Convert an RGB image to discrete luminance and chrominance channels, and apply color and contrast treatments.

Blurring an Image

Filter an image by convolving it with custom and high-speed kernels.

Adding a Bokeh Effect

Simulate a bokeh effect by applying dilation.

Standardizing Arbitrary Image Formats for Processing

Convert assets with disparate color spaces and bit depths to a standard working format for applying vImage operations.

Specifying Histograms with vImage

Calculate the histogram of one image and apply it to a second image.

Reducing Artifacts in Resampled Images

Avoid ringing effects introduced by the default Lanczos algorithm when scaling an image by using a custom resampling filter.

Finding the Sharpest Image in a Sequence of Captured Images

Share image data between vDSP and vImage to compute the sharpest image from a bracketed photo sequence.

vImage Operations

Apply image manipulation operations to vImage buffers.