Feature: colprof -u (Structured JSON Calculation Progress) #13

Merged
gronod merged 1 commits from feature/colprof-json-progress into main 2026-08-21 21:52:12 +01:00
6 changed files with 86 additions and 25 deletions
+4
View File
@@ -2700,6 +2700,10 @@ White            &nb
useful in creating a profile for a device that is known to have a useful in creating a profile for a device that is known to have a
perfectly linear response, such as a camera in RAW mode.<br> perfectly linear response, such as a camera in RAW mode.<br>
<br> <br>
<a name="u_progress"></a> <span style="font-weight: bold;">-u:</span>
Emit structured JSON progress events to <tt>stdout</tt> during profile
calculation (e.g. <tt>{"event": "progress", "stage": "gamut_mapping", "percent": 75}</tt>).<br>
<br>
<a name="u"></a> <span style="font-weight: bold;">-u:</span> Input <a name="u"></a> <span style="font-weight: bold;">-u:</span> Input
profiles will normally be created such that the white patch of the 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 test chart will be mapped to perfect white when used with any of the
+14 -4
View File
@@ -75,6 +75,14 @@
#define DEMPH_DEFAULT 1.0 /* Default dark region emphasis == none */ #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: 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," 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 = gamma+matrix, s = shaper+matrix, m = matrix only,\n");
fprintf(stderr," G = single gamma+matrix, S = single shaper+matrix\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," -u If input profile, auto scale WP to allow extrapolation\n");
fprintf(stderr," -ua If input profile, force absolute intent\n"); fprintf(stderr," -ua If input profile, force absolute intent\n");
fprintf(stderr," -uc If input profile, clip cLUT values above WP\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') { else if (argv[fa][1] == 'u') {
autowpsc = 1;
clipovwp = 0;
if (argv[fa][2] == 'a') { if (argv[fa][2] == 'a') {
autowpsc = 2; autowpsc = 2;
clipovwp = 0; clipovwp = 0;
@@ -502,12 +509,15 @@ int main(int argc, char *argv[]) {
clipovwp = 1; clipovwp = 1;
} else if (argv[fa][2] != '\000') { } else if (argv[fa][2] != '\000') {
usage("Unknown flag '%c' after -u",argv[fa][2]); usage("Unknown flag '%c' after -u",argv[fa][2]);
} else if (na != NULL && (isdigit(na[0]) || (na[0] == '.' && isdigit(na[1])))) {
} else if (na != NULL) {
fa = nfa; fa = nfa;
iwpscale = atof(na); iwpscale = atof(na);
if (iwpscale < 0.0 || iwpscale > 200.0) if (iwpscale < 0.0 || iwpscale > 200.0)
usage("Argument '%s' to flag -u out of range",na); usage("Argument '%s' to flag -u out of range",na);
autowpsc = 1;
clipovwp = 0;
} else {
json_progress = 1;
} }
} }
/* Clip primaries */ /* Clip primaries */
+3
View File
@@ -32,6 +32,9 @@ typedef enum {
prof_matonly = 7 /* XYZ matrix, linear */ prof_matonly = 7 /* XYZ matrix, linear */
} prof_atype; } prof_atype;
extern int json_progress;
void emit_json_progress(const char *stage, int percent);
/* Output or Display device */ /* Output or Display device */
void make_output_icc( void make_output_icc(
prof_atype ptype, /* Profile output type */ prof_atype ptype, /* Profile output type */
+12 -2
View File
@@ -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(("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])) 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 && tn == 0
) { /* Output percent intervals */ ) { /* Output percent intervals */
int pc; int pc;
p->count++; p->count++;
pc = (int)(p->count * 100.0/p->total + 0.5); pc = (int)(p->count * 100.0/p->total + 0.5);
if (pc != p->last) { if (pc != p->last) {
if (p->verb) {
printf("%c%2d%%",cr_char,pc); fflush(stdout); printf("%c%2d%%",cr_char,pc); fflush(stdout);
}
if (json_progress) {
emit_json_progress("b2a_table", pc);
}
p->last = pc; p->last = pc;
} }
} }
@@ -1171,7 +1176,7 @@ make_input_icc(
/* We now setup an exact inverse, colorimetric style */ /* We now setup an exact inverse, colorimetric style */
/* Use helper function to do the hard work. */ /* Use helper function to do the hard work. */
if (cx.verb) { if (cx.verb || json_progress) {
unsigned int ui; unsigned int ui;
int extra; int extra;
cx.count = 0; cx.count = 0;
@@ -1182,9 +1187,14 @@ make_input_icc(
for (extra = 1, ui = 0; ui < 3; extra *= (blut_clutPoints[ui++]-1)) for (extra = 1, ui = 0; ui < 3; extra *= (blut_clutPoints[ui++]-1))
; ;
cx.total += extra; cx.total += extra;
if (cx.verb) {
printf("Creating B to A tables\n"); printf("Creating B to A tables\n");
printf(" 0%%"); fflush(stdout); printf(" 0%%"); fflush(stdout);
} }
if (json_progress) {
emit_json_progress("b2a_table", 0);
}
}
if (wr_icco->create_lut_xforms( if (wr_icco->create_lut_xforms(
wr_icco, wr_icco,
+41 -7
View File
@@ -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])) 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; int pc;
p->count++; p->count++;
pc = (int)(p->count * 100.0/p->total + 0.5); pc = (int)(p->count * 100.0/p->total + 0.5);
if (pc != p->last) { if (pc != p->last) {
if (p->verb) {
printf("%c%2d%%",cr_char,pc); fflush(stdout); printf("%c%2d%%",cr_char,pc); fflush(stdout);
}
if (json_progress) {
emit_json_progress("a2b_table", pc);
}
p->last = 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))) 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; int pc;
p->count++; p->count++;
pc = (int)(p->count * 100.0/p->total + 0.5); pc = (int)(p->count * 100.0/p->total + 0.5);
if (pc != p->last) { if (pc != p->last) {
if (p->verb) {
printf("%c%2d%%",cr_char,pc); fflush(stdout); printf("%c%2d%%",cr_char,pc); fflush(stdout);
}
if (json_progress) {
emit_json_progress("b2a_table", pc);
}
p->last = 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; out[0] = (gdist + 20.0)/40.0;
//printf("~1 bdist returning %f\n",out[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; int pc;
p->count++; p->count++;
pc = (int)(p->count * 100.0/p->total + 0.5); pc = (int)(p->count * 100.0/p->total + 0.5);
if (pc != p->last) { if (pc != p->last) {
if (p->verb) {
printf("%c%2d%%",cr_char,pc); fflush(stdout); printf("%c%2d%%",cr_char,pc); fflush(stdout);
}
if (json_progress) {
emit_json_progress("gamut_table", pc);
}
p->last = pc; p->last = pc;
} }
} }
@@ -2551,6 +2566,7 @@ make_output_icc(
if (csgamp == NULL) { if (csgamp == NULL) {
if (verb) if (verb)
printf(" Finding Source Colorspace Perceptual Gamut\n"); printf(" Finding Source Colorspace Perceptual Gamut\n");
emit_json_progress("gamut_mapping", 10);
if ((csgamp = cx.ixp->get_gamut(cx.ixp, gres)) == NULL) if ((csgamp = cx.ixp->get_gamut(cx.ixp, gres)) == NULL)
error ("%d, %s",src_xicc->e.c, src_xicc->e.m); error ("%d, %s",src_xicc->e.c, src_xicc->e.m);
@@ -2567,6 +2583,7 @@ make_output_icc(
if (verb) if (verb)
printf(" Finding Source Colorspace Saturation Gamut\n"); printf(" Finding Source Colorspace Saturation Gamut\n");
emit_json_progress("gamut_mapping", 25);
if ((csgams = ixs->get_gamut(ixs, gres)) == NULL) if ((csgams = ixs->get_gamut(ixs, gres)) == NULL)
error ("%d, %s",src_xicc->e.c, src_xicc->e.m); error ("%d, %s",src_xicc->e.c, src_xicc->e.m);
@@ -2614,6 +2631,7 @@ make_output_icc(
/* Creat the destination gamut surface */ /* Creat the destination gamut surface */
if (verb) if (verb)
printf(" Finding Destination Gamut\n"); printf(" Finding Destination Gamut\n");
emit_json_progress("gamut_mapping", 40);
if ((ogam = cx.ox->get_gamut(cx.ox, gres)) == NULL) if ((ogam = cx.ox->get_gamut(cx.ox, gres)) == NULL)
error ("%d, %s",wr_xicc->e.c, wr_xicc->e.m); error ("%d, %s",wr_xicc->e.c, wr_xicc->e.m);
@@ -2657,6 +2675,7 @@ make_output_icc(
if (verb) if (verb)
printf(" Creating Gamut match\n"); printf(" Creating Gamut match\n");
emit_json_progress("gamut_mapping", 75);
/* The real range of Lab 0..100,-128..128,1-28..128 cube */ /* The real range of Lab 0..100,-128..128,1-28..128 cube */
/* when mapped to CAM is ridiculously large (ie. */ /* when mapped to CAM is ridiculously large (ie. */
@@ -2673,6 +2692,7 @@ make_output_icc(
); );
if (cx.pmap == NULL) if (cx.pmap == NULL)
error ("Failed to make perceptual gamut map transform"); error ("Failed to make perceptual gamut map transform");
emit_json_progress("gamut_mapping", 100);
if (sepsat) { if (sepsat) {
@@ -2784,7 +2804,7 @@ make_output_icc(
for (i = 0; i < cx.ochan; i++) for (i = 0; i < cx.ochan; i++)
a2bgres[i] = a2bres; a2bgres[i] = a2bres;
if (cx.verb) { if (cx.verb || json_progress) {
unsigned int ui; unsigned int ui;
int extra; int extra;
cx.count = 0; cx.count = 0;
@@ -2797,10 +2817,14 @@ make_output_icc(
; ;
cx.total += extra; cx.total += extra;
#endif #endif
if (cx.verb) {
printf("Creating inverse gamut mapped A to B tables\n"); printf("Creating inverse gamut mapped A to B tables\n");
printf(" 0%%"); fflush(stdout); printf(" 0%%"); fflush(stdout);
} }
if (json_progress) {
emit_json_progress("a2b_table", 0);
}
}
#ifdef DEBUG_IGM_ONE #ifdef DEBUG_IGM_ONE
#define IGM_DBGNO 2 /* Up to 10 */ #define IGM_DBGNO 2 /* Up to 10 */
@@ -2879,7 +2903,7 @@ make_output_icc(
/* for perceptual and saturation intents */ /* for perceptual and saturation intents */
/* Use helper function to do the hard work. */ /* Use helper function to do the hard work. */
if (cx.verb) { if (cx.verb || json_progress) {
unsigned int ui; unsigned int ui;
int extra; int extra;
cx.count = 0; cx.count = 0;
@@ -2892,9 +2916,14 @@ make_output_icc(
; ;
cx.total += extra; cx.total += extra;
#endif #endif
if (cx.verb) {
printf("Creating B to A tables\n"); printf("Creating B to A tables\n");
printf(" 0%%"); fflush(stdout); printf(" 0%%"); fflush(stdout);
} }
if (json_progress) {
emit_json_progress("b2a_table", 0);
}
}
#ifdef DEBUG_ONE #ifdef DEBUG_ONE
# define DBGNO 1 /* Up to 10 */ # define DBGNO 1 /* Up to 10 */
@@ -3137,14 +3166,19 @@ make_output_icc(
for (i = 0; i < cx.ichan; i++) for (i = 0; i < cx.ichan; i++)
clutres[i] = b2ares; clutres[i] = b2ares;
if (cx.verb) { if (cx.verb || json_progress) {
unsigned int ui; unsigned int ui;
cx.count = 0; cx.count = 0;
cx.last = -1; cx.last = -1;
for (cx.total = 1, ui = 0; ui < cx.ichan; cx.total *= clutres[ui++]) for (cx.total = 1, ui = 0; ui < cx.ichan; cx.total *= clutres[ui++])
; ;
if (cx.verb) {
printf(" 0%%"); fflush(stdout); printf(" 0%%"); fflush(stdout);
} }
if (json_progress) {
emit_json_progress("gamut_table", 0);
}
}
/* Create Gamut table */ /* Create Gamut table */
+1 -1
View File
@@ -8917,7 +8917,7 @@ static void calc_ocent(rspl *s) {
/* Hmm. This isn't very reliable in detecting failure. */ /* Hmm. This isn't very reliable in detecting failure. */
if (ctx.oog) 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); //printf("Final angle = %f\n", ctx.ret);