blob: 344fecb64c24dd02f7e86648d3616ffa859a3fe1 [file] [log] [blame]
/*
* 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 <stdnoreturn.h>
#define SKCMS_NORETURN noreturn
#endif
#include "skcms.h"
#include "src/TransferFunction.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
SKCMS_NORETURN
static void fatal(const char* msg) {
fprintf(stderr, "ERROR: %s\n", msg);
exit(1);
}
static void signature_to_string(uint32_t sig, char* str) {
str[0] = (char)(sig >> 24);
str[1] = (char)(sig >> 16);
str[2] = (char)(sig >> 8);
str[3] = (char)(sig >> 0);
str[4] = 0;
}
static void dump_sig_field(const char* name, uint32_t val) {
char valStr[5];
signature_to_string(val, valStr);
printf("%20s : 0x%08X : '%s'\n", name, val, valStr);
}
static void dump_transfer_function(const char* name, const skcms_TransferFunction* tf) {
printf("%4s : %f, %f, %f, %f, %f, %f, %f", name,
(double)tf->g, (double)tf->a, (double)tf->b, (double)tf->c,
(double)tf->d, (double)tf->e, (double)tf->f);
if (skcms_IsSRGB(tf)) {
printf(" (sRGB)");
}
printf("\n");
}
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 void dump_curve(const char* name, const skcms_Curve* curve, bool verbose) {
if (curve->table_entries) {
printf("%4s : %d-bit table with %u entries", name,
curve->table_8 ? 8 : 16, curve->table_entries);
if (verbose) {
char filename[32];
snprintf(filename, sizeof(filename), "%s.csv", name);
FILE* fp = fopen(filename, "wb");
if (fp) {
for (uint32_t i = 0; i < curve->table_entries; ++i) {
double x = i / (curve->table_entries - 1.0);
double t = curve->table_8
? curve->table_8[i] * (1.0 / 255)
: read_big_u16(curve->table_16 + 2 * i) * (1.0 / 65535);
fprintf(fp, "%f,%f\n", x, t);
}
fclose(fp);
printf(" (wrote to %s)", filename);
}
}
printf("\n");
} else {
dump_transfer_function(name, &curve->parametric);
}
}
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 double svg_map_x(double x) {
return x * kSVGScaleX + kSVGMarginLeft;
}
static double svg_map_y(double y) {
return (1.0 - y) * kSVGScaleY + kSVGMarginTop;
}
static void dump_curves_svg(const char* name, const skcms_Curve* curve) {
char filename[256];
snprintf(filename, sizeof(filename), "%s.svg", name);
FILE* fp = fopen(filename, "wb");
if (!fp) {
return;
}
fprintf(fp, "<svg width=\"%f\" height=\"%f\" xmlns=\"http://www.w3.org/2000/svg\">\n",
kSVGMarginLeft + kSVGScaleX + kSVGMarginRight,
kSVGMarginTop + kSVGScaleY + kSVGMarginBottom);
// Axes
fprintf(fp, "<polyline fill=\"none\" stroke=\"black\" points=\"%f,%f %f,%f %f,%f\"/>\n",
svg_map_x(0), svg_map_y(1), svg_map_x(0), svg_map_y(0), svg_map_x(1), svg_map_y(0));
// Curves
static const char* colors[3] = { "red", "green", "blue" };
for (int c = 0; c < 3; ++c) {
uint32_t num_entries = curve[c].table_entries ? curve[c].table_entries : 256;
double yScale = curve[c].table_8 ? (1.0 / 255) : curve[c].table_16 ? (1.0 / 65535) : 1.0;
fprintf(fp, "<polyline fill=\"none\" stroke=\"%s\" vector-effect=\"non-scaling-stroke\" "
"transform=\"matrix(%f 0 0 %f %f %f)\" points=\"\n",
colors[c],
kSVGScaleX / (num_entries - 1.0), -kSVGScaleY * yScale,
kSVGMarginLeft, kSVGScaleY + kSVGMarginTop);
for (uint32_t i = 0; i < num_entries; ++i) {
if (curve[c].table_8) {
fprintf(fp, "%3u, %3u\n", i, curve[c].table_8[i]);
} else if (curve[c].table_16) {
fprintf(fp, "%4u, %5u\n", i, read_big_u16(curve[c].table_16 + 2 * i));
} else {
double x = i / (num_entries - 1.0);
double t = (double)skcms_TransferFunction_eval(&curve[c].parametric, (float)x);
fprintf(fp, "%f, %f\n", x, t);
}
}
fprintf(fp, "\"/>\n");
}
fprintf(fp, "</svg>\n");
fclose(fp);
}
int main(int argc, char** argv) {
const char* filename = NULL;
bool verbose = false;
bool svg = false;
for (int i = 1; i < argc; ++i) {
if (0 == strcmp(argv[i], "-v")) {
verbose = true;
} else if (0 == strcmp(argv[i], "-s")) {
svg = true;
} else {
filename = argv[i];
}
}
if (!filename) {
printf("usage: %s [-v] [-s] <ICC filename>\n", argv[0]);
return 1;
}
FILE* fp = fopen(filename, "rb");
if (!fp) {
fatal("Unable to open input file");
}
fseek(fp, 0L, SEEK_END);
long slen = ftell(fp);
if (slen <= 0) {
fatal("ftell failed");
}
size_t len = (size_t)slen;
rewind(fp);
void* buf = malloc(len);
size_t bytesRead = fread(buf, 1, len, fp);
fclose(fp);
if (bytesRead != len) {
fatal("Unable to read file");
}
skcms_ICCProfile profile;
if (!skcms_Parse(buf, bytesRead, &profile)) {
fatal("Unable to parse ICC profile");
}
printf("%20s : 0x%08X : %u\n", "Size", profile.size, profile.size);
dump_sig_field("CMM type", profile.cmm_type);
printf("%20s : 0x%08X : %u.%u.%u\n", "Version", profile.version,
profile.version >> 24, (profile.version >> 20) & 0xF, (profile.version >> 16) & 0xF);
dump_sig_field("Profile class", profile.profile_class);
dump_sig_field("Data color space", profile.data_color_space);
dump_sig_field("PCS", profile.pcs);
printf("%20s : : %u-%02u-%02u %02u:%02u:%02u\n", "Creation date/time",
profile.creation_date_time.year, profile.creation_date_time.month,
profile.creation_date_time.day, profile.creation_date_time.hour,
profile.creation_date_time.minute, profile.creation_date_time.second);
dump_sig_field("Signature", profile.signature);
dump_sig_field("Platform", profile.platform);
printf("%20s : 0x%08X\n", "Flags", profile.flags);
dump_sig_field("Device manufacturer", profile.device_manufacturer);
dump_sig_field("Device model", profile.device_model);
printf("%20s : 0x%08X\n", "Device attributes", (uint32_t)profile.device_attributes);
printf("%20s : 0x%08X\n", "", (uint32_t)(profile.device_attributes >> 32));
printf("%20s : 0x%08X : %u\n", "Rendering intent", profile.rendering_intent,
profile.rendering_intent);
printf("%20s : : %f\n", "Illuminant X", (double)profile.illuminant_X);
printf("%20s : : %f\n", "Illuminant Y", (double)profile.illuminant_Y);
printf("%20s : : %f\n", "Illuminant Z", (double)profile.illuminant_Z);
dump_sig_field("Creator", profile.creator);
printf("%20s : 0x%08X : %u\n", "Tag count", profile.tag_count, profile.tag_count);
printf("\n");
printf(" Tag : Type : Size : Data\n");
printf(" ------ : ------ : ------ : --------\n");
for (uint32_t i = 0; i < profile.tag_count; ++i) {
skcms_ICCTag tag;
skcms_GetTagByIndex(&profile, i, &tag);
char tagSig[5];
char typeSig[5];
signature_to_string(tag.signature, tagSig);
signature_to_string(tag.type, typeSig);
printf(" '%s' : '%s' : %6u : %p\n", tagSig, typeSig, tag.size, (const void*)tag.buf);
}
printf("\n");
skcms_TransferFunction tf;
float max_error;
if (profile.has_tf) {
dump_transfer_function("TRC", &profile.tf);
} else if (skcms_ApproximateTransferFunction(&profile, &tf, &max_error)) {
printf("%4s : %f, %f, %f, %f, %f, %f, %f (Max error: %f)\n", "~TRC",
(double)tf.g, (double)tf.a, (double)tf.b, (double)tf.c,
(double)tf.d, (double)tf.e, (double)tf.f, (double)max_error);
}
if (!profile.has_tf && profile.has_trc) {
const char* trcNames[3] = { "rTRC", "gTRC", "bTRC" };
for (int i = 0; i < 3; ++i) {
dump_curve(trcNames[i], &profile.trc[i], verbose);
}
}
if (svg && profile.has_trc) {
dump_curves_svg(filename, profile.trc);
}
if (profile.has_toXYZD50) {
skcms_Matrix3x3 toXYZ = profile.toXYZD50;
printf(" XYZ : | %.7f %.7f %.7f |\n"
" | %.7f %.7f %.7f |\n"
" | %.7f %.7f %.7f |\n",
(double)toXYZ.vals[0][0], (double)toXYZ.vals[0][1], (double)toXYZ.vals[0][2],
(double)toXYZ.vals[1][0], (double)toXYZ.vals[1][1], (double)toXYZ.vals[1][2],
(double)toXYZ.vals[2][0], (double)toXYZ.vals[2][1], (double)toXYZ.vals[2][2]);
}
return 0;
}