From 22f703cab05b7cd368f4de9e03991b7664dc5022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Mon, 1 Sep 2014 13:56:46 +0200 Subject: Initial import of argyll version 1.5.1-8 --- gamut/gammap.c | 2552 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2552 insertions(+) create mode 100644 gamut/gammap.c (limited to 'gamut/gammap.c') diff --git a/gamut/gammap.c b/gamut/gammap.c new file mode 100644 index 0000000..a9bef1e --- /dev/null +++ b/gamut/gammap.c @@ -0,0 +1,2552 @@ + +/* + * Argyll Gamut Mapping Library + * + * Author: Graeme W. Gill + * Date: 1/10/00 + * Version: 2.00 + * + * Copyright 2000 - 2006 Graeme W. Gill + * All rights reserved. + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * For a discussion of gamut mapping strategy used, + * see gammap.txt + */ + +/* + * TTBD: + * Improve error handling. + * + * There is a general expectation (especially in comparing products) + * that the profile colorimetric intent be not strictly minimum delta E, + * but that it correct neutral axis, luminence range and keep hue + * proportionality. Ideally there should be an intent that matches + * this, that can be selected for the colorimetric table (or perhaps be default). + * + * It might be good to offer the black mapping method as an option (gmm_BPmap), + * as well as offering different profile (xicc/xlut.c) black point options + * (neutral, K hue max density, CMY max density, any max density). + * + * The gamut mapping code here and the near smooth code don't actually mesh + * very well. For instance, the black point bend approach in < V1.3.4 + * means that the dest gamut isn't actually contained within the source, + * messing up the guide vector mappings. Even if this is fixed, the + * actual neutral aim point within nearsmooth is Jab 0,0, while + * the mapping in gammap is from the source neutral to the chosen + * ?????? + */ + + + +#define VERBOSE /* [Def] Print out extra interesting information when verbose is set */ +#undef PLOT_DIAG_WRL /* [Und] Always plot "gammap.wrl" */ + + /* What do display when user requests disgnostic .wrl */ +#define PLOT_SRC_GMT /* [Def] Plot the source surface to "gammap.wrl" as well */ +#define PLOT_DST_GMT /* [Def] Plot the dest surface to "gammap.wrl" as well */ +#undef PLOT_SRC_CUSPS /* [Und] Plot the source surface cusps to "gammap.wrl" as well */ +#undef PLOT_DST_CUSPS /* [Und] Plot the dest surface cusps to "gammap.wrl" as well */ +#undef PLOT_TRANSSRC_CUSPS /* [Und] Plot the gamut mapped source surface cusps to "gammap.wrl" */ +#undef PLOT_AXES /* [Und] Plot the axes to "gammap.wrl" as well */ +#undef SHOW_VECTOR_INDEXES /* [Und] Show the mapping vector index numbers */ +#define SHOW_MAP_VECTORS /* [Def] Show the mapping vectors */ +#undef SHOW_SUB_SURF /* [Und] Show the sub-surface mapping vector */ +#undef SHOW_CUSPMAP /* [Und] Show the cusp mapped vectors rather than final vectors */ +#undef SHOW_ACTUAL_VECTORS /* [Und?] Show how the source vectors actually map thought xform */ +#undef SHOW_ACTUAL_VEC_DIFF /* [Und] Show how the difference between guide and actual vectors */ + +#undef PLOT_LMAP /* [Und] Plot L map */ +#undef PLOT_GAMUTS /* [Und] Save (part mapped) input and output gamuts as */ + /* src.wrl, img.wrl, dst.wrl, gmsrc.wrl */ +#undef PLOT_3DKNEES /* [Und] Plot the 3D compression knees */ +#undef CHECK_NEARMAP /* [Und] Check how accurately near map vectors are represented by rspl */ + +#define USE_GLUMKNF /* [Define] Enable luminence knee function points */ +#define USE_GREYMAP /* [Define] Enable 3D->3D mapping points down the grey axis */ +#define USE_GAMKNF /* [Define] Enable 3D knee function points */ +#define USE_BOUND /* [Define] Enable grid boundary anchor points */ + +#undef SHOW_NEIGBORS /* [Und] Show nearsmth neigbors in gammap.wrl */ + +#undef PLOT_DIGAM /* [Und] Rather than DST_GMT - don't free it (#def in nearsmth.c too) */ + + +#define XRES 100 /* Res of plots */ + +/* Optional marker points for gamut mapping diagnosotic */ +struct { + int type; /* 1 = src point (xlate), 2 = dst point (no xlate) */ + /* 0 = end marker */ + double pos[3]; /* Position, (usually in Jab space) */ + double col[3]; /* RGB color */ +} markers[] = { + { 0, }, /* End marker */ + { 1, { 12.062, -0.87946, 0.97008 }, { 1.0, 0.3, 0.3 } }, /* Black point */ + { 1, { 67.575411, -37.555250, -36.612862 }, { 1.0, 0.3, 0.3 } }, /* bad source in red (Red) */ + { 1, { 61.003078, -44.466554, 1.922585 }, { 0.0, 1.0, 0.3 } }, /* good source in green */ + { 2, { 49.294793, 50.749543, -51.383167 }, { 1.0, 0.0, 0.0 } }, + { 2, { 42.783425, 49.089363, -37.823712 }, { 0.0, 1.0, 0.0 } }, + { 2, { 41.222695, 63.911823, 37.695310 }, { 0.0, 1.0, 0.3 } }, /* destination in green */ + { 1, { 41.951770, 60.220284, 34.788195 }, { 1.0, 0.3, 0.3 } }, /* source in red (Red) */ + { 2, { 41.222695, 63.911823, 37.695310 }, { 0.3, 1.3, 0.3 } }, /* Dest in green */ + { 1, { 85.117353, -60.807580, -22.195118 }, { 0.3, 0.3, 1 } }, /* Cyan Source (Blue) */ + { 2, { 61.661622, -38.164411, -18.090824 }, { 1.0, 0.3, 0.3 } }, /* CMYK destination (Red) */ + { 0 } /* End marker */ +}; + +/* Optional marker rings for gamut mapping diagnosotic */ +struct { + int type; /* 1 = src ring point, 2 = ignore, */ + /* 0 = end marker */ + double ppoint[3]; /* Location of a point on the plane in source space */ + double pnorm[3]; /* Plane normal direction in source space */ + int nverts; /* Number of points to make ring */ + double rad; /* Relative Radius from neutral to source surface (0.0 - 1.0) */ + double scol[3]; /* Source RGB color */ + double dcol[3]; /* Destination RGB color */ +} rings[] = { + { 0 }, /* End marker */ + { 1, + { 60.0, 0.0, 0.0 }, { 1.0, 0.8, 0.0 }, /* plane point and normal */ + 100, 1.0, /* 20 vertexes at source radius */ + { 0.0, 1.0, 0.0 }, /* Green source */ + { 1.0, 0.0, 0.0 } /* Red destination */ + }, + { 1, + { 60.0, 0.0, 0.0 }, { 1.0, 0.8, 0.0 }, /* plane point and normal */ + 100, 0.9, /* 20 vertexes at source radius */ + { 0.0, 1.0, 0.0 }, /* Green source */ + { 1.0, 0.0, 0.0 } /* Red destination */ + }, + { 1, + { 60.0, 0.0, 0.0 }, { 1.0, 0.8, 0.0 }, /* plane point and normal */ + 100, 0.8, /* 20 vertexes at source radius */ + { 0.0, 1.0, 0.0 }, /* Green source */ + { 1.0, 0.0, 0.0 } /* Red destination */ + }, + { 0 } /* End marker */ +}; + +/* Degree to which the hue & saturation of the black point axes should be aligned: */ +#define GREYBPHSMF 0.0 + +#include +#include +#include +#include +#include +#include +#include "counters.h" +#include "icc.h" +#include "numlib.h" +#include "xicc.h" +#include "gamut.h" +#include "rspl.h" +#include "gammap.h" +#include "nearsmth.h" +#include "vrml.h" +#ifdef PLOT_LMAP +#include "plot.h" +#endif + +/* Callback context for enhancing the saturation of the clut values */ +typedef struct { + gamut *dst; /* Destination colorspace gamut */ + double wp[3], bp[3];/* Destination colorspace white and black points */ + double satenh; /* Saturation engancement value */ +} adjustsat; + +/* Callback context for making clut relative to white and black points */ +typedef struct { + double mat[3][4]; +} adjustwb; + +static void inv_grey_func(void *pp, double *out, double *in); +static void adjust_wb_func(void *pp, double *out, double *in); +static void adjust_sat_func(void *pp, double *out, double *in); + +#define XVRA 4.0 /* Extra mapping vertex ratio over no. tri verts from gamut */ + +/* The smoothed near weighting control values. */ +/* These weightings setup the detailed behaviour of the */ +/* gamut mapping for the fully perceptual and saturation intents. */ +/* They are ordered here by increasing priority. A -ve value is ignored */ + +/* Perceptual mapping weights, where smoothness and proportionality are important.. */ +gammapweights pweights[] = { + { + gmm_default, /* Non hue specific defaults */ + { /* Cusp alignment control */ + { + 0.1, /* Cusp luminance alignment weighting 0 = none, 1 = full */ + 0.0, /* Cusp chroma alignment weighting 0 = none, 1 = full */ + 0.3 /* Cusp hue alignment weighting 0 = none, 1 = full */ + }, + 1.00 /* Chroma expansion 1 = none */ + }, + { /* Radial weighting */ + 0.0, /* Radial error overall weight, 0 + */ + 0.5, /* Radial hue dominance vs l+c, 0 - 1 */ + 0.5 /* Radial l dominance vs, c, 0 - 1 */ + }, + { /* Weighting of absolute error of destination from source */ + 1.0, /* Absolute error overall weight */ + 0.6, /* Hue dominance vs l+c, 0 - 1 */ + + 0.8, /* White l dominance vs, c, 0 - 1 */ + 0.5, /* Grey l dominance vs, c, 0 - 1 */ + 0.97, /* Black l dominance vs, c, 0 - 1 */ + + 0.4, /* White l blend start radius, 0 - 1, at white = 0 */ + 0.7, /* Black l blend power, linear = 1.0, enhance < 1.0 */ + + 1.5, /* L error extra power with size, none = 1.0 */ + 10.0 /* L error extra xover threshold in DE */ + }, + { /* Relative vector smoothing */ + 25.0, 35.0 /* Relative Smoothing radius L* H* */ + }, + { /* Weighting of excessive compression error, which is */ + /* the src->dst vector length over the available dst depth. */ + /* The depth is half the distance to the intersection of the */ + /* vector to the other side of the gamut. (doesn't get triggered much ?) */ + 10.0, /* Compression depth weight */ + 10.0 /* Expansion depth weight */ + }, + { + 0.0 /* Fine tuning expansion weight, 0 - 1 */ + } + }, +#ifdef NEVER + { + gmm_l_d_blue, /* Increase maintaining hue importance for blue */ + { + { + -1.0, /* Cusp luminance alignment weighting 0 = none, 1 = full */ + -1.0, /* Cusp chroma alignment weighting 0 = none, 1 = full */ + 0.0 /* Cusp hue alignment weighting 0 = none, 1 = full */ + }, + -1.0 /* Chroma expansion 1 = none */ + }, + { /* Radial weighting */ + -1.0, /* Radial error overall weight, 0 + */ + -1.0, /* Radial hue dominance vs l+c, 0 - 1 */ + -1.0 /* Radial l dominance vs, c, 0 - 1 */ + }, + { /* Weighting of absolute error of destination from source */ + -1.0, /* Absolute error overall weight */ + 0.8, /* Hue dominance vs l+c, 0 - 1 */ + + -1.0, /* White l dominance vs, c, 0 - 1 */ + -1.0, /* Grey l dominance vs, c, 0 - 1 */ + -1.0, /* Black l dominance vs, c, 0 - 1 */ + + -1.0, /* White l threshold ratio to grey distance, 0 - 1 */ + -1.0, /* Black l threshold ratio to grey distance, 0 - 1 */ + + -1.0, /* L error extra power, none = 1.0 */ + -1.0 /* L error xover threshold in DE */ + }, + { /* Relative error preservation using smoothing */ + -1.0, 25.0 /* Relative Smoothing radius L* H* */ + }, + { /* Weighting of excessive compression error, which is */ + /* the src->dst vector length over the available dst depth. */ + /* The depth is half the distance to the intersection of the */ + /* vector to the other side of the gamut. (doesn't get triggered much ?) */ + -1.0, /* Compression depth weight */ + -1.0 /* Expansion depth weight */ + }, + { + -1.0 /* Fine tuning expansion weight, 0 - 1 */ + } + }, +#endif /* NEVER */ + { + gmm_light_yellow, /* Treat yellow differently, to get purer result. */ + { + { + 0.9, /* Cusp luminance alignment weighting 0 = none, 1 = full */ + 0.8, /* Cusp chroma alignment weighting 0 = none, 1 = full */ + 0.7 /* Cusp hue alignment weighting 0 = none, 1 = full */ + }, + 1.15 /* Chroma expansion 1 = none */ + }, + { /* Radial weighting */ + -1.0, /* Radial error overall weight, 0 + */ + -1.0, /* Radial hue dominance vs l+c, 0 - 1 */ + -1.0 /* Radial l dominance vs, c, 0 - 1 */ + }, + { /* Weighting of absolute error of destination from source */ + -1.0, /* Absolute error overall weight */ + -1.0, /* Hue dominance vs l+c, 0 - 1 */ + + -1.0, /* White l dominance vs, c, 0 - 1 */ + -1.0, /* Grey l dominance vs, c, 0 - 1 */ + -1.0, /* Black l dominance vs, c, 0 - 1 */ + + -1.0, /* White l threshold ratio to grey distance, 0 - 1 */ + -1.0, /* Black l threshold ratio to grey distance, 0 - 1 */ + + -1.0, /* L error extra power, none = 1.0 */ + -1.0 /* L error xover threshold in DE */ + }, + { /* Relative error preservation using smoothing */ + 20.0, 20.0 /* Relative Smoothing radius L* H* */ + }, + { /* Weighting of excessive compression error, which is */ + /* the src->dst vector length over the available dst depth. */ + /* The depth is half the distance to the intersection of the */ + /* vector to the other side of the gamut. (doesn't get triggered much ?) */ + -1.0, /* Compression depth weight */ + -1.0 /* Expansion depth weight */ + }, + { + 0.5 /* Fine tuning expansion weight, 0 - 1 */ + } + }, + { + gmm_end, + } +}; +double psmooth = 5.0; /* [5.0] Level of RSPL smoothing for perceptual, 1 = nominal */ + +/* Saturation mapping weights, where saturation has priority over smoothness */ +gammapweights sweights[] = { + { + gmm_default, /* Non hue specific defaults */ + { /* Cusp alignment control */ + { + 0.6, /* Cusp luminance alignment weighting 0 = none, 1 = full */ + 0.5, /* Cusp chroma alignment weighting 0 = none, 1 = full */ + 0.6 /* Cusp hue alignment weighting 0 = none, 1 = full */ + }, + 1.05 /* Chroma expansion 1 = none */ + }, + { /* Radial weighting */ + 0.0, /* Radial error overall weight, 0 + */ + 0.5, /* Radial hue dominance vs l+c, 0 - 1 */ + 0.5 /* Radial l dominance vs, c, 0 - 1 */ + }, + { /* Weighting of absolute error of destination from source */ + 1.0, /* Absolute error overall weight */ + 0.4, /* Hue dominance vs l+c, 0 - 1 */ + + 0.6, /* White l dominance vs, c, 0 - 1 */ + 0.4, /* Grey l dominance vs, c, 0 - 1 */ + 0.6, /* Black l dominance vs, c, 0 - 1 */ + + 0.5, /* wl blend start radius, 0 - 1 */ + 1.0, /* bl blend power, linear = 1.0, enhance < 1.0 */ + + 1.0, /* L error extra power with size, none = 1.0 */ + 10.0 /* L error extra xover threshold in DE */ + }, + { /* Relative vector smoothing */ + 15.0, 20.0 /* Relative Smoothing radius L* H* */ + }, + { /* Weighting of excessive compression error, which is */ + /* the src->dst vector length over the available dst depth. */ + /* The depth is half the distance to the intersection of the */ + /* vector to the other side of the gamut. (doesn't get triggered much ?) */ + 10.0, /* Compression depth weight */ + 10.0 /* Expansion depth weight */ + }, + { + 0.5 /* Fine tuning expansion weight, 0 - 1 */ + } + }, + { + gmm_light_yellow, /* Treat yellow differently, to get purer result. */ + { + { + 1.0, /* Cusp luminance alignment weighting 0 = none, 1 = full */ + 1.0, /* Cusp chroma alignment weighting 0 = none, 1 = full */ + 1.0 /* Cusp hue alignment weighting 0 = none, 1 = full */ + }, + 1.20 /* Chroma expansion 1 = none */ + }, + { /* Radial weighting */ + -1.0, /* Radial error overall weight, 0 + */ + -1.0, /* Radial hue dominance vs l+c, 0 - 1 */ + -1.0 /* Radial l dominance vs, c, 0 - 1 */ + }, + { /* Weighting of absolute error of destination from source */ + 1.0, /* Absolute error overall weight */ + 0.3, /* Hue dominance vs l+c, 0 - 1 */ + + -1.0, /* White l dominance vs, c, 0 - 1 */ + -1.0, /* Grey l dominance vs, c, 0 - 1 */ + -1.0, /* Black l dominance vs, c, 0 - 1 */ + + -1.0, /* White l threshold ratio to grey distance, 0 - 1 */ + -1.0, /* Black l threshold ratio to grey distance, 0 - 1 */ + + -1.0, /* L error extra power, none = 1.0 */ + -1.0 /* L error xover threshold in DE */ + }, + { /* Relative error preservation using smoothing */ + 10.0, 15.0 /* Relative smoothing radius */ + }, + { /* Weighting of excessive compression error, which is */ + /* the src->dst vector length over the available dst depth. */ + /* The depth is half the distance to the intersection of the */ + /* vector to the other side of the gamut. (doesn't get triggered much ?) */ + -1.0, /* Compression depth weight */ + -1.0 /* Expansion depth weight */ + }, + { + -1.0 /* Fine tuning expansion weight, 0 - 1 */ + } + }, + { + gmm_end + } +}; +double ssmooth = 2.0; /* Level of RSPL smoothing for saturation */ + +/* + * Notes: + * The "knee" shape produced by the rspl (regular spline) code + * is not what one would expect for expansion. It is not + * symetrical with compression, and is less "sharp". This + * is due to the rspl "smoothness" criteria being based on + * grid value difference rather than smoothness being measured, + * as curvature. This means that the spline gets "stiffer" as + * it increases in slope. + * Possibly rspl could be improved in this respect ??? + * (Doesn't matter for L compression now, because rspl is + * being inverted for expansion). + */ + +static void del_gammap(gammap *s); +static void domap(gammap *s, double *out, double *in); +static void dopartialmap1(gammap *s, double *out, double *in); +static void dopartialmap2(gammap *s, double *out, double *in); +static gamut *parttransgamut(gammap *s, gamut *src); +#ifdef PLOT_GAMUTS +static void map_trans(void *cntx, double out[3], double in[3]); +#endif + +/* Return a gammap to map from the input space to the output space */ +/* Return NULL on error. */ +gammap *new_gammap( + int verb, /* Verbose flag */ + gamut *sc_gam, /* Source colorspace gamut */ + gamut *si_gam, /* Source image gamut (NULL if none) */ + gamut *d_gam, /* Destination colorspace gamut */ + icxGMappingIntent *gmi, /* Gamut mapping specification */ + int src_kbp, /* Use K only black point as src gamut black point */ + int dst_kbp, /* Use K only black point as dst gamut black point */ + int dst_cmymap, /* masks C = 1, M = 2, Y = 4 to force 100% cusp map */ + int rel_oride, /* 0 = normal, 1 = clip like, 2 = max relative */ + int mapres, /* Gamut map resolution, typically 9 - 33 */ + double *mn, /* If not NULL, set minimum mapping input range */ + double *mx, /* for rspl grid. */ + char *diagname /* If non-NULL, write a gamut mapping diagnostic WRL */ +) { + gmm_BPmap bph = gmm_bendBP; /* Prefered algorithm */ +// gmm_BPmap bph = gmm_clipBP; /* Alternatives tried */ +// gmm_BPmap bph = gmm_BPadpt; +// gmm_BPmap bph = gmm_noBPadpt; + + gammap *s; /* This */ + gamut *scl_gam; /* Source colorspace gamut with rotation and L mapping applied */ + gamut *sil_gam; /* Source image gamut with rotation and L mapping applied */ + + double s_cs_wp[3]; /* Source colorspace white point */ + double s_cs_bp[3]; /* Source colorspace black point */ + double s_ga_wp[3]; /* Source (image) gamut white point */ + double s_ga_bp[3]; /* Source (image) gamut black point */ + double d_cs_wp[3]; /* Destination colorspace white point */ + double d_cs_bp[3]; /* Destination colorspace black point */ + + double sr_cs_wp[3]; /* Source rotated colorspace white point */ + double sr_cs_bp[3]; /* Source rotated colorspace black point */ + double sr_ga_wp[3]; /* Source rotated (image) gamut white point */ + double sr_ga_bp[3]; /* Source rotated (image) gamut black point */ + double dr_cs_wp[3]; /* Target (gmi->greymf aligned) white point */ + double dr_cs_bp[3]; /* Target (gmi->greymf aligned) black point */ + double dr_be_bp[3]; /* Bend at start in source neutral axis direction */ + /* Target black point (Same as dr_cs_bp[] otherwise) */ + + double sl_cs_wp[3]; /* Source rotated and L mapped colorspace white point */ + double sl_cs_bp[3]; /* Source rotated and L mapped colorspace black point */ + + double s_mt_wp[3]; /* Overall source mapping target white point (used for finetune) */ + double s_mt_bp[3]; /* Overall source mapping target black point (used for finetune) */ + double d_mt_wp[3]; /* Overall destination mapping white point (used for finetune) */ + double d_mt_bp[3]; /* Overall destination mapping black point (used for finetune) */ + + int defrgrid = 6; /* mapping range surface default anchor point resolution */ + int nres = 512; /* Neutral axis resolution */ + cow lpnts[10]; /* Mapping points to create grey axis map */ + int revrspl = 0; /* Reverse grey axis rspl construction */ + int ngreyp = 0; /* Number of grey axis mapping points */ + int ngamp = 0; /* Number of gamut mapping points */ + double xvra = XVRA; /* Extra ss vertex ratio to src gamut vertex count */ + int j; + +#if defined(PLOT_LMAP) || defined(PLOT_GAMUTS) || defined(PLOT_3DKNEES) + fprintf(stderr,"##### A gammap.c PLOT is #defined ####\n"); +#endif + + if (verb) { + xicc_dump_gmi(gmi); + printf("Gamut map resolution: %d\n",mapres); + if (si_gam != NULL) + printf("Image gamut supplied\n"); + switch(bph) { + case gmm_clipBP: printf("Neutral axis no-adapt extend and clip\n"); break; + case gmm_BPadpt: printf("Neutral axis fully adapt\n"); break; + case gmm_bendBP: printf("Neutral axis no-adapt extend and bend\n"); break; + case gmm_noBPadpt: printf("Neutral axis no-adapt\n"); break; + } + } + + /* Allocate the object */ + if ((s = (gammap *)calloc(1, sizeof(gammap))) == NULL) + error("gammap: calloc failed on gammap object"); + + /* Setup methods */ + s->del = del_gammap; + s->domap = domap; + + /* Now create everything */ + + /* Grab the white and black points */ + if (src_kbp) { + // ~~99 Hmm. Shouldn't this be colorspace rather than gamut ???? + if (sc_gam->getwb(sc_gam, NULL, NULL, NULL, s_cs_wp, NULL, s_cs_bp)) { +// if (sc_gam->getwb(sc_gam, s_cs_wp, NULL, s_cs_bp, NULL, NULL, NULL)) + fprintf(stderr,"gamut map: Unable to read source colorspace white and black points\n"); + free(s); + return NULL; + } + } else { + if (sc_gam->getwb(sc_gam, NULL, NULL, NULL, s_cs_wp, s_cs_bp, NULL)) { +// if (sc_gam->getwb(sc_gam, s_cs_wp, s_cs_bp, NULL, NULL, NULL, NULL)) + fprintf(stderr,"gamut map: Unable to read source colorspace white and black points\n"); + free(s); + return NULL; + } + } + + /* If source space is source gamut */ + if (si_gam == NULL) { + si_gam = sc_gam; + for (j = 0; j < 3; j++) { + s_ga_wp[j] = s_cs_wp[j]; + s_ga_bp[j] = s_cs_bp[j]; + } + + /* Else have explicit sourcegamut */ + } else { + + if (src_kbp) { + if (si_gam->getwb(si_gam, NULL, NULL, NULL, s_ga_wp, NULL, s_ga_bp)) { + fprintf(stderr,"gamut map: Unable to read source gamut white and black points\n"); + free(s); + return NULL; + } + } else { + if (si_gam->getwb(si_gam, NULL, NULL, NULL, s_ga_wp, s_ga_bp, NULL)) { + fprintf(stderr,"gamut map: Unable to read source gamut white and black points\n"); + free(s); + return NULL; + } + } + + /* Guard against silliness. Image must be within colorspace */ + if (s_ga_wp[0] > s_cs_wp[0]) { + int j; + double t; +#ifdef VERBOSE + if (verb) + printf("Fixing wayward image white point\n"); +#endif + t = (s_cs_wp[0] - s_ga_bp[0])/(s_ga_wp[0] - s_ga_bp[0]); + for (j = 0; j < 3; j++) + s_ga_wp[j] = s_ga_bp[j] + t * (s_ga_wp[j] - s_ga_bp[j]); + + } + if (s_ga_bp[0] < s_cs_bp[0]) { + int j; + double t; +#ifdef VERBOSE + if (verb) + printf("Fixing wayward image black point\n"); +#endif + t = (s_cs_bp[0] - s_ga_wp[0])/(s_ga_bp[0] - s_ga_wp[0]); + for (j = 0; j < 3; j++) + s_ga_bp[j] = s_ga_wp[j] + t * (s_ga_bp[j] - s_ga_wp[j]); + } + } + + if (dst_kbp) { + if (d_gam->getwb(d_gam, NULL, NULL, NULL, d_cs_wp, NULL, d_cs_bp)) { + fprintf(stderr,"gamut map: Unable to read destination white and black points\n"); + free(s); + return NULL; + } + } else { + if (d_gam->getwb(d_gam, NULL, NULL, NULL, d_cs_wp, d_cs_bp, NULL)) { + fprintf(stderr,"gamut map: Unable to read destination white and black points\n"); + free(s); + return NULL; + } + } + +#ifdef VERBOSE + if (verb) { + if (src_kbp) + printf("Using Src K only black point\n"); + + if (dst_kbp) + printf("Using Dst K only black point\n"); + + printf("Src colorspace white/black are %f %f %f, %f %f %f\n", + s_cs_wp[0], s_cs_wp[1], s_cs_wp[2], s_cs_bp[0], s_cs_bp[1], s_cs_bp[2]); + + printf("Src gamut white/black are %f %f %f, %f %f %f\n", + s_ga_wp[0], s_ga_wp[1], s_ga_wp[2], s_ga_bp[0], s_ga_bp[1], s_ga_bp[2]); + + printf("Dst colorspace white/black are %f %f %f, %f %f %f\n", + d_cs_wp[0], d_cs_wp[1], d_cs_wp[2], d_cs_bp[0], d_cs_bp[1], d_cs_bp[2]); + } +#endif /* VERBOSE */ + + /* ------------------------------------ */ + /* Figure out the destination grey axis alignment */ + /* This is all done using colorspace white & black points */ + { + double t, svl, dvl; + double wrot[3][3]; /* Rotation about 0,0,0 to match white points */ + double sswp[3], ssbp[3]; /* Temporary source white & black points */ + double fawp[3], fabp[3]; /* Fully adapted destination white & black */ + double hawp[3], habp[3]; /* Half (full white, not black) adapted destination w & b */ + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + /* The first task is to decide what our target destination */ + /* white and black points are going to be. */ + + /* Figure out what our initial target destination white point is going to be: */ + + /* Compute source white and black points with same L value as the destination */ + t = (d_cs_wp[0] - s_cs_bp[0])/(s_cs_wp[0] - s_cs_bp[0]); + for (j = 0; j < 3; j++) + sswp[j] = s_cs_bp[j] + t * (s_cs_wp[j] - s_cs_bp[j]); + + t = (d_cs_bp[0] - s_cs_wp[0])/(s_cs_bp[0] - s_cs_wp[0]); + for (j = 0; j < 3; j++) + ssbp[j] = s_cs_wp[j] + t * (s_cs_bp[j] - s_cs_wp[j]); + + /* The raw grey axis alignment target is a blend between the */ + /* source colorspace (NOT gamut) and the destination */ + /* colorspace. */ + + for (j = 0; j < 3; j++) { + dr_cs_wp[j] = gmi->greymf * d_cs_wp[j] + (1.0 - gmi->greymf) * sswp[j]; + dr_cs_bp[j] = gmi->greymf * d_cs_bp[j] + (1.0 - gmi->greymf) * ssbp[j]; + } + +#ifdef VERBOSE + if (verb) { + printf("Target (blended) dst wp/bp = %f %f %f, %f %f %f\n", + dr_cs_wp[0], dr_cs_wp[1], dr_cs_wp[2], dr_cs_bp[0], dr_cs_bp[1], dr_cs_bp[2]); + } +#endif /* VERBOSE */ + + /* Compute full adaptation target destinations */ + for (j = 0; j < 3; j++) { + fawp[j] = dr_cs_wp[j]; /* White fully adapted */ + fabp[j] = dr_cs_bp[j]; /* Black fully adapted */ + } + + /* Clip the target grey axis to the destination gamut */ + if (d_gam->vector_isect(d_gam, fabp, fawp, fabp, fawp, NULL, NULL, NULL, NULL) == 0) + error("gamut: vector_isect failed!"); + + /* To work around the problem that vector_isect() is not entirely accurate, */ + /* special case the situation where gmi->greymf == 1.0 */ + if (gmi->greymf > 0.99) { + for (j = 0; j < 3; j++) { + fawp[j] = d_cs_wp[j]; + fabp[j] = d_cs_bp[j]; + } + } + + /* If dst_kbp is set, then clipping to the dest gamut doesn't do what we want, */ + /* since it extends the black to a full composite black point. */ + /* A "K only" gamut is hard to define, so do a hack: */ + /* scale fabp[] towards fawp[] so that it has the same L as */ + /* the destination K only black point. */ + if (dst_kbp && fabp[0] < d_cs_bp[0]) { + t = (d_cs_bp[0] - fawp[0])/(fabp[0] - fawp[0]); + + for (j = 0; j < 3; j++) + fabp[j] = fawp[j] + t * (fabp[j] - fawp[j]); + } + + /* Compute half adapted (full white, not black) target destinations */ + for (j = 0; j < 3; j++) + hawp[j] = dr_cs_wp[j]; /* White fully adapted */ + + /* Compute the rotation matrix that maps the source white point */ + /* onto the target white point. */ + icmRotMat(wrot, sswp, dr_cs_wp); + + /* Compute the target black point as the rotated source black point */ + icmMulBy3x3(habp, wrot, s_cs_bp); + + /* Now intersect the target white and black points with the destination */ + /* colorspace gamut to arrive at the best possible in gamut values for */ + /* the target white and black points. */ + if (d_gam->vector_isect(d_gam, habp, hawp, habp, hawp, NULL, NULL, NULL, NULL) == 0) + error("gamut: vector_isect failed!"); + + /* To work around the problem that vector_isect() is not entirely accurate, */ + /* special case the situation where gmi->greymf == 1.0 */ + if (gmi->greymf > 0.99) { + for (j = 0; j < 3; j++) { + hawp[j] = d_cs_wp[j]; + } + } + + /* If dst_kbp is set, then clipping to the dest gamut doesn't do what we want, */ + /* since it extends the black to a full composite black point. */ + /* A "K only" gamut is hard to define, so do a hack: */ + /* scale habp[] towards hawp[] so that it has the same L as */ + /* the destination K only black point. */ + if (dst_kbp && habp[0] < d_cs_bp[0]) { + t = (d_cs_bp[0] - hawp[0])/(habp[0] - hawp[0]); + + for (j = 0; j < 3; j++) + habp[j] = hawp[j] + t * (habp[j] - hawp[j]); + } + + /* Now decide the detail of the white and black alignment */ + if (bph == gmm_BPadpt || bph == gmm_bendBP) { /* Adapt to destination white and black */ + + + /* Use the fully adapted white and black points */ + for (j = 0; j < 3; j++) { + dr_cs_wp[j] = fawp[j]; + dr_cs_bp[j] = fabp[j]; + } + + if (bph == gmm_bendBP) { + + /* Extend the half adapted (white = dst, black = src) black point */ + /* to the same L as the target (dst), to use as the initial (bent) black point */ + t = (dr_cs_bp[0] - dr_cs_wp[0])/(habp[0] - dr_cs_wp[0]); + for (j = 0; j < 3; j++) + dr_be_bp[j] = dr_cs_wp[j] + t * (habp[j] - dr_cs_wp[j]); + + } else { + + /* Set bent black point target to be the same as our actual */ + /* black point target, so that the "bend" code does nothing. */ + for (j = 0; j < 3; j++) + dr_be_bp[j] = dr_cs_bp[j]; + } + + } else { /* Adapt to destination white but not black */ + + /* Use the half adapted (white = dst, black = src) white and black points */ + for (j = 0; j < 3; j++) { + dr_cs_wp[j] = hawp[j]; + dr_cs_bp[j] = habp[j]; + } + +#ifdef VERBOSE + if (verb) { + printf("Adapted target wp/bp = %f %f %f, %f %f %f\n", + dr_cs_wp[0], dr_cs_wp[1], dr_cs_wp[2], dr_cs_bp[0], dr_cs_bp[1], dr_cs_bp[2]); + } +#endif + if (bph == gmm_clipBP) { + + /* Extend the target black point to accomodate the */ + /* bent or clipped destination space L* range */ + if (fabp[0] < dr_cs_bp[0]) { + t = (fabp[0] - dr_cs_wp[0])/(dr_cs_bp[0] - dr_cs_wp[0]); + for (j = 0; j < 3; j++) + dr_cs_bp[j] = dr_cs_wp[j] + t * (dr_cs_bp[j] - d_cs_wp[j]); + } + } + + /* Set the bent black point target to be the same as our actual */ + /* black point target, so that the "bend" code does nothing. */ + for (j = 0; j < 3; j++) + dr_be_bp[j] = dr_cs_bp[j]; + } + +#ifdef VERBOSE + if (verb) { + printf("Adapted & extended tgt wp/bp = %f %f %f, %f %f %f\n", + dr_cs_wp[0], dr_cs_wp[1], dr_cs_wp[2], dr_cs_bp[0], dr_cs_bp[1], dr_cs_bp[2]); + } +#endif /* VERBOSE */ + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + /* Now we need to figure out what origin alignment is needed, as well as */ + /* making sure the vectors are the same length to avoid rescaling. */ + /* (Scaling is meant to be done with the L curve though.) */ + + /* Create temporary source white point that has the same L as the */ + /* target destination white point. */ + t = (dr_cs_wp[0] - s_cs_bp[0])/(s_cs_wp[0] - s_cs_bp[0]); + for (j = 0; j < 3; j++) + sswp[j] = s_cs_bp[j] + t * (s_cs_wp[j] - s_cs_bp[j]); + + /* Create temporary source black point that will form a vector to the src white */ + /* point with same length as the target destination black->white vector. */ + for (svl = dvl = 0.0, j = 0; j < 3; j++) { + double tt; + tt = sswp[j] - s_cs_bp[j]; + svl += tt * tt; + tt = dr_cs_wp[j] - dr_cs_bp[j]; + dvl += tt * tt; + } + svl = sqrt(svl); + dvl = sqrt(dvl); + for (j = 0; j < 3; j++) + ssbp[j] = sswp[j] + dvl/svl * (s_cs_bp[j] - sswp[j]); + +#ifdef VERBOSE + if (verb) { + printf("Rotate matrix src wp/bp = %f %f %f, %f %f %f\n", + sswp[0], sswp[1], sswp[2], ssbp[0], ssbp[1], ssbp[2]); + printf("Rotate matrix dst wp/bp = %f %f %f, %f %f %f\n", + dr_cs_wp[0], dr_cs_wp[1], dr_cs_wp[2], dr_cs_bp[0], dr_cs_bp[1], dr_cs_bp[2]); + } +#endif /* VERBOSE */ + + /* Now create the general rotation and translation to map the source grey */ + /* axis to our destination grey axis. */ + icmVecRotMat(s->grot, sswp, ssbp, dr_cs_wp, dr_cs_bp); + + /* And create the inverse as well: */ + icmVecRotMat(s->igrot, dr_cs_wp, dr_cs_bp, sswp, ssbp); + + /* Create rotated versions of source colorspace & image white and */ + /* black points for use from now on, given that rotation will */ + /* be applied first to all source points. */ + icmMul3By3x4(sr_cs_wp, s->grot, s_cs_wp); + icmMul3By3x4(sr_cs_bp, s->grot, s_cs_bp); + icmMul3By3x4(sr_ga_wp, s->grot, s_ga_wp); + icmMul3By3x4(sr_ga_bp, s->grot, s_ga_bp); + +#ifdef VERBOSE + if (verb) { + printf("Bend target bp = %f %f %f\n", + dr_be_bp[0], dr_be_bp[1], dr_be_bp[2]); + printf("Rotated source grey axis wp/bp %f %f %f, %f %f %f\n", + sr_cs_wp[0], sr_cs_wp[1], sr_cs_wp[2], sr_cs_bp[0], sr_cs_bp[1], sr_cs_bp[2]); + printf("Rotated gamut grey axis wp/bp %f %f %f, %f %f %f\n", + sr_ga_wp[0], sr_ga_wp[1], sr_ga_wp[2], sr_ga_bp[0], sr_ga_bp[1], sr_ga_bp[2]); + printf("Destination axis target wp/bp %f %f %f, %f %f %f\n", + dr_cs_wp[0], dr_cs_wp[1], dr_cs_wp[2], dr_cs_bp[0], dr_cs_bp[1], dr_cs_bp[2]); + } +#endif + } + +#ifdef NEVER +sr_cs_wp[0] = 100.0; +sr_cs_bp[0] = 30.0; +dr_cs_wp[0] = 80.0; +dr_cs_bp[0] = 10.0; +glumknf = 1.0; +#endif /* NEVER */ + + /* Create the mapping points needed to build the 1D L mapping rspl. */ + /* If we have a gamut (ie. image) range that is smaller than the */ + /* L range of the colorspace, then use its white and black L values */ + /* as the source to be compressed to the destination L range. */ + /* We expand only a colorspace range, not a gamut/image range. */ + { + double swL, dwL; /* Source and destination white point L */ + double sbL, dbL; /* Source and destination black point L */ + int j; + double t; + + /* Setup white point mapping */ + if (sr_cs_wp[0] <= dr_cs_wp[0]) { /* Needs possible expansion */ + swL = sr_cs_wp[0]; + dwL = gmi->glumwexf * dr_cs_wp[0] + (1.0 - gmi->glumwexf) * sr_cs_wp[0]; + + } else { + if (sr_ga_wp[0] > dr_cs_wp[0]) { /* Gamut or colorspace needs compression */ + + swL = (1.0 - gmi->glumwcpf) * dr_cs_wp[0] + gmi->glumwcpf * sr_ga_wp[0]; + dwL = dr_cs_wp[0]; + + } else { /* Neither needed */ + swL = sr_ga_wp[0]; + dwL = sr_ga_wp[0]; + } + } + + /* Setup black point mapping */ + if (sr_cs_bp[0] >= dr_cs_bp[0]) { /* Needs possible expansion */ + sbL = sr_cs_bp[0]; + dbL = gmi->glumbexf * dr_cs_bp[0] + (1.0 - gmi->glumbexf) * sr_cs_bp[0]; + + } else { + if (sr_ga_bp[0] < dr_cs_bp[0]) { /* Gamut or colorspace needs compression */ + + sbL = (1.0 - gmi->glumbcpf) * dr_cs_bp[0] + gmi->glumbcpf * sr_ga_bp[0]; + dbL = dr_cs_bp[0]; + + } else { /* Neither needed */ + sbL = sr_ga_bp[0]; + dbL = sr_ga_bp[0]; + } + } + + /* To ensure symetry between compression and expansion, always create RSPL */ + /* for compression and its inverse, and then swap grey and igrey rspl to compensate. */ + if ((dwL - dbL) > (swL - sbL)) + revrspl = 1; + + /* White point end */ + lpnts[ngreyp].p[0] = swL; + lpnts[ngreyp].v[0] = dwL; + lpnts[ngreyp++].w = 10.0; /* Must go through here */ + + /* Black point end */ + lpnts[ngreyp].p[0] = sbL; + lpnts[ngreyp].v[0] = dbL; + lpnts[ngreyp++].w = 10.0; /* Must go through here */ + +//printf("~1 white loc %f, val %f\n",swL,dwL); +//printf("~1 black loc %f, val %f\n",sbL,dbL); + +#ifdef USE_GLUMKNF + if (gmi->glumknf < 0.05) +#endif /* USE_GLUMKNF */ + { /* make sure curve is firmly anchored */ + lpnts[ngreyp].p[0] = 0.3 * lpnts[ngreyp-1].p[0] + 0.7 * lpnts[ngreyp-2].p[0]; + lpnts[ngreyp].v[0] = 0.3 * lpnts[ngreyp-1].v[0] + 0.7 * lpnts[ngreyp-2].v[0]; + lpnts[ngreyp++].w = 1.0; + + lpnts[ngreyp].p[0] = 0.7 * lpnts[ngreyp-2].p[0] + 0.3 * lpnts[ngreyp-3].p[0]; + lpnts[ngreyp].v[0] = 0.7 * lpnts[ngreyp-2].v[0] + 0.3 * lpnts[ngreyp-3].v[0]; + lpnts[ngreyp++].w = 1.0; + } +#ifdef USE_GLUMKNF + else { /* There is at least some weight in knee points */ + double cppos = 0.50; /* Center point ratio between black and white */ + double cplv; /* Center point location and value */ + double kppos = 0.30; /* Knee point ratio between white/black & center */ + double kwl, kbl, kwv, kbv; /* Knee point values and locations */ + double kwx, kbx; /* Knee point extra */ + + +//printf("sbL = %f, swL = %f\n",sbL,swL); +//printf("dbL = %f, dwL = %f\n",dbL,dwL); + + /* Center point */ + cplv = cppos * (swL - sbL) + sbL; +//printf("~1 computed cplv = %f\n",cplv); + +#ifdef NEVER /* Don't use a center point */ + lpnts[ngreyp].p[0] = cplv; + lpnts[ngreyp].v[0] = cplv; + lpnts[ngreyp++].w = 0.5; +#endif + +//printf("~1 black half diff = %f\n",dbL - sbL); +//printf("~1 white half diff = %f\n",dwL - swL); + + /* Knee point locations */ + kwl = kppos * (cplv - swL) + swL; + kbl = kppos * (cplv - sbL) + sbL; + + /* Extra compression for white and black knees */ + kwx = 0.6 * (dbL - sbL) + 1.0 * (swL - dwL); + kbx = 1.0 * (dbL - sbL) + 0.6 * (swL - dwL); + +//kwx = 0.0; +//kbx = 0.0; +//glumknf = 0.0; + + /* Knee point values */ + kwv = (dwL + kwx - cplv) * (kwl - cplv)/(swL - cplv) + cplv; + if (kwv > dwL) /* Sanity check */ + kwv = dwL; + + kbv = (dbL - kbx - cplv) * (kbl - cplv)/(sbL - cplv) + cplv; + if (kbv < dbL) /* Sanity check */ + kbv = dbL; + + +//printf("~1 kbl = %f, kbv = %f\n",kbl, kbv); +//printf("~1 kwl = %f, kwv = %f\n",kwl, kwv); + + /* Emphasise points to cause "knee" curve */ + lpnts[ngreyp].p[0] = kwl; + lpnts[ngreyp].v[0] = kwv; + lpnts[ngreyp++].w = gmi->glumknf * gmi->glumknf; + + lpnts[ngreyp].p[0] = kbl; + lpnts[ngreyp].v[0] = kbv; + lpnts[ngreyp++].w = 1.5 * gmi->glumknf * 1.5 * gmi->glumknf; + } +#endif /* USE_GLUMKNF */ + + /* Remember our source and destinatio mapping targets */ + /* so that we can use them for fine tuning later. */ + + /* We scale the source and target white and black */ + /* points to match the L values of the source and destination */ + /* L curve mapping, as this is how we have chosen the */ + /* white and black point mapping for the link. */ + /* Put them back in pre-rotated space, so that we can */ + /* check the overall transform of the white and black points. */ + t = (swL - sr_cs_bp[0])/(sr_cs_wp[0] - sr_cs_bp[0]); + for (j = 0; j < 3; j++) + s_mt_wp[j] = sr_cs_bp[j] + t * (sr_cs_wp[j] - sr_cs_bp[j]); + icmMul3By3x4(s_mt_wp, s->igrot, s_mt_wp); + + t = (sbL - sr_cs_wp[0])/(sr_cs_bp[0] - sr_cs_wp[0]); + for (j = 0; j < 3; j++) + s_mt_bp[j] = sr_cs_wp[j] + t * (sr_cs_bp[j] - sr_cs_wp[j]); +//printf("~1 check black point rotated = %f %f %f\n",s_mt_bp[0],s_mt_bp[1],s_mt_bp[2]); + icmMul3By3x4(s_mt_bp, s->igrot, s_mt_bp); +//printf("~1 check black point prerotated = %f %f %f\n",s_mt_bp[0],s_mt_bp[1],s_mt_bp[2]); + + t = (dwL - dr_cs_bp[0])/(dr_cs_wp[0] - dr_cs_bp[0]); + for (j = 0; j < 3; j++) + d_mt_wp[j] = dr_cs_bp[j] + t * (dr_cs_wp[j] - dr_cs_bp[j]); + + for (j = 0; j < 3; j++) + d_mt_bp[j] = dr_cs_wp[j] + t * (dr_cs_bp[j] - dr_cs_wp[j]); + } + + /* We now create the 1D rspl L map, that compresses or expands the luminence */ + /* range, independent of grey axis alignment, or gamut compression. */ + /* Because the rspl isn't symetrical when we swap X & Y, and we would */ + /* like a conversion from profile A to B to be the inverse of profile B to A */ + /* (as much as possible), we contrive here to always create a compression */ + /* RSPL, and create an inverse for it, and swap the two of them so that */ + /* the transform is correct and has an accurate inverse available. */ + { + datai il, ih; + datao ol, oh; + double avgdev[MXDO]; + int gres = 256; + + if (revrspl) { /* Invert creation and usage for symetry between compress and exp. */ + int i; + for (i = 0; i < ngreyp; i++) { + double tt = lpnts[i].p[0]; /* Swap source and dest */ + lpnts[i].p[0] = lpnts[i].v[0]; + lpnts[i].v[0] = tt; + } + } + + /* Create a 1D rspl, that is used to */ + /* form the overall L compression mapping. */ + if ((s->grey = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) /* Allocate 1D -> 1D */ + error("gamut: grey new_rspl failed"); + + il[0] = -1.0; /* Set possible input range */ + ih[0] = 101.0; + ol[0] = 0.0; /* Set normalisation output range */ + oh[0] = 100.0; + +#ifdef NEVER /* Dump out the L mapping points */ + { + int i; + printf("1D rspl L mapping points:\n"); + for (i = 0; i < ngreyp; i++) + printf("%d %f -> %f (w %f)\n",i,lpnts[i].p[0],lpnts[i].v[0],lpnts[i].w); + } +#endif + /* Create spline from the data points, with appropriate smoothness. */ + avgdev[0] = GAMMAP_RSPLAVGDEV; + if (s->grey->fit_rspl_w(s->grey, GAMMAP_RSPLFLAGS, lpnts, ngreyp, il, ih, &gres, ol, oh, 5.0, avgdev, NULL)) { + fprintf(stderr,"Warning: Grey axis mapping is non-monotonic - may not be very smooth ?\n"); + } + + /* Create an inverse mapping too, for reverse gamut and/or expansion. */ + il[0] = -1.0; /* Set possible input range */ + ih[0] = 101.0; + ol[0] = 0.0; /* Set normalisation output range */ + oh[0] = 100.0; + + if ((s->igrey = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) /* Allocate 1D -> 1D */ + error("gamut: igrey new_rspl failed"); + + /* Create it from inverse lookups of s->grey */ + s->igrey->set_rspl(s->igrey, 0, (void *)s->grey, inv_grey_func, il, ih, &gres, ol, oh); + + if (revrspl) { /* Swap to compensate for expansion */ + rspl *tt = s->grey; + s->grey = s->igrey; + s->igrey = tt; + } + } + +#ifdef PLOT_LMAP + { /* Plot the 1D mapping */ + double xx[XRES]; + double y1[XRES]; + int i; + + for (i = 0; i < XRES; i++) { + double x; + co cp; /* Conversion point */ + x = sr_cs_bp[0] + (i/(double)(XRES-1)) * (sr_cs_wp[0] - sr_cs_bp[0]); + xx[i] = x; + cp.p[0] = x; + s->grey->interp(s->grey, &cp); + y1[i] = cp.v[0]; + } + do_plot(xx,y1,NULL,NULL,XRES); + } +#endif /* PLOT_LMAP */ + + { + /* We want to rotate and then map L independently of everything else, */ + /* so transform source csape & image gamuts through the rotation and L mapping */ + /* before we create the surface 3D mapping from them */ + + /* Create L mapped versions of rotated src colorspace white/black points */ +#ifdef NEVER + co cp; + double t; + int i; + + cp.p[0] = sr_cs_wp[0]; + s->grey->interp(s->grey, &cp); + + t = (cp.v[0] - sr_cs_bp[0])/(sr_cs_wp[0] - sr_cs_bp[0]); + for (j = 0; j < 3; j++) + sl_cs_wp[j] = sr_cs_bp[j] + t * (sr_cs_wp[j] - sr_cs_bp[j]); + + cp.p[0] = sr_cs_bp[0]; + s->grey->interp(s->grey, &cp); + t = (cp.v[0] - sr_cs_wp[0])/(sr_cs_bp[0] - sr_cs_wp[0]); + for (j = 0; j < 3; j++) + sl_cs_bp[j] = sr_cs_wp[j] + t * (sr_cs_bp[j] - sr_cs_wp[j]); +#else + dopartialmap1(s, sl_cs_wp, s_cs_wp); + dopartialmap1(s, sl_cs_bp, s_cs_bp); +#endif + +#ifdef VERBOSE + if (verb) { + printf("Mapped source grey axis wp/bp %f %f %f, %f %f %f\n", + sl_cs_wp[0], sl_cs_wp[1], sl_cs_wp[2], sl_cs_bp[0], sl_cs_bp[1], sl_cs_bp[2]); + } +#endif + + if ((scl_gam = parttransgamut(s, sc_gam)) == NULL) { + fprintf(stderr,"gamut map: parttransgamut failed\n"); + free(s); + return NULL; + } + + if (sc_gam == si_gam) + sil_gam = scl_gam; + + else { + if ((sil_gam = parttransgamut(s, si_gam)) == NULL) { + fprintf(stderr,"gamut map: parttransgamut failed\n"); + free(s); + return NULL; + } + } + } + + /* Create all the 3D->3D gamut mapping points and 3D rspl, */ + /* if there is any compression or expansion to do. */ + if (gmi->gamcpf > 1e-6 || gmi->gamexf > 1e-6) { + cow *gpnts = NULL; /* Mapping points to create gamut mapping */ + int nspts; /* Number of source gamut surface points */ + int rgridpts; /* Number of range surface grid points */ + int i, j; + datai il, ih; + datao ol, oh; + int gres[MXDI]; + double avgdev[MXDO]; + nearsmth *nsm = NULL; /* Returned list of near smooth points */ + int nnsm; /* Number of near smoothed points */ + double brad = 0.0; /* Black bend radius */ + gammapweights xpweights[14], xsweights[14]; /* Explicit perceptial and sat. weights */ + gammapweights xwh[14]; /* Structure holding blended weights */ + double smooth = 1.0; /* Level of 3D RSPL smoothing, blend of psmooth and ssmooth */ + vrml *wrl = NULL; /* Gamut mapping illustration (hulls + guide vectors) */ + cgats *locus = NULL; /* Diagnostic locus to plot in wrl, NULL if none */ + +#ifdef PLOT_3DKNEES +typedef struct { + double v0[3], v1[3]; +} p3dk_lpoint; + p3dk_lpoint *p3dk_locus; + int p3dk_ix = 0; +#endif /* PLOT_3DKNEES */ + + /* Get the maximum number of points that will be created */ + nspts = near_smooth_np(scl_gam, sil_gam, d_gam, xvra); + + rgridpts = 0; +#ifdef USE_BOUND + if (defrgrid >= 2) { + rgridpts = defrgrid * defrgrid * defrgrid + - (defrgrid -2) * (defrgrid -2) * (defrgrid -2); + } +#endif + + if ((gpnts = (cow *)malloc((nres + 3 * nspts + rgridpts) * sizeof(cow))) == NULL) { + fprintf(stderr,"gamut map: Malloc of mapping setup points failed\n"); + s->grey->del(s->grey); + s->igrey->del(s->igrey); + if (sil_gam != scl_gam) + sil_gam->del(sil_gam); + scl_gam->del(scl_gam); + free(s); + return NULL; + } + +#ifdef PLOT_3DKNEES + if ((p3dk_locus = (p3dk_lpoint *)malloc((2 * nspts) * sizeof(p3dk_lpoint))) == NULL) + error("gamut: Diagnostic array p3dk_locus malloc failed"); +#endif /* PLOT_3DKNEES */ + + /* ------------------------------------------- */ + /* Finish off the grey axis mapping by creating the */ + /* grey axis 3D->3D mapping points */ + /* We use 4 times the grid density, and create */ + /* points that span the source colorspace (this may exceed) */ + /* the source image gamut, and map to points outside the */ + /* destination gamut) */ + + /* See how much to bend the black - compute the color difference */ + /* We start out in the direction of dr_be_bp at white, and at */ + /* the end we bend towards the overall bp dr_cs_bp */ + /* (brad will be 0 for non gmm_bendBP because dr_be_bp dr_cs_bp */ + for (brad = 0.0, i = 1; i < 3; i++) { + double tt = dr_be_bp[i] - dr_cs_bp[i]; + brad += tt * tt; + } + brad = sqrt(brad); + +//printf("~1 brad = %f, Bend target = %f %f %f, straight = %f %f %f\n", +//brad, dr_be_bp[0], dr_be_bp[1], dr_be_bp[2], dr_cs_bp[0], dr_cs_bp[1], dr_cs_bp[2]); + +#ifdef USE_GREYMAP + for (i = 0; i < nres; i++) { /* From black to white */ + double t; + double bv[3]; /* Bent (initial) destination value */ + double dv[3]; /* Straight (final) destination value */ + double wt = 1.0; /* Default grey axis point weighting */ + + /* Create source grey axis point */ + t = i/(nres - 1.0); + + /* Cover L = 0.0 to 100.0 */ + t = ((100.0 * t) - sl_cs_bp[0])/(sl_cs_wp[0] - sl_cs_bp[0]); + for (j = 0; j < 3; j++) + gpnts[ngamp].p[j] = sl_cs_bp[j] + t * (sl_cs_wp[j] - sl_cs_bp[j]); + + /* L values are the same, as they have been mapped prior to 3D */ + gpnts[ngamp].v[0] = gpnts[ngamp].p[0]; + + /* Figure destination point on initial bent grey axis */ + t = (gpnts[ngamp].v[0] - dr_cs_wp[0])/(dr_be_bp[0] - dr_cs_wp[0]); + for (j = 0; j < 3; j++) + bv[j] = dr_cs_wp[j] + t * (dr_be_bp[j] - dr_cs_wp[j]); +//printf("~1 t = %f, bent dest %f %f %f\n",t, bv[0], bv[1],bv[2]); + + /* Figure destination point on final straight grey axis */ + t = (gpnts[ngamp].v[0] - dr_cs_wp[0])/(dr_cs_bp[0] - dr_cs_wp[0]); + for (j = 0; j < 3; j++) + dv[j] = dr_cs_wp[j] + t * (dr_cs_bp[j] - dr_cs_wp[j]); +//printf("~1 t = %f, straight dest %f %f %f\n",t, dv[0], dv[1],dv[2]); + + /* Figure out a blend value between the bent value */ + /* and the straight value, so that it curves smoothly from */ + /* one to the other. */ + if (brad > 0.001) { + double ty; + t = ((dr_cs_bp[0] + brad) - gpnts[ngamp].v[0])/brad; + if (t < 0.0) + t = 0.0; + else if (t > 1.0) + t = 1.0; + /* Make it a spline ? */ + t = t * t * (3.0 - 2.0 * t); + ty = t * t * (3.0 - 2.0 * t); /* spline blend value */ + t = (1.0 - t) * ty + t * t; /* spline at t == 0, linear at t == 1 */ + + wt *= (1.0 + t * brad); /* Increase weigting with the bend */ + + } else { + t = 0.0; /* stick to straight, it will be close anyway. */ + } + + for (j = 0; j < 3; j++) /* full straight when t == 1 */ + gpnts[ngamp].v[j] = t * dv[j] + (1.0 - t) * bv[j]; + gpnts[ngamp].w = wt; +//printf("~1 t = %f, blended %f %f %f\n",t, gpnts[ngamp].v[0], gpnts[ngamp].v[1],gpnts[ngamp].v[2]); + +#ifdef NEVER + printf("Grey axis %d maps %f %f %f -> %f %f %f wit %f\n",ngamp, + gpnts[ngamp].p[0], gpnts[ngamp].p[1], gpnts[ngamp].p[2], + gpnts[ngamp].v[0], gpnts[ngamp].v[1], gpnts[ngamp].v[2], + gpnts[ngamp].w); +#endif + ngamp++; + } +#endif /* USE_GREYMAP */ + + /* ---------------------------------------------------- */ + /* Do preliminary computation of the rspl input and output bounding values */ + for (j = 0; j < 3; j++) { + il[j] = ol[j] = 1e60; + ih[j] = oh[j] = -1e60; + } + + /* From grey axis points */ + for (i = 0; i < ngamp; i++) { + for (j = 0; j < 3; j++) { + if (gpnts[i].p[j] < il[j]) + il[j] = gpnts[i].p[j]; + if (gpnts[i].p[j] > ih[j]) + ih[j] = gpnts[i].p[j]; + } + } + + /* From the source gamut */ + { + double tmx[3], tmn[3]; + scl_gam->getrange(scl_gam, tmn, tmx); + for (j = 0; j < 3; j++) { + if (tmn[j] < il[j]) + il[j] = tmn[j]; + if (tmx[j] > ih[j]) + ih[j] = tmx[j]; + } + } + + /* from input arguments override */ + if (mn != NULL && mx != NULL) { + + for (j = 0; j < 3; j++) { + if (mn[j] < il[j]) + il[j] = mn[j]; + if (mx[j] > ih[j]) + ih[j] = mx[j]; + } + } + + /* From the destination gamut */ + { + double tmx[3], tmn[3]; + d_gam->getrange(d_gam, tmn, tmx); + for (j = 0; j < 3; j++) { + if (tmn[j] < ol[j]) + ol[j] = tmn[j]; + if (tmx[j] > oh[j]) + oh[j] = tmx[j]; + } + } + + /* ---------------------------------------------------- */ + /* Deal with gamut hull guide vector creation. */ + + /* For compression, create a mapping for each vertex of */ + /* the source gamut (image) surface towards the destination gamut */ + /* For expansion, do the opposite. */ + + /* Convert from compact to explicit hextant weightings */ + if (expand_weights(xpweights, pweights) + || expand_weights(xsweights, sweights)) { + fprintf(stderr,"gamut map: expand_weights() failed\n"); + s->grey->del(s->grey); + s->igrey->del(s->igrey); + if (sil_gam != scl_gam) + sil_gam->del(sil_gam); + scl_gam->del(scl_gam); + free(s); + return NULL; + } + /* Create weights as blend between perceptual and saturation */ + near_xwblend(xwh, xpweights, gmi->gampwf, xsweights, gmi->gamswf); + if ((gmi->gampwf + gmi->gamswf) > 0.1) + smooth = (gmi->gampwf * psmooth) + (gmi->gamswf * ssmooth); + + /* Tweak gamut mappings according to extra cmy cusp flags or rel override */ + if (dst_cmymap != 0 || rel_oride != 0) { + tweak_weights(xwh, dst_cmymap, rel_oride); + } + + /* Create the near point mapping, which is our fundamental gamut */ + /* hull to gamut hull mapping. */ + nsm = near_smooth(verb, &nnsm, scl_gam, sil_gam, d_gam, src_kbp, dst_kbp, + dr_cs_bp, xwh, gmi->gamcknf, gmi->gamxknf, + gmi->gamcpf > 1e-6, gmi->gamexf > 1e-6, + xvra, mapres, smooth, il, ih, ol, oh); + if (nsm == NULL) { + fprintf(stderr,"Creating smoothed near points failed\n"); + s->grey->del(s->grey); + s->igrey->del(s->igrey); + if (sil_gam != scl_gam) + sil_gam->del(sil_gam); + scl_gam->del(scl_gam); + free(s); + return NULL; + } + /* --------------------------- */ + + /* Make sure the input range to encompasss the guide vectors. */ + for (i = 0; i < nnsm; i++) { + for (j = 0; j < 3; j++) { + if (nsm[i].sv[j] < il[j]) + il[j] = nsm[i].sv[j];; + if (nsm[i].sv[j] > ih[j]) + ih[j] = nsm[i].sv[j]; + } + } + +#ifdef NEVER + if (verb) { + fprintf(stderr,"Input bounding box:\n"); + fprintfstderr,("%f -> %f, %f -> %f, %f -> %f\n", + il[0], ih[0], il[1], ih[1], il[2], ih[2]); + } +#endif + + /* Now expand the bounding box by aprox 5% margin, but scale grid res */ + /* to match, so that the natural or given boundary still lies on the grid. */ + { + int xmapres; + double scale; + + xmapres = (int) ((mapres-1) * 0.05 + 0.5); + if (xmapres < 1) + xmapres = 1; + + scale = (double)(mapres-1 + xmapres)/(double)(mapres-1); + + for (j = 0; j < 3; j++) { + double low, high; + high = ih[j]; + low = il[j]; + ih[j] = (scale * (high - low)) + low; + il[j] = (scale * (low - high)) + high; + } + + mapres += 2 * xmapres; +#ifdef NEVER + if (verb) { + fprintf(stderr,"After incresing mapres to %d, input bounding box for 3D gamut mapping is:\n",mapres); + fprintf(stderr,"%f -> %f, %f -> %f, %f -> %f\n", + il[0], ih[0], il[1], ih[1], il[2], ih[2]); + } +#endif + } + + /* ---------------------------------------------------- */ + /* Setup for diagnostic plot, that will have elements added */ + /* as we create the final 3D gamut mapping rspl */ + /* (The plot is of the already rotated and L mapped source space) */ + { + int doaxes = 0; + +#ifdef PLOT_AXES + doaxes = 1; +#endif + if (diagname != NULL) + wrl = new_vrml(diagname, doaxes, 0); +#ifdef PLOT_DIAG_WRL + else + wrl = new_vrml("gammap.wrl", doaxes, 0); +#endif + } + + if (wrl != NULL) { + /* See if there is a diagnostic locus to plot too */ + if ((locus = new_cgats()) == NULL) + error("Failed to create cgats object"); + + locus->add_other(locus, "TS"); + + if (locus->read_name(locus, "locus.ts")) { + locus->del(locus); + locus = NULL; + } else { + if (verb) + printf("!! Found diagnostic locus.ts file !!\n"); + /* locus will be added later */ + } + + /* Add diagnostic markers from markers structure */ + for (i = 0; ; i++) { + double pp[3]; + co cp; + if (markers[i].type == 0) + break; + + if (markers[i].type == 1) { /* Src point - do luminance mapping */ + dopartialmap1(s, pp, markers[i].pos); + } else { + pp[0] = markers[i].pos[0]; + pp[1] = markers[i].pos[1]; + pp[2] = markers[i].pos[2]; + } + wrl->add_marker(wrl, pp, markers[i].col, 1.0); + } + } + + /* --------------------------- */ + /* Now computue our 3D mapping points from the near point mapping. */ + for (i = 0; i < nnsm; i++) { + double cpexf; /* The effective compression or expansion factor */ + + if (nsm[i].vflag == 0) { /* Unclear whether compression or expansion */ + /* Use larger to the the two factors */ + cpexf = gmi->gamcpf > gmi->gamexf ? gmi->gamcpf : gmi->gamexf; + + } else if (nsm[i].vflag == 1) { /* Compression */ + cpexf = gmi->gamcpf; + + } else if (nsm[i].vflag == 2) { /* Expansion */ + cpexf = gmi->gamexf; + + } else { + error("gammap: internal, unknown guide point flag"); + } + + /* Compute destination value which is a blend */ + /* between the source value and the fully mapped destination value. */ + icmBlend3(nsm[i].div, nsm[i].sv, nsm[i].dv, cpexf); + +#ifdef NEVER + printf("%s mapping:\n",nsm[i].vflag == 0 ? "Unclear" : nsm[i].vflag == 1 ? "Compression" : "Expansion"); + printf("Src point = %f %f %f radius %f\n",nsm[i].sv[0], nsm[i].sv[1], nsm[i].sv[2], nsm[i].sr); + printf("Dst point = %f %f %f radius %f\n",nsm[i].dv[0], nsm[i].dv[1], nsm[i].dv[2], nsm[i].dr); + printf("Blended dst point = %f %f %f\n",nsm[i].div[0], nsm[i].div[1], nsm[i].div[2]); +#endif /* NEVER */ + /* Set the main gamut hull mapping point */ + for (j = 0; j < 3; j++) { + gpnts[ngamp].p[j] = nsm[i].sv[j]; + gpnts[ngamp].v[j] = nsm[i].div[j]; + } + gpnts[ngamp++].w = 1.01; /* Main gamut surface mapping point */ + /* (Use 1.01 as a marker value) */ + +#ifdef USE_GAMKNF + /* Add sub surface mapping point if available */ + if (nsm[i].vflag != 0) { /* Sub surface point is available */ + + /* Compute destination value which is a blend */ + /* between the source value and the fully mapped destination value. */ + icmBlend3(nsm[i].div2, nsm[i].sv2, nsm[i].dv2, cpexf); + +#ifdef NEVER + printf("Src2 point = %f %f %f radius %f\n",nsm[i].sv2[0], nsm[i].sv2[1], nsm[i].sv2[2], nsm[i].sr); + printf("Dst2 point = %f %f %f radius %f\n",nsm[i].dv2[0], nsm[i].dv2[1], nsm[i].dv2[2], nsm[i].dr); + printf("Blended dst2 point = %f %f %f\n",nsm[i].div2[0], nsm[i].div2[1], nsm[i].div2[2]); + printf("\n"); +#endif /* NEVER */ + /* Set the sub-surface gamut hull mapping point */ + for (j = 0; j < 3; j++) { + gpnts[ngamp].p[j] = nsm[i].sv2[j]; + gpnts[ngamp].v[j] = nsm[i].div2[j]; + } + gpnts[ngamp++].w = nsm[i].w2; /* Sub-suface mapping points */ + } +#endif /* USE_GAMKNF */ + } + + /* Create preliminary gamut mapping rspl, without grid boundary values. */ + /* We use this to lookup the mapping for points on the source space gamut */ + /* that result from clipping our grid boundary points */ +#ifdef USE_BOUND + for (j = 0; j < 3; j++) { /* Set resolution for all axes */ + gres[j] = (mapres+1)/2; + avgdev[j] = GAMMAP_RSPLAVGDEV; + } + s->map = new_rspl(RSPL_NOFLAGS, 3, 3); /* Allocate 3D -> 3D */ + s->map->fit_rspl_w(s->map, GAMMAP_RSPLFLAGS, gpnts, ngamp, il, ih, gres, ol, oh, smooth, avgdev, NULL); + + /* Add input range grid surface anchor points to improve clipping behaviour. */ + if (defrgrid >= 2) { + DCOUNT(gc, 3, 3, 0, 0, defrgrid); + double cent[3]; + + sc_gam->getcent(d_gam, cent); + + DC_INIT(gc); + for (;;) { + /* If point is on the grid surface */ + if ( gc[0] == 0 || gc[0] == (defrgrid-1) + || gc[1] == 0 || gc[1] == (defrgrid-1) + || gc[2] == 0 || gc[2] == (defrgrid-1)) { + double grid2gamut, gamut2cent, ww; + co cp; + + /* Clip the point to the closest location on the source */ + /* colorspace gamut. */ + for (j = 0; j < 3; j++) + gpnts[ngamp].p[j] = il[j] + gc[j]/(defrgrid-1.0) * (ih[j] - il[j]); + sc_gam->nearest(sc_gam, cp.p, gpnts[ngamp].p); + + /* Then lookup the equivalent gamut mapped value */ + s->map->interp(s->map, &cp); + + for (j = 0; j < 3; j++) + gpnts[ngamp].v[j] = cp.v[j]; + + /* Compute the distance of the grid surface point to the to the */ + /* source colorspace gamut, as well as the distance from there */ + /* to the gamut center point. */ + for (grid2gamut = gamut2cent = 0.0, j = 0; j < 3; j++) { + double tt; + tt = gpnts[ngamp].p[j] - cp.p[j]; + grid2gamut += tt * tt; + tt = cp.p[j] - cent[j]; + gamut2cent += tt * tt; + } + grid2gamut = sqrt(grid2gamut); + gamut2cent = sqrt(gamut2cent); + + /* Make the weighting inversely related to distance, */ + /* to reduce influence on in gamut mapping shape, */ + /* while retaining some influence at the edge of the */ + /* grid. */ + ww = grid2gamut / gamut2cent; + if (ww > 1.0) + ww = 1.0; + + /* A low weight seems to be enough ? */ + /* the lower the better in terms of geting best hull mapping fidelity */ + gpnts[ngamp++].w = 0.05 * ww; + } + DC_INC(gc); + if (DC_DONE(gc)) + break; + } + } +#else /* !USE_BOUND */ + printf("!!!! Warning - gammap boundary points disabled !!!!\n"); +#endif /* !USE_BOUND */ + + /* --------------------------- */ + /* Compute the output bounding values, and check input range hasn't changed */ + for (i = 0; i < ngamp; i++) { + for (j = 0; j < 3; j++) { + if (gpnts[i].p[j] < (il[j]-1e-5) || gpnts[i].p[j] > (ih[j]+1e-5)) + warning("gammap internal: input bounds has changed! %f <> %f <> %f",il[j],gpnts[i].p[j],ih[j]); + if (gpnts[i].v[j] < ol[j]) + ol[j] = gpnts[i].v[j]; + if (gpnts[i].v[j] > oh[j]) + oh[j] = gpnts[i].v[j]; + } + } + + /* --------------------------- */ + +#ifdef NEVER /* Dump out all the mapping points */ + { + for (i = 0; i < ngamp; i++) { + printf("%d: %f %f %f -> %f %f %f\n",i, + gpnts[i].p[0], gpnts[i].p[1], gpnts[i].p[2], + gpnts[i].v[0], gpnts[i].v[1], gpnts[i].v[2]); + } + } +#endif + + /* Create the final gamut mapping rspl. */ + /* [ The smoothing is not as useful as it should be, because */ + /* if it is increased it tends to push colors out of gamut */ + /* where they get clipped. Some cleverer scheme which makes */ + /* sure that smoothness errs on the side of more compression */ + /* is needed. - Addressed in nearsmth now ? ] */ + /* How about converting to a delta filer ? ie. */ + /* create curren filter, then create point list of delta from */ + /* smoothed value, filtering that and then un-deltering it ?? */ + if (s->map != NULL) + s->map->del(s->map); + if (verb) + printf("Creating rspl..\n"); + for (j = 0; j < 3; j++) { /* Set resolution for all axes */ + gres[j] = mapres; + avgdev[j] = GAMMAP_RSPLAVGDEV; + } + s->map = new_rspl(RSPL_NOFLAGS, 3, 3); /* Allocate 3D -> 3D */ + if (s->map->fit_rspl_w(s->map, GAMMAP_RSPLFLAGS, gpnts, ngamp, il, ih, gres, ol, oh, smooth, avgdev, NULL)) { + if (verb) + fprintf(stderr,"Warning: Gamut mapping is non-monotonic - may not be very smooth !\n"); + } + /* return the min and max of the input values valid in the grid */ + s->map->get_in_range(s->map, s->imin, s->imax); + +#ifdef CHECK_NEARMAP + /* Check how accurate gamut shell mapping is against nsm */ + /* (This isn't a good indication now that vectors have been adjusted */ + /* to counteract the rspl smoothing at the edges.) */ + if (verb) { + double de, avgde = 0.0, maxde = 0.0; /* DE stats */ + + for (i = 0; i < nnsm; i++) { + double av[3]; + + /* Compute the mapping error */ + dopartialmap2(s, av, nsm[i].sv); /* Just the rspl */ + + de = icmLabDE(nsm[i].div, av); + avgde += de; + if (de > maxde) + maxde = de; + } + printf("Gamut hull fit to guides: = avg %f, max %f\n",avgde/nnsm,maxde); + } +#endif /* CHECK_NEARMAP */ + + /* If requested, enhance the saturation of the output values. */ + if (gmi->satenh > 0.0) { + adjustsat cx; /* Adjustment context */ + + /* Compute what our source white and black points actually maps to */ + s->domap(s, cx.wp, s_mt_wp); + s->domap(s, cx.bp, s_mt_bp); + + cx.dst = d_gam; + cx.satenh = gmi->satenh; + + /* Saturation enhance the output values */ + s->map->re_set_rspl( + s->map, /* this */ + 0, /* Combination of flags */ + (void *)&cx, /* Opaque function context */ + adjust_sat_func /* Function to set from */ + ); + } + + /* Test the gamut white and black point mapping, and "fine tune" */ + /* the mapping, to ensure an accurate transform of the white */ + /* and black points to the destination colorspace. */ + /* This compensates for any inacuracy introduced in the */ + /* various rspl mappings. */ + { + adjustwb cx; /* Adjustment context */ + double a_wp[3]; /* actual white point */ + double a_bp[3]; /* actual black point */ + + if (verb) + printf("Fine tuning white and black point mapping\n"); + + /* Check what the source white and black points actually maps to */ + s->domap(s, a_wp, s_mt_wp); + s->domap(s, a_bp, s_mt_bp); + +#ifdef VERBOSE + if (verb) { + printf("White is %f %f %f, should be %f %f %f\n", + a_wp[0], a_wp[1], a_wp[2], d_mt_wp[0], d_mt_wp[1], d_mt_wp[2]); + printf("Black is %f %f %f, should be %f %f %f\n", + a_bp[0], a_bp[1], a_bp[2], d_mt_bp[0], d_mt_bp[1], d_mt_bp[2]); + } +#endif /* VERBOSE */ + + /* Setup the fine tune transform */ + + /* We've decided not to fine tune the black point if we're */ + /* bending to the destination black, as the bend is not */ + /* followed perfectly (too sharp, or in conflict with */ + /* the surface mapping ?) and we don't want to shift */ + /* mid neutrals due to this. */ + /* We do fine tune it if dst_kbp is set though, since */ + /* we would like perfect K only out. */ + + /* Compute rotation/scale relative white point matrix */ + icmVecRotMat(cx.mat, a_wp, a_bp, d_mt_wp, d_mt_bp); /* wp & bp */ + + /* Fine tune the 3D->3D mapping */ + s->map->re_set_rspl( + s->map, /* this */ + 0, /* Combination of flags */ + (void *)&cx, /* Opaque function context */ + adjust_wb_func /* Function to set from */ + ); + +#ifdef VERBOSE + if (verb) { + /* Check what the source white and black points actually maps to */ + s->domap(s, a_wp, s_mt_wp); + s->domap(s, a_bp, s_mt_bp); + + printf("After fine tuning:\n"); + printf("White is %f %f %f, should be %f %f %f\n", + a_wp[0], a_wp[1], a_wp[2], d_mt_wp[0], d_mt_wp[1], d_mt_wp[2]); + printf("Black is %f %f %f, should be %f %f %f\n", + a_bp[0], a_bp[1], a_bp[2], d_mt_bp[0], d_mt_bp[1], d_mt_bp[2]); + } +#endif /* VERBOSE */ + } + + if (wrl != NULL) { + int arerings = 0; + double cc[3] = { 0.7, 0.7, 0.7 }; + double nc[3] = { 1.0, 0.4, 0.7 }; /* Pink for neighbors */ + int nix = -1; /* Index of point to show neighbour */ + +#ifdef SHOW_NEIGBORS +#ifdef NEVER + /* Show all neighbours */ + wrl->start_line_set(wrl, 0); + for (i = 0; i < nnsm; i++) { + for (j = 0; j < XNNB; j++) { + nearsmth *np = nsm[i].n[j]; /* Pointer to neighbor */ + + if (np == NULL) + break; + + wrl->add_col_vertex(wrl, 0, nsm[i].sv, nc); /* Source value */ + wrl->add_col_vertex(wrl, 0, np->sv, nc); /* Neighbpor value */ + } + } + wrl->make_lines(wrl, 0, 2); +#else + /* Show neighbours of points near source markers */ + for (i = 0; ; i++) { /* Add diagnostic markers */ + double pp[3]; + co cp; + int ix, bix; + double bdist = 1e6; + + if (markers[i].type == 0) + break; + + if (markers[i].type != 1) + continue; + + /* Rotate and map marker point the same as the src gamuts */ + icmMul3By3x4(pp, s->grot, markers[i].pos); + cp.p[0] = pp[0]; /* L value */ + s->grey->interp(s->grey, &cp); + pp[0] = cp.v[0]; +//printf("~1 looking for closest point to marker %d at %f %f %f\n",i,pp[0],pp[1],pp[2]); + + /* Locate the nearest source point */ + for (ix = 0; ix < nnsm; ix++) { + double dist = icmNorm33(pp, nsm[ix].sv); + if (dist < bdist) { + bdist = dist; + bix = ix; + } + } +//printf("~1 closest src point ix %d at %f %f %f\n",bix,nsm[bix].sv[0],nsm[bix].sv[1],nsm[bix].sv[2]); +//printf("~1 there are %d neighbours\n",nsm[bix].nnb); + + wrl->start_line_set(wrl, 0); + for (j = 0; j < nsm[bix].nnb; j++) { + nearsmth *np = nsm[bix].n[j].n; /* Pointer to neighbor */ + + wrl->add_col_vertex(wrl, 0, nsm[bix].sv, nc); /* Source value */ + wrl->add_col_vertex(wrl, 0, np->sv, nc); /* Neighbpor value */ + } + wrl->make_lines(wrl, 0, 2); + } +#endif +#endif /* SHOW_NEIGBORS */ + + /* Add the source and dest gamut surfaces */ +#ifdef PLOT_SRC_GMT + wrl->make_gamut_surface_2(wrl, sil_gam, 0.6, 0, cc); +#endif /* PLOT_SRC_GMT */ +#ifdef PLOT_DST_GMT + cc[0] = -1.0; + wrl->make_gamut_surface(wrl, d_gam, 0.2, cc); +#endif /* PLOT_DST_GMT */ +#ifdef PLOT_DIGAM + if (nsm[0].dgam == NULL) + error("Need to #define PLOT_DIGAM in nearsmth.c!"); + cc[0] = -1.0; + wrl->make_gamut_surface(wrl, nsm[0].dgam, 0.2, cc); +#endif /* PLOT_DIGAM */ +#ifdef PLOT_SRC_CUSPS + wrl->add_cusps(wrl, sil_gam, 0.6, NULL); +#endif /* PLOT_SRC_CUSPS */ +#ifdef PLOT_DST_CUSPS + wrl->add_cusps(wrl, d_gam, 0.3, NULL); +#endif /* PLOT_DST_CUSPS */ + +#ifdef PLOT_TRANSSRC_CUSPS + /* Add transformed source cusp markers */ + { + int i; + double cusps[6][3]; + double ccolors[6][3] = { + { 1.0, 0.1, 0.1 }, /* Red */ + { 1.0, 1.0, 0.1 }, /* Yellow */ + { 0.1, 1.0, 0.1 }, /* Green */ + { 0.1, 1.0, 1.0 }, /* Cyan */ + { 0.1, 0.1, 1.0 }, /* Blue */ + { 1.0, 0.1, 1.0 } /* Magenta */ + }; + + if (sc_gam->getcusps(sc_gam, cusps) == 0) { + + for (i = 0; i < 6; i++) { + double val[3]; + + s->domap(s, val, cusps[i]); + wrl->add_marker(wrl, val, ccolors[i], 2.5); + } + } + } +#endif + +#if defined(SHOW_MAP_VECTORS) || defined(SHOW_SUB_SURF) || defined(SHOW_ACTUAL_VECTORS) || defined(SHOW_ACTUAL_VEC_DIFF) + /* Start of guide vector plot */ + wrl->start_line_set(wrl, 0); + + for (i = 0; i < nnsm; i++) { + double cpexf; /* The effective compression or expansion factor */ + double yellow[3] = { 1.0, 1.0, 0.0 }; + double red[3] = { 1.0, 0.0, 0.0 }; + double green[3] = { 0.0, 1.0, 0.0 }; + double lgrey[3] = { 0.8, 0.8, 0.8 }; + double purp[3] = { 0.6, 0.0, 1.0 }; + double blue[3] = { 0.2, 0.2, 1.0 }; + double *ccc; + double mdst[3]; + +#if defined(SHOW_ACTUAL_VECTORS) || defined(SHOW_ACTUAL_VEC_DIFF) +# ifdef SHOW_ACTUAL_VECTORS + wrl->add_col_vertex(wrl, 0, nsm[i].sv, yellow); +# else /* SHOW_ACTUAL_VEC_DIFF */ + wrl->add_col_vertex(wrl, 0, nsm[i].div, yellow); +# endif + dopartialmap2(s, mdst, nsm[i].sv); + wrl->add_col_vertex(wrl, 0, mdst, red); + +#else +# ifdef SHOW_MAP_VECTORS + ccc = yellow; + + if (nsm[i].gflag == 0) + ccc = green; /* Mark "no clear direction" vectors in green->red */ +# ifdef SHOW_CUSPMAP + wrl->add_col_vertex(wrl, 0, nsm[i].csv, ccc); /* Cusp mapped source value */ +# else + wrl->add_col_vertex(wrl, 0, nsm[i].sv, ccc); /* Source value */ +# endif + wrl->add_col_vertex(wrl, 0, nsm[i].div, red); /* Blended destination value */ +# endif /* SHOW_MAP_VECTORS */ + +# ifdef SHOW_SUB_SURF + if (nsm[i].vflag != 0) { /* Sub surface point is available */ + + wrl->add_col_vertex(wrl, 0, nsm[i].sv2, lgrey); /* Subs-surf Source value */ + wrl->add_col_vertex(wrl, 0, nsm[i].div2, purp); /* Blended destination value */ + } +# endif /* SHOW_SUB_SURF */ +#endif /* !SHOW_ACTUAL_VECTORS */ + } + wrl->make_lines(wrl, 0, 2); /* Guide vectors */ +#endif /* Show vectors */ + +#ifdef SHOW_VECTOR_INDEXES + for (i = 0; i < nnsm; i++) { + double cream[3] = { 0.7, 0.7, 0.5 }; + char buf[100]; + sprintf(buf, "%d", i); + wrl->add_text(wrl, buf, nsm[i].sv, cream, 0.5); + } +#endif /* SHOW_VECTOR_INDEXES */ + + /* add the locus from locus.ts file */ + if (locus != NULL) { + int table, npoints; + char *fnames[3] = { "LAB_L", "LAB_A", "LAB_B" }; + int ix[3]; + double v0[3], v1[3]; + double rgb[3]; + + /* Each table holds a separate locus */ + for (table = 0; table < locus->ntables; table++) { + + if ((npoints = locus->t[table].nsets) <= 0) + error("No sets of data in diagnostic locus"); + + for (j = 0; j < 3; j++) { + if ((ix[j] = locus->find_field(locus, 0, fnames[j])) < 0) + error ("Locus file doesn't contain field %s",fnames[j]); + if (locus->t[table].ftype[ix[j]] != r_t) + error ("Field %s is wrong type",fnames[j]); + } + + /* Source locus */ + rgb[0] = 1.0; + rgb[1] = 0.5; + rgb[2] = 0.5; + for (i = 0; i < npoints; i++) { + co cp; + + for (j = 0; j < 3; j++) + v1[j] = *((double *)locus->t[table].fdata[i][ix[j]]); + + /* Rotate and locus verticies the same as the src gamuts */ + dopartialmap1(s, v1, v1); + if (i > 0 ) + wrl->add_cone(wrl, v0, v1, rgb, 0.5); + icmAry2Ary(v0,v1); + } + + /* Gamut mapped locus */ + rgb[0] = 1.0; + rgb[1] = 1.0; + rgb[2] = 1.0; + for (i = 0; i < npoints; i++) { + co cp; + + for (j = 0; j < 3; j++) + v1[j] = *((double *)locus->t[table].fdata[i][ix[j]]); + + s->domap(s, v1, v1); + if (i > 0 ) + wrl->add_cone(wrl, v0, v1, rgb, 0.5); + icmAry2Ary(v0,v1); + } + } + + locus->del(locus); + locus = NULL; + } + + /* Add any ring mapping diagnostics */ + for (i = 0; ; i++) { + if (rings[i].type == 0) + break; + + if (rings[i].type == 2) + continue; + + if (rings[i].type == 1) { + double pconst; + double cpoint[3]; + double mat[3][4]; /* translate to our plane */ + double imat[3][4]; /* translate from our plane */ + double s1[3], s0[3], t1[3]; + int j; + double maxa, mina; + double maxb, minb; + + if (arerings == 0) { + arerings = 1; + wrl->start_line_set(wrl, 1); /* Source ring */ + wrl->start_line_set(wrl, 2); /* Destination ring */ + } + + if (icmNormalize3(rings[i].pnorm, rings[i].pnorm, 1.0)) + error("Ring %d diagnostic plane normal failed",i); + + pconst = -icmDot3(rings[i].ppoint, rings[i].pnorm); + + /* Locate intersection of source neautral axis and plane */ + if (icmVecPlaneIsect(cpoint, pconst, rings[i].pnorm, s_cs_wp, s_cs_bp)) + error("Ring %d diagnostic center point intersection failed",i); + + /* Compute the rotation and translation between */ + /* a plane in ab and the plane we are using */ + s0[0] = s0[1] = s0[2] = 0.0; + s1[0] = 1.0, s1[1] = s1[2] = 0.0; + t1[0] = cpoint[0] + rings[i].pnorm[0]; + t1[1] = cpoint[1] + rings[i].pnorm[1]; + t1[2] = cpoint[2] + rings[i].pnorm[2]; + icmVecRotMat(mat, s1, s0, t1, cpoint); + icmVecRotMat(imat, t1, cpoint, s1, s0); + + /* Do a min/max of a circle of vectors so as to */ + /* establish an offset to the centroid for this slice */ + maxa = maxb = -1e60; + mina = minb = 1e60; + for (j = 0; j < 20; j++) { + double ang = 2 * 3.1415926 * j/(20 - 1.0); + double vec[3], isect[3]; + double pp[3]; + co cp; + int k; + + vec[0] = 0.0; + vec[1] = sin(ang); + vec[2] = cos(ang); + icmMul3By3x4(vec, mat, vec); + + /* Intersect it with the source gamut */ + if (si_gam->vector_isect(si_gam, vec, cpoint, isect, + NULL, NULL, NULL, NULL, NULL) == 0) { + continue; + } + + /* Translate back to plane */ + icmMul3By3x4(pp, imat, isect); + + if (pp[1] > maxa) + maxa = pp[1]; + if (pp[1] < mina) + mina = pp[1]; + if (pp[2] > maxb) + maxb = pp[2]; + if (pp[2] < minb) + minb = pp[2]; + } + /* Move center to centroid of min/max box */ + t1[0] = 0.0; + t1[1] = (maxa + mina) * 0.5; + t1[2] = (maxb + minb) * 0.5; + if (t1[1] < -200.0 || t1[1] > 200.0 + || t1[2] < -200.0 || t1[2] > 200.0) + error("Failed to locate centroid of slice"); + icmMul3By3x4(cpoint, mat, t1); + +//printf("~1 ring centroid point = %f %f %f\n", cpoint[0],cpoint[1],cpoint[2]); + + /* Recompute the rotation and translation between */ + /* a plane in ab and the plane we are using */ + s0[0] = s0[1] = s0[2] = 0.0; + s1[0] = 1.0, s1[1] = s1[2] = 0.0; + t1[0] = cpoint[0] + rings[i].pnorm[0]; + t1[1] = cpoint[1] + rings[i].pnorm[1]; + t1[2] = cpoint[2] + rings[i].pnorm[2]; + icmVecRotMat(mat, s1, s0, t1, cpoint); + icmVecRotMat(imat, t1, cpoint, s1, s0); + +//printf("~1 generating %d ring verts\n",rings[i].nverts); + /* Create a circle of vectors in the plane from the center */ + /* point, to intersect with the source gamut surface. */ + /* (Duplicate start and end vertex) */ + for (j = 0; j <= rings[i].nverts; j++) { + double ang = 2 * 3.1415926 * j/((double) rings[i].nverts); + double vec[3], isect[3]; + double pp[3]; + co cp; + int k; + + vec[0] = 0.0; + vec[1] = sin(ang); + vec[2] = cos(ang); + icmMul3By3x4(vec, mat, vec); + + /* Intersect it with the source gamut */ + if (si_gam->vector_isect(si_gam, vec, cpoint, isect, + NULL, NULL, NULL, NULL, NULL) == 0) { + warning("Ring %d vect %d diagnostic vector intersect failed",i,j); + continue; + } + +//printf("~1 vec %d = %f %f %f\n",j,isect[0],isect[1],isect[2]); + + /* Scale them to the ratio */ + for (k = 0; k < 3; k++) + vec[k] = isect[k] * rings[i].rad + (1.0 - rings[i].rad) * cpoint[k]; + +//printf("~1 rad vec %d = %f %f %f\n",j,vec[0],vec[1],vec[2]); + + /* Transform them into rotated and scaled destination space */ + dopartialmap1(s, vec, vec); +//printf("~1 trans vec %d = %f %f %f\n",j,vec[0],vec[1],vec[2]); + + /* Add to plot */ + wrl->add_col_vertex(wrl, 1, vec, rings[i].scol); +//printf("~1 src vec %d = %f %f %f\n",j,vec[0],vec[1],vec[2]); + + /* Gamut map and add to plot */ + s->domap(s, vec, vec); +//printf("~1 dst vec %d = %f %f %f\n",j,vec[0],vec[1],vec[2]); + wrl->add_col_vertex(wrl, 2, vec, rings[i].dcol); + } + wrl->make_last_vertex(wrl, 1); /* Source ring */ + wrl->make_last_vertex(wrl, 2); /* Destination ring */ + } + if (arerings) { + wrl->make_lines(wrl, 1, 1000000); /* Source ring */ + wrl->make_lines(wrl, 2, 1000000); /* Destination ring */ + } + } + + wrl->del(wrl); /* Write and delete */ + wrl = NULL; + } + +#ifdef PLOT_3DKNEES + /* Plot one graph per 3D gamut boundary mapping point */ + for (j = 0; j < p3dk_ix; j++) { + double xx[XRES]; + double yy[XRES]; + + printf("Vector %f %f %f -> %f %f %f\n", p3dk_locus[j].v0[0], p3dk_locus[j].v0[1], p3dk_locus[j].v0[2], p3dk_locus[j].v1[0], p3dk_locus[j].v1[1], p3dk_locus[j].v1[2]); + + for (i = 0; i < XRES; i++) { + double v; + co cp; /* Conversion point */ + v = (i/(double)(XRES-1.0)); + cp.p[0] = p3dk_locus[j].v0[0] + v * (p3dk_locus[j].v1[0] - p3dk_locus[j].v0[0]); + cp.p[1] = p3dk_locus[j].v0[1] + v * (p3dk_locus[j].v1[1] - p3dk_locus[j].v0[1]); + cp.p[2] = p3dk_locus[j].v0[2] + v * (p3dk_locus[j].v1[2] - p3dk_locus[j].v0[2]); + xx[i] = sqrt(cp.p[1] * cp.p[1] + cp.p[2] * cp.p[2]); + s->map->interp(s->map, &cp); + yy[i] = sqrt(cp.v[1] * cp.v[1] + cp.v[2] * cp.v[2]); + } + do_plot(xx,yy,NULL,NULL,XRES); + } + free(p3dk_locus); +#endif /* PLOT_3DKNEES */ + + + free(gpnts); + free_nearsmth(nsm, nnsm); + + } else if (diagname != NULL && verb) { + printf("Warning: Won't create '%s' because there is no 3D gamut mapping\n",diagname); + } + +#ifdef PLOT_GAMUTS + scl_gam->write_vrml(scl_gam, "src.wrl", 1, 0); + sil_gam->write_vrml(sil_gam, "img.wrl", 1, 0); + d_gam->write_vrml(d_gam, "dst.wrl", 1, 0); + sc_gam->write_trans_vrml(sc_gam, "gmsrc.wrl", 1, 0, map_trans, s); +#endif + + if (sil_gam != scl_gam) + sil_gam->del(sil_gam); + scl_gam->del(scl_gam); + + return s; +} + +#ifdef PLOT_GAMUTS + +/* Debug */ +static void map_trans(void *cntx, double out[3], double in[3]) { + gammap *map = (gammap *)cntx; + + map->domap(map, out, in); +} + +#endif + +/* Object methods */ +static void del_gammap( +gammap *s +) { + if (s->grey != NULL) + s->grey->del(s->grey); + if (s->igrey != NULL) + s->igrey->del(s->igrey); + if (s->map != NULL) + s->map->del(s->map); + + free(s); +} + +/* Apply the gamut mapping to the given color value */ +static void domap( +gammap *s, +double *out, +double *in +) { + double rin[3]; + co cp; + + if (s->dbg) printf("domap: got input %f %f %f\n",in[0],in[1],in[2]); + icmMul3By3x4(rin, s->grot, in); /* Rotate */ + + if (s->dbg) printf("domap: after rotate %f %f %f\n",rin[0],rin[1],rin[2]); + cp.p[0] = rin[0]; + s->grey->interp(s->grey, &cp); /* L map */ + + if (s->dbg) printf("domap: after L map %f %f %f\n",cp.v[0],rin[1],rin[2]); + + /* If there is a 3D->3D mapping */ + if (s->map != NULL) { + int e; + + /* Clip out of range a, b proportionately */ + if (rin[1] < s->imin[1] || rin[1] > s->imax[1] + || rin[2] < s->imin[2] || rin[2] > s->imax[2]) { + double as = 1.0, bs = 1.0; + if (rin[1] < s->imin[1]) + as = s->imin[1]/rin[1]; + else if (rin[1] > s->imax[1]) + as = s->imax[1]/rin[1]; + if (rin[2] < s->imin[2]) + bs = s->imin[2]/rin[2]; + else if (rin[2] > s->imax[2]) + bs = s->imax[2]/rin[2]; + if (bs < as) + as = bs; + rin[1] *= as; + rin[2] *= as; + } + + cp.p[0] = cp.v[0]; /* 3D map */ + cp.p[1] = rin[1]; + cp.p[2] = rin[2]; + s->map->interp(s->map, &cp); + + for (e = 0; e < s->map->fdi; e++) + out[e] = cp.v[e]; + + if (s->dbg) printf("domap: after 3D map %s\n\n",icmPdv(s->map->fdi, out)); + } else { + out[0] = cp.v[0]; + out[1] = rin[1]; + out[2] = rin[2]; + } +} + +/* Apply the matrix and grey mapping to the given color value */ +static void dopartialmap1( +gammap *s, +double *out, +double *in +) { + double rin[3]; + co cp; + + icmMul3By3x4(rin, s->grot, in); /* Rotate */ + cp.p[0] = rin[0]; + s->grey->interp(s->grey, &cp); /* L map */ + out[0] = cp.v[0]; + out[1] = rin[1]; + out[2] = rin[2]; +} + +/* Apply just the rspl mapping to the given color value */ +/* (ie. to a color already rotated and L mapped) */ +static void dopartialmap2( +gammap *s, +double *out, +double *in +) { + co cp; + + /* If there is a 3D->3D mapping */ + if (s->map != NULL) { + int e; + + icmCpy3(cp.p, in); + + /* Clip out of range a, b proportionately */ + if (cp.p[1] < s->imin[1] || cp.p[1] > s->imax[1] + || cp.p[2] < s->imin[2] || cp.p[2] > s->imax[2]) { + double as = 1.0, bs = 1.0; + if (cp.p[1] < s->imin[1]) + as = s->imin[1]/cp.p[1]; + else if (cp.p[1] > s->imax[1]) + as = s->imax[1]/cp.p[1]; + if (cp.p[2] < s->imin[2]) + bs = s->imin[2]/cp.p[2]; + else if (cp.p[2] > s->imax[2]) + bs = s->imax[2]/cp.p[2]; + if (bs < as) + as = bs; + cp.p[1] *= as; + cp.p[2] *= as; + } + + s->map->interp(s->map, &cp); + + icmCpy3(out, cp.v); + } else { + icmCpy3(out, in); + } +} + +/* Function to pass to rspl to invert grey curve */ +static void inv_grey_func( + void *cntx, + double *out, + double *in +) { + rspl *fwd = (rspl *)cntx; + int nsoln; /* Number of solutions found */ + co pp[2]; /* Room for all the solutions found */ + + pp[0].p[0] = + pp[0].v[0] = in[0]; + + nsoln = fwd->rev_interp( + fwd, + RSPL_NEARCLIP, /* Clip to nearest (faster than vector) */ + 2, /* Maximum number of solutions allowed for */ + NULL, /* No auxiliary input targets */ + NULL, /* Clip vector direction and length */ + pp); /* Input and output values */ + + nsoln &= RSPL_NOSOLNS; /* Get number of solutions */ + + if (nsoln != 1) + error("gammap: Unexpected failure to find reverse solution for grey axis lookup"); + + out[0] = pp[0].p[0]; +} + +/* Function to pass to rspl to alter output values, */ +/* to enhance the saturation. */ +static void +adjust_sat_func( + void *pp, /* adjustsat structure */ + double *out, /* output value to be adjusted */ + double *in /* corresponding input value */ +) { + adjustsat *p = (adjustsat *)pp; + double cp[3]; /* Center point */ + double rr, t1[3], p1; + double t2[3], p2; + + /* Locate center point on the white/black axis corresponding to this color */ + cp[0] = out[0]; + rr = (out[0] - p->bp[0])/(p->wp[0] - p->bp[0]); /* Relative location on the white/black axis */ + cp[1] = p->bp[1] + rr * (p->wp[1] - p->bp[1]); + cp[2] = p->bp[2] + rr * (p->wp[2] - p->bp[2]); + + /* Locate the point on the destination gamut surface in the direction */ + /* from the center point to the point being processed. */ + if (p->dst->vector_isect(p->dst, cp, out, t2, t1, &p2, &p1, NULL, NULL) != 0) { + + if (p1 > 1.0) { /* If this point is within gamut */ + double ep1, bf; + +//printf("\n"); +//printf("~1 cp %f %f %f input %f %f %f\n",cp[0],cp[1],cp[2], out[0], out[1], out[2]); +//printf("~1 min %f %f %f mint %f\n",t2[0],t2[1],t2[2],p2); +//printf("~1 max %f %f %f maxt %f\n",t1[0],t1[1],t1[2],p1); + + p1 = 1.0/p1; /* Position of out from cp to t1 */ + +#ifdef NEVER + /* Enhanced parameter value */ + ep1 = (p1 + p->satenh * p1)/(1.0 + p->satenh * p1); + /* Make blend between linear p1 and enhanced p1, */ + /* to reduce effects on near neutrals. */ + p1 = (1.0 - p1) * p1 + p1 * ep1; +#else + /* Compute Enhanced p1 */ + ep1 = (p1 + p->satenh * p1)/(1.0 + p->satenh * p1); + + /* Make blend factor between linear p1 and enhanced p1, */ + /* to reduce effects on near neutrals. */ + { + double pp = 4.0; /* Sets where the 50% transition is */ + double g = 2.0; /* Sets rate of transition */ + double sec, vv = p1; + + vv = vv/(pp - pp * vv + 1.0); + + vv *= 2.0; + sec = floor(vv); + if (((int)sec) & 1) + g = -g; /* Alternate action in each section */ + vv -= sec; + if (g >= 0.0) { + vv = vv/(g - g * vv + 1.0); + } else { + vv = (vv - g * vv)/(1.0 - g * vv); + } + vv += sec; + vv *= 0.5; + + bf = (vv + pp * vv)/(1.0 + pp * vv); + } + /* Do the blend */ + p1 = (1.0 - bf) * p1 + bf * ep1; +#endif + /* Compute enhanced values position */ + out[0] = cp[0] + (t1[0] - cp[0]) * p1; + out[1] = cp[1] + (t1[1] - cp[1]) * p1; + out[2] = cp[2] + (t1[2] - cp[2]) * p1; +//printf("~1 output %f %f %f, param %f\n",out[0],out[1],out[2],p1); + } + } +} + +/* Function to pass to rspl to re-set output values, */ +/* to adjust the white and black points */ +static void +adjust_wb_func( + void *pp, /* adjustwb structure */ + double *out, /* output value to be adjusted */ + double *in /* corresponding input value */ +) { + adjustwb *p = (adjustwb *)pp; + + /* Do a linear mapping from swp -> dwp and sbp -> dbp, */ + /* to compute the adjusted value. */ + icmMul3By3x4(out, p->mat, out); +} + + +/* Create a new gamut that the the given gamut transformed by the */ +/* gamut mappings rotation and grey curve mapping. Return NULL on error. */ +static gamut *parttransgamut(gammap *s, gamut *src) { + gamut *dst; + double cusps[6][3]; + double wp[3], bp[3], kp[3]; + double p[3]; + int i; + + if ((dst = new_gamut(src->getsres(src), src->getisjab(src), src->getisrast(src))) == NULL) + return NULL; + + dst->setnofilt(dst); + + /* Translate all the surface nodes */ + for (i = 0;;) { + if ((i = src->getrawvert(src, p, i)) < 0) + break; + + dopartialmap1(s, p, p); + dst->expand(dst, p); + } + /* Translate cusps */ + if (src->getcusps(src, cusps) == 0) { + dst->setcusps(dst, 0, NULL); + for (i = 0; i < 6; i++) { + dopartialmap1(s, p, cusps[i]); + dst->setcusps(dst, 1, p); + } + dst->setcusps(dst, 2, NULL); + } + /* Translate white and black points */ + if (src->getwb(src, wp, bp, kp, NULL, NULL, NULL) == 0) { + dopartialmap1(s, wp, wp); + dopartialmap1(s, bp, bp); + dopartialmap1(s, kp, kp); + dst->setwb(dst, wp, bp, kp); + } + + return dst; +} + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3