| /* |
| * Copyright 2018 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "../skcms.h" |
| #include "Macros.h" |
| #include "TransferFunction.h" |
| #include <assert.h> |
| #include <limits.h> |
| #include <math.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| |
| static uint32_t make_signature(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { |
| return (uint32_t)(a << 24) |
| | (uint32_t)(b << 16) |
| | (uint32_t)(c << 8) |
| | (uint32_t)(d << 0); |
| } |
| |
| static uint16_t read_big_u16(const uint8_t* ptr) { |
| uint16_t be; |
| memcpy(&be, ptr, sizeof(be)); |
| #if defined(_MSC_VER) |
| return _byteswap_ushort(be); |
| #else |
| return __builtin_bswap16(be); |
| #endif |
| } |
| |
| static uint32_t read_big_u32(const uint8_t* ptr) { |
| uint32_t be; |
| memcpy(&be, ptr, sizeof(be)); |
| #if defined(_MSC_VER) |
| return _byteswap_ulong(be); |
| #else |
| return __builtin_bswap32(be); |
| #endif |
| } |
| |
| static int32_t read_big_i32(const uint8_t* ptr) { |
| return (int32_t)read_big_u32(ptr); |
| } |
| |
| static uint64_t read_big_u64(const uint8_t* ptr) { |
| uint64_t be; |
| memcpy(&be, ptr, sizeof(be)); |
| #if defined(_MSC_VER) |
| return _byteswap_uint64(be); |
| #else |
| return __builtin_bswap64(be); |
| #endif |
| } |
| |
| static float read_big_fixed(const uint8_t* ptr) { |
| return read_big_i32(ptr) * (1.0f / 65536.0f); |
| } |
| |
| static skcms_ICCDateTime read_big_date_time(const uint8_t* ptr) { |
| skcms_ICCDateTime date_time; |
| date_time.year = read_big_u16(ptr + 0); |
| date_time.month = read_big_u16(ptr + 2); |
| date_time.day = read_big_u16(ptr + 4); |
| date_time.hour = read_big_u16(ptr + 6); |
| date_time.minute = read_big_u16(ptr + 8); |
| date_time.second = read_big_u16(ptr + 10); |
| return date_time; |
| } |
| |
| // Maps to an in-memory profile so that fields line up to the locations specified |
| // in ICC.1:2010, section 7.2 |
| typedef struct { |
| uint8_t size [ 4]; |
| uint8_t cmm_type [ 4]; |
| uint8_t version [ 4]; |
| uint8_t profile_class [ 4]; |
| uint8_t data_color_space [ 4]; |
| uint8_t pcs [ 4]; |
| uint8_t creation_date_time [12]; |
| uint8_t signature [ 4]; |
| uint8_t platform [ 4]; |
| uint8_t flags [ 4]; |
| uint8_t device_manufacturer [ 4]; |
| uint8_t device_model [ 4]; |
| uint8_t device_attributes [ 8]; |
| uint8_t rendering_intent [ 4]; |
| uint8_t illuminant_X [ 4]; |
| uint8_t illuminant_Y [ 4]; |
| uint8_t illuminant_Z [ 4]; |
| uint8_t creator [ 4]; |
| uint8_t profile_id [16]; |
| uint8_t reserved [28]; |
| uint8_t tag_count [ 4]; // Technically not part of header, but required |
| } header_Layout; |
| |
| typedef struct { |
| uint8_t signature [4]; |
| uint8_t offset [4]; |
| uint8_t size [4]; |
| } tag_Layout; |
| |
| static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) { |
| return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout)); |
| } |
| |
| // XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of |
| // the type are for tags/data that store exactly one triple. |
| typedef struct { |
| uint8_t type [4]; |
| uint8_t reserved [4]; |
| uint8_t X [4]; |
| uint8_t Y [4]; |
| uint8_t Z [4]; |
| } XYZ_Layout; |
| |
| static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) { |
| const XYZ_Layout* xyzTag = NULL; |
| if (tag && |
| tag->type == make_signature('X','Y','Z',' ') && |
| tag->size >= SAFE_SIZEOF(XYZ_Layout)) { |
| xyzTag = (const XYZ_Layout*)tag->buf; |
| } |
| |
| if (!xyzTag || !x || !y || !z) { |
| return false; |
| } |
| |
| *x = read_big_fixed(xyzTag->X); |
| *y = read_big_fixed(xyzTag->Y); |
| *z = read_big_fixed(xyzTag->Z); |
| return true; |
| } |
| |
| static bool read_to_XYZD50(const skcms_ICCProfile* profile, skcms_Matrix3x3* toXYZ) { |
| if (!profile || !toXYZ) { return false; } |
| skcms_ICCTag rXYZ, gXYZ, bXYZ; |
| if (!skcms_GetTagBySignature(profile, make_signature('r', 'X', 'Y', 'Z'), &rXYZ) || |
| !skcms_GetTagBySignature(profile, make_signature('g', 'X', 'Y', 'Z'), &gXYZ) || |
| !skcms_GetTagBySignature(profile, make_signature('b', 'X', 'Y', 'Z'), &bXYZ)) { |
| return false; |
| } |
| |
| return read_tag_xyz(&rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) && |
| read_tag_xyz(&gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) && |
| read_tag_xyz(&bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]); |
| } |
| |
| typedef struct { |
| uint8_t type [4]; |
| uint8_t reserved_a [4]; |
| uint8_t function_type [2]; |
| uint8_t reserved_b [2]; |
| uint8_t parameters [ ]; // 1, 3, 4, 5, or 7 s15.16 parameters, depending on function_type |
| } para_Layout; |
| |
| static bool read_curve_para(const uint8_t* buf, uint32_t size, |
| skcms_Curve* curve, uint32_t* curve_size) { |
| if (size < SAFE_SIZEOF(para_Layout)) { |
| return false; |
| } |
| |
| const para_Layout* paraTag = (const para_Layout*)buf; |
| |
| enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 }; |
| uint16_t function_type = read_big_u16(paraTag->function_type); |
| if (function_type > kGABCDEF) { |
| return false; |
| } |
| |
| static const uint32_t curve_bytes[] = { 4, 12, 16, 20, 28 }; |
| if (size < SAFE_SIZEOF(para_Layout) + curve_bytes[function_type]) { |
| return false; |
| } |
| |
| if (curve_size) { |
| *curve_size = SAFE_SIZEOF(para_Layout) + curve_bytes[function_type]; |
| } |
| |
| curve->table_8 = NULL; |
| curve->table_16 = NULL; |
| curve->table_entries = 0; |
| curve->parametric.a = 1.0f; |
| curve->parametric.b = 0.0f; |
| curve->parametric.c = 0.0f; |
| curve->parametric.d = 0.0f; |
| curve->parametric.e = 0.0f; |
| curve->parametric.f = 0.0f; |
| curve->parametric.g = read_big_fixed(paraTag->parameters); |
| |
| switch (function_type) { |
| case kGAB: |
| curve->parametric.a = read_big_fixed(paraTag->parameters + 4); |
| curve->parametric.b = read_big_fixed(paraTag->parameters + 8); |
| if (curve->parametric.a == 0) { |
| return false; |
| } |
| curve->parametric.d = -curve->parametric.b / curve->parametric.a; |
| break; |
| case kGABC: |
| curve->parametric.a = read_big_fixed(paraTag->parameters + 4); |
| curve->parametric.b = read_big_fixed(paraTag->parameters + 8); |
| curve->parametric.e = read_big_fixed(paraTag->parameters + 12); |
| if (curve->parametric.a == 0) { |
| return false; |
| } |
| curve->parametric.d = -curve->parametric.b / curve->parametric.a; |
| curve->parametric.f = curve->parametric.e; |
| break; |
| case kGABCD: |
| curve->parametric.a = read_big_fixed(paraTag->parameters + 4); |
| curve->parametric.b = read_big_fixed(paraTag->parameters + 8); |
| curve->parametric.c = read_big_fixed(paraTag->parameters + 12); |
| curve->parametric.d = read_big_fixed(paraTag->parameters + 16); |
| break; |
| case kGABCDEF: |
| curve->parametric.a = read_big_fixed(paraTag->parameters + 4); |
| curve->parametric.b = read_big_fixed(paraTag->parameters + 8); |
| curve->parametric.c = read_big_fixed(paraTag->parameters + 12); |
| curve->parametric.d = read_big_fixed(paraTag->parameters + 16); |
| curve->parametric.e = read_big_fixed(paraTag->parameters + 20); |
| curve->parametric.f = read_big_fixed(paraTag->parameters + 24); |
| break; |
| } |
| return true; |
| } |
| |
| typedef struct { |
| uint8_t type [4]; |
| uint8_t reserved [4]; |
| uint8_t value_count [4]; |
| uint8_t parameters [ ]; // value_count parameters (8.8 if 1, uint16 (n*65535) if > 1) |
| } curv_Layout; |
| |
| static bool read_curve_curv(const uint8_t* buf, uint32_t size, |
| skcms_Curve* curve, uint32_t* curve_size) { |
| if (size < SAFE_SIZEOF(curv_Layout)) { |
| return false; |
| } |
| |
| const curv_Layout* curvTag = (const curv_Layout*)buf; |
| |
| uint32_t value_count = read_big_u32(curvTag->value_count); |
| if (size < SAFE_SIZEOF(curv_Layout) + value_count * SAFE_SIZEOF(uint16_t)) { |
| return false; |
| } |
| |
| if (curve_size) { |
| *curve_size = SAFE_SIZEOF(curv_Layout) + value_count * SAFE_SIZEOF(uint16_t); |
| } |
| |
| if (value_count < 2) { |
| curve->table_8 = NULL; |
| curve->table_16 = NULL; |
| curve->table_entries = 0; |
| curve->parametric.a = 1.0f; |
| curve->parametric.b = 0.0f; |
| curve->parametric.c = 0.0f; |
| curve->parametric.d = 0.0f; |
| curve->parametric.e = 0.0f; |
| curve->parametric.f = 0.0f; |
| if (value_count == 0) { |
| // Empty tables are a shorthand for linear |
| curve->parametric.g = 1.0f; |
| } else { |
| // Single entry tables are a shorthand for simple gamma |
| curve->parametric.g = read_big_u16(curvTag->parameters) * (1.0f / 256.0f); |
| } |
| } else { |
| memset(&curve->parametric, 0, SAFE_SIZEOF(curve->parametric)); |
| curve->table_8 = NULL; |
| curve->table_16 = curvTag->parameters; |
| curve->table_entries = value_count; |
| } |
| |
| return true; |
| } |
| |
| // Parses both curveType and parametricCurveType data. Ensures that at most 'size' bytes are read. |
| // If curve_size is not NULL, writes the number of bytes used by the curve in (*curve_size). |
| static bool read_curve(const uint8_t* buf, uint32_t size, |
| skcms_Curve* curve, uint32_t* curve_size) { |
| if (!buf || size < 4 || !curve) { |
| return false; |
| } |
| |
| uint32_t type = read_big_u32(buf); |
| if (type == make_signature('p', 'a', 'r', 'a')) { |
| return read_curve_para(buf, size, curve, curve_size); |
| } else if (type == make_signature('c', 'u', 'r', 'v')) { |
| return read_curve_curv(buf, size, curve, curve_size); |
| } |
| |
| return false; |
| } |
| |
| static bool get_transfer_function(const skcms_ICCProfile* profile, |
| skcms_TransferFunction* transferFunction) { |
| if (!profile || !profile->has_trc || !transferFunction) { |
| return false; |
| } |
| |
| const skcms_Curve* trc = profile->trc; |
| if (trc[0].table_entries || trc[1].table_entries || trc[2].table_entries) { |
| return false; |
| } |
| |
| if (0 != memcmp(&trc[0].parametric, &trc[1].parametric, SAFE_SIZEOF(trc[0].parametric)) || |
| 0 != memcmp(&trc[0].parametric, &trc[2].parametric, SAFE_SIZEOF(trc[0].parametric))) { |
| return false; |
| } |
| |
| *transferFunction = trc[0].parametric; |
| return true; |
| } |
| |
| bool skcms_ApproximateTransferFunction(const skcms_ICCProfile* profile, |
| skcms_TransferFunction* fn, |
| float* max_error) { |
| if (!profile || !fn) { return false; } |
| |
| const skcms_Curve* curves = profile->trc; |
| if (!curves[0].table_16 || !curves[1].table_16 || !curves[2].table_16) { |
| return false; |
| } |
| |
| uint64_t n = (uint64_t)curves[0].table_entries |
| + (uint64_t)curves[1].table_entries |
| + (uint64_t)curves[2].table_entries; |
| if (n > INT_MAX) { |
| return false; |
| } |
| |
| uint64_t buf_size = 2 * n * SAFE_SIZEOF(float); |
| |
| if (buf_size != (size_t)buf_size) { |
| return false; |
| } |
| float* data = malloc((size_t)buf_size); |
| |
| float* x = data; |
| float* t = data + n; |
| |
| // Merge all channels' tables into a single array. |
| for (int c = 0; c < 3; ++c) { |
| for (uint32_t i = 0; i < curves[c].table_entries; ++i) { |
| *x++ = i / (curves[c].table_entries - 1.0f); |
| *t++ = read_big_u16(curves[c].table_16 + 2 * i) * (1 / 65535.0f); |
| } |
| } |
| |
| x = data; |
| t = data + n; |
| |
| bool result = skcms_TransferFunction_approximate(fn, x, t, (int)n, max_error); |
| free(data); |
| return result; |
| } |
| |
| // mft1 and mft2 share a large chunk of data |
| typedef struct { |
| uint8_t type [ 4]; |
| uint8_t reserved_a [ 4]; |
| uint8_t input_channels [ 1]; |
| uint8_t output_channels [ 1]; |
| uint8_t grid_points [ 1]; |
| uint8_t reserved_b [ 1]; |
| uint8_t matrix [36]; |
| } mft_CommonLayout; |
| |
| typedef struct { |
| mft_CommonLayout common [ 1]; |
| |
| uint8_t tables [ ]; |
| } mft1_Layout; |
| |
| typedef struct { |
| mft_CommonLayout common [ 1]; |
| |
| uint8_t input_table_entries [ 2]; |
| uint8_t output_table_entries [ 2]; |
| uint8_t tables [ ]; |
| } mft2_Layout; |
| |
| static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) { |
| // MFT matrices are applied before the first set of curves, but must be identity unless the |
| // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the |
| // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another |
| // field/flag. |
| a2b->matrix_channels = 0; |
| |
| a2b->input_channels = mftTag->input_channels[0]; |
| a2b->output_channels = mftTag->output_channels[0]; |
| |
| // We require exactly three (ie XYZ/Lab/RGB) output channels |
| if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) { |
| return false; |
| } |
| // We require at least one, and no more than four (ie CMYK) input channels |
| if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) { |
| return false; |
| } |
| |
| for (uint32_t i = 0; i < a2b->input_channels; ++i) { |
| a2b->grid_points[i] = mftTag->grid_points[0]; |
| } |
| // The grid only makes sense with at least two points along each axis |
| if (a2b->grid_points[0] < 2) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool init_a2b_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width, |
| uint32_t input_table_entries, uint32_t output_table_entries, |
| skcms_A2B* a2b) { |
| // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow |
| uint32_t byte_len_per_input_table = input_table_entries * byte_width; |
| uint32_t byte_len_per_output_table = output_table_entries * byte_width; |
| |
| // [input|output]_channels are <= 4, so still no overflow |
| uint32_t byte_len_all_input_tables = a2b->input_channels * byte_len_per_input_table; |
| uint32_t byte_len_all_output_tables = a2b->output_channels * byte_len_per_output_table; |
| |
| uint64_t grid_size = a2b->output_channels * byte_width; |
| for (uint32_t axis = 0; axis < a2b->input_channels; ++axis) { |
| grid_size *= a2b->grid_points[axis]; |
| } |
| |
| if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) { |
| return false; |
| } |
| |
| for (uint32_t i = 0; i < a2b->input_channels; ++i) { |
| a2b->input_curves[i].table_entries = input_table_entries; |
| if (byte_width == 1) { |
| a2b->input_curves[i].table_8 = table_base + i * byte_len_per_input_table; |
| a2b->input_curves[i].table_16 = NULL; |
| } else { |
| a2b->input_curves[i].table_8 = NULL; |
| a2b->input_curves[i].table_16 = table_base + i * byte_len_per_input_table; |
| } |
| } |
| |
| if (byte_width == 1) { |
| a2b->grid_8 = table_base + byte_len_all_input_tables; |
| a2b->grid_16 = NULL; |
| } else { |
| a2b->grid_8 = NULL; |
| a2b->grid_16 = table_base + byte_len_all_input_tables; |
| } |
| |
| const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size; |
| for (uint32_t i = 0; i < a2b->output_channels; ++i) { |
| a2b->output_curves[i].table_entries = output_table_entries; |
| if (byte_width == 1) { |
| a2b->output_curves[i].table_8 = output_table_base + i * byte_len_per_output_table; |
| a2b->output_curves[i].table_16 = NULL; |
| } else { |
| a2b->output_curves[i].table_8 = NULL; |
| a2b->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table; |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool read_tag_mft1(const skcms_ICCTag* tag, skcms_A2B* a2b) { |
| if (tag->size < SAFE_SIZEOF(mft1_Layout)) { |
| return false; |
| } |
| |
| const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf; |
| if (!read_mft_common(mftTag->common, a2b)) { |
| return false; |
| } |
| |
| uint32_t input_table_entries = 256; |
| uint32_t output_table_entries = 256; |
| |
| return init_a2b_tables(mftTag->tables, tag->size - SAFE_SIZEOF(mft1_Layout), 1, |
| input_table_entries, output_table_entries, a2b); |
| } |
| |
| static bool read_tag_mft2(const skcms_ICCTag* tag, skcms_A2B* a2b) { |
| if (tag->size < SAFE_SIZEOF(mft2_Layout)) { |
| return false; |
| } |
| |
| const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf; |
| if (!read_mft_common(mftTag->common, a2b)) { |
| return false; |
| } |
| |
| uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries); |
| uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries); |
| |
| // ICC spec mandates that 2 <= table_entries <= 4096 |
| if (input_table_entries < 2 || input_table_entries > 4096 || |
| output_table_entries < 2 || output_table_entries > 4096) { |
| return false; |
| } |
| |
| return init_a2b_tables(mftTag->tables, tag->size - SAFE_SIZEOF(mft2_Layout), 2, |
| input_table_entries, output_table_entries, a2b); |
| } |
| |
| static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset, |
| uint32_t num_curves, skcms_Curve* curves) { |
| for (uint32_t i = 0; i < num_curves; ++i) { |
| if (curve_offset > size) { |
| return false; |
| } |
| |
| uint32_t curve_bytes; |
| if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) { |
| return false; |
| } |
| |
| if (curve_bytes > UINT32_MAX - 3) { |
| return false; |
| } |
| curve_bytes = (curve_bytes + 3) & ~3U; |
| |
| uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes; |
| curve_offset = (uint32_t)new_offset_64; |
| if (new_offset_64 != curve_offset) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| typedef struct { |
| uint8_t type [ 4]; |
| uint8_t reserved_a [ 4]; |
| uint8_t input_channels [ 1]; |
| uint8_t output_channels [ 1]; |
| uint8_t reserved_b [ 2]; |
| uint8_t b_curve_offset [ 4]; |
| uint8_t matrix_offset [ 4]; |
| uint8_t m_curve_offset [ 4]; |
| uint8_t clut_offset [ 4]; |
| uint8_t a_curve_offset [ 4]; |
| } mAB_Layout; |
| |
| typedef struct { |
| uint8_t grid_points [16]; |
| uint8_t grid_byte_width [ 1]; |
| uint8_t reserved [ 3]; |
| uint8_t data [ ]; |
| } mABCLUT_Layout; |
| |
| static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b) { |
| if (tag->size < SAFE_SIZEOF(mAB_Layout)) { |
| return false; |
| } |
| |
| const mAB_Layout* mABTag = (const mAB_Layout*)tag->buf; |
| |
| a2b->input_channels = mABTag->input_channels[0]; |
| a2b->output_channels = mABTag->output_channels[0]; |
| |
| // We require exactly three (ie XYZ/Lab/RGB) output channels |
| if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) { |
| return false; |
| } |
| // We require no more than four (ie CMYK) input channels |
| if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) { |
| return false; |
| } |
| |
| uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset); |
| uint32_t matrix_offset = read_big_u32(mABTag->matrix_offset); |
| uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset); |
| uint32_t clut_offset = read_big_u32(mABTag->clut_offset); |
| uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset); |
| |
| // "B" curves must be present |
| if (0 == b_curve_offset) { |
| return false; |
| } |
| |
| if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels, |
| a2b->output_curves)) { |
| return false; |
| } |
| |
| // "M" curves and Matrix must be used together |
| if (0 != m_curve_offset) { |
| if (0 == matrix_offset) { |
| return false; |
| } |
| a2b->matrix_channels = a2b->output_channels; |
| if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels, |
| a2b->matrix_curves)) { |
| return false; |
| } |
| |
| // Read matrix, which is stored as a row-major 3x3, followed by the fourth column |
| if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) { |
| return false; |
| } |
| const uint8_t* mtx_buf = tag->buf + matrix_offset; |
| a2b->matrix.vals[0][0] = read_big_fixed(mtx_buf + 0); |
| a2b->matrix.vals[0][1] = read_big_fixed(mtx_buf + 4); |
| a2b->matrix.vals[0][2] = read_big_fixed(mtx_buf + 8); |
| a2b->matrix.vals[1][0] = read_big_fixed(mtx_buf + 12); |
| a2b->matrix.vals[1][1] = read_big_fixed(mtx_buf + 16); |
| a2b->matrix.vals[1][2] = read_big_fixed(mtx_buf + 20); |
| a2b->matrix.vals[2][0] = read_big_fixed(mtx_buf + 24); |
| a2b->matrix.vals[2][1] = read_big_fixed(mtx_buf + 28); |
| a2b->matrix.vals[2][2] = read_big_fixed(mtx_buf + 32); |
| a2b->matrix.vals[0][3] = read_big_fixed(mtx_buf + 36); |
| a2b->matrix.vals[1][3] = read_big_fixed(mtx_buf + 40); |
| a2b->matrix.vals[2][3] = read_big_fixed(mtx_buf + 44); |
| } else { |
| if (0 != matrix_offset) { |
| return false; |
| } |
| a2b->matrix_channels = 0; |
| } |
| |
| // "A" curves and CLUT must be used together |
| if (0 != a_curve_offset) { |
| if (0 == clut_offset) { |
| return false; |
| } |
| if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels, |
| a2b->input_curves)) { |
| return false; |
| } |
| |
| if (tag->size < clut_offset + SAFE_SIZEOF(mABCLUT_Layout)) { |
| return false; |
| } |
| const mABCLUT_Layout* clut = (const mABCLUT_Layout*)(tag->buf + clut_offset); |
| |
| if (clut->grid_byte_width[0] == 1) { |
| a2b->grid_8 = clut->data; |
| a2b->grid_16 = NULL; |
| } else if (clut->grid_byte_width[0] == 2) { |
| a2b->grid_8 = NULL; |
| a2b->grid_16 = clut->data; |
| } else { |
| return false; |
| } |
| |
| uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0]; |
| for (uint32_t i = 0; i < a2b->input_channels; ++i) { |
| a2b->grid_points[i] = clut->grid_points[i]; |
| // The grid only makes sense with at least two points along each axis |
| if (a2b->grid_points[i] < 2) { |
| return false; |
| } |
| grid_size *= a2b->grid_points[i]; |
| } |
| if (tag->size < clut_offset + SAFE_SIZEOF(mABCLUT_Layout) + grid_size) { |
| return false; |
| } |
| } else { |
| if (0 != clut_offset) { |
| return false; |
| } |
| |
| // If there is no CLUT, the number of input and output channels must match |
| if (a2b->input_channels != a2b->output_channels) { |
| return false; |
| } |
| |
| // Zero out the number of input channels to signal that we're skipping this stage |
| a2b->input_channels = 0; |
| } |
| |
| return true; |
| } |
| |
| static bool read_a2b(const skcms_ICCProfile* profile, skcms_A2B* a2b) { |
| if (!profile || !a2b) { |
| return false; |
| } |
| |
| skcms_ICCTag tag; |
| if (!skcms_GetTagBySignature(profile, make_signature('A', '2', 'B', '0'), &tag)) { |
| return false; |
| } |
| |
| if (tag.type == make_signature('m', 'f', 't', '1')) { |
| return read_tag_mft1(&tag, a2b); |
| } else if (tag.type == make_signature('m', 'f', 't', '2')) { |
| return read_tag_mft2(&tag, a2b); |
| } else if (tag.type == make_signature('m', 'A', 'B', ' ')) { |
| return read_tag_mab(&tag, a2b); |
| } |
| |
| return false; |
| } |
| |
| void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) { |
| if (!profile || !profile->buffer || !tag) { return; } |
| if (idx > profile->tag_count) { return; } |
| const tag_Layout* tags = get_tag_table(profile); |
| tag->signature = read_big_u32(tags[idx].signature); |
| tag->size = read_big_u32(tags[idx].size); |
| tag->buf = read_big_u32(tags[idx].offset) + profile->buffer; |
| tag->type = read_big_u32(tag->buf); |
| } |
| |
| bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) { |
| if (!profile || !profile->buffer || !tag) { return false; } |
| const tag_Layout* tags = get_tag_table(profile); |
| for (uint32_t i = 0; i < profile->tag_count; ++i) { |
| if (read_big_u32(tags[i].signature) == sig) { |
| tag->signature = sig; |
| tag->size = read_big_u32(tags[i].size); |
| tag->buf = read_big_u32(tags[i].offset) + profile->buffer; |
| tag->type = read_big_u32(tag->buf); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) { |
| static_assert(SAFE_SIZEOF(header_Layout) == 132, "ICC header size"); |
| |
| if (!profile) { |
| return false; |
| } |
| memset(profile, 0, SAFE_SIZEOF(*profile)); |
| |
| if (len < SAFE_SIZEOF(header_Layout)) { |
| return false; |
| } |
| |
| // Byte-swap all header fields |
| const header_Layout* header = buf; |
| profile->buffer = buf; |
| profile->size = read_big_u32(header->size); |
| profile->cmm_type = read_big_u32(header->cmm_type); |
| profile->version = read_big_u32(header->version); |
| profile->profile_class = read_big_u32(header->profile_class); |
| profile->data_color_space = read_big_u32(header->data_color_space); |
| profile->pcs = read_big_u32(header->pcs); |
| profile->creation_date_time = read_big_date_time(header->creation_date_time); |
| profile->signature = read_big_u32(header->signature); |
| profile->platform = read_big_u32(header->platform); |
| profile->flags = read_big_u32(header->flags); |
| profile->device_manufacturer = read_big_u32(header->device_manufacturer); |
| profile->device_model = read_big_u32(header->device_model); |
| profile->device_attributes = read_big_u64(header->device_attributes); |
| profile->rendering_intent = read_big_u32(header->rendering_intent); |
| profile->illuminant_X = read_big_fixed(header->illuminant_X); |
| profile->illuminant_Y = read_big_fixed(header->illuminant_Y); |
| profile->illuminant_Z = read_big_fixed(header->illuminant_Z); |
| profile->creator = read_big_u32(header->creator); |
| static_assert(SAFE_SIZEOF(profile->profile_id) == SAFE_SIZEOF(header->profile_id), |
| "profile_id size"); |
| memcpy(profile->profile_id, header->profile_id, SAFE_SIZEOF(header->profile_id)); |
| profile->tag_count = read_big_u32(header->tag_count); |
| |
| // Validate signature, size (smaller than buffer, large enough to hold tag table), |
| // and major version |
| uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout); |
| if (profile->signature != make_signature('a', 'c', 's', 'p') || |
| profile->size > len || |
| profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size || |
| (profile->version >> 24) > 4) { |
| return false; |
| } |
| |
| // Validate that illuminant is D50 white |
| if (fabsf(profile->illuminant_X - 0.9642f) > 0.0100f || |
| fabsf(profile->illuminant_Y - 1.0000f) > 0.0100f || |
| fabsf(profile->illuminant_Z - 0.8249f) > 0.0100f) { |
| return false; |
| } |
| |
| // Validate that all tag entries have sane offset + size |
| const tag_Layout* tags = get_tag_table(profile); |
| for (uint32_t i = 0; i < profile->tag_count; ++i) { |
| uint32_t tag_offset = read_big_u32(tags[i].offset); |
| uint32_t tag_size = read_big_u32(tags[i].size); |
| uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size; |
| if (tag_size < 4 || tag_end > profile->size) { |
| return false; |
| } |
| } |
| |
| // Pre-parse commonly used tags. |
| skcms_ICCTag rTRC, gTRC, bTRC; |
| profile->has_trc = |
| skcms_GetTagBySignature(profile, make_signature('r', 'T', 'R', 'C'), &rTRC) && |
| skcms_GetTagBySignature(profile, make_signature('g', 'T', 'R', 'C'), &gTRC) && |
| skcms_GetTagBySignature(profile, make_signature('b', 'T', 'R', 'C'), &bTRC) && |
| read_curve(rTRC.buf, rTRC.size, &profile->trc[0], NULL) && |
| read_curve(gTRC.buf, gTRC.size, &profile->trc[1], NULL) && |
| read_curve(bTRC.buf, bTRC.size, &profile->trc[2], NULL); |
| |
| profile->has_tf = get_transfer_function(profile, &profile->tf); |
| profile->has_toXYZD50 = read_to_XYZD50 (profile, &profile->toXYZD50); |
| profile->has_A2B = read_a2b (profile, &profile->A2B); |
| |
| return true; |
| } |