Refactored some test code and tweaked SVG output
- Moves more file IO code into shared helpers
- Fixes a scale bug with plots of parametric curves
- Split up SVG code into several functions, uses
groups to isolate transforms
- Adds approximate TF plot over the TRC table plots
- Adds gamut diagram output
Change-Id: Ifad7049ae97e39514bd41a79f996463fcddf32b1
Reviewed-on: https://skia-review.googlesource.com/111482
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
diff --git a/iccdump.c b/iccdump.c
index f07885e..57bb0b8 100644
--- a/iccdump.c
+++ b/iccdump.c
@@ -27,6 +27,81 @@
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));
@@ -45,61 +120,86 @@
static const double kSVGScaleX = 800.0;
static const double kSVGScaleY = 800.0;
-static double svg_map_x(double x) {
- return x * kSVGScaleX + kSVGMarginLeft;
-}
+static const char* kSVG_RGB_Colors[3] = { "red", "green", "blue" };
+static const char* kSVG_CMYK_Colors[4] = { "cyan", "magenta", "yellow", "black" };
-static double svg_map_y(double y) {
- return (1.0 - y) * kSVGScaleY + kSVGMarginTop;
-}
-
-static void dump_curves_svg(const char* name, uint32_t num_curves, const skcms_Curve* curves) {
- char filename[256];
- if (snprintf(filename, sizeof(filename), "%s.svg", name) < 0) {
- return;
- }
+static FILE* svg_open(const char* filename) {
FILE* fp = fopen(filename, "wb");
if (!fp) {
+ fatal("Unable to open output file");
+ }
+
+ fprintf(fp, "<svg width=\"%g\" height=\"%g\" xmlns=\"http://www.w3.org/2000/svg\">\n",
+ kSVGMarginLeft + kSVGScaleX + kSVGMarginRight,
+ kSVGMarginTop + kSVGScaleY + kSVGMarginBottom);
+ return fp;
+}
+
+static void svg_close(FILE* fp) {
+ fprintf(fp, "</svg>\n");
+ fclose(fp);
+}
+
+#define svg_push_group(fp, fmt, ...) fprintf(fp, "<g " fmt ">\n", __VA_ARGS__)
+
+static void svg_pop_group(FILE* fp) {
+ fprintf(fp, "</g>\n");
+}
+
+static void svg_axes(FILE* fp) {
+ fprintf(fp, "<polyline fill=\"none\" stroke=\"black\" vector-effect=\"non-scaling-stroke\" "
+ "points=\"0,1 0,0 1,0\"/>\n");
+}
+
+static void svg_transfer_function(FILE* fp, const skcms_TransferFunction* tf, const char* color) {
+ fprintf(fp, "<polyline fill=\"none\" stroke=\"%s\" vector-effect=\"non-scaling-stroke\" "
+ "points=\"\n", color);
+
+ for (int i = 0; i < 256; ++i) {
+ float x = i / 255.0f;
+ float t = skcms_TransferFunction_eval(tf, x);
+ fprintf(fp, "%g, %g\n", (double)x, (double)t);
+ }
+ 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;
}
- 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));
+ double xScale = 1.0 / (curve->table_entries - 1.0);
+ double yScale = curve->table_8 ? (1.0 / 255) : (1.0 / 65535);
+ fprintf(fp, "<polyline fill=\"none\" stroke=\"%s\" vector-effect=\"non-scaling-stroke\" "
+ "transform=\"scale(%g %g)\" points=\"\n",
+ color, xScale, yScale);
- // Curves
- const char* rgb_colors[3] = { "red", "green", "blue" };
- const char* cmyk_colors[4] = { "cyan", "magenta", "yellow", "black" };
- const char** colors = (num_curves == 3) ? rgb_colors : cmyk_colors;
- for (uint32_t c = 0; c < num_curves; ++c) {
- uint32_t num_entries = curves[c].table_entries ? curves[c].table_entries : 256;
- double yScale = curves[c].table_8 ? (1.0 / 255) : curves[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 (curves[c].table_8) {
- fprintf(fp, "%3u, %3u\n", i, curves[c].table_8[i]);
- } else if (curves[c].table_16) {
- fprintf(fp, "%4u, %5u\n", i, read_big_u16(curves[c].table_16 + 2 * i));
- } else {
- double x = i / (num_entries - 1.0);
- double t = (double)skcms_TransferFunction_eval(&curves[c].parametric, (float)x);
- fprintf(fp, "%f, %f\n", x, t);
- }
+ for (uint32_t i = 0; i < curve->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");
}
+ fprintf(fp, "\"/>\n");
+}
- fprintf(fp, "</svg>\n");
- fclose(fp);
+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);
}
int main(int argc, char** argv) {
@@ -119,27 +219,10 @@
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);
- if (!buf) {
- fatal("malloc failed");
- }
- size_t bytesRead = fread(buf, 1, len, fp);
- fclose(fp);
- if (bytesRead != len) {
- fatal("Unable to read file");
+ void* buf = NULL;
+ size_t len = 0;
+ if (!load_file(filename, &buf, &len)) {
+ fatal("Unable to load input file");
}
skcms_ICCProfile profile;
@@ -150,21 +233,59 @@
dump_profile(&profile, stdout, false);
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, "<polygon fill=\"none\" stroke=\"black\" "
+ "vector-effect=\"non-scaling-stroke\" points=\"\n");
+ for (int i = 0; i < ARRAY_COUNT(kSpectralHull); i += 2) {
+ fprintf(fp, "%g, %g\n", kSpectralHull[i], kSpectralHull[i + 1]);
+ }
+ fprintf(fp, "\"/>\n");
+
+ const skcms_Matrix3x3* m = &profile.toXYZD50;
+ 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, "<polygon fill=\"none\" stroke=\"black\" "
+ "vector-effect=\"non-scaling-stroke\" points=\"%g,%g %g,%g %g,%g\"/>\n",
+ (double)(m->vals[0][0] / rSum), (double)(m->vals[1][0] / rSum),
+ (double)(m->vals[0][1] / gSum), (double)(m->vals[1][1] / gSum),
+ (double)(m->vals[0][2] / bSum), (double)(m->vals[1][2] / bSum));
+
+ svg_pop_group(fp);
+ svg_close(fp);
+ }
+
if (profile.has_trc) {
- dump_curves_svg("TRC_curves", 3, profile.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);
+ skcms_TransferFunction approx;
+ float max_error;
+ if (skcms_ApproximateTransferFunction(&profile, &approx, &max_error)) {
+ svg_transfer_function(fp, &approx, "magenta");
+ }
+ 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", a2b->input_channels, a2b->input_curves);
+ dump_curves_svg("A_curves.svg", a2b->input_channels, a2b->input_curves);
}
if (a2b->matrix_channels) {
- dump_curves_svg("M_curves", a2b->matrix_channels, a2b->matrix_curves);
+ dump_curves_svg("M_curves.svg", a2b->matrix_channels, a2b->matrix_curves);
}
- dump_curves_svg("B_curves", a2b->output_channels, a2b->output_curves);
+ dump_curves_svg("B_curves.svg", a2b->output_channels, a2b->output_curves);
}
}
diff --git a/test_only.c b/test_only.c
index 39ad27e..96c4d50 100644
--- a/test_only.c
+++ b/test_only.c
@@ -12,6 +12,7 @@
#include "skcms.h"
#include "test_only.h"
#include "src/TransferFunction.h"
+#include <stdlib.h>
static void signature_to_string(uint32_t sig, char* str) {
str[0] = (char)((sig >> 24) & 0xFF);
@@ -172,3 +173,46 @@
}
}
}
+
+bool load_file_fp(FILE* fp, void** buf, size_t* len) {
+ if (fseek(fp, 0L, SEEK_END) != 0) {
+ return false;
+ }
+ long size = ftell(fp);
+ if (size <= 0) {
+ return false;
+ }
+ *len = (size_t)size;
+ rewind(fp);
+
+ *buf = malloc(*len);
+ if (!*buf) {
+ return false;
+ }
+
+ if (fread(*buf, 1, *len, fp) != *len) {
+ free(*buf);
+ return false;
+ }
+ return true;
+}
+
+bool load_file(const char* filename, void** buf, size_t* len) {
+ FILE* fp = fopen(filename, "rb");
+ if (!fp) {
+ return false;
+ }
+ bool result = load_file_fp(fp, buf, len);
+ fclose(fp);
+ return result;
+}
+
+bool write_file(const char* filename, void* buf, size_t len) {
+ FILE* fp = fopen(filename, "wb");
+ if (!fp) {
+ return false;
+ }
+ bool result = (fwrite(buf, 1, len, fp) == len);
+ fclose(fp);
+ return result;
+}
diff --git a/test_only.h b/test_only.h
index 6bee2d9..a30d874 100644
--- a/test_only.h
+++ b/test_only.h
@@ -11,3 +11,8 @@
#include <stdio.h>
void dump_profile(const skcms_ICCProfile* profile, FILE* fp, bool for_unit_test);
+
+bool load_file_fp(FILE* fp, void** buf, size_t* len);
+bool load_file(const char* filename, void** buf, size_t* len);
+
+bool write_file(const char* filename, void* buf, size_t len);
diff --git a/tests.c b/tests.c
index abac43b..0034238 100644
--- a/tests.c
+++ b/tests.c
@@ -526,33 +526,6 @@
{ "profiles/fuzz/a2b_too_many_input_channels.icc", NULL }, // oss-fuzz:6521
};
-static void load_file_fp(FILE* fp, void** buf, size_t* len) {
- expect(fseek(fp, 0L, SEEK_END) == 0);
- long size = ftell(fp);
- expect(size > 0);
- *len = (size_t)size;
- rewind(fp);
-
- *buf = malloc(*len);
- expect(*buf);
-
- expect(fread(*buf, 1, *len, fp) == *len);
-}
-
-static void load_file(const char* filename, void** buf, size_t* len) {
- FILE* fp = fopen(filename, "rb");
- expect(fp);
- load_file_fp(fp, buf, len);
- fclose(fp);
-}
-
-static void write_file(const char* filename, void* buf, size_t len) {
- FILE* fp = fopen(filename, "wb");
- expect(fp);
- expect(fwrite(buf, 1, len, fp) == len);
- fclose(fp);
-}
-
static void check_roundtrip_transfer_functions(const skcms_TransferFunction* fwd,
const skcms_TransferFunction* rev,
float tol) {
@@ -570,7 +543,7 @@
void* buf = NULL;
size_t len = 0;
- load_file(test->filename, &buf, &len);
+ expect(load_file(test->filename, &buf, &len));
skcms_ICCProfile profile;
bool parsed = skcms_Parse(buf, len, &profile);
@@ -585,7 +558,7 @@
void* dump_buf = NULL;
size_t dump_len = 0;
- load_file_fp(dump, &dump_buf, &dump_len);
+ expect(load_file_fp(dump, &dump_buf, &dump_len));
fclose(dump);
char ref_filename[256];
@@ -595,12 +568,12 @@
if (regen) {
// Just write out new test data if in regen mode
- write_file(ref_filename, dump_buf, dump_len);
+ expect(write_file(ref_filename, dump_buf, dump_len));
} else {
// Read in existing test data
void* ref_buf = NULL;
size_t ref_len = 0;
- load_file(ref_filename, &ref_buf, &ref_len);
+ expect(load_file(ref_filename, &ref_buf, &ref_len));
if (dump_len != ref_len || memcmp(dump_buf, ref_buf, dump_len) != 0) {
// Write out the new data on a mismatch
@@ -830,7 +803,7 @@
// We'll test that parametric sRGB roundtrips with itself, bytes -> bytes.
void* srgb_ptr;
size_t srgb_len;
- load_file("profiles/mobile/sRGB_parametric.icc", &srgb_ptr, &srgb_len);
+ expect(load_file("profiles/mobile/sRGB_parametric.icc", &srgb_ptr, &srgb_len));
skcms_ICCProfile srgbA, srgbB;
expect(skcms_Parse(srgb_ptr, srgb_len, &srgbA));
@@ -879,20 +852,20 @@
static void test_FloatRoundTrips() {
void* srgb_ptr;
size_t srgb_len;
- load_file("profiles/mobile/sRGB_parametric.icc", &srgb_ptr, &srgb_len);
+ expect(load_file("profiles/mobile/sRGB_parametric.icc", &srgb_ptr, &srgb_len));
void* dp3_ptr;
size_t dp3_len;
- load_file("profiles/mobile/Display_P3_parametric.icc", &dp3_ptr, &dp3_len);
+ expect(load_file("profiles/mobile/Display_P3_parametric.icc", &dp3_ptr, &dp3_len));
void* ll_ptr;
size_t ll_len;
- load_file("profiles/color.org/Lower_Left.icc", &ll_ptr, &ll_len);
+ expect(load_file("profiles/color.org/Lower_Left.icc", &ll_ptr, &ll_len));
void* lr_ptr;
size_t lr_len;
- load_file("profiles/color.org/Lower_Right.icc", &lr_ptr, &lr_len);
+ expect(load_file("profiles/color.org/Lower_Right.icc", &lr_ptr, &lr_len));
skcms_ICCProfile srgb, dp3, ll, lr;
expect(skcms_Parse(srgb_ptr, srgb_len, &srgb));
@@ -918,16 +891,16 @@
size_t len;
skcms_ICCProfile p;
- load_file("profiles/mobile/sRGB_parametric.icc", &ptr, &len);
+ expect( load_file("profiles/mobile/sRGB_parametric.icc", &ptr, &len) );
expect( skcms_Parse(ptr, len, &p) && p.has_tf && skcms_IsSRGB(&p.tf) );
free(ptr);
- load_file("profiles/mobile/Display_P3_parametric.icc", &ptr, &len);
+ expect( load_file("profiles/mobile/Display_P3_parametric.icc", &ptr, &len) );
expect( skcms_Parse(ptr, len, &p) && p.has_tf && skcms_IsSRGB(&p.tf) );
free(ptr);
// TODO: relax skcms_IsSRGB() so that this one is seen as sRGB too? It's not far.
- load_file("profiles/mobile/iPhone7p.icc", &ptr, &len);
+ expect( load_file("profiles/mobile/iPhone7p.icc", &ptr, &len) );
expect( skcms_Parse(ptr, len, &p) && p.has_tf && !skcms_IsSRGB(&p.tf) );
free(ptr);
}
@@ -938,7 +911,7 @@
void* ptr;
size_t len;
skcms_ICCProfile sRGB;
- load_file("profiles/mobile/sRGB_parametric.icc", &ptr, &len);
+ expect( load_file("profiles/mobile/sRGB_parametric.icc", &ptr, &len) );
expect( skcms_Parse(ptr, len, &sRGB) );
skcms_ICCProfile linear_sRGB = sRGB;