From cf044899937cb1b7d10d1bbdbcd684d8637fe2ef Mon Sep 17 00:00:00 2001 From: Gandalf Date: Fri, 21 Aug 2026 21:44:32 +0100 Subject: [PATCH] feat(profile): add -u flag to colprof for structured JSON progress reporting Adds a -u command-line flag to colprof to emit newline-delimited JSON progress events (stage and percent) during ICC profile calculation. Fixes #4 --- doc/colprof.html | 4 +++ profile/colprof.c | 18 ++++++++++--- profile/prof.h | 3 +++ profile/profin.c | 20 +++++++++++---- profile/profout.c | 64 ++++++++++++++++++++++++++++++++++++----------- rspl/rev.c | 2 +- 6 files changed, 86 insertions(+), 25 deletions(-) diff --git a/doc/colprof.html b/doc/colprof.html index b99f7c6..2d46b5d 100755 --- a/doc/colprof.html +++ b/doc/colprof.html @@ -2700,6 +2700,10 @@ White            &nb useful in creating a profile for a device that is known to have a perfectly linear response, such as a camera in RAW mode.

+ -u: + Emit structured JSON progress events to stdout during profile + calculation (e.g. {"event": "progress", "stage": "gamut_mapping", "percent": 75}).
+
-u: Input profiles will normally be created such that the white patch of the test chart will be mapped to perfect white when used with any of the diff --git a/profile/colprof.c b/profile/colprof.c index 27c154c..14a3307 100644 --- a/profile/colprof.c +++ b/profile/colprof.c @@ -75,6 +75,14 @@ #define DEMPH_DEFAULT 1.0 /* Default dark region emphasis == none */ +int json_progress = 0; + +void emit_json_progress(const char *stage, int percent) { + if (!json_progress) return; + printf("{\"event\": \"progress\", \"stage\": \"%s\", \"percent\": %d}\n", stage, percent); + fflush(stdout); +} + /* Flags used: @@ -129,6 +137,7 @@ void usage(char *diag, ...) { fprintf(stderr," X = display XYZ cLUT + matrix, Y = display XYZ cLUT + debug matrix\n"); fprintf(stderr," g = gamma+matrix, s = shaper+matrix, m = matrix only,\n"); fprintf(stderr," G = single gamma+matrix, S = single shaper+matrix\n"); + fprintf(stderr," -u Emit structured JSON calculation progress to stdout\n"); fprintf(stderr," -u If input profile, auto scale WP to allow extrapolation\n"); fprintf(stderr," -ua If input profile, force absolute intent\n"); fprintf(stderr," -uc If input profile, clip cLUT values above WP\n"); @@ -492,8 +501,6 @@ int main(int argc, char *argv[]) { } else if (argv[fa][1] == 'u') { - autowpsc = 1; - clipovwp = 0; if (argv[fa][2] == 'a') { autowpsc = 2; clipovwp = 0; @@ -502,12 +509,15 @@ int main(int argc, char *argv[]) { clipovwp = 1; } else if (argv[fa][2] != '\000') { usage("Unknown flag '%c' after -u",argv[fa][2]); - - } else if (na != NULL) { + } else if (na != NULL && (isdigit(na[0]) || (na[0] == '.' && isdigit(na[1])))) { fa = nfa; iwpscale = atof(na); if (iwpscale < 0.0 || iwpscale > 200.0) usage("Argument '%s' to flag -u out of range",na); + autowpsc = 1; + clipovwp = 0; + } else { + json_progress = 1; } } /* Clip primaries */ diff --git a/profile/prof.h b/profile/prof.h index 6de0638..cebec55 100644 --- a/profile/prof.h +++ b/profile/prof.h @@ -32,6 +32,9 @@ typedef enum { prof_matonly = 7 /* XYZ matrix, linear */ } prof_atype; +extern int json_progress; +void emit_json_progress(const char *stage, int percent); + /* Output or Display device */ void make_output_icc( prof_atype ptype, /* Profile output type */ diff --git a/profile/profin.c b/profile/profin.c index 2fd9a70..16f8131 100644 --- a/profile/profin.c +++ b/profile/profin.c @@ -229,14 +229,19 @@ void in_b2a_clut(void *cntx, double *out, double in[3] DBG(("convert PCS' to DEV' got %f %f %f %f\n",out[0],out[1],out[2],out[3])) DBG(("in_b2a_clut returning DEV' %f %f %f\n",out[0],out[1],out[2])) - if (p->verb + if ((p->verb || json_progress) && tn == 0 ) { /* Output percent intervals */ int pc; p->count++; pc = (int)(p->count * 100.0/p->total + 0.5); if (pc != p->last) { - printf("%c%2d%%",cr_char,pc); fflush(stdout); + if (p->verb) { + printf("%c%2d%%",cr_char,pc); fflush(stdout); + } + if (json_progress) { + emit_json_progress("b2a_table", pc); + } p->last = pc; } } @@ -1171,7 +1176,7 @@ make_input_icc( /* We now setup an exact inverse, colorimetric style */ /* Use helper function to do the hard work. */ - if (cx.verb) { + if (cx.verb || json_progress) { unsigned int ui; int extra; cx.count = 0; @@ -1182,8 +1187,13 @@ make_input_icc( for (extra = 1, ui = 0; ui < 3; extra *= (blut_clutPoints[ui++]-1)) ; cx.total += extra; - printf("Creating B to A tables\n"); - printf(" 0%%"); fflush(stdout); + if (cx.verb) { + printf("Creating B to A tables\n"); + printf(" 0%%"); fflush(stdout); + } + if (json_progress) { + emit_json_progress("b2a_table", 0); + } } if (wr_icco->create_lut_xforms( diff --git a/profile/profout.c b/profile/profout.c index 0d2e709..356cc1f 100644 --- a/profile/profout.c +++ b/profile/profout.c @@ -431,12 +431,17 @@ void in_a2b_clut(void *cntx, double out[3], double *in, int itn) { } DBG(("convert PCS to PCS' got %f %f %f\n",out[0],out[1],out[2])) - if (p->verb && itn == 0) { /* Output percent intervals */ + if ((p->verb || json_progress) && itn == 0) { /* Output percent intervals */ int pc; p->count++; pc = (int)(p->count * 100.0/p->total + 0.5); if (pc != p->last) { - printf("%c%2d%%",cr_char,pc); fflush(stdout); + if (p->verb) { + printf("%c%2d%%",cr_char,pc); fflush(stdout); + } + if (json_progress) { + emit_json_progress("a2b_table", pc); + } p->last = pc; } } @@ -658,12 +663,17 @@ void out_b2a_clut(void *cntx, double *out, double in[3], int itn) { DBG(("out_b2a_clut returning DEV' %s\n",icmPdv(p->ochan, out))) - if (p->verb && itn == 0) { /* Output percent intervals */ + if ((p->verb || json_progress) && itn == 0) { /* Output percent intervals */ int pc; p->count++; pc = (int)(p->count * 100.0/p->total + 0.5); if (pc != p->last) { - printf("%c%2d%%",cr_char,pc); fflush(stdout); + if (p->verb) { + printf("%c%2d%%",cr_char,pc); fflush(stdout); + } + if (json_progress) { + emit_json_progress("b2a_table", pc); + } p->last = pc; } } @@ -728,12 +738,17 @@ static void PCSp_bdist(void *cntx, double out[1], double in[3], int tn out[0] = (gdist + 20.0)/40.0; //printf("~1 bdist returning %f\n",out[0]); - if (p->verb) { /* Output percent intervals */ + if (p->verb || json_progress) { /* Output percent intervals */ int pc; p->count++; pc = (int)(p->count * 100.0/p->total + 0.5); if (pc != p->last) { - printf("%c%2d%%",cr_char,pc); fflush(stdout); + if (p->verb) { + printf("%c%2d%%",cr_char,pc); fflush(stdout); + } + if (json_progress) { + emit_json_progress("gamut_table", pc); + } p->last = pc; } } @@ -2551,6 +2566,7 @@ make_output_icc( if (csgamp == NULL) { if (verb) printf(" Finding Source Colorspace Perceptual Gamut\n"); + emit_json_progress("gamut_mapping", 10); if ((csgamp = cx.ixp->get_gamut(cx.ixp, gres)) == NULL) error ("%d, %s",src_xicc->e.c, src_xicc->e.m); @@ -2567,6 +2583,7 @@ make_output_icc( if (verb) printf(" Finding Source Colorspace Saturation Gamut\n"); + emit_json_progress("gamut_mapping", 25); if ((csgams = ixs->get_gamut(ixs, gres)) == NULL) error ("%d, %s",src_xicc->e.c, src_xicc->e.m); @@ -2614,6 +2631,7 @@ make_output_icc( /* Creat the destination gamut surface */ if (verb) printf(" Finding Destination Gamut\n"); + emit_json_progress("gamut_mapping", 40); if ((ogam = cx.ox->get_gamut(cx.ox, gres)) == NULL) error ("%d, %s",wr_xicc->e.c, wr_xicc->e.m); @@ -2657,6 +2675,7 @@ make_output_icc( if (verb) printf(" Creating Gamut match\n"); + emit_json_progress("gamut_mapping", 75); /* The real range of Lab 0..100,-128..128,1-28..128 cube */ /* when mapped to CAM is ridiculously large (ie. */ @@ -2673,6 +2692,7 @@ make_output_icc( ); if (cx.pmap == NULL) error ("Failed to make perceptual gamut map transform"); + emit_json_progress("gamut_mapping", 100); if (sepsat) { @@ -2784,7 +2804,7 @@ make_output_icc( for (i = 0; i < cx.ochan; i++) a2bgres[i] = a2bres; - if (cx.verb) { + if (cx.verb || json_progress) { unsigned int ui; int extra; cx.count = 0; @@ -2797,9 +2817,13 @@ make_output_icc( ; cx.total += extra; #endif - printf("Creating inverse gamut mapped A to B tables\n"); - - printf(" 0%%"); fflush(stdout); + if (cx.verb) { + printf("Creating inverse gamut mapped A to B tables\n"); + printf(" 0%%"); fflush(stdout); + } + if (json_progress) { + emit_json_progress("a2b_table", 0); + } } #ifdef DEBUG_IGM_ONE @@ -2879,7 +2903,7 @@ make_output_icc( /* for perceptual and saturation intents */ /* Use helper function to do the hard work. */ - if (cx.verb) { + if (cx.verb || json_progress) { unsigned int ui; int extra; cx.count = 0; @@ -2892,8 +2916,13 @@ make_output_icc( ; cx.total += extra; #endif - printf("Creating B to A tables\n"); - printf(" 0%%"); fflush(stdout); + if (cx.verb) { + printf("Creating B to A tables\n"); + printf(" 0%%"); fflush(stdout); + } + if (json_progress) { + emit_json_progress("b2a_table", 0); + } } #ifdef DEBUG_ONE @@ -3137,13 +3166,18 @@ make_output_icc( for (i = 0; i < cx.ichan; i++) clutres[i] = b2ares; - if (cx.verb) { + if (cx.verb || json_progress) { unsigned int ui; cx.count = 0; cx.last = -1; for (cx.total = 1, ui = 0; ui < cx.ichan; cx.total *= clutres[ui++]) ; - printf(" 0%%"); fflush(stdout); + if (cx.verb) { + printf(" 0%%"); fflush(stdout); + } + if (json_progress) { + emit_json_progress("gamut_table", 0); + } } diff --git a/rspl/rev.c b/rspl/rev.c index 4505fef..d25cb14 100644 --- a/rspl/rev.c +++ b/rspl/rev.c @@ -8917,7 +8917,7 @@ static void calc_ocent(rspl *s) { /* Hmm. This isn't very reliable in detecting failure. */ if (ctx.oog) - printf("calc_ocent failed to return in-gamut focal point!\n"); + warning("calc_ocent failed to return in-gamut focal point!"); //printf("Final angle = %f\n", ctx.ret); -- 2.39.5