How to Embed ICC Profile into AVIF with libavif

This article provides a straightforward technical guide on how to embed a custom ICC (International Color Consortium) color profile into an AVIF image using the libavif C library. It covers the essential API calls and provides a concise code example demonstrating how to read an ICC profile file and attach it to an avifImage structure before encoding.

Understanding ICC Profiles in libavif

To ensure accurate color reproduction across different displays, AVIF images can store color management information either as NCLX (color property coordinates) or as an embedded ICC profile. When you embed a custom ICC profile, libavif packages the raw profile payload into the colr box of the AVIF container.

The primary function used to attach an ICC profile in libavif is:

void avifImageSetProfileICC(avifImage * image, const uint8_t * icc, size_t iccSize);

This function copies the provided buffer containing the raw ICC profile data into the avifImage structure.

Step-by-Step Implementation

To embed a custom ICC profile, you must read the profile data from a source (such as a .icc or .icm file) and apply it to your image structure before passing it to the encoder.

1. Read the ICC Profile Data

First, load the raw binary data of your custom ICC profile into a memory buffer.

size_t iccSize = 0;
uint8_t * iccBuffer = NULL;

FILE * iccFile = fopen("custom_profile.icc", "rb");
if (iccFile) {
    fseek(iccFile, 0, SEEK_END);
    iccSize = ftell(iccFile);
    fseek(iccFile, 0, SEEK_SET);

    iccBuffer = (uint8_t *)malloc(iccSize);
    fread(iccBuffer, 1, iccSize, iccFile);
    fclose(iccFile);
}

2. Allocate and Configure the AVIF Image

Create your avifImage structure and set its basic properties (width, height, depth, and pixel format).

avifImage * image = avifImageCreate(width, height, depth, AVIF_PIXEL_FORMAT_YUV444);
// Allocate pixels and populate image->yuvPlanes here...

3. Embed the ICC Profile

Call avifImageSetProfileICC to copy the loaded buffer into the image structure. Once copied, you can safely free your local buffer.

if (iccBuffer && iccSize > 0) {
    avifImageSetProfileICC(image, iccBuffer, iccSize);
    free(iccBuffer);
}

4. Encode the Image to AVIF

Create the encoder, set your desired quality parameters, and write the final AVIF container. The encoder will automatically detect the presence of the ICC profile and write it to the output file.

avifEncoder * encoder = avifEncoderCreate();
encoder->quality = 80; // Set quality (0-100)

avifRWData avifOutput = AVIF_RAW_DATA_CHARS_ZERO;
avifResult result = avifEncoderWrite(encoder, image, &avifOutput);

if (result == AVIF_RESULT_OK) {
    // Save avifOutput.data to a file
    FILE * outFile = fopen("output.avif", "wb");
    fwrite(avifOutput.data, 1, avifOutput.size, outFile);
    fclose(outFile);
}

// Cleanup
avifRWDataFree(&avifOutput);
avifEncoderDestroy(encoder);
avifImageDestroy(image);