Article

Applying vImage Operations to Regions of Interest

Limit the effect of vImage operations to rectangular regions of interest.

Overview

You can apply vImage operations—for example, blurs and color transforms—to specified rectangular areas in an image, commonly referred to as regions of interest (ROI). Limiting the effect of an operation is useful when, for example, you want to overlay user interface elements on top of a blurred part of an image to make them stand out.

This article walks you through three approaches to apply operations to an ROI:

  • Apply an in-place operation to an ROI.

  • Apply an out-of-place operation to an ROI.

  • Crop and apply an out-of-place operation to an ROI.

The following image is an example of the effects possible when you use the techniques in this article. The image shows a single photograph with a landscape format ROI that's been blurred, and a portrait format ROI that's been desaturated.

Photograph with a blurred horizontal strip, and a monochrome vertical strip.

The functions described in this article are all implemented as extensions to the vImage_Buffer structure. This means it's extremely simple to use them; the image above was created with just two function calls:

// source is a `vImage_Buffer` that contains an ARGB8888 image.

source.desaturate_ARGB8888(regionOfInterest: CGRect( ... ))

let blurred = source.blurred_ARGB8888(regionOfInterest: CGRect( ... ),
                                      blurRadius: 100)

Apply an In-Place Operation to an ROI

For vImage routines that operate in-place (that is, the operation mutates the source buffer's contents), create a mutating function that applies that routine to an ROI. The following code is the function header for a desaturation function based around vImageMatrixMultiply_ARGB8888(_:_:_:_:_:_:_:):

extension vImage_Buffer {

    mutating func desaturate_ARGB8888(regionOfInterest roi: CGRect) {

Next, check that the supplied ROI is within the bounds of the buffer:

        guard Int(roi.maxX) <= width && Int(roi.maxY) <= height &&
            Int(roi.minX) >= 0 && Int(roi.minY) >= 0 else {
                print("ROI is out of bounds.")
                return
        }

Calculate the first pixel in the source buffer for the ROI. To find this pixel, multiply the y origin of the ROI by the source buffer's rowBytes, added to the x origin of the ROI multiplied by the number of bytes that describe each pixel:

        let bytesPerPixel = 4
        
        let start = Int(roi.origin.y) * rowBytes +
            Int(roi.origin.x) * bytesPerPixel

Use this start value to define a second vImage_Buffer structure, that references the source buffer's data (that is, the image data is shared between both buffers and not copied) with a size that equals the ROI:

        var desaturationBuffer = vImage_Buffer(data: data.advanced(by: start),
                                               height: vImagePixelCount(roi.height),
                                               width: vImagePixelCount(roi.width),
                                               rowBytes: rowBytes)

desaturationBuffer is now a reference to the data in the source buffer defined by the supplied ROI. Calling vImageMatrixMultiply_ARGB8888(_:_:_:_:_:_:_:) with desaturationBuffer as the source and destination performs the matrix multiplication on the pixels in the ROI:

        let divisor: Int32 = 0x1000
        
        let desaturationMatrix = [
            0.0722, 0.0722, 0.0722, 0,
            0.7152, 0.7152, 0.7152, 0,
            0.2126, 0.2126, 0.2126, 0,
            0,      0,      0,      1
            ].map {
                return Int16($0 * Float(divisor))
        }
        
        let error = vImageMatrixMultiply_ARGB8888(&desaturationBuffer,
                                                  &desaturationBuffer,
                                                  desaturationMatrix,
                                                  divisor,
                                                  nil, nil,
                                                  vImage_Flags(kvImageNoFlags))
        
        if error != kvImageNoError {
            print("Error: \(error)")
        }
    }
}

To learn more about using matrix multiplication to convert color images to grayscale, see Converting Color Images to Grayscale.

The following shows the result of desaturating an ROI:

Photograph with a monochrome vertical strip.

Apply an Out-of-Place Operation to an ROI

For vImage routines that don't operate in-place, create a non-mutating function that applies that routine to an ROI and returns a new vImage_Buffer structure that contains the result.

The following code is the function header for a blurring function based around vImageTentConvolve_ARGB8888(_:_:_:_:_:_:_:_:_:):

extension vImage_Buffer {
    
    func blurred_ARGB8888(regionOfInterest roi: CGRect,
                          blurRadius: Int) -> vImage_Buffer? {

Perform the same check as Apply an In-Place Operation to an ROI on the ROI size:

        guard Int(roi.maxX) <= width && Int(roi.maxY) <= height &&
            Int(roi.minX) >= 0 && Int(roi.minY) >= 0 else {
                print("ROI is out of bounds.")
                return nil
        }

vImage_Buffer.blurred_ARGB8888(regionOfInterest:blurRadius:) returns a buffer that is the same size as the source, and all pixels outside of the ROI will equal the respective pixels in the source. Create the buffer that the function returns, and copy the source pixels into the new buffer:

        guard var destination = try? vImage_Buffer(width: Int(width),
                                                   height: Int(height),
                                                   bitsPerPixel: 32) else {
                                                    return nil
        }
        
        let bytesPerPixel = 4
        
        _ = withUnsafePointer(to: self) { src in
            vImageCopyBuffer(src,
                             &destination,
                             bytesPerPixel,
                             vImage_Flags(kvImageNoFlags))
        }

Using the same approach as Apply an In-Place Operation to an ROI, calculate the start of the ROI, and create a buffer for the blur operation that references the copied pixels in destination:

        let start = Int(roi.origin.y) * destination.rowBytes +
            Int(roi.origin.x) * bytesPerPixel
        
        var blurDestination = vImage_Buffer(data: destination.data.advanced(by: start),
                                            height: vImagePixelCount(roi.height),
                                            width: vImagePixelCount(roi.width),
                                            rowBytes: destination.rowBytes)

The final step is to apply the blur using vImageTentConvolve_ARGB8888(_:_:_:_:_:_:_:_:_:) to blurDestination and return the destination buffer:

        var error = kvImageNoError
        
        _ = withUnsafePointer(to: self) { src in
            let blurDiameter = UInt32(blurRadius * 2 + 1)
            error = vImageTentConvolve_ARGB8888(src,
                                                &blurDestination,
                                                nil,
                                                vImagePixelCount(roi.origin.x),
                                                vImagePixelCount(roi.origin.y),
                                                blurDiameter, blurDiameter,
                                                [0],
                                                vImage_Flags(kvImageTruncateKernel))
        }
        
        if error != kvImageNoError {
            destination.free()
            print("Error: \(error)")
            return nil
        }
        
        return destination
    }
}

The following shows the result of blurring an ROI:

Photograph with a blurred horizontal strip.

Crop and Apply an Out-of-Place Operation to an ROI

When you need to apply an operation and crop to an ROI, create a simplified version of the function described in Apply an Out-of-Place Operation to an ROI.

The following code is the function header and ROI size check for a function that blurs and crops to a specified ROI:

extension vImage_Buffer {
    
    func blurredCropped_ARGB8888(regionOfInterest roi: CGRect,
                                 blurRadius: Int) -> vImage_Buffer? {
        
        guard Int(roi.maxX) <= width && Int(roi.maxY) <= height &&
            Int(roi.minX) >= 0 && Int(roi.minY) >= 0 else {
                print("ROI is out of bounds.")
                return nil
        }

You will return a buffer the same size as the ROI, so create a destination buffer using the ROI's dimensions:

        guard var destination = try? vImage_Buffer(width: Int(roi.width),
                                                   height: Int(roi.height),
                                                   bitsPerPixel: 32) else {
                                                    return nil
        }

In this blur and crop function, you don't need to calculate the start pixel. Instead, set the srcOffsetToROI_X and srcOffsetToROI_Y parameters of vImageTentConvolve_ARGB8888(_:_:_:_:_:_:_:_:_:) to the ROI's origin:

        var error = kvImageNoError
        
        _ = withUnsafePointer(to: self) { src in
            let blurDiameter = UInt32(blurRadius * 2 + 1)
            error = vImageTentConvolve_ARGB8888(src,
                                                &destination,
                                                nil,
                                                vImagePixelCount(roi.origin.x),
                                                vImagePixelCount(roi.origin.y),
                                                blurDiameter, blurDiameter,
                                                [0],
                                                vImage_Flags(kvImageTruncateKernel))
        }
        
        if error != kvImageNoError {
            print("Error: \(error)")
            return nil
        }
        
        return destination
    }
}

The following shows the result of blurring and cropping an ROI:

Cropped and blurred photograph.

See Also

First Steps

Creating a Core Graphics Image Format

Provide descriptions of Core Graphics image formats for conversions to and from vImage.

Creating and Populating Buffers from Core Graphics Images

Initialize vImage buffers from Core Graphics images.

Creating a Core Graphics Image from a vImage Buffer

Create displayable representations of vImage buffers.