// Copyright 2023 Yuan Tong. All rights reserved. // SPDX-License-Identifier: BSD-2-Clause #include "iccmaker.h" #include #include // ICCv2 profile specification: https://www.color.org/icc32.pdf /** * Color Profile Structure * * Header: * size = 376 bytes (*1) * CMM = 'lcms' (*2) * Version = 2.2.0 * Device Class = Display * Color Space = RGB * Conn. Space = XYZ * Date, Time = 1 Jan 2000, 0:00:00 * Platform = Microsoft * Flags = Not Embedded Profile, Use anywhere * Dev. Mnfctr. = 0x0 * Dev. Model = 0x0 * Dev. Attrbts = Reflective, Glossy, Positive, Color * Rndrng Intnt = Perceptual * Illuminant = 0.96420288, 1.00000000, 0.82490540 [Lab 100.000000, 0.000000, 0.000000] * Creator = 'avif' * * Profile Tags: * Tag ID Offset Size Value * ---- ------ ------ ---- ----- * profileDescriptionTag 'desc' 240 95 avif * mediaWhitePointTag 'wtpt' 268 (*3) 20 (to be filled) * redColorantTag 'rXYZ' 288 20 (to be filled) * greenColorantTag 'gXYZ' 308 20 (to be filled) * blueColorantTag 'bXYZ' 328 20 (to be filled) * redTRCTag 'rTRC' 348 (*4) 16 (to be filled) * greenTRCTag 'gTRC' 348 16 (to be filled) * blueTRCTag 'bTRC' 348 16 (to be filled) * copyrightTag 'cprt' 364 12 CC0 * * (*1): The template data is padded to 448 bytes according to MD5 specification, so that computation can be applied * directly on it. The actual ICC profile data is the first 376 bytes. * (*2): 6.1.2 CMM Type: The signatures must be registered in order to avoid conflicts. * The registry can be found at https://www.color.org/signatures2.xalter (Private and ICC tag and CMM registry) * Therefore we are using the signature of Little CMS. * (*3): The profileDescriptionTag requires 95 bytes of data, but with some trick, the content of the last 67 bytes * can be anything. Therefore we are placing the following tags in this region to reduce profile size. * (*4): The transfer characteristic (gamma) of the 3 channels are the same, so the data can be shared. */ static const uint8_t iccColorTemplate[448] = { 0x00, 0x00, 0x01, 0x78, 0x6c, 0x63, 0x6d, 0x73, 0x02, 0x20, 0x00, 0x00, 0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20, 0x07, 0xd0, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x63, 0x73, 0x70, 0x4d, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x61, 0x76, 0x69, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x5f, 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00, 0x00, 0x14, 0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x14, 0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x34, 0x00, 0x00, 0x00, 0x14, 0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x48, 0x00, 0x00, 0x00, 0x14, 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x5c, 0x00, 0x00, 0x00, 0x10, 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x5c, 0x00, 0x00, 0x00, 0x10, 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x5c, 0x00, 0x00, 0x00, 0x10, 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x01, 0x6c, 0x00, 0x00, 0x00, 0x0c, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x61, 0x76, 0x69, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3, 0x54, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xc9, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xa0, 0x00, 0x00, 0x38, 0xf2, 0x00, 0x00, 0x03, 0x8f, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x96, 0x00, 0x00, 0xb7, 0x89, 0x00, 0x00, 0x18, 0xda, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0xa0, 0x00, 0x00, 0x0f, 0x85, 0x00, 0x00, 0xb6, 0xc4, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x43, 0x43, 0x30, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; static const size_t iccColorLength = 376; static const ptrdiff_t colorWhiteOffset = 0x114; static const ptrdiff_t colorRedOffset = 0x128; static const ptrdiff_t colorGreenOffset = 0x13c; static const ptrdiff_t colorBlueOffset = 0x150; static const ptrdiff_t colorGammaOffset = 0x168; /** * Gray Profile Structure * * Header: * size = 275 bytes * CMM = 'lcms' * Version = 2.2.0 * Device Class = Display * Color Space = Gray * Conn. Space = XYZ * Date, Time = 1 Jan 2000, 0:00:00 * Platform = Microsoft * Flags = Not Embedded Profile, Use anywhere * Dev. Mnfctr. = 0x0 * Dev. Model = 0x0 * Dev. Attrbts = Reflective, Glossy, Positive, Color * Rndrng Intnt = Perceptual * Illuminant = 0.96420288, 1.00000000, 0.82490540 [Lab 100.000000, 0.000000, 0.000000] * Creator = 'avif' * * Profile Tags: * Tag ID Offset Size Value * ---- ------ ------ ---- ----- * profileDescriptionTag 'desc' 180 95 avif * mediaWhitePointTag 'wtpt' 208 20 (to be filled) * grayTRCTag 'kTRC' 228 16 (to be filled) * copyrightTag 'cprt' 244 12 CC0 */ static const uint8_t iccGrayTemplate[320] = { 0x00, 0x00, 0x01, 0x13, 0x6c, 0x63, 0x6d, 0x73, 0x02, 0x20, 0x00, 0x00, 0x6d, 0x6e, 0x74, 0x72, 0x47, 0x52, 0x41, 0x59, 0x58, 0x59, 0x5a, 0x20, 0x07, 0xd0, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x63, 0x73, 0x70, 0x4d, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x61, 0x76, 0x69, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x5f, 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x14, 0x6b, 0x54, 0x52, 0x43, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0x10, 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0x0c, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x61, 0x76, 0x69, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3, 0x54, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xc9, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x43, 0x43, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; static const size_t iccGrayLength = 275; static const ptrdiff_t grayWhiteOffset = 0xd8; static const ptrdiff_t grayGammaOffset = 0xf0; static const ptrdiff_t checksumOffset = 0x54; static const double small = 1e-12; static uint32_t readLittleEndianU32(const uint8_t * data) { return ((uint32_t)data[0] << 0) | ((uint32_t)data[1] << 8) | ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24); } static void writeLittleEndianU32(uint8_t * data, uint32_t value) { data[0] = (value >> 0) & 0xff; data[1] = (value >> 8) & 0xff; data[2] = (value >> 16) & 0xff; data[3] = (value >> 24) & 0xff; } static void writeBigEndianU16(uint8_t * data, uint16_t value) { data[0] = (value >> 8) & 0xff; data[1] = (value >> 0) & 0xff; } static void writeBigEndianU32(uint8_t * data, uint32_t value) { data[0] = (value >> 24) & 0xff; data[1] = (value >> 16) & 0xff; data[2] = (value >> 8) & 0xff; data[3] = (value >> 0) & 0xff; } static avifBool putS15Fixed16(uint8_t * data, double value) { value = round(value * 65536); if (value > INT32_MAX || value < INT32_MIN) { return AVIF_FALSE; } int32_t fixed = (int32_t)value; // reinterpret into uint32_t to ensure the exact bits are written. writeBigEndianU32(data, *(uint32_t *)&fixed); return AVIF_TRUE; } static avifBool putU8Fixed8(uint8_t * data, float value) { value = roundf(value * 256); if (value > UINT16_MAX || value < 1) { return AVIF_FALSE; } uint16_t fixed = (uint16_t)value; writeBigEndianU16(data, fixed); return AVIF_TRUE; } static avifBool putColorant(uint8_t * data, const double XYZ[3]) { if (!putS15Fixed16(data, XYZ[0])) { return AVIF_FALSE; } if (!putS15Fixed16(data + 4, XYZ[1])) { return AVIF_FALSE; } if (!putS15Fixed16(data + 8, XYZ[2])) { return AVIF_FALSE; } return AVIF_TRUE; } static avifBool xyToXYZ(const float xy[2], double XYZ[3]) { if (fabsf(xy[1]) < small) { return AVIF_FALSE; } const double factor = 1 / xy[1]; XYZ[0] = xy[0] * factor; XYZ[1] = 1; XYZ[2] = (1 - xy[0] - xy[1]) * factor; return AVIF_TRUE; } // Computes I = M^-1. Returns false if M seems to be singular. static avifBool matInv(const double M[3][3], double I[3][3]) { double det = M[0][0] * (M[1][1] * M[2][2] - M[2][1] * M[1][2]) - M[0][1] * (M[1][0] * M[2][2] - M[1][2] * M[2][0]) + M[0][2] * (M[1][0] * M[2][1] - M[1][1] * M[2][0]); if (fabs(det) < small) { return AVIF_FALSE; } det = 1 / det; I[0][0] = (M[1][1] * M[2][2] - M[2][1] * M[1][2]) * det; I[0][1] = (M[0][2] * M[2][1] - M[0][1] * M[2][2]) * det; I[0][2] = (M[0][1] * M[1][2] - M[0][2] * M[1][1]) * det; I[1][0] = (M[1][2] * M[2][0] - M[1][0] * M[2][2]) * det; I[1][1] = (M[0][0] * M[2][2] - M[0][2] * M[2][0]) * det; I[1][2] = (M[1][0] * M[0][2] - M[0][0] * M[1][2]) * det; I[2][0] = (M[1][0] * M[2][1] - M[2][0] * M[1][1]) * det; I[2][1] = (M[2][0] * M[0][1] - M[0][0] * M[2][1]) * det; I[2][2] = (M[0][0] * M[1][1] - M[1][0] * M[0][1]) * det; return AVIF_TRUE; } // Computes C = A*B static void matMul(const double A[3][3], const double B[3][3], double C[3][3]) { C[0][0] = A[0][0] * B[0][0] + A[0][1] * B[1][0] + A[0][2] * B[2][0]; C[0][1] = A[0][0] * B[0][1] + A[0][1] * B[1][1] + A[0][2] * B[2][1]; C[0][2] = A[0][0] * B[0][2] + A[0][1] * B[1][2] + A[0][2] * B[2][2]; C[1][0] = A[1][0] * B[0][0] + A[1][1] * B[1][0] + A[1][2] * B[2][0]; C[1][1] = A[1][0] * B[0][1] + A[1][1] * B[1][1] + A[1][2] * B[2][1]; C[1][2] = A[1][0] * B[0][2] + A[1][1] * B[1][2] + A[1][2] * B[2][2]; C[2][0] = A[2][0] * B[0][0] + A[2][1] * B[1][0] + A[2][2] * B[2][0]; C[2][1] = A[2][0] * B[0][1] + A[2][1] * B[1][1] + A[2][2] * B[2][1]; C[2][2] = A[2][0] * B[0][2] + A[2][1] * B[1][2] + A[2][2] * B[2][2]; } // Set M to have values of d on the leading diagonal, and zero elsewhere. static void matDiag(const double d[3], double M[3][3]) { M[0][0] = d[0]; M[0][1] = 0; M[0][2] = 0; M[1][0] = 0; M[1][1] = d[1]; M[1][2] = 0; M[2][0] = 0; M[2][1] = 0; M[2][2] = d[2]; } static void swap(double * a, double * b) { double tmp = *a; *a = *b; *b = tmp; } // Transpose M static void matTrans(double M[3][3]) { swap(&M[0][1], &M[1][0]); swap(&M[0][2], &M[2][0]); swap(&M[1][2], &M[2][1]); } // Computes y = M.x static void vecMul(const double M[3][3], const double x[3], double y[3]) { y[0] = M[0][0] * x[0] + M[0][1] * x[1] + M[0][2] * x[2]; y[1] = M[1][0] * x[0] + M[1][1] * x[1] + M[1][2] * x[2]; y[2] = M[2][0] * x[0] + M[2][1] * x[1] + M[2][2] * x[2]; } // MD5 algorithm. See https://www.ietf.org/rfc/rfc1321.html#appendix-A.3 // This function writes the MD5 checksum in place at offset `checksumOffset` of `data`. // This function shall only be called with a copy of iccColorTemplate or iccGrayTemplate, and sizeof(icc*Template). static void computeMD5(uint8_t * data, size_t length) { static const uint32_t sineparts[64] = { 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, }; static const uint8_t shift[64] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, }; uint32_t a0 = 0x67452301, b0 = 0xefcdab89, c0 = 0x98badcfe, d0 = 0x10325476; for (uint32_t i = 0; i < length; i += 64) { uint32_t a = a0, b = b0, c = c0, d = d0, f, g; for (uint32_t j = 0; j < 64; j++) { if (j < 16) { f = (b & c) | ((~b) & d); g = j; } else if (j < 32) { f = (d & b) | ((~d) & c); g = (5 * j + 1) & 0xf; } else if (j < 48) { f = b ^ c ^ d; g = (3 * j + 5) & 0xf; } else { f = c ^ (b | (~d)); g = (7 * j) & 0xf; } uint32_t u = readLittleEndianU32(data + i + g * 4); f += a + sineparts[j] + u; a = d; d = c; c = b; b += (f << shift[j]) | (f >> (32u - shift[j])); } a0 += a; b0 += b; c0 += c; d0 += d; } uint8_t * output = data + checksumOffset; writeLittleEndianU32(output, a0); writeLittleEndianU32(output + 4, b0); writeLittleEndianU32(output + 8, c0); writeLittleEndianU32(output + 12, d0); } // Bradford chromatic adaptation matrix // from https://www.researchgate.net/publication/253799640_A_uniform_colour_space_based_upon_CIECAM97s static const double bradford[3][3] = { { 0.8951, 0.2664, -0.1614 }, { -0.7502, 1.7135, 0.0367 }, { 0.0389, -0.0685, 1.0296 }, }; // LMS values for D50 whitepoint static const double lmsD50[3] = { 0.996284, 1.02043, 0.818644 }; avifBool avifGenerateRGBICC(avifRWData * icc, float gamma, const float primaries[8]) { uint8_t buffer[sizeof(iccColorTemplate)]; memcpy(buffer, iccColorTemplate, sizeof(iccColorTemplate)); double whitePointXYZ[3]; if (!xyToXYZ(&primaries[6], whitePointXYZ)) { return AVIF_FALSE; } if (!putColorant(buffer + colorWhiteOffset, whitePointXYZ)) { return AVIF_FALSE; } double rgbPrimaries[3][3] = { { primaries[0], primaries[2], primaries[4] }, { primaries[1], primaries[3], primaries[5] }, { 1.0 - primaries[0] - primaries[1], 1.0 - primaries[2] - primaries[3], 1.0 - primaries[4] - primaries[5] } }; double rgbPrimariesInv[3][3]; if (!matInv(rgbPrimaries, rgbPrimariesInv)) { return AVIF_FALSE; } double rgbCoefficients[3]; vecMul(rgbPrimariesInv, whitePointXYZ, rgbCoefficients); double rgbCoefficientsMat[3][3]; matDiag(rgbCoefficients, rgbCoefficientsMat); double rgbXYZ[3][3]; matMul(rgbPrimaries, rgbCoefficientsMat, rgbXYZ); // ICC stores primaries XYZ under PCS. // Adapt using linear bradford transform // from https://onlinelibrary.wiley.com/doi/pdf/10.1002/9781119021780.app3 double lms[3]; vecMul(bradford, whitePointXYZ, lms); for (int i = 0; i < 3; ++i) { if (fabs(lms[i]) < small) { return AVIF_FALSE; } lms[i] = lmsD50[i] / lms[i]; } double adaptation[3][3]; matDiag(lms, adaptation); double tmp[3][3]; matMul(adaptation, bradford, tmp); double bradfordInv[3][3]; matInv(bradford, bradfordInv); matMul(bradfordInv, tmp, adaptation); double rgbXYZD50[3][3]; matMul(adaptation, rgbXYZ, rgbXYZD50); matTrans(rgbXYZD50); if (!putColorant(buffer + colorRedOffset, rgbXYZD50[0])) { return AVIF_FALSE; } if (!putColorant(buffer + colorGreenOffset, rgbXYZD50[1])) { return AVIF_FALSE; } if (!putColorant(buffer + colorBlueOffset, rgbXYZD50[2])) { return AVIF_FALSE; } if (!putU8Fixed8(buffer + colorGammaOffset, gamma)) { return AVIF_FALSE; } computeMD5(buffer, sizeof(iccColorTemplate)); if (avifRWDataSet(icc, buffer, iccColorLength) != AVIF_RESULT_OK) { return AVIF_FALSE; } return AVIF_TRUE; } avifBool avifGenerateGrayICC(avifRWData * icc, float gamma, const float white[2]) { uint8_t buffer[sizeof(iccGrayTemplate)]; memcpy(buffer, iccGrayTemplate, sizeof(iccGrayTemplate)); double whitePointXYZ[3]; if (!xyToXYZ(white, whitePointXYZ)) { return AVIF_FALSE; } if (!putColorant(buffer + grayWhiteOffset, whitePointXYZ)) { return AVIF_FALSE; } if (!putU8Fixed8(buffer + grayGammaOffset, gamma)) { return AVIF_FALSE; } computeMD5(buffer, sizeof(iccGrayTemplate)); if (avifRWDataSet(icc, buffer, iccGrayLength) != AVIF_RESULT_OK) { return AVIF_FALSE; } return AVIF_TRUE; }