/* * Copyright 2018 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifdef _MSC_VER #define _CRT_SECURE_NO_WARNINGS #define SKCMS_NORETURN __declspec(noreturn) #else #include #include #define SKCMS_NORETURN noreturn #endif #include "skcms.h" #include "skcms_internal.h" #include "test_only.h" #include #include #include SKCMS_NORETURN static void fatal(const char* msg) { fprintf(stderr, "ERROR: %s\n", msg); exit(1); } // xy co-ordinates of the CIE 1931 standard observer XYZ functions. // wavelength is sampled every 5 nm in [360, 700]. // This is effectively the hull of the horseshoe in a chromaticity diagram. static const double kSpectralHull[] = { 0.17556, 0.00529384, 0.175161, 0.00525635, 0.174821, 0.0052206, 0.17451, 0.00518164, 0.174112, 0.00496373, 0.174008, 0.00498055, 0.173801, 0.00491541, 0.17356, 0.0049232, 0.173337, 0.00479674, 0.173021, 0.00477505, 0.172577, 0.0047993, 0.172087, 0.00483252, 0.171407, 0.00510217, 0.170301, 0.00578851, 0.168878, 0.00690024, 0.166895, 0.00855561, 0.164412, 0.0108576, 0.161105, 0.0137934, 0.156641, 0.0177048, 0.150985, 0.0227402, 0.14396, 0.029703, 0.135503, 0.0398791, 0.124118, 0.0578025, 0.109594, 0.0868425, 0.0912935, 0.132702, 0.0687059, 0.200723, 0.0453907, 0.294976, 0.0234599, 0.412703, 0.00816803, 0.538423, 0.00385852, 0.654823, 0.0138702, 0.750186, 0.0388518, 0.812016, 0.0743024, 0.833803, 0.114161, 0.826207, 0.154722, 0.805863, 0.192876, 0.781629, 0.22962, 0.754329, 0.265775, 0.724324, 0.301604, 0.692308, 0.337363, 0.658848, 0.373102, 0.624451, 0.408736, 0.589607, 0.444062, 0.554714, 0.478775, 0.520202, 0.512486, 0.486591, 0.544787, 0.454434, 0.575151, 0.424232, 0.602933, 0.396497, 0.627037, 0.372491, 0.648233, 0.351395, 0.665764, 0.334011, 0.680079, 0.319747, 0.691504, 0.308342, 0.700606, 0.299301, 0.707918, 0.292027, 0.714032, 0.285929, 0.719033, 0.280935, 0.723032, 0.276948, 0.725992, 0.274008, 0.728272, 0.271728, 0.729969, 0.270031, 0.731089, 0.268911, 0.731993, 0.268007, 0.732719, 0.267281, 0.733417, 0.266583, 0.734047, 0.265953, 0.73439, 0.26561, 0.734592, 0.265408, 0.73469, 0.26531, }; 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 } // TODO: Put state into struct with FP static int desmos_id = 0; static FILE* desmos_open(const char* filename) { FILE* fp = fopen(filename, "wb"); if (!fp) { fatal("Unable to open output file"); } fprintf(fp, "\n"); fprintf(fp, "\n"); fprintf(fp, "\n"); fprintf(fp, "\n"); fprintf(fp, "\n"); fprintf(fp, "\n"); fprintf(fp, "\n"); fprintf(fp, "
\n"); fprintf(fp, "\n"); fprintf(fp, "\n"); fprintf(fp, "\n"); fclose(fp); } static void desmos_transfer_function(FILE* fp, const skcms_TransferFunction* tf, const char* color) { fprintf(fp, "{\n"); fprintf(fp, " \"type\": \"expression\",\n"); fprintf(fp, " \"id\": \"%d\",\n", desmos_id++); fprintf(fp, " \"color\": \"%s\",\n", color); fprintf(fp, " \"latex\": \"\\\\left\\\\{" "0 \\\\le x < %.5f: %.5fx + %.5f, " // 0 <= x < d: cx + f "%.5f \\\\le x \\\\le 1: (%.5fx + %.5f)^{%.5f} + %.5f" // d <= x <= 1: (ax + b)^g + e "\\\\right\\\\}\"\n", tf->d, tf->c, tf->f, tf->d, tf->a, tf->b, tf->g, tf->e); fprintf(fp, "},\n"); } typedef double table_func(int i, const void* ctx); static void desmos_table(FILE* fp, int N, const char* label, const char* color, table_func* x, const void* x_ctx, table_func* y, const void* y_ctx) { int folder_id = desmos_id++, table_id = desmos_id++, subscript = desmos_id++; // Folder fprintf(fp, "{\n"); fprintf(fp, " \"type\": \"folder\",\n"); fprintf(fp, " \"id\": \"%d\",\n", folder_id); fprintf(fp, " \"title\": \"%s\",\n", label); fprintf(fp, " \"collapsed\": true,\n"); fprintf(fp, " \"memberIds\": { \"%d\": true }\n", table_id); fprintf(fp, "},\n"); // Table fprintf(fp, "{\n"); fprintf(fp, " \"type\": \"table\",\n"); fprintf(fp, " \"id\": \"%d\",\n", table_id); fprintf(fp, " \"columns\": [\n"); // X Column fprintf(fp, " {\n"); fprintf(fp, " \"values\": ["); for (int i = 0; i < N; ++i) { if (i % 6 == 0) { fprintf(fp, "\n "); } fprintf(fp, " \"%.5f\",", x(i, x_ctx)); } fprintf(fp, " ],\n"); fprintf(fp, " \"hidden\": true,\n"); fprintf(fp, " \"id\": \"%d\",\n", desmos_id++); fprintf(fp, " \"color\": \"%s\",\n", color); fprintf(fp, " \"latex\": \"x_{%d}\"\n", subscript); fprintf(fp, " },\n"); // Y Column fprintf(fp, " {\n"); fprintf(fp, " \"values\": [\n"); for (int i = 0; i < N; ++i) { if (i % 6 == 0) { fprintf(fp, "\n "); } fprintf(fp, " \"%.5f\",", y(i, y_ctx)); } fprintf(fp, " ],\n"); fprintf(fp, " \"id\": \"%d\",\n", desmos_id++); fprintf(fp, " \"color\": \"%s\",\n", color); fprintf(fp, " \"latex\": \"y_{%d}\"\n", subscript); fprintf(fp, " }\n"); fprintf(fp, " ]\n"); fprintf(fp, "},\n"); } static double uniform_scale_table_func(int i, const void* ctx) { double scale = *((const double*)ctx); return i * scale; } static double curve_table_func(int i, const void* ctx) { const skcms_Curve* curve = (const skcms_Curve*)ctx; return curve->table_8 ? curve->table_8[i] / 255.0 : read_big_u16(curve->table_16 + 2*i) / 65535.0; } static void desmos_curve(FILE* fp, const skcms_Curve* curve, const char* color) { if (!curve->table_entries) { desmos_transfer_function(fp, &curve->parametric, color); return; } char label[64]; (void)snprintf(label, sizeof(label), "%s Table", color); double xScale = 1.0 / (curve->table_entries - 1.0); desmos_table(fp, (int)curve->table_entries, label, color, uniform_scale_table_func, &xScale, curve_table_func, curve); char approx_color[64]; (void)snprintf(approx_color, sizeof(approx_color), "Dark%s", color); skcms_TransferFunction approx_tf; float max_error; if (skcms_ApproximateCurve(curve, &approx_tf, &max_error)) { desmos_transfer_function(fp, &approx_tf, approx_color); } } static void desmos_curves(FILE* fp, uint32_t num_curves, const skcms_Curve* curves, const char** colors) { for (uint32_t c = 0; c < num_curves; ++c) { desmos_curve(fp, curves + c, colors[c]); } } static void desmos_inv_curve(FILE* fp, const skcms_Curve* curve, const char* color) { if (!curve->table_entries) { skcms_TransferFunction inv; if (skcms_TransferFunction_invert(&curve->parametric, &inv)) { desmos_transfer_function(fp, &inv, color); } return; } char label[64]; (void)snprintf(label, sizeof(label), "%s Inverse Table", color); double xScale = 1.0 / (curve->table_entries - 1.0); desmos_table(fp, (int)curve->table_entries, label, color, curve_table_func, curve, uniform_scale_table_func, &xScale); char approx_color[64]; (void)snprintf(approx_color, sizeof(approx_color), "Dark%s", color); skcms_TransferFunction approx_tf; float max_error; if (skcms_ApproximateCurve(curve, &approx_tf, &max_error)) { skcms_TransferFunction inv; if (skcms_TransferFunction_invert(&approx_tf, &inv)) { desmos_transfer_function(fp, &inv, approx_color); } } } static void desmos_inv_curves(FILE* fp, uint32_t num_curves, const skcms_Curve* curves, const char** colors) { for (uint32_t c = 0; c < num_curves; ++c) { desmos_inv_curve(fp, curves + c, colors[c]); } } static const double kSVGMarginLeft = 100.0; static const double kSVGMarginRight = 10.0; static const double kSVGMarginTop = 10.0; static const double kSVGMarginBottom = 50.0; static const double kSVGScaleX = 800.0; static const double kSVGScaleY = 800.0; static const char* kSVG_RGB_Colors[3] = { "Red", "Green", "Blue" }; static const char* kSVG_CMYK_Colors[4] = { "cyan", "magenta", "yellow", "black" }; static FILE* svg_open(const char* filename) { FILE* fp = fopen(filename, "wb"); if (!fp) { fatal("Unable to open output file"); } fprintf(fp, "\n", kSVGMarginLeft + kSVGScaleX + kSVGMarginRight, kSVGMarginTop + kSVGScaleY + kSVGMarginBottom); return fp; } static void svg_close(FILE* fp) { fprintf(fp, "\n"); fclose(fp); } #define svg_push_group(fp, fmt, ...) fprintf(fp, "\n", __VA_ARGS__) static void svg_pop_group(FILE* fp) { fprintf(fp, "\n"); } static void svg_axes(FILE* fp) { fprintf(fp, "\n"); } static void svg_transfer_function(FILE* fp, const skcms_TransferFunction* tf, const char* color) { fprintf(fp, "\n"); } static void svg_curve(FILE* fp, const skcms_Curve* curve, const char* color) { if (!curve->table_entries) { svg_transfer_function(fp, &curve->parametric, color); return; } double xScale = 1.0 / (curve->table_entries - 1.0); double yScale = curve->table_8 ? (1.0 / 255) : (1.0 / 65535); fprintf(fp, "table_entries; ++i) { if (curve->table_8) { fprintf(fp, "%3u, %3u\n", i, curve->table_8[i]); } else { fprintf(fp, "%4u, %5u\n", i, read_big_u16(curve->table_16 + 2 * i)); } } fprintf(fp, "\"/>\n"); skcms_TransferFunction approx_tf; float max_error; if (skcms_ApproximateCurve(curve, &approx_tf, &max_error)) { svg_transfer_function(fp, &approx_tf, "magenta"); } } static void svg_curves(FILE* fp, uint32_t num_curves, const skcms_Curve* curves, const char** colors) { for (uint32_t c = 0; c < num_curves; ++c) { svg_curve(fp, curves + c, colors[c]); } } static void dump_curves_svg(const char* filename, uint32_t num_curves, const skcms_Curve* curves) { FILE* fp = svg_open(filename); svg_push_group(fp, "transform=\"translate(%g %g) scale(%g %g)\"", kSVGMarginLeft, kSVGMarginTop + kSVGScaleY, kSVGScaleX, -kSVGScaleY); svg_axes(fp); svg_curves(fp, num_curves, curves, (num_curves == 3) ? kSVG_RGB_Colors : kSVG_CMYK_Colors); svg_pop_group(fp); svg_close(fp); } static const uint8_t png_signature[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a }; #if defined(_MSC_VER) static bool parse_png_profile(const uint8_t* buf, size_t len, skcms_ICCProfile* profile) { (void)buf; (void)len; (void)profile; (void)read_big_u32; return false; } #else static bool parse_png_profile(const uint8_t* buf, size_t len, skcms_ICCProfile* profile) { void* zlib = NULL; if (!zlib) { zlib = dlopen("libz.so", RTLD_LAZY); } if (!zlib) { zlib = dlopen("libz.dylib", RTLD_LAZY); } if (!zlib) { return false; } typedef int(*UncompressFn)(uint8_t*, unsigned long*, const uint8_t*, unsigned long); UncompressFn uncompress = (UncompressFn)dlsym(zlib, "uncompress"); if (!uncompress) { return false; } const uint8_t* end = buf+len; // skip over signature buf += sizeof(png_signature); const uint32_t IEND = 0x49454e44, iCCP = 0x69434350; uint32_t size, tag = 0; while (buf < end && tag != IEND) { size = read_big_u32(buf+0); tag = read_big_u32(buf+4); buf += 8; if (tag == iCCP) { const char* name = (const char*)buf; printf("Profile name from .png: '%s'\n", name); size_t header = strlen(name) + 1/*NUL*/ + 1/*PNG compression method, always 0 == zlib*/; unsigned long inf_size, guess = len; void* inflated = NULL; int err; do { inf_size = guess; inflated = realloc(inflated, inf_size); err = uncompress(inflated, &inf_size, (const uint8_t*)name+header, size-header); guess *= 2; } while (err == -5/*Z_BUF_ERROR*/); bool ok = err == 0/*Z_OK*/ && skcms_Parse(inflated, inf_size, profile); free(inflated); return ok; } buf += size; buf += 4/*skip the PNG CRC*/; } return false; } #endif int main(int argc, char** argv) { const char* filename = NULL; bool svg = false; bool desmos = false; for (int i = 1; i < argc; ++i) { if (0 == strcmp(argv[i], "-s")) { svg = true; } else if (0 == strcmp(argv[i], "-d")) { desmos = true; } else { filename = argv[i]; } } if (!filename) { printf("usage: %s [-s] \n", argv[0]); return 1; } void* buf = NULL; size_t len = 0; if (!load_file(filename, &buf, &len)) { fatal("Unable to load input file"); } skcms_ICCProfile profile; if (len >= sizeof(png_signature) && 0 == memcmp(buf, png_signature, sizeof(png_signature))) { if (!parse_png_profile(buf, len, &profile)) { fatal("Could not find an ICC profile in this .png"); } } else if (!skcms_Parse(buf, len, &profile)) { fatal("Unable to parse ICC profile"); } dump_profile(&profile, stdout); if (desmos) { if (profile.has_trc) { FILE* fp = desmos_open("TRC_curves.html"); desmos_curves(fp, 3, profile.trc, kSVG_RGB_Colors); desmos_inv_curves(fp, 3, profile.trc, kSVG_RGB_Colors); desmos_close(fp); } } if (svg) { if (profile.has_toXYZD50) { FILE* fp = svg_open("gamut.svg"); svg_push_group(fp, "transform=\"translate(%g %g) scale(%g %g)\"", kSVGMarginLeft, kSVGMarginTop + kSVGScaleY, kSVGScaleX, -kSVGScaleY); svg_axes(fp); fprintf(fp, "\n"); skcms_Matrix3x3 m = profile.toXYZD50; skcms_Matrix3x3 chad; if (skcms_GetCHAD(&profile, &chad) && skcms_Matrix3x3_invert(&chad, &chad)) { m = skcms_Matrix3x3_concat(&chad, &m); } float rSum = m.vals[0][0] + m.vals[1][0] + m.vals[2][0]; float gSum = m.vals[0][1] + m.vals[1][1] + m.vals[2][1]; float bSum = m.vals[0][2] + m.vals[1][2] + m.vals[2][2]; fprintf(fp, "\n", (m.vals[0][0] / rSum), (m.vals[1][0] / rSum), (m.vals[0][1] / gSum), (m.vals[1][1] / gSum), (m.vals[0][2] / bSum), (m.vals[1][2] / bSum)); svg_pop_group(fp); svg_close(fp); } if (profile.has_trc) { FILE* fp = svg_open("TRC_curves.svg"); svg_push_group(fp, "transform=\"translate(%g %g) scale(%g %g)\"", kSVGMarginLeft, kSVGMarginTop + kSVGScaleY, kSVGScaleX, -kSVGScaleY); svg_axes(fp); svg_curves(fp, 3, profile.trc, kSVG_RGB_Colors); svg_pop_group(fp); svg_close(fp); } if (profile.has_A2B) { const skcms_A2B* a2b = &profile.A2B; if (a2b->input_channels) { dump_curves_svg("A_curves.svg", a2b->input_channels, a2b->input_curves); } if (a2b->matrix_channels) { dump_curves_svg("M_curves.svg", a2b->matrix_channels, a2b->matrix_curves); } dump_curves_svg("B_curves.svg", a2b->output_channels, a2b->output_curves); } } return 0; }