- macOS 10.15+
- Xcode 11.2+
This sample code project allows you to adjust the hue of an image by treating the chrominance information as 2D coordinates, and transforming those values with a rotation matrix. You can convert an RGB image—with its pixels represented as red, green, and blue values—to L*a*b*, where luminance and chrominance are stored discretely. The L* in L*a*b* refers to the lightness, and the a* and b* refer to the red-green, and blue-yellow values, respectively.
The image below shows an approximation of a L*a*b* color chart. The a* value transitions horizontally (left to right) from negative, through zero, to positive, and the b* transitions vertically (bottom to top) from negative, through zero, to positive. Because this sample code focuses on color rather than lightness, the image doesn’t consider L*:
This sample walks you through these steps:
Derive RGB image format from source image.
Create L*a*b* image format.
Create RGB-to-L*a*b* and L*a*b*-to-RGB converters.
Create a vImage buffer from the source image.
Convert RGB to L*a*b*.
Convert the interleaved L*a*b* buffer to planar buffers.
Apply the hue adjustment.
Convert the planar L*a*b* buffers to an interleaved buffer.
Convert L*a*b* to RGB.
The following image shows four photographs, from left to right, with a hue adjustment of -90º, 0º (an unchanged hue), 90º, and 180º:
Derive RGB Image Format from Source Image
The converter that the sample uses to convert the RGB pixels to L*a*b* color space requires two
v structures that describe the source and destination images. Use the
init(cg initializer to create the RGB format from the source image:
Create L*a*b* Image Format
lab describes the interleaved L*a*b* pixels over which this sample works. The first channel in each pixel is the lightness, and the second and third channels are the a* and b* respectively.
Create RGB-to-L*a*b* and L*a*b*-to-RGB Converters
Use the RGB and L*a*b* image formats to create
v instances to convert between the two color spaces:
To learn more about vImage’s convert-any-to-any functionality, see Building a Basic Conversion Workflow.
Create a vImage Buffer from the Source Image
Declare the vImage buffer,
argb, to store the source image, and use the
init(cg initializer to populate it with the Core Graphics image:
argb contains the image.
Convert RGB to L*a*b*
Initialize a vImage buffer that’s the same size as the source image and the L*a*b* image format’s
convert(source: function performs the conversion:
On return, the
lab contains the L*a*b* representation of the source image.
Convert the Interleaved L*a*b* to Planar Buffers.
The function you use to apply the hue adjustment,
v, operates on a set of planar buffers. To convert the interleaved L*a*b* buffer to planar buffers, initialize three buffers with a
bits that is equal to
v to populate the planar buffers with the contents of the interleaved buffer
To learn more about working with planar buffers, see Optimizing Image Processing Performance.
Apply the Hue Adjustment
Hue adjustment is achieved by rotating a two-element vector, described by a* and b*. To learn more about working with rotation matrices, see Working with Matrices.
The following visualizes a sample color (marked A) rotated by -90º (marked C) and 45º (marked B):
Use the following code to generate the rotation matrix based on
post values effectively shift the a* and b* values from
-128...127, so the rotation is centered where a* and b* are zero, which is represented by the
UInt8 values as
v multiplies each pixel in the source buffers by the matrix and writes the result to the destination buffers. In this example, the matrix multiplication is done in-place, so the source and destination point to the same buffers.
Use the following code to create the source and destination as
Unsafe structures from the a* and b* planar buffers, and pass them to the matrix multiply function:
b contain the hue adjusted a* and b* channels.
Convert L*a*b* to RGB
Finally, convert the hue adjusted buffers back to RGB, by converting the planar buffers back to a single, interleaved buffer:
lab converter to populate
rgb with the RGB representation of the hue-adjusted image: