/**
* 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 <http://www.gnu.org/licenses/>.
*/
#include "cg-base.h"
#include <libclut.h>
#include <stdio.h>
#include <stdlib.h>
/**
* 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";
/**
* 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))))
usage();
/* TODO */
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;
}