/** * cg-tools -- Cooperative gamma-enabled tools * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "cg-base.h" #include #include #include /** * Magic number for dual-byte precision lookup table based profiles */ #define MLUT_TAG 0x6D4C5554L /** * Magic number for gamma–brightness–contrast based profiles * and for variable precision lookup table profiles */ #define VCGT_TAG 0x76636774L /** * The default filter priority for the program */ const int64_t default_priority = 0; /** * The default class for the program */ char* const default_class = PKGNAME "::cg-icc::standard"; /** * The panhame of the selected ICC profile */ static const char* icc_filepath = NULL; /** * Print usage information and exit */ void usage(void) { fprintf(stderr, "Usage: %s [-M method] [-S site] [-c crtc]... [-R rule] " "(-x | [-p priority] [-d] [file])\n", argv0); exit(1); } /** * Handle a command line option * * @param opt The option, it is a NUL-terminate two-character * string starting with either '-' or '+', if the * argument is not recognised, call `usage`. This * string will not be "-M", "-S", "-c", "-p", or "-R". * @param arg The argument associated with `opt`, * `NULL` there is no next argument, if this * parameter is `NULL` but needed, call `usage` * @return 0 if `arg` was not used, * 1 if `arg` was used, * -1 on error */ int handle_opt(char* opt, char* arg) { if (opt[0] == '-') switch (opt[1]) { case 'd': if (dflag || xflag) usage(); dflag = 1; break; case 'x': if (xflag || dflag) usage(); xflag = 1; break; default: usage(); } return 0; } /** * This function is called after the last * call to `handle_opt` * * @param argc The number of unparsed arguments * @param argv `NULL` terminated list of unparsed arguments * @param method The argument associated with the "-M" option * @param site The argument associated with the "-S" option * @param crtcs The arguments associated with the "-c" options, `NULL`-terminated * @param prio The argument associated with the "-p" option * @param rule The argument associated with the "-R" option * @return Zero on success, -1 on error */ int handle_args(int argc, char* argv[], char* method, char* site, char** crtcs, char* prio, char* rule) { int free_fflag = 0, saved_errno; int q = xflag + dflag; q += (method != NULL) && !strcmp(method, "?"); q += (prio != NULL) && !strcmp(prio, "?"); q += (rule != NULL) && (!strcmp(rule, "?") || !strcmp(rule, "??")); for (; *crtcs; crtcs++) q += !strcmp(*crtcs, "?"); if ((q > 1) || (xflag && ((argc > 0) || (prio != NULL))) || (argc > 1)) usage(); icc_filepath = *argv; return 0; fail: saved_errno = errno; if (free_fflag) free(fflag), fflag = NULL; errno = saved_errno; return cleanup(-1); } /** * Read an unsigned 64-bit integer * * @param content The beginning of the encoded integer * @return The integer, decoded */ static uint64_t icc_uint64(const char* restrict content) { uint64_t rc; rc = (uint64_t)(unsigned char)(content[0]), rc <<= 8; rc |= (uint64_t)(unsigned char)(content[1]), rc <<= 8; rc |= (uint64_t)(unsigned char)(content[2]), rc <<= 8; rc |= (uint64_t)(unsigned char)(content[3]), rc <<= 8; rc |= (uint64_t)(unsigned char)(content[4]), rc <<= 8; rc |= (uint64_t)(unsigned char)(content[5]), rc <<= 8; rc |= (uint64_t)(unsigned char)(content[6]), rc <<= 8; rc |= (uint64_t)(unsigned char)(content[7]); return rc; } /** * Read an unsigned 32-bit integer * * @param content The beginning of the encoded integer * @return The integer, decoded */ static uint32_t icc_uint32(const char* restrict content) { uint32_t rc; rc = (uint32_t)(unsigned char)(content[0]), rc <<= 8; rc |= (uint32_t)(unsigned char)(content[1]), rc <<= 8; rc |= (uint32_t)(unsigned char)(content[2]), rc <<= 8; rc |= (uint32_t)(unsigned char)(content[3]); return rc; } /** * Read an unsigned 16-bit integer * * @param content The beginning of the encoded integer * @return The integer, decoded */ static uint16_t icc_uint16(const char* restrict content) { uint16_t rc; rc = (uint16_t)(unsigned char)(content[0]), rc <<= 8; rc |= (uint16_t)(unsigned char)(content[1]); return rc; } /** * Read an unsigned 8-bit integer * * @param content The beginning of the encoded integer * @return The integer, decoded */ static uint16_t icc_uint8(const char* restrict content) { return (uint8_t)(content[0]) } /** * Read a floating-point value * * @param content The beginning of the encoded value * @param width The number of bytes with which the value is encoded * @return The value, decoded */ static double icc_double(const char* restrict content, size_t width) { double ret = 0; size_t i; for (i = 0; i < width; i++) { ret /= 256; ret += (double)(unsigned char)(content[width - 1 - i]); } ret /= 255; return ret } /** * Parse an ICC profile * * @param content The content of the ICC profile file * @param n The byte-size of `content` * @param ramps Output parameter for the filter stored in the ICC profile, * `.red_size`, `.green_size`, `.blue_size` should already be * set (these values can however be modified.) * @param depth Output parameter for ramps stop value type * @return Zero on success, -1 on error, -2 if no usable data is * available in the profile. */ static int parse_icc(const char* restrict content, size_t n, libcoopgamma_ramps_t* ramps, libcoopgamma_depth_t* depth) { uint32_t i_tag, n_tags; size_t i, ptr = 0, xptr; /* Skip header */ if (n - ptr < 128) return -2; ptr += 128; /* Get the number of tags */ if (n - ptr < 4) return -2; n_tags = icc_uint32(content + ptr), ptr += 4; for (i_tag = 0, xptr = ptr; i_tag < n_tags; i_tag++, ptr = xptr) { uint32_t tag_name, tag_offset, tag_size, gamma_type; /* Get profile encoding type, offset to the profile and the encoding size of its data */ if (n - ptr < 12) return -2; tag_name = icc_uint32(content + ptr), ptr += 4; tag_offset = icc_uint32(content + ptr), ptr += 4; tag_size = icc_uint32(content + ptr), ptr += 4; xptr = ptr; /* Jump to the profile data */ if (tag_offset > INT32_MAX - tag_size) return -2; if (tag_offset + tag_size > n) return -2; ptr = tag_offset; if (tag_name == MLUT_TAG) { /* The profile is encododed as an dual-byte precision lookup table */ /* Initialise ramps */ *depth = LIBCOOPGAMMA_UINT16; ramps->u16.red_size = 256; ramps->u16.green_size = 256; ramps->u16.blue_size = 256; if (libcoopgamma_ramps_initialise(&(ramps->u16)) < 0) return -1; /* Get the lookup table */ if (n - ptr < 3 * 256 * 2) continue; for (i = 0; i < 256; i++) ramps->u16.red[i] = icc_uint16(content + ptr), ptr += 2; for (i = 0; i < 256; i++) ramps->u16.green[i] = icc_uint16(content + ptr), ptr += 2; for (i = 0; i < 256; i++) ramps->u16.blue[i] = icc_uint16(content + ptr), ptr += 2; return 0; } else if (tag_name == VCGT_TAG) { /* The profile is encoded as with gamma, brightness and contrast values * or as a variable precision lookup table profile */ /* VCGT profiles starts where their magic number */ if (n - ptr < 4) continue; tag_name = icc_uint32(content + ptr), ptr += 4; if (tag_name == VCGT_TAG) continue; /* Skip four bytes */ if (n - ptr < 4) continue; ptr += 4; /* Get the actual encoding type */ if (n - ptr < 4) continue; gamma_type = icc_uint32(content + ptr), ptr += 4; if (gamma_type == 0) { /* The profile is encoded as a variable precision lookup table */ uint16_t n_channels, n_entries, entry_size; /* Get metadata */ if (n - ptr < 3 * 4) continue; n_channels = icc_uint32(content + ptr), ptr += 4; n_entries = icc_uint32(content + ptr), ptr += 4; entry_size = icc_uint32(content + ptr), ptr += 4; if (tag_size == 1584) n_channels = 3, n_entries = 256, entry_size = 2; if (n_channels != 3) /* Assuming sRGB, can only be an correct assumption if there are exactly three channels */ continue; /* Check data availability */ if (n_channels > SIZE_MAX / n_entries) continue; if (entry_size > SIZE_MAX / (n_entries * n_channels)) continue; if (n - ptr < (size_t)n_channels * (size_t)n_entries * (size_t)entry_size) continue; /* Initialise ramps */ ramps->u8.red_size = (size_t)n_entries; ramps->u8.green_size = (size_t)n_entries; ramps->u8.blue_size = (size_t)n_entries; switch (entry_size) { case 1: *depth = LIBCOOPGAMMA_UINT8; if (libcoopgamma_ramps_initialise(&(ramps->u8)) < 0) return -1; break; case 2: *depth = LIBCOOPGAMMA_UINT16; if (libcoopgamma_ramps_initialise(&(ramps->u16)) < 0) return -1; break; case 4: *depth = LIBCOOPGAMMA_UINT32; if (libcoopgamma_ramps_initialise(&(ramps->u32)) < 0) return -1; break; case 8: *depth = LIBCOOPGAMMA_UINT64; if (libcoopgamma_ramps_initialise(&(ramps->u64)) < 0) return -1; break; default: *depth = LIBCOOPGAMMA_DOUBLE; if (libcoopgamma_ramps_initialise(&(ramps->d)) < 0) return -1; break; } /* Get the lookup table */ switch (*depth) { case LIBCOOPGAMMA_UINT8: for (i = 0; i < ramps->u8.red_size; i++) ramps->u8.red[i] = icc_uint8(content + ptr), ptr += 1; for (i = 0; i < ramps->u8.green_size; i++) ramps->u8.green[i] = icc_uint8(content + ptr), ptr += 1; for (i = 0; i < ramps->u8.blue_size; i++) ramps->u8.blue[i] = icc_uint8(content + ptr), ptr += 1; break; case LIBCOOPGAMMA_UINT16: for (i = 0; i < ramps->u16.red_size; i++) ramps->u16.red[i] = icc_uint16(content + ptr), ptr += 2; for (i = 0; i < ramps->u16.green_size; i++) ramps->u16.green[i] = icc_uint16(content + ptr), ptr += 2; for (i = 0; i < ramps->u16.blue_size; i++) ramps->u16.blue[i] = icc_uint16(content + ptr), ptr += 2; break; case LIBCOOPGAMMA_UINT32: for (i = 0; i < ramps->u32.red_size; i++) ramps->u32.red[i] = icc_uint32(content + ptr), ptr += 4; for (i = 0; i < ramps->u32.green_size; i++) ramps->u32.green[i] = icc_uint32(content + ptr), ptr += 4; for (i = 0; i < ramps->u32.blue_size; i++) ramps->u32.blue[i] = icc_uint32(content + ptr), ptr += 4; break; case LIBCOOPGAMMA_UINT64: for (i = 0; i < ramps->u64.red_size; i++) ramps->u64.red[i] = icc_uint64(content + ptr), ptr += 8; for (i = 0; i < ramps->u64.green_size; i++) ramps->u64.green[i] = icc_uint64(content + ptr), ptr += 8; for (i = 0; i < ramps->u64.blue_size; i++) ramps->u64.blue[i] = icc_uint64(content + ptr), ptr += 8; break; default: for (i = 0; i < ramps->d.red_size; i++) ramps->d.red[i] = icc_double(content + ptr, entry_size), ptr += entry_size; for (i = 0; i < ramps->d.green_size; i++) ramps->d.green[i] = icc_double(content + ptr, entry_size), ptr += entry_size; for (i = 0; i < ramps->d.blue_size; i++) ramps->d.blue[i] = icc_double(content + ptr, entry_size), ptr += entry_size; break; } return 0; } else if (gamma_type == 1) { /* The profile is encoded with gamma, brightness and contrast values */ double r_gamma, r_min, r_max, g_gamma, g_min, g_max, b_gamma, b_min, b_max; /* Get the gamma, brightness and contrast */ if (n - ptr < 9 * 4) continue; r_gamma = (double)icc_uint32(content + ptr) / 65536L, ptr += 4; r_min = (double)icc_uint32(content + ptr) / 65536L, ptr += 4; r_max = (double)icc_uint32(content + ptr) / 65536L, ptr += 4; g_gamma = (double)icc_uint32(content + ptr) / 65536L, ptr += 4; g_min = (double)icc_uint32(content + ptr) / 65536L, ptr += 4; g_max = (double)icc_uint32(content + ptr) / 65536L, ptr += 4; b_gamma = (double)icc_uint32(content + ptr) / 65536L, ptr += 4; b_min = (double)icc_uint32(content + ptr) / 65536L, ptr += 4; b_max = (double)icc_uint32(content + ptr) / 65536L, ptr += 4; /* Initialise ramps */ *depth = LIBCOOPGAMMA_DOUBLE; if (libcoopgamma_ramps_initialise(&(ramps->d)) < 0) return -1; /* Set ramps */ libclut_start_over(&(ramps->d), (double)1, double, 1, 1, 1); libclut_gamma(&(ramps->d), (double)1, double, r_gamma, g_gamma, b_gamma); libclut_rgb_limits(&(ramps->d), (double)1, double, r_min, r_max, g_min, g_max, b_min, b_max); return 0; } } } return -2; }