Gavin Wiggins

About  /  Notes  /  Photos

Image from array data

Written on July 18, 2023

An image can be created from numerical data using the Core Graphics and Accelerate frameworks. The sections below demonstrate how to create a gray scale image from normalized data and color images from RGB data.

Gray scale image

The function shown below creates a gray scale image from an array using CGContext.

import Accelerate

/// Create a gray scale image from an array using CGContext.
/// - Parameters:
///   - data: Array contains values from 0 to 1. Assume size of array is width x height.
///   - width: Width of the generated image.
///   - height: Height of the generated image.
/// - Returns: A gray scale CGImage.
func makeGrayImage(from data: [Float], width: Int, height: Int) -> CGImage {

    // Convert array to gray scale RGB values
    // Note gray scale RGB values are all same number such as 255, 255, 255 and 80, 80, 80
    let dataGray = vDSP.multiply(255, data)

    // Convert array to UInt8
    var gray = vDSP.floatingPointToInteger(dataGray, integerType: UInt8.self, rounding: .towardNearestInteger)

    // Create image
    let colorSpace = CGColorSpace(name: CGColorSpace.linearGray)!
    let bitmapInfo = CGImageAlphaInfo.none.rawValue

    let context = CGContext(data: &gray,
                            width: width,
                            height: height,
                            bitsPerComponent: 8,
                            bytesPerRow: width,
                            space: colorSpace,
                            bitmapInfo: bitmapInfo)!

    let image = context.makeImage()!
    return image
}

To use this function, provide the width and height of the desired image along with an array of data values. In this example, the data is a linear ramp of values from 0 to 1.

// Define width and height of image
let width = 200
let height = 200

// Gray scale image from data that is a linear ramp from 0 to 1
let dataLinear = vDSP.ramp(in: Float.zero...1, count: width * height)

let grayImage1 = makeGrayImage(from: dataLinear, width: width, height: height)

gray image

An array of random data from 0 to 1 can also be used to generate a gray scale image as demonstrated below.

// Define width and height of image
let width = 200
let height = 200

// Gray scale image using random data that is from 0 to 1
var dataRandom = [Float](repeating: 0, count: width * height)

for i in 0..<dataRandom.count {
    dataRandom[i] = .random(in: 0...1)
}

let grayImage2 = makeGrayImage(from: dataRandom, width: width, height: height)

gray image

RGBA image

This function creates a color image from an array of RGBA values using CGContext.

import Accelerate

/// Create an image from an array of RGBA values using CGContext.
/// - Parameters:
///   - data: Array contains RGBA values from 0 to 255. Each group of four values is R, G, B, A. The alpha value is ignored. Assume size of array is width x height x 4.
///   - width: Width of the generated image.
///   - height: Height of the generated image.
/// - Returns: A color CGImage.
func makeRGBAImage(from data: [Float], width: Int, height: Int) -> CGImage {

    // Convert array to UInt8
    var rgb = vDSP.floatingPointToInteger(data, integerType: UInt8.self, rounding: .towardNearestInteger)

    // Create image
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = CGImageAlphaInfo.noneSkipLast.rawValue

    let context = CGContext(data: &rgb,
                            width: width,
                            height: height,
                            bitsPerComponent: 8,
                            bytesPerRow: width * 4,
                            space: colorSpace,
                            bitmapInfo: bitmapInfo)!

    let image = context.makeImage()!
    return image
}

The size of the array passed to the function is width × height × 4 because each group of four values represents R, G, B, and A channels.

// Define width and height of image
let width = 200
let height = 200

// Color image from RGBA data that is random numbers from 0 to 255
// where each group of four values is R, G, B, A
var dataRGB4 = [Float](repeating: 0, count: width * height * 4)

for i in 0..<dataRGB4.count {
    dataRGB4[i] = .random(in: 0...255)
}

let rgbImage1 = makeRGBAImage(from: dataRGB4, width: width, height: height)

rgba image

RGB image

The last example creates a color image from an array of RGB values using vImage. Notice the size of the array is width × height × 3 because each group of 3 values is the R, G, B channels.

import Accelerate

/// Create an image from an array of RGB values using vImage.
/// - Parameters:
///   - data: Array containing RGB values from 0 to 255. Each group of three values is R, G, B. Assume size of array is width x height x 3.
///   - width: Width of the generated image.
///   - height: Height of the generated image.
/// - Returns: A color CGImage.
func makeRGBImage(from data: [Float], width: Int, height: Int) -> CGImage {

    // Convert array to UInt8
    let rgb = vDSP.floatingPointToInteger(data, integerType: UInt8.self, rounding: .towardNearestInteger)

    // Create image
    let buffer = vImage.PixelBuffer(
        pixelValues: rgb,
        size: .init(width: width, height: height),
        pixelFormat: vImage.Interleaved8x3.self
    )

    let format = vImage_CGImageFormat(
        bitsPerComponent: 8,
        bitsPerPixel: 8 * 3,
        colorSpace: CGColorSpaceCreateDeviceRGB(),
        bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
    )!

    let image = buffer.makeCGImage(cgImageFormat: format)!
    return image
}
// Define width and height of image
let width = 200
let height = 200

// Color image from RGB data that is random numbers from 0 to 255
// where each group of three values is R, G, B
var dataRGB3 = [Float](repeating: 0, count: width * height * 3)

for i in 0..<dataRGB3.count {
    dataRGB3[i] = .random(in: 0...255)
}

let rgbImage2 = makeRGBImage(from: dataRGB3, width: width, height: height)

rgb image

Gavin Wiggins © 2024.
Made on a Mac with Genja. Hosted on GitHub Pages.