summaryrefslogtreecommitdiff
path: root/link/collink.c
diff options
context:
space:
mode:
Diffstat (limited to 'link/collink.c')
-rw-r--r--link/collink.c1752
1 files changed, 1597 insertions, 155 deletions
diff --git a/link/collink.c b/link/collink.c
index 6c40efd..3bb93b7 100644
--- a/link/collink.c
+++ b/link/collink.c
@@ -1,4 +1,6 @@
+/* Version with Lab bt.1886 */
+
/*
* collink
*
@@ -22,6 +24,7 @@
*
* Abstract link support intent doesn't work properly for anything
* other than absolute. This should really be fixed.
+ *
*/
/* NOTES:
@@ -51,7 +54,6 @@
The source profile per channel curve plus
a Y to L* curve it's a Matrix profile.
-
Colorspace representations are a bit of a mess. It's hard to know what space
color is in at any point, and difficult to transform to match
some other element. Putting the different colorspace support within
@@ -96,6 +98,8 @@
#undef NEUTKDEBUG /* print info about neutral L -> K mapping */
+#undef LINTERP_OR /* Use simple extrapolation of Video encoded overrage values */
+
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
@@ -104,12 +108,12 @@
#include <math.h>
#include "copyright.h"
#include "aconfig.h"
+#include "counters.h"
#include "numlib.h"
#include "icc.h"
#include "xicc.h"
#include "gamut.h"
#include "gammap.h"
-// ~~~99
#include "vrml.h"
void usage(char *diag, ...) {
@@ -140,6 +144,8 @@ void usage(char *diag, ...) {
fprintf(stderr," -F Special :- Force all colors to be K only output\n");
fprintf(stderr," -fcmy Special :- Force 100%% C,M or Y only to stay pure \n");
fprintf(stderr," -p absprof Include abstract profile in link\n");
+ fprintf(stderr," -a file.cal Apply calibration curves to link output and append linear\n");
+ fprintf(stderr," -H file.cal Append calibration curves to 3dlut\n");
fprintf(stderr," -s Simple Mode (default)\n");
fprintf(stderr," -g [src.gam] Gamut Mapping Mode [optional source image gamut]\n");
fprintf(stderr," -G [src.gam] Gamut Mapping Mode using inverse outprofile A2B\n");
@@ -176,10 +182,11 @@ void usage(char *diag, ...) {
fprintf(stderr," w:x:y Adapted white point as x, y\n");
fprintf(stderr," a:adaptation Adaptation luminance in cd.m^2 (default 50.0)\n");
fprintf(stderr," b:background Background %% of image luminance (default 20)\n");
- fprintf(stderr," l:scenewhite Scene white in cd.m^2 if surround = auto (default 250)\n");
- fprintf(stderr," f:flare Flare light %% of image luminance (default 1)\n");
- fprintf(stderr," f:X:Y:Z Flare color as XYZ (default media white)\n");
- fprintf(stderr," f:x:y Flare color as x, y\n");
+ fprintf(stderr," l:imagewhite Image white in cd.m^2 if surround = auto (default 250)\n");
+ fprintf(stderr," f:flare Flare light %% of image luminance (default 0)\n");
+ fprintf(stderr," g:glare Flare light %% of ambient (default 1)\n");
+ fprintf(stderr," g:X:Y:Z Flare color as XYZ (default media white, Abs: D50)\n");
+ fprintf(stderr," g:x:y Flare color as x, y\n");
fprintf(stderr," -t tlimit set source total ink limit, 0 - 400%% (estimate by default)\n");
fprintf(stderr," -T klimit set source black ink limit, 0 - 100%% (estimate by default)\n");
fprintf(stderr," Inverse outprofile A2B Options:\n");
@@ -193,11 +200,56 @@ void usage(char *diag, ...) {
fprintf(stderr," -K parameters Same as -k, but target is K locus rather than K value itself\n");
fprintf(stderr," -l tlimit set destination total ink limit, 0 - 400%% (estimate by default)\n");
fprintf(stderr," -L klimit set destination black ink limit, 0 - 100%% (estimate by default)\n");
+ fprintf(stderr," -3 flag Create \"3DLut\" output file as well as devlink\n");
+ fprintf(stderr," e eeColor .txt file\n");
+ fprintf(stderr," m MadVR .3dlut\t file\n");
+ fprintf(stderr," -I b Apply BT.1886-like mapping with effective gamma 2.2 to input\n");
+ fprintf(stderr," -I b:g.g Apply BT.1886-like mapping with effective gamma g.g to input\n");
+ fprintf(stderr," -I B Apply BT.1886 mapping with technical gamma 2.4 to input\n");
+ fprintf(stderr," -I B:g.g Apply BT.1886 mapping with technical gamma g.g to input\n");
+ fprintf(stderr," -e flag Video encode input as:\n");
+ fprintf(stderr," -E flag Video encode output as:\n");
+ fprintf(stderr," n normal 0..1 full range RGB levels (default)\n");
+ fprintf(stderr," t (16-235)/255 \"TV\" RGB levels\n");
+ fprintf(stderr," 6 Rec601 YCbCr SD (16-235,240)/255 \"TV\" levels\n");
+ fprintf(stderr," 7 Rec709 1125/60Hz YCbCr HD (16-235,240)/255 \"TV\" levels\n");
+ fprintf(stderr," 5 Rec709 1250/50Hz YCbCr HD (16-235,240)/255 \"TV\" levels\n");
+ fprintf(stderr," 2 Rec2020 YCbCr UHD (16-235,240)/255 \"TV\" levels\n");
+ fprintf(stderr," C Rec2020 Constant Luminance YCbCr UHD (16-235,240)/255 \"TV\" levels\n");
+ fprintf(stderr," x xvYCC Rec601 YCbCr Rec709 Prims. SD (16-235,240)/255 \"TV\" levels\n");
+ fprintf(stderr," X xvYCC Rec709 YCbCr Rec709 Prims. HD (16-235,240)/255 \"TV\" levels\n");
fprintf(stderr," -P Create gamut gammap.wrl diagostic\n");
exit(1);
}
/* ------------------------------------------- */
+
+#ifdef NEVER
+/* If video encoding is being used, the edge of the video range */
+/* may not fall on a gid boundary, leading to innacuracies */
+/* in black, white, or the saturated colors. */
+/* To fix this, we notice which gid values just get clipped */
+/* by the video encoding, and after the grid is complete, */
+/* do a fixup pass to set the grid value to that needed */
+/* to make the edge value perferct. */
+
+/* For the eeColor wich has a fixed grod res. of 65, */
+/* the Luminance range 16-235 lands perfectly on a grid point, */
+/* and the CbCr maximum value of 240 missed by 1/256, */
+/* so we haven't yet implemented this fixup */
+
+/* Instead we are just tweaking the cLUT values of the black */
+/* point to avoid any error at this critical value. */
+
+typedef struct {
+ int clip; /* Clip mask */
+ int ix[MXDI]; /* Index of the grid point that was clipped */
+ double in[MXDI]; /* grid value that was clipped */
+ double out[MXDO]; /* clipped value */
+} edgepoints;
+#endif
+
+/* ------------------------------------------- */
/* structures to support icc calbacks */
/* Information needed from a profile */
@@ -205,6 +257,7 @@ struct _profinfo {
/* Setup parameters */
icRenderingIntent intent; /* Selected ICC rendering intent */
icxViewCond vc; /* Viewing Condition for CAM */
+ int vc_set; /* vc may not be default (for verb) */
int inking; /* k inking algorithm, 0 = input, 1 = min, */
/* 2 = 0.5, 3 = max, 4 = ramp, 5 = curve, 6 = dual curve */
/* 7 = outpupt profile K value */
@@ -222,6 +275,21 @@ struct _profinfo {
int nocurve; /* NZ to not use device curve in per channel curve */
int lcurve; /* 1 to apply a Y like to L* curve for XYZ Matrix profiles */
/* 2 to apply a Y to L* curve for XYZ space */
+ int tvenc; /* 0 = full range RGB, 1 = RGB Video Level encoding, */
+ /* 2 = Rec601 YCbCr encoding, 3 = Rec709 1150/60/2:1 YCbCr encoding */
+ /* 4 = Rec709 1250/50/2:1 YCbCr encoding */
+ /* 5 = Rec2020 Non-constant Luminance YCbCr encoding */
+ /* 6 = Rec2020 Constant Luminance YCbCr encoding */
+ /* 7 = xvYCC with Rec601 YCbCr encoding with Rec709 primaries */
+ /* 8 = xvYCC with Rec709 YCbCr encoding with Rec709 primaries */
+ /* (We save Video YCbCr as "RGB" space ICC profile) */
+ int bt1886; /* 1 to apply BT.1886 black point & effective gamma to input */
+ /* 2 to apply BT.1886 black point & technical gamma to input */
+ double egamma; /* effective gamma to ain for */
+ double tgamma; /* technical gamma to ain for */
+ bt1886_info bt; /* BT.1886 adjustment info */
+ double rgb_bk[3]; /* Linear light input RGB black to bend to */
+ double bt_bk[3]; /* Input profile bt.1886 modified black point in gamut map space */
double wp[3]; /* Lab/Jab white point for profile used by wphack & xyzscale */
icxLuBase *b2aluo; /* B2A lookup for inking == 7 */
}; typedef struct _profinfo profinfo;
@@ -238,6 +306,7 @@ struct _clink {
int src_kbp; /* nz = Use K only black point as src gamut black point */
int dst_kbp; /* nz = 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 tdlut; /* nz = 3DLut output, 1 = eeColor format, 2 = MadVR format */
icColorSpaceSignature pcsor; /* PCS to use between in & out profiles */
@@ -256,6 +325,10 @@ struct _clink {
xicc *abs_xicc;
icxLuBase *abs_luo; /* NULL if none */
+ int addcal; /* 1 = apply cal to 3dLut and set linear cal1 */
+ /* 2 = set cal1 to cal */
+ xcal *cal; /* Calibration to apply, NULL if none */
+
/* (We current assume that xyzscale can't be used with gmi) */
double xyzscale; /* < 1.0 if Y is to be scaled in destination XYZ space */
double swxyz[3]; /* Source white point in XYZ */
@@ -264,6 +337,8 @@ struct _clink {
gammap *map; /* Gamut mapping */
gammap *Kmap; /* Gamut mapping K in to K out nhack == 2 and K in to K out */
+// edgepoints *epl; /* Edge point list for fixups when in.tvenc */
+ /* we need 2 * di * gres ^ (di-1) entries max. */
/* Per profile setup information */
profinfo in;
@@ -334,6 +409,130 @@ static void l2y_curve(double *out, double *in, int isXYZ) {
}
/* ------------------------------------------- */
+
+/* Clip a value to the RGB Video range 16..235 RGB */
+/* Return a bit mask of the channels that have clipped */
+/* Clip the incoming value clip[] in place */
+/* Return the uncliped value in unclipped[] */
+/* Return the full value in the clip direction in full[] */
+static int clipVidRGB(double full[3], double unclipped[3], double clip[3]) {
+ int i, os = 0;
+ for (i = 0; i < 3; i++) {
+ unclipped[i] = clip[i];
+ if (clip[i] < (16.0/255.0)) {
+ clip[i] = (16.0/255.0);
+ full[i] = 0.0;
+ os |= (1 << i);
+ } else if (clip[i] > (235.0/255.0)) {
+ clip[i] = (235.0/255.0);
+ full[i] = 1.0;
+ os |= (1 << i);
+ }
+ }
+ return os;
+}
+
+/* Clip a value to the YCbCr range range 16..235, 16..240 */
+/* Return a bit mask of the channels that have clipped */
+/* Clip the incoming value clip[] in place */
+/* Return the uncliped value in unclipped[] */
+/* Return the full value in the clip direction in full[] */
+static int clipYCrCb(double full[3], double unclipped[3], double clip[3]) {
+ int os = 0;
+
+ unclipped[0] = clip[0];
+ if (clip[0] < (16.0/255.0)) {
+ clip[0] = (16.0/255.0);
+ full[0] = 0.0;
+ os |= 1;
+ } else if (clip[0] > (235.0/255.0)) {
+ clip[0] = (235.0/255.0);
+ full[0] = 1.0;
+ os |= 1;
+ }
+
+ unclipped[1] = clip[1];
+ if (clip[1] < (16.0/255.0)) {
+ clip[1] = (16.0/255.0);
+ full[1] = 0.0;
+ os |= 2;
+ } else if (clip[1] > (240.0/255.0)) {
+ clip[1] = (240.0/255.0);
+ full[1] = 1.0;
+ os |= 2;
+ }
+
+ unclipped[2] = clip[2];
+ if (clip[2] < (16.0/255.0)) {
+ clip[2] = (16.0/255.0);
+ full[2] = 0.0;
+ os |= 4;
+ } else if (clip[2] > (240.0/255.0)) {
+ clip[2] = (240.0/255.0);
+ full[2] = 1.0;
+ os |= 4;
+ }
+
+ return os;
+}
+
+/* Clip a value to the xvYCC range range 16..235, 0..255 */
+/* (We should clip CbCr to 1..254 range, but unless we are using */
+/* a 256 res cLUT, this would mess up the mapping at the edges.) */
+/* Return a bit mask of the channels that have clipped */
+/* Clip the incoming value clip[] in place */
+/* Return the uncliped value in unclipped[] */
+/* Return the full value in the clip direction in full[] */
+static int clip_xvYCC(double full[3], double unclipped[3], double clip[3]) {
+ int os = 0;
+
+ unclipped[0] = clip[0];
+ if (clip[0] < (16.0/255.0)) {
+ clip[0] = (16.0/255.0);
+ full[0] = 0.0;
+ os |= 1;
+ } else if (clip[0] > (235.0/255.0)) {
+ clip[0] = (235.0/255.0);
+ full[0] = 1.0;
+ os |= 1;
+ }
+
+ unclipped[1] = clip[1];
+ unclipped[2] = clip[2];
+
+ return os;
+}
+
+/* Apply the Rec709 power curve to extended values using symetry */
+static void xvYCC_fwd_curve(double *out, double *in) {
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ if (fabs(in[i]) <= 0.081)
+ out[i] = in[i]/4.5;
+ else {
+ if (in[i] < 0.0)
+ out[i] = -pow((0.099 + -in[i])/1.099, 1.0/0.45);
+ else
+ out[i] = pow((0.099 + in[i])/1.099, 1.0/0.45);
+ }
+ }
+}
+
+/* Apparently xvYCC601 uses the Rec709 primaries */
+
+/* Convert RGB to D50 Rec709 RGB */
+static void xvYCC_fwd_matrix(double *out, double *in) {
+ double mat[3][3] = {
+ { 0.436029, 0.385099, 0.143072 },
+ { 0.222438, 0.716942, 0.060621 },
+ { 0.013897, 0.097076, 0.713926 }
+ };
+
+ icmMulBy3x3(out, mat, in);
+}
+
+/* ======================================================= */
/* Functions called back in setting up the transform table */
#ifdef DEBUGC
@@ -346,6 +545,10 @@ static int tt = 0;
void devi_devip(void *cntx, double *out, double *in) {
int rv = 0;
clink *p = (clink *)cntx;
+ int i, clip = 0;
+ double uc[3]; /* Unclipped values (Video) */
+ double cin[3]; /* clipped input value (Video) */
+ double full[3]; /* Full value in clip direction (Video) */
#ifdef DEBUGC
if (in[0] == 1.0 && in[1] == 1.0 && in[2] == 1.0 && in[3])
@@ -356,30 +559,46 @@ void devi_devip(void *cntx, double *out, double *in) {
#ifdef DEBUGC
DEBUGC
#endif
- printf("DevIn->DevIn' got %f %f %f %f\n",in[0], in[1], in[2], in[3]); fflush(stdout);
+ printf("DevIn->DevIn' got %s\n",icmPdv(p->in.chan, in));
#endif
- if (p->in.nocurve) { /* Don't use profile per channel curves */
- int i;
- for (i = 0; i < p->in.chan; i++)
- out[i] = in[i];
- } else { /* Use input profile per channel curves */
+ for (i = 0; i < p->in.chan; i++)
+ out[i] = in[i];
+
+ if (!p->in.nocurve) { /* Using profile per channel curves */
+
+ /* Video encoding decode */
+ if (p->in.tvenc == 1) { /* Video 16-235 range */
+ clip = clipVidRGB(full, uc, out);
+ icmCpy3(cin, out);
+ icmVidRGB_2_RGB(out, out);
+#ifdef DEBUG
+#ifdef DEBUGC
+ DEBUGC
+#endif
+ printf("After TVdecode:\n",icmPdv(p->in.chan, out));
+#endif
+
+ } else if (p->in.tvenc >= 2) { /* YCbCr */
+ error("Can't use input curves with YCbCr input encoding");
+ }
+
switch(p->in.alg) {
case icmMonoFwdType: {
icxLuMono *lu = (icxLuMono *)p->in.luo; /* Safe to coerce */
- rv |= lu->fwd_curve(lu, out, in);
+ rv |= lu->fwd_curve(lu, out, out);
break;
}
case icmMatrixFwdType: {
icxLuMatrix *lu = (icxLuMatrix *)p->in.luo; /* Safe to coerce */
- rv |= lu->fwd_curve(lu, out, in);
+ rv |= lu->fwd_curve(lu, out, out);
break;
}
case icmLutType: {
icxLuLut *lu = (icxLuLut *)p->in.luo; /* Safe to coerce */
/* Since not PCS, in_abs and matrix cannot be valid, */
/* so input curve on own is ok to use. */
- rv |= lu->input(lu, out, in);
+ rv |= lu->input(lu, out, out);
break;
}
default:
@@ -387,18 +606,41 @@ void devi_devip(void *cntx, double *out, double *in) {
}
if (rv >= 2)
error("icc lookup failed: %d, %s",p->in.c->errc,p->in.c->err);
+
+ /* Create linear interpolation from clip to full range */
+ if (clip && p->in.tvenc) {
+ for (i = 0; i < 3; i++) {
+ if (clip & (1 << i)) {
+ out[i] = out[i] + (uc[i] - cin[i])/(full[i] - cin[i]) * (full[i] - out[i]);
+ }
+ }
+ }
}
if (p->in.lcurve) { /* Apply Y to L* */
-//printf("~1 y2l_curve got %f %f %f, isXYZ %d\n",in[0],in[1],in[2],p->in.lcurve == 2);
y2l_curve(out, out, p->in.lcurve == 2);
}
+ /* eeColor cLUT is fake 65^3 - only 64^3 is usable. This affects */
+ /* full range and xvYCC RGB, so map inputs to cLUT to only use 64^3 */
+ if (p->tdlut == 1) {
+ if (p->in.tvenc == 0) { /* Full range */
+ for (i = 0; i < p->in.chan; i++)
+ out[i] = out[i] * (p->clutres-2.0)/(p->clutres-1.0);
+
+ /* This isn't actually usable, because the eeColor does its own YCbCr conversion */
+ } else if (p->in.tvenc == 7 || p->in.tvenc == 8) { /* xvYCC */
+ out[0] = out[0];
+ out[1] = (out[1] * (p->clutres-3.0) + 1.0)/(p->clutres-1.0); /* Keep symetrical */
+ out[2] = (out[2] * (p->clutres-3.0) + 1.0)/(p->clutres-1.0);
+ }
+ }
+
#ifdef DEBUG
#ifdef DEBUGC
DEBUGC
#endif
- printf("DevIn->DevIn' ret %f %f %f %f\n",out[0], out[1], out[2], in[3]); fflush(stdout);
+ printf("DevIn->DevIn' ret %s\n",icmPdv(p->in.chan, out));
#endif
}
@@ -406,6 +648,7 @@ void devi_devip(void *cntx, double *out, double *in) {
/* - - - - - - - - - - - - */
/* clut, DevIn' -> DevOut' */
void devip_devop(void *cntx, double *out, double *in) {
+ double oin[MAX_CHAN]; /* original input values */
double win[MAX_CHAN]; /* working input values */
double pcsv[MAX_CHAN]; /* PCS intermediate value, pre-gamut map */
double pcsvm[MAX_CHAN]; /* PCS intermediate value, post-gamut map */
@@ -416,14 +659,89 @@ void devip_devop(void *cntx, double *out, double *in) {
int cmytrig = 0; /* CMY output hack triggered */
int i, rv = 0;
clink *p = (clink *)cntx;
+ int clip = 0; /* clip mask (Video) */
+ double cin[3]; /* clipped input value (Video) */
+ double uc[3]; /* Unclipped values (Video) */
+ double full[3]; /* Full value in clip direction (Video) */
#ifdef DEBUG
#ifdef DEBUGC
DEBUGC
#endif
- printf("DevIn'->DevOut' got %f %f %f %f\n",in[0], in[1], in[2], in[3]); fflush(stdout);
+ printf("DevIn'->DevOut' got %s\n",icmPdv(p->in.chan, in));
#endif
+ /* Make a copy so we can modify it and are not affected when we write */
+ /* to out when out == in */
+ for (i = 0; i < p->in.chan; i++)
+ win[i] = oin[i] = in[i];
+
+ /* eeColor cLUT is fake 65^3 - only 64^3 is usable. This affects */
+ /* full range and xvYCC RGB, so un-map inputs to cLUT to only use 64^3 */
+ if (p->tdlut == 1) {
+ if (p->in.tvenc == 0) {
+ for (i = 0; i < p->in.chan; i++)
+ win[i] = win[i] * (p->clutres-1.0)/(p->clutres-2.0);
+
+ /* This isn't actually usable, because the eeColor does its own YCbCr conversion */
+ } else if (p->in.tvenc == 7 || p->in.tvenc == 8) { /* xvYCC */
+ win[0] = win[0];
+ win[1] = (win[1] * (p->clutres-1.0) - 1.0)/(p->clutres-3.0);
+ win[2] = (win[2] * (p->clutres-1.0) - 1.0)/(p->clutres-3.0);
+ }
+ }
+
+ if (p->in.nocurve) { /* Not using profile per channel curves */
+ /* Video encoding decode */
+ if (p->in.tvenc == 1) { /* Video 16-235 range */
+ clip = clipVidRGB(full, uc, win);
+ icmCpy3(cin, win);
+ icmVidRGB_2_RGB(win, win);
+ } else if (p->in.tvenc == 2) { /* Rec601 YCbCr */
+ clip = clipYCrCb(full, uc, win);
+ icmCpy3(cin, win);
+ icmRecXXX_YCbCr_2_YPbPr(win, win);
+ icmRec601_YPbPr_2_RGBd(win, win);
+ } else if (p->in.tvenc == 3) { /* Rec709 1150/60/2:1 YCbCr */
+ clip = clipYCrCb(full, uc, win);
+ icmCpy3(cin, win);
+ icmRecXXX_YCbCr_2_YPbPr(win, win);
+ icmRec709_YPbPr_2_RGBd(win, win);
+ } else if (p->in.tvenc == 4) { /* Rec709 1250/50/2:1 YCbCr */
+ clip = clipYCrCb(full, uc, win);
+ icmCpy3(cin, win);
+ icmRecXXX_YCbCr_2_YPbPr(win, win);
+ icmRec709_50_YPbPr_2_RGBd(win, win);
+ } else if (p->in.tvenc == 5) { /* Rec2020 Non-constant Luminance YCbCr encoding */
+ clip = clipYCrCb(full, uc, win);
+ icmCpy3(cin, win);
+ icmRecXXX_YCbCr_2_YPbPr(win, win);
+ icmRec2020_NCL_YPbPr_2_RGBd(win, win);
+ } else if (p->in.tvenc == 6) { /* Rec2020 Constant Luminance YCbCr encoding */
+ clip = clipYCrCb(full, uc, win);
+ icmCpy3(cin, win);
+ icmRecXXX_YCbCr_2_YPbPr(win, win);
+ icmRec2020_CL_YPbPr_2_RGBd(win, win);
+ } else if (p->in.tvenc == 7) { /* SD xvYCC with Rec601 YCbCr encoding */
+ clip = clip_xvYCC(full, uc, win);
+ icmCpy3(cin, win);
+ icmRecXXX_YCbCr_2_YPbPr(win, win);
+ icmRec601_YPbPr_2_RGBd(win, win);
+ } else if (p->in.tvenc == 8) { /* HD xvYCC with Rec709 YCbCr encoding */
+ clip = clip_xvYCC(full, uc, win);
+ icmCpy3(cin, win);
+ icmRecXXX_YCbCr_2_YPbPr(win, win);
+ icmRec709_YPbPr_2_RGBd(win, win);
+ }
+
+#ifdef DEBUG
+#ifdef DEBUGC
+ DEBUGC
+#endif
+ printf("After TVdecode: %s\n",icmPdv(p->in.chan, win));
+#endif
+ }
+
#ifdef ENKHACK
/* Handle neutral recognition/output K only hack */
/* (see discussion at top of file for generalization of this idea) */
@@ -436,30 +754,30 @@ void devip_devop(void *cntx, double *out, double *in) {
/* input space device values here. It also made sure that there are at */
/* least 3 input channels. */
- if (fabs(in[0] - in[1]) < thr
- && fabs(in[1] - in[2]) < thr
- && fabs(in[2] - in[0]) < thr)
+ if (fabs(win[0] - win[1]) < thr
+ && fabs(win[1] - win[2]) < thr
+ && fabs(win[2] - win[0]) < thr)
ntrig = 1; /* K only output triggered flag */
} else if (p->nhack == 2) {
double maxcmy; /* Comute a degree of source "K onlyness" */
double maxcmyk;
- maxcmy = in[0]; /* Compute minimum of CMY */
- if (in[1] > maxcmy)
- maxcmy = in[1];
- if (in[2] > maxcmy)
- maxcmy = in[2];
+ maxcmy = win[0]; /* Compute minimum of CMY */
+ if (win[1] > maxcmy)
+ maxcmy = win[1];
+ if (win[2] > maxcmy)
+ maxcmy = win[2];
maxcmyk = maxcmy; /* Compute minimum of all inks */
- if (in[3] > maxcmyk)
- maxcmyk = in[3];
+ if (win[3] > maxcmyk)
+ maxcmyk = win[3];
-//printf("~1 maxcmy = %f, maxcmyk = %f, in[3] = %f\n",maxcmy,maxcmyk,in[3]);
- if (in[3] <= 0.0 || maxcmy > in[3]) {
+//printf("~1 maxcmy = %f, maxcmyk = %f, win[3] = %f\n",maxcmy,maxcmyk,win[3]);
+ if (win[3] <= 0.0 || maxcmy > win[3]) {
konlyness = 0.0;
} else {
- konlyness = (in[3] - maxcmy)/in[3];
+ konlyness = (win[3] - maxcmy)/win[3];
}
/* As we approach no colorant, blend towards no Konlyness */
@@ -467,9 +785,9 @@ void devip_devop(void *cntx, double *out, double *in) {
konlyness *= maxcmyk/0.2;
/* We want to see if the input colors are exactly K only. */
- if (in[0] < thr
- && in[1] < thr
- && in[2] < thr)
+ if (win[0] < thr
+ && win[1] < thr
+ && win[2] < thr)
ntrig = 1; /* K only output triggered flag */
#ifdef DEBUG
#ifdef DEBUGC
@@ -485,46 +803,36 @@ void devip_devop(void *cntx, double *out, double *in) {
double thr = (0.5)/(p->clutres-1.0); /* Match threshold */
if (p->cmyhack & 1) {
- if (in[0] > (1.0 - thr)
- && in[1] < thr
- && in[2] < thr
- && (p->in.chan < 4 || in[3] < thr))
+ if (win[0] > (1.0 - thr)
+ && win[1] < thr
+ && win[2] < thr
+ && (p->in.chan < 4 || win[3] < thr))
cmytrig |= 1;
}
if (p->cmyhack & 2) {
- if (in[0] < thr
- && in[1] > (1.0 - thr)
- && in[2] < thr
- && (p->in.chan < 4 || in[3] < thr))
+ if (win[0] < thr
+ && win[1] > (1.0 - thr)
+ && win[2] < thr
+ && (p->in.chan < 4 || win[3] < thr))
cmytrig |= 2;
}
if (p->cmyhack & 4) {
- if (in[0] < thr
- && in[1] < thr
- && in[2] > (1.0 - thr)
- && (p->in.chan < 4 || in[3] < thr))
+ if (win[0] < thr
+ && win[1] < thr
+ && win[2] > (1.0 - thr)
+ && (p->in.chan < 4 || win[3] < thr))
cmytrig |= 4;
}
}
#endif /* ENKHACK */
if (p->in.lcurve) { /* Apply L* to Y */
- l2y_curve(win, in, p->in.lcurve == 2);
-#ifdef DEBUG
-#ifdef DEBUGC
- DEBUGC
-#endif
- printf("win[] set to L* value %f %f %f %f\n",win[0], win[1], win[2], win[3]); fflush(stdout);
-#endif
-
- } else {
- for (i = 0; i < p->in.chan; i++)
- win[i] = in[i];
+ l2y_curve(win, win, p->in.lcurve == 2);
#ifdef DEBUG
#ifdef DEBUGC
- DEBUGC
+ DEBUGC
#endif
- printf("win[] set to in[] value %f %f %f %f\n",win[0], win[1], win[2], win[3]); fflush(stdout);
+ printf("win[] set to L* value %s\n",icmPdv(p->in.chan, win));
#endif
}
@@ -544,13 +852,38 @@ void devip_devop(void *cntx, double *out, double *in) {
}
case icmMatrixFwdType: {
icxLuMatrix *lu = (icxLuMatrix *)p->in.luo; /* Safe to coerce */
+ icmLuMatrix *plu = (icmLuMatrix *)lu->plu; /* Safe to coerce */
if (p->in.nocurve) { /* No explicit curve, so do implicit here */
- rv |= lu->fwd_curve(lu, pcsv, win);
- rv |= lu->fwd_matrix(lu, pcsv, pcsv);
+
+ if (p->in.tvenc == 7 || p->in.tvenc == 8) { /* xvYCC */
+ xvYCC_fwd_curve(pcsv, win); /* Allow for overrange values */
+ xvYCC_fwd_matrix(pcsv, pcsv); /* Rec709 primaries */
+ } else {
+ rv |= lu->fwd_curve(lu, pcsv, win);
+ rv |= lu->fwd_matrix(lu, pcsv, pcsv);
+ }
} else {
- rv |= lu->fwd_matrix(lu, pcsv, win);
+ if (p->in.tvenc == 7 || p->in.tvenc == 8) /* xvYCC */
+ xvYCC_fwd_matrix(pcsv, pcsv); /* Rec709 primaries */
+ else
+ rv |= lu->fwd_matrix(lu, pcsv, win);
}
+#ifdef DEBUG
+#ifdef DEBUGC
+ DEBUGC
+#endif
+ printf("After matrix PCS' XYZ %s Lab %s\n",icmPdv(p->in.chan, pcsv), icmPLab(pcsv));
+#endif
+ if (p->in.bt1886) {
+ bt1886_apply(&p->in.bt, plu, pcsv, pcsv);
+#ifdef DEBUG
+#ifdef DEBUGC
+ DEBUGC
+#endif
+ printf("After bt1886 PCS' XYZ %s Lab %s\n",icmPdv(p->in.chan, pcsv), icmPLab(pcsv));
+#endif
+ }
rv |= lu->fwd_abs(lu, pcsv, pcsv);
break;
}
@@ -625,7 +958,7 @@ void devip_devop(void *cntx, double *out, double *in) {
#ifdef DEBUGC
DEBUGC
#endif
- printf("PCS before map %f %f %f\n",pcsv[0], pcsv[1], pcsv[2]); fflush(stdout);
+ printf("PCS before map %f %f %f\n",pcsv[0], pcsv[1], pcsv[2]);
#endif
if (p->wphack) {
@@ -693,7 +1026,7 @@ void devip_devop(void *cntx, double *out, double *in) {
#ifdef DEBUGC
DEBUGC
#endif
- printf("PCS after Y scale %f %f %f\n",pcsv[0], pcsv[1], pcsv[2]); fflush(stdout);
+ printf("PCS after Y scale %f %f %f\n",pcsv[0], pcsv[1], pcsv[2]);
#endif
}
@@ -737,7 +1070,7 @@ void devip_devop(void *cntx, double *out, double *in) {
#ifdef DEBUGC
DEBUGC
#endif
- printf("PCS after map %f %f %f\n",pcsvm[0], pcsvm[1], pcsvm[2]); fflush(stdout);
+ printf("PCS after map %f %f %f\n",pcsvm[0], pcsvm[1], pcsvm[2]);
#endif
} else {
pcsvm[0] = pcsv[0];
@@ -766,7 +1099,7 @@ void devip_devop(void *cntx, double *out, double *in) {
#ifdef DEBUGC
DEBUGC
#endif
- printf("PCS after abstract %f %f %f\n",pcsvm[0], pcsvm[1], pcsvm[2]); fflush(stdout);
+ printf("PCS after abstract %f %f %f\n",pcsvm[0], pcsvm[1], pcsvm[2]);
#endif
}
@@ -931,14 +1264,99 @@ void devip_devop(void *cntx, double *out, double *in) {
error("icc lookup failed: %d, %s",p->in.c->errc,p->in.c->err);
}
- if (p->out.lcurve) /* Apply Y to L* */
+ if (p->cal != NULL && p->addcal == 1 && p->out.nocurve) {
+#ifdef DEBUG
+#ifdef DEBUGC
+ DEBUGC
+#endif
+ printf("DevOut' before cal curve %s\n\n",icmPdv(p->out.chan, out));
+#endif
+ p->cal->interp(p->cal, out, out);
+ }
+
+ if (p->out.lcurve) { /* Apply Y to L* */
+#ifdef DEBUG
+#ifdef DEBUGC
+ DEBUGC
+#endif
+ printf("DevOut' before y2l_curve %s\n\n",icmPdv(p->out.chan, out));
+#endif
y2l_curve(out, out, p->out.lcurve == 2);
+ }
+ /* Video encoding encode */
+ if (p->out.tvenc) {
#ifdef DEBUG
#ifdef DEBUGC
DEBUGC
#endif
- printf("DevIn'->DevOut' ret %f %f %f %f\n\n",out[0], out[1], out[2], out[3]); fflush(stdout);
+ printf("DevOut' before TVenc %s\n\n",icmPdv(p->out.chan, out));
+#endif
+ for (i = 0; i < p->out.chan; i++) {
+ if (out[i] < 0.0)
+ out[i] = 0.0;
+ else if (out[i] > 1.0)
+ out[i] = 1.0;
+ }
+ if (p->out.tvenc == 1) { /* Video 16-235 range */
+ icmRGB_2_VidRGB(out, out);
+ } else if (p->out.tvenc == 2) { /* Rec601 YCbCr */
+ icmRec601_RGBd_2_YPbPr(out, out);
+ icmRecXXX_YPbPr_2_YCbCr(out, out);
+ } else if (p->out.tvenc == 3) { /* Rec709 1150/60/2:1 YCbCr */
+ icmRec709_RGBd_2_YPbPr(out, out);
+ icmRecXXX_YPbPr_2_YCbCr(out, out);
+ } else if (p->out.tvenc == 4) { /* Rec709 1250/50/2:1 YCbCr */
+ icmRec709_50_RGBd_2_YPbPr(out, out);
+ icmRecXXX_YPbPr_2_YCbCr(out, out);
+ } else if (p->out.tvenc == 5) { /* Rec2020 Non-constant Luminance YCbCr encoding */
+ icmRec2020_NCL_RGBd_2_YPbPr(out, out);
+ icmRecXXX_YPbPr_2_YCbCr(out, out);
+ } else if (p->out.tvenc == 6) { /* Rec2020 Constant Luminance YCbCr encoding */
+ icmRec2020_CL_RGBd_2_YPbPr(out, out);
+ icmRecXXX_YPbPr_2_YCbCr(out, out);
+ }
+
+#ifdef NEVER
+ else if (p->out.tvenc == 7) { /* SD xvYCC with Rec601 YCbCr encoding */
+ icmRec601_RGBd_2_YPbPr(out, out);
+ icmRecXXX_YPbPr_2_YCbCr(out, out);
+ } else if (p->out.tvenc == 8) { /* HD xvYCC with Rec709 YCbCr encoding */
+ icmRec709_RGBd_2_YPbPr(out, out);
+ icmRecXXX_YPbPr_2_YCbCr(out, out);
+ }
+#endif /* NEVER */
+ }
+
+ /* Create linear interpolation from clip to full range */
+ if (clip && p->out.tvenc) {
+ for (i = 0; i < 3; i++) {
+ if (clip & (1 << i)) {
+#ifdef LINTERP_OR
+ /* Linear interpolate overrange to full */
+//printf("~1 clip[%d] = out %f + (uc %f - cin %f)/(full %f - cin %f) * (full %f - out %f) = ",
+//i, out[i], uc[i], cin[i], full[i], cin[i], full[i], out[i]);
+ out[i] = out[i] + (uc[i] - cin[i])/(full[i] - cin[i]) * (full[i] - out[i]);
+//printf("%f\n",out[i]);
+#else
+ double ifull = 1.0 - full[i]; /* Opposite limit to full */
+
+ /* Do simple extrapolation (Not perfect though) */
+ out[i] = ifull + (out[i] - ifull) * (uc[i] - ifull)/(cin[i] - ifull);
+
+ if (out[i] < 0.0 || out[i] > 1.0 /* clip */
+ || fabs(uc[i] - full[i]) < 1e-6) /* or input is at sync level */
+ out[i] = full[i];
+#endif
+ }
+ }
+ }
+
+#ifdef DEBUG
+#ifdef DEBUGC
+ DEBUGC
+#endif
+ printf("DevIn'->DevOut' ret %s\n\n",icmPdv(p->out.chan, out));
#endif
@@ -958,23 +1376,35 @@ void devip_devop(void *cntx, double *out, double *in) {
void devop_devo(void *cntx, double *out, double *in) {
int rv = 0;
clink *p = (clink *)cntx;
- int i;
+ int i, clip = 0;
+ double uc[3]; /* Unclipped values (Video) */
+ double cin[3]; /* clipped input value (Video) */
+ double full[3]; /* Full value in clip direction (Video) */
#ifdef DEBUG
#ifdef DEBUGC
DEBUGC
#endif
- printf("DevOut'->DevOut got %f %f %f %f\n",in[0], in[1], in[2], in[4]); fflush(stdout);
+ printf("DevOut'->DevOut got %s\n",icmPdv(p->out.chan, in));
#endif
for (i = 0; i < p->out.chan; i++)
out[i] = in[i];
- if (p->out.lcurve) /* Apply L* to Y */
+ if (p->out.lcurve) /* Apply L* to Y */
l2y_curve(out, out, p->out.lcurve == 2);
if (p->out.nocurve == 0) { /* Using per channel curves */
+ /* Video encoding decode */
+ if (p->out.tvenc == 1) { /* Video 16-235 range */
+ clip = clipVidRGB(full, uc, out);
+ icmCpy3(cin, out);
+ icmVidRGB_2_RGB(out, out);
+ } else if (p->out.tvenc >= 2) { /* YCbCr */
+ error("Can't use output curves with YCbCr output encoding");
+ }
+
switch(p->out.alg) {
case icmMonoBwdType: {
icxLuMono *lu = (icxLuMono *)p->out.luo; /* Safe to coerce */
@@ -1004,16 +1434,35 @@ void devop_devo(void *cntx, double *out, double *in) {
}
if (rv >= 2)
error("icc lookup failed: %d, %s",p->in.c->errc,p->in.c->err);
+
+ if (p->cal != NULL && p->addcal == 1) {
+ p->cal->interp(p->cal, out, out);
+ }
+
+ /* Video encoding encode */
+ if (p->out.tvenc == 1) { /* Video 16-235 range */
+ icmRGB_2_VidRGB(out, out);
+ }
+
+ /* Create linear interpolation from clip to full range */
+ if (clip && p->out.tvenc) {
+ for (i = 0; i < 3; i++) {
+ if (clip & (1 << i)) {
+ out[i] = out[i] + (uc[i] - cin[i])/(full[i] - cin[i]) * (full[i] - out[i]);
+ }
+ }
+ }
}
#ifdef DEBUG
#ifdef DEBUGC
DEBUGC
#endif
- printf("DevOut'->DevOut ret %f %f %f %f\n",out[0], out[1], out[2], out[3]); fflush(stdout);
+ printf("DevOut'->DevOut ret %s\n",icmPdv(p->out.chan, out));
#endif
#ifdef DEBUGC
tt = 0;
#endif
+//printf("DevOut'->DevOut ret %s\n",icmPdv(p->out.chan, out));
}
/* ------------------------------------------- */
@@ -1114,19 +1563,29 @@ static double xyzoptfunc(void *cntx, double *v) {
/* ------------------------------------------- */
+int write_eeColor1DinputLuts(clink *li, char *tdlut_name);
+int write_eeColor3DLut(icc *icc, char *fname);
+int write_eeColor1DoutputLuts(clink *li, char *tdlut_name);
+
+int write_MadVR_3DLut(clink *li, icc *icc, char *fname);
+
int
main(int argc, char *argv[]) {
int fa, nfa, mfa; /* argument we're looking at */
char in_name[MAXNAMEL+1];
char sgam_name[MAXNAMEL+1] = "\000"; /* Source gamut name */
char abs_name[MAXNAMEL+1] = "\000"; /* Abstract profile name */
+ char cal_name[MAXNAMEL+1] = "\000"; /* Calibration filename */
char out_name[MAXNAMEL+1];
char link_name[MAXNAMEL+1];
+ char tdlut_name[MAXNAMEL+1];
int verify = 0; /* Do verify pass */
int outinkset = 0; /* The user specfied an output inking */
int intentset = 0; /* The user specified an intent */
int vcset = 0; /* Viewing conditions were set by user */
int modeset = 0; /* The gamut mapping mode was set by the user */
+ int addcal = 0; /* 1 = Incorporate cal. curves in 3dLUT and set linear cal1 */
+ /* 2 = Set 3dLut cal1 to calibration curves */
int rv = 0;
icxViewCond ivc, ovc; /* Viewing Condition Overrides for in and out profiles */
int ivc_e = -1, ovc_e = -1; /* Enumerated viewing condition */
@@ -1147,7 +1606,7 @@ main(int argc, char *argv[]) {
li.count = 0;
li.last = -1;
li.mode = 0; /* Default simple link mode */
- li.quality = 1; /* Medium quality */
+ li.quality = -1; /* Not set */
li.clutres = 0; /* No resolution override */
li.nhack = 0;
li.cmyhack = 0; /* Mask for 100% purity through mapping of CMY */
@@ -1166,6 +1625,9 @@ main(int argc, char *argv[]) {
li.in.locus = 0; /* Default K value target */
li.in.nocurve = 0; /* Preserve device linearisation curve */
li.in.lcurve = 0; /* Don't apply a Y to L* curve after device curve */
+ li.in.tvenc = -1;
+ li.in.egamma = 2.2; /* Default effective gamma */
+ li.in.tgamma = 2.4; /* Default technical gamma */
li.out.intent = icmDefaultIntent; /* Default */
li.out.ink.tlimit = -1.0; /* Default no total limit */
li.out.ink.klimit = -1.0; /* Default no black limit */
@@ -1178,6 +1640,7 @@ main(int argc, char *argv[]) {
li.out.locus = 0; /* Default K value target */
li.out.nocurve = 0; /* Preserve device linearisation curve */
li.out.lcurve = 0; /* Don't apply an L* to Y curve before device curve */
+ li.out.tvenc = -1;
li.out.b2aluo = NULL; /* B2A lookup for inking == 7 */
xicc_enum_gmapintent(&li.gmi, icxDefaultGMIntent, NULL); /* Set default overall intent */
@@ -1189,7 +1652,8 @@ main(int argc, char *argv[]) {
ivc.Yb = -1.0;
ivc.Lv = -1.0;
ivc.Yf = -1.0;
- ivc.Fxyz[0] = -1.0; ivc.Fxyz[1] = -1.0; ivc.Fxyz[2] = -1.0;
+ ivc.Yg = -1.0;
+ ivc.Gxyz[0] = -1.0; ivc.Gxyz[1] = -1.0; ivc.Gxyz[2] = -1.0;
ovc.Ev = -1;
ovc.Wxyz[0] = -1.0; ovc.Wxyz[1] = -1.0; ovc.Wxyz[2] = -1.0;
@@ -1197,7 +1661,8 @@ main(int argc, char *argv[]) {
ovc.Yb = -1.0;
ovc.Lv = -1.0;
ovc.Yf = -1.0;
- ovc.Fxyz[0] = -1.0; ovc.Fxyz[1] = -1.0; ovc.Fxyz[2] = -1.0;
+ ovc.Yg = -1.0;
+ ovc.Gxyz[0] = -1.0; ovc.Gxyz[1] = -1.0; ovc.Gxyz[2] = -1.0;
if (argc < 4)
usage("Too few arguments, got %d expect at least 3",argc-1);
@@ -1262,13 +1727,14 @@ main(int argc, char *argv[]) {
verify = 1;
/* Disable profile per channel curve use in device link output */
- else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') {
+ else if (argv[fa][1] == 'n') {
li.in.nocurve = 1;
li.out.nocurve = 1;
}
/* Hack to force input neutrals to K only output */
- else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') {
+ else if (argv[fa][1] == 'f'
+ || argv[fa][1] == 'F') {
if (argv[fa][1] == 'f') {
if (na != NULL) { /* XXXK -> XXXK hack */
@@ -1299,7 +1765,7 @@ main(int argc, char *argv[]) {
}
/* Quality */
- else if (argv[fa][1] == 'q' || argv[fa][1] == 'Q') {
+ else if (argv[fa][1] == 'q') {
fa = nfa;
if (na == NULL) usage("Quality flag (-q) needs an argument");
switch (na[0]) {
@@ -1328,22 +1794,33 @@ main(int argc, char *argv[]) {
}
/* CLUT resolution override */
- else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') {
+ else if (argv[fa][1] == 'r') {
int rr;
fa = nfa;
if (na == NULL) usage("Resolution flag (-r) needs an argument");
rr = atoi(na);
- if (rr < 1 || rr > 255) usage("Resolution flag (-r) argument out of range (%d)",rr);
+ if (rr < 1 || rr > 256) usage("Resolution flag (-r) argument out of range (%d)",rr);
li.clutres = rr;
}
/* Abstract profile */
else if (argv[fa][1] == 'p') {
- if (na == NULL) usage("Expected abstract profile filename after -a");
+ if (na == NULL) usage("Expected abstract profile filename after -p");
fa = nfa;
strncpy(abs_name,na,MAXNAMEL); abs_name[MAXNAMEL] = '\000';
}
+ /* Calibration curves */
+ else if (argv[fa][1] == 'a'
+ || argv[fa][1] == 'H') {
+ if (na == NULL) usage("Expected calibration filename after -%c",argv[fa][1]);
+ strncpy(cal_name,na,MAXNAMEL); cal_name[MAXNAMEL] = '\000';
+ addcal = 1;
+ if (argv[fa][1] == 'H')
+ addcal = 2;
+ fa = nfa;
+ }
+
/* Simple mode */
else if (argv[fa][1] == 's') {
li.mode = 0;
@@ -1351,7 +1828,8 @@ main(int argc, char *argv[]) {
}
/* Maping mode */
- else if (argv[fa][1] == 'g' || argv[fa][1] == 'G') {
+ else if (argv[fa][1] == 'g'
+ || argv[fa][1] == 'G') {
li.mode = 1;
if (argv[fa][1] == 'G') {
li.mode = 2;
@@ -1365,7 +1843,7 @@ main(int argc, char *argv[]) {
}
/* White point hack */
- else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') {
+ else if (argv[fa][1] == 'w') {
li.wphack = 1;
if (na != NULL) { // To a particular white point
fa = nfa;
@@ -1376,7 +1854,7 @@ main(int argc, char *argv[]) {
}
}
/* Input profile Intent or Mapping mode intent */
- else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') {
+ else if (argv[fa][1] == 'i') {
fa = nfa;
if (na == NULL) usage("Input intent flag (-i) needs an argument");
/* Record it for simple mode */
@@ -1407,7 +1885,7 @@ main(int argc, char *argv[]) {
}
/* Output profile Intent */
- else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') {
+ else if (argv[fa][1] == 'o') {
fa = nfa;
if (na == NULL) usage("Output intent flag (-o) needs an argument");
switch (na[0]) {
@@ -1433,18 +1911,17 @@ main(int argc, char *argv[]) {
}
/* Viewing conditions */
- else if (argv[fa][1] == 'c' || argv[fa][1] == 'C'
- || argv[fa][1] == 'd' || argv[fa][1] == 'D') {
+ else if (argv[fa][1] == 'c' || argv[fa][1] == 'd') {
icxViewCond *vc;
- if (argv[fa][1] == 'c' || argv[fa][1] == 'C') {
+ if (argv[fa][1] == 'c') {
vc = &ivc;
} else {
vc = &ovc;
}
fa = nfa;
- if (na == NULL) usage("Viewing conditions flag (-[cd]) needs an argument");
+ if (na == NULL) usage("Viewing conditions flag (-%c) needs an argument",argv[fa][1]);
#ifdef NEVER
if (na[0] >= '0' && na[0] <= '9') {
if (vc == &ivc)
@@ -1464,7 +1941,7 @@ main(int argc, char *argv[]) {
}
} else if (na[0] == 's' || na[0] == 'S') {
if (na[1] != ':')
- usage("Viewing conditions (-[cd]s) missing ':'");
+ usage("Viewing conditions (-%cs) missing ':'",argv[fa][1]);
if (na[2] == 'n' || na[2] == 'N') {
vc->Ev = vc_none; /* Automatic */
} else if (na[2] == 'a' || na[2] == 'A') {
@@ -1476,7 +1953,7 @@ main(int argc, char *argv[]) {
} else if (na[2] == 'c' || na[2] == 'C') {
vc->Ev = vc_cut_sheet;
} else
- usage("Viewing condition (-[cd]) unrecognised surround '%c'",na[2]);
+ usage("Viewing condition (-%c) unrecognised surround '%c'",argv[fa][1],na[2]);
} else if (na[0] == 'w' || na[0] == 'W') {
double x, y, z;
if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) {
@@ -1484,36 +1961,41 @@ main(int argc, char *argv[]) {
} else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) {
vc->Wxyz[0] = x; vc->Wxyz[1] = y;
} else
- usage("Viewing condition (-[cd]w) unrecognised white point '%s'",na+1);
+ usage("Viewing condition (-%cw) unrecognised white point '%s'",argv[fa][1],na+1);
} else if (na[0] == 'a' || na[0] == 'A') {
if (na[1] != ':')
- usage("Viewing conditions (-[cd]a) missing ':'");
+ usage("Viewing conditions (-ca) missing ':'");
vc->La = atof(na+2);
} else if (na[0] == 'b' || na[0] == 'B') {
if (na[1] != ':')
- usage("Viewing conditions (-[cd]b) missing ':'");
+ usage("Viewing conditions (-cb) missing ':'");
vc->Yb = atof(na+2)/100.0;
} else if (na[0] == 'l' || na[0] == 'L') {
if (na[1] != ':')
- usage("Viewing conditions (-[cd]l) missing ':'");
+ usage("Viewing conditions (-l) missing ':'");
vc->Lv = atof(na+2);
} else if (na[0] == 'f' || na[0] == 'F') {
+ if (na[1] != ':')
+ usage("Viewing conditions (-cf) missing ':'");
+ vc->Yf = atof(na+2);
+ } else if (na[0] == 'g' || na[0] == 'G') {
double x, y, z;
if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) {
- vc->Fxyz[0] = x; vc->Fxyz[1] = y; vc->Fxyz[2] = z;
+ vc->Gxyz[0] = x; vc->Gxyz[1] = y; vc->Gxyz[2] = z;
} else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) {
- vc->Fxyz[0] = x; vc->Fxyz[1] = y;
+ vc->Gxyz[0] = x; vc->Gxyz[1] = y;
} else if (sscanf(na+1,":%lf",&x) == 1) {
vc->Yf = x/100.0;
} else
- usage("Viewing condition (-[cd]f) unrecognised flare '%s'",na+1);
+ usage("Viewing condition (-%cf) unrecognised flare '%s'",argv[fa][1],na+1);
} else
- usage("Viewing condition (-[cd]) unrecognised sub flag '%c'",na[0]);
+ usage("Viewing condition (-%c) unrecognised sub flag '%c'",argv[fa][1],na[0]);
vcset = 1; /* Viewing conditions were set by user */
}
/* Inking rule */
- else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') {
+ else if (argv[fa][1] == 'k'
+ || argv[fa][1] == 'K') {
fa = nfa;
if (na == NULL) usage("Inking rule flag (-k) needs an argument");
if (argv[fa][1] == 'k')
@@ -1648,6 +2130,88 @@ main(int argc, char *argv[]) {
li.mode = 2;
}
+ /* 3DLut output */
+ else if (argv[fa][1] == '3') {
+ fa = nfa;
+ if (na == NULL) usage("3dLut format flag (-3) needs an argument");
+ switch (na[0]) {
+ case 'e':
+ li.tdlut = 1;
+ break;
+ case 'm':
+ li.tdlut = 2;
+ break;
+ default:
+ usage("3DLut format (-3) argument '%s' not recognised",na);
+ }
+ }
+
+ /* Intent modifier */
+ else if (argv[fa][1] == 'I') {
+ double gamma = 0.0;
+ fa = nfa;
+ if (na == NULL) usage("Intent modifier flag (-I) needs an argument");
+
+ switch (na[0]) {
+ case 'b':
+ li.in.bt1886 = 1;
+ if (sscanf(na+1,":%lf",&gamma) == 1)
+ li.in.egamma = gamma;
+ break;
+ case 'B':
+ li.in.bt1886 = 2;
+ if (sscanf(na+1,":%lf",&gamma) == 1)
+ li.in.tgamma = gamma;
+ break;
+ default:
+ if (gamma == 0.0)
+ usage("Intent modifier (-I) argument '%s' not recognised",na);
+ }
+ }
+
+ /* Video RGB in and out encoding */
+ else if (argv[fa][1] == 'e'
+ || argv[fa][1] == 'E') {
+ int enc;
+ if (na == NULL) usage("Video encoding flag (-%c) needs an argument",argv[fa][1]);
+ switch (na[0]) {
+ case 'n': /* Normal */
+ enc = 0;
+ break;
+ case 't': /* TV 16 .. 235 */
+ enc = 1;
+ break;
+ case '6': /* Rec601 YCbCr */
+ enc = 2;
+ break;
+ case '7': /* Rec709 1150/60/2:1 YCbCr (HD) */
+ enc = 3;
+ break;
+ case '5': /* Rec709 1250/50/2:1 YCbCr (HD) */
+ enc = 4;
+ break;
+ case '2': /* Rec2020 Non-constant Luminance YCbCr (UHD) */
+ enc = 5;
+ break;
+ case 'C': /* Rec2020 Constant Luminance YCbCr (UHD) */
+ enc = 6;
+ break;
+ case 'x': /* xvYCC Rec601 YCbCr encoding (SD) */
+ enc = 7;
+ break;
+ case 'X': /* xvYCC Rec709 YCbCr encoding (HD) */
+ enc = 8;
+ break;
+ default:
+ usage("Video encoding (-E) argument not recognised");
+ }
+ if (argv[fa][1] == 'e')
+ li.in.tvenc = enc;
+ else
+ li.out.tvenc = enc;
+ fa = nfa;
+ }
+
/* Gammut mapping diagnostic plots */
else if (argv[fa][1] == 'P')
li.gamdiag = 1;
@@ -1667,9 +2231,52 @@ main(int argc, char *argv[]) {
if (fa >= argc || argv[fa][0] == '-') usage("Missing result profile");
strncpy(link_name,argv[fa++],MAXNAMEL); link_name[MAXNAMEL] = '\000';
+ if (li.tdlut) {
+ char *xl;
+ if (li.tdlut == 1) { /* eeColor */
+ strncpy(tdlut_name,link_name,MAXNAMEL-4); tdlut_name[MAXNAMEL-4] = '\000';
+ if ((xl = strrchr(tdlut_name, '.')) == NULL) /* Figure where extention is */
+ xl = tdlut_name + strlen(tdlut_name);
+ strcpy(xl,".txt");
+
+ if (li.clutres > 255) usage("Resolution flag (-r) argument out of range (%d)",li.clutres);
+
+ if (li.clutres == 0)
+ li.clutres = 65;
+
+ } else if (li.tdlut == 2) { /* MadVR */
+ strncpy(tdlut_name,link_name,MAXNAMEL-6); tdlut_name[MAXNAMEL-6] = '\000';
+ if ((xl = strrchr(tdlut_name, '.')) == NULL) /* Figure where extention is */
+ xl = tdlut_name + strlen(tdlut_name);
+ strcpy(xl,".3dlut");
+
+ if (li.clutres == 0)
+ li.clutres = 65; /* This is good for video encoding levels */
+ }
+ } else {
+ if (li.clutres > 255) usage("Resolution flag (-r) argument out of range (%d)",li.clutres);
+ }
+
+ if (li.in.tvenc < 0)
+ li.in.tvenc = 0;
+ if (li.out.tvenc < 0)
+ li.out.tvenc = 0;
+
+ /* Need to allow spec. of gamut/matrix for xvYCC & bt.1886 */
+ if (li.out.tvenc == 7 || li.out.tvenc == 8) { /* xvYCC */
+ usage("xvYCC output encoding is not supported");
+ }
+
if (xpi.profDesc == NULL)
xpi.profDesc = link_name; /* Default description */
+ if (li.quality < 0) { /* Not set by user */
+ if (li.tdlut)
+ li.quality = 2; /* Use high quality gamut mapping */
+ else
+ li.quality = 1; /* Default to medium quality */
+ }
+
if (li.verb)
printf("Got options\n");
@@ -1677,13 +2284,84 @@ main(int argc, char *argv[]) {
#ifndef ENKHACK /* Enable K hack code */
warning("!!!!!! linkl/collink.c ENKHACK not enabled !!!!!!");
#endif
+
+ /* - - - - - - - - - - - - - - - - - - - */
+ /* Set some implied flags for bt1886 */
+
+ if (li.in.bt1886) {
+ if (li.mode == 0) { /* Simple mode */
+ if (li.in.intent == icmDefaultIntent) {
+ warning("Setting BT.1886 input intent to Relative Colorimetric");
+ li.in.intent = icRelativeColorimetric;
+ } else if (li.in.intent != icRelativeColorimetric
+ && li.in.intent != icAbsoluteColorimetric) {
+ warning("BT.1886 in simple link mode is intended to work with a colorimetric in intent");
+ }
+
+ if (li.out.intent == icmDefaultIntent) {
+ warning("Setting BT.1886 output intent to Relative Colorimetric");
+ li.out.intent = icRelativeColorimetric;
+ } else if (li.out.intent != icRelativeColorimetric
+ && li.out.intent != icAbsoluteColorimetric) {
+ warning("BT.1886 in simple link mode is intended to work with a colorimetric in intent");
+ }
+ } else {
+ if (!intentset) {
+ warning("Setting BT.1886 intent to Relative Colorimetric");
+ if (xicc_enum_gmapintent(&li.gmi, icxNoGMIntent, "r") == -999)
+ error("Internal - intent 'r' isn't recognised");
+ }
+
+#ifdef NEVER
+ if (li.gmi.glumbcpf != 0.0 || li.gmi.glumbexf != 0.0) {
+ warning("Gamut mapping will do black point mapping, so BT.1886 black point mapping will be disabled");
+ li.in.bt1886 = 2; /* Disable black point adjustment */
+ }
+#endif
+ }
+ }
+
+ /* Set some implied flags for 3dLuts */
+ if (li.tdlut) {
+
+ /* eeColor format. Currently we assume no input or output curves, */
+ /* even though it's technically possible to use them */
+ if (li.tdlut == 1) {
+
+ if (li.in.nocurve == 0) {
+ warning("Disabling input curves for eeColor 3DLut creation");
+ li.in.nocurve = 1;
+ }
+ if (li.out.nocurve == 0) {
+ warning("Disabling output curves for eeColor 3DLut creation");
+ li.out.nocurve = 1;
+ }
+
+ if (li.in.tvenc != li.out.tvenc)
+ warning("eeColor usually needs same input & output encoding");
+ }
+
+ /* MadVR format. It doesn't support in and out per channel curves */
+ else if (li.tdlut == 2) {
+
+ if (li.in.nocurve == 0) {
+ warning("Disabling input curves for MadVR 3DLut creation");
+ li.in.nocurve = 1;
+ }
+ if (li.out.nocurve == 0) {
+ warning("Disabling output curves for MadVR 3DLut creation");
+ li.out.nocurve = 1;
+ }
+ }
+ }
+
/* - - - - - - - - - - - - - - - - - - - */
/* Sanity checking/defaulting of options */
/* Deal with options that need link mode -g */
if (li.mode < 1
&& (li.in.intent == icMaxEnumIntent /* User set a smart linking intent */
- || vcset /* Viewing conditions were set by user */
+ || vcset /* Viewing conditions were set by user */
|| li.wphack)) {
if (modeset) {
if (li.in.intent == icMaxEnumIntent)
@@ -1916,6 +2594,20 @@ main(int argc, char *argv[]) {
&& li.out.h->deviceClass != icSigColorSpaceClass) /* For sRGB etc. */
error("Output profile isn't a device profile");
+ /* Grab the calibration if requested */
+ if (addcal) {
+ if ((li.cal = new_xcal()) == NULL)
+ error("new_xcal failed");
+
+ if ((li.cal->read(li.cal, cal_name)) != 0)
+ error("%s",li.cal->err);
+
+ li.addcal = addcal;
+
+ /* (Don't use vcgt in output profile, because it may include tv encoding, */
+ /* and we don't currently have a way of detecting this */
+ }
+
/* Wrap with an expanded icc */
if ((li.out.x = new_xicc(li.out.c)) == NULL)
error ("Creation of output profile xicc failed");
@@ -1990,15 +2682,18 @@ main(int argc, char *argv[]) {
xicc *x;
icxViewCond *v, *vc;
int es;
+ int *set;
if (i == 0) {
v = &ivc; /* Override parameters */
vc = &li.in.vc; /* Target parameters */
+ set = &li.in.vc_set;
es = ivc_e;
x = li.in.x; /* xicc */
} else {
v = &ovc; /* Override parameters */
vc = &li.out.vc; /* Target parameters */
+ set = &li.out.vc_set;
es = ovc_e;
x = li.out.x; /* xicc */
}
@@ -2012,14 +2707,18 @@ main(int argc, char *argv[]) {
if (es != -1) {
if (xicc_enum_viewcond(x, vc, es, NULL, 0, NULL) == -999)
error ("%d, %s",x->errc, x->err);
+ *set = 1;
}
/* Then any individual paramaters */
- if (v->Ev >= 0)
+ if (v->Ev >= 0) {
vc->Ev = v->Ev;
+ *set = 1;
+ }
if (v->Wxyz[0] >= 0.0 && v->Wxyz[1] > 0.0 && v->Wxyz[2] >= 0.0) {
/* Normalise XYZ to current media white */
vc->Wxyz[0] = v->Wxyz[0]/v->Wxyz[1] * vc->Wxyz[1];
vc->Wxyz[2] = v->Wxyz[2]/v->Wxyz[1] * vc->Wxyz[1];
+ *set = 1;
}
if (v->Wxyz[0] >= 0.0 && v->Wxyz[1] >= 0.0 && v->Wxyz[2] < 0.0) {
/* Convert Yxy to XYZ */
@@ -2028,27 +2727,42 @@ main(int argc, char *argv[]) {
double z = 1.0 - x - y;
vc->Wxyz[0] = x/y * vc->Wxyz[1];
vc->Wxyz[2] = z/y * vc->Wxyz[1];
+ *set = 1;
}
- if (v->La >= 0.0)
+ if (v->La >= 0.0) {
vc->La = v->La;
- if (v->Yb >= 0.0)
+ *set = 1;
+ }
+ if (v->Yb >= 0.0) {
vc->Yb = v->Yb;
- if (v->Lv >= 0.0)
+ *set = 1;
+ }
+ if (v->Lv >= 0.0) {
vc->Lv = v->Lv;
- if (v->Yf >= 0.0)
+ *set = 1;
+ }
+ if (v->Yf >= 0.0) {
vc->Yf = v->Yf;
- if (v->Fxyz[0] >= 0.0 && v->Fxyz[1] > 0.0 && v->Fxyz[2] >= 0.0) {
+ *set = 1;
+ }
+ if (v->Yg >= 0.0) {
+ vc->Yg = v->Yg;
+ *set = 1;
+ }
+ if (v->Gxyz[0] >= 0.0 && v->Gxyz[1] > 0.0 && v->Gxyz[2] >= 0.0) {
/* Normalise XYZ to current media white */
- vc->Fxyz[0] = v->Fxyz[0]/v->Fxyz[1] * vc->Fxyz[1];
- vc->Fxyz[2] = v->Fxyz[2]/v->Fxyz[1] * vc->Fxyz[1];
+ vc->Gxyz[0] = v->Gxyz[0]/v->Gxyz[1] * vc->Gxyz[1];
+ vc->Gxyz[2] = v->Gxyz[2]/v->Gxyz[1] * vc->Gxyz[1];
+ *set = 1;
}
- if (v->Fxyz[0] >= 0.0 && v->Fxyz[1] >= 0.0 && v->Fxyz[2] < 0.0) {
+ if (v->Gxyz[0] >= 0.0 && v->Gxyz[1] >= 0.0 && v->Gxyz[2] < 0.0) {
/* Convert Yxy to XYZ */
- double x = v->Fxyz[0];
- double y = v->Fxyz[1]; /* If Y == 1.0, then X+Y+Z = 1/y */
+ double x = v->Gxyz[0];
+ double y = v->Gxyz[1]; /* If Y == 1.0, then X+Y+Z = 1/y */
double z = 1.0 - x - y;
- vc->Fxyz[0] = x/y * vc->Fxyz[1];
- vc->Fxyz[2] = z/y * vc->Fxyz[1];
+ vc->Gxyz[0] = x/y * vc->Gxyz[1];
+ vc->Gxyz[2] = z/y * vc->Gxyz[1];
+ *set = 1;
}
}
@@ -2068,11 +2782,11 @@ main(int argc, char *argv[]) {
/* the intents and pcsor appropriately. */
if (li.mode > 0) {
- if ((li.gmi.usecas & 0xff) != 0) {
+ if ((li.gmi.usecas & 0xff) >= 0x2) {
li.pcsor = icxSigJabData; /* Use CAM as PCS */
isJab = 1;
- if ((li.gmi.usecas & 0xff) == 0x2) { /* Absolute Appearance space */
+ if ((li.gmi.usecas & 0xff) == 0x3) { /* Absolute Appearance space */
double mxw;
li.in.intent = li.out.intent = li.abs_intent = icxAbsAppearance;
@@ -2090,13 +2804,16 @@ main(int argc, char *argv[]) {
/* Set the output vc to be the same as the input */
li.out.vc = li.in.vc; /* Structure copy */
- } else {
+ } else { /* usecas & ff == 0x2 */
/* Not Abs Appearance space */
li.in.intent = li.out.intent = li.abs_intent = icxAppearance;
}
} else {
- /* Not Appearance space */
- li.in.intent = li.out.intent = li.abs_intent = icAbsoluteColorimetric;
+ /* Not Appearance space - use L*a*b* */
+ if ((li.gmi.usecas & 0xff) == 0)
+ li.in.intent = li.out.intent = li.abs_intent = icRelativeColorimetric;
+ else
+ li.in.intent = li.out.intent = li.abs_intent = icAbsoluteColorimetric;
}
}
@@ -2114,13 +2831,15 @@ main(int argc, char *argv[]) {
fl |= ICX_MERGE_CLUT;
#endif
-#ifdef NEVER
- printf("~1 input space flags = 0x%x\n",fl);
- printf("~1 input space intent = %s\n",icx2str(icmRenderingIntent,li.in.intent));
- printf("~1 input space pcs = %s\n",icx2str(icmColorSpaceSignature,li.pcsor));
- printf("~1 input space viewing conditions =\n"); xicc_dump_viewcond(&li.in.vc);
- printf("~1 input space inking =\n"); xicc_dump_inking(&li.in.ink);
-#endif
+ if (li.verb && li.mode > 0) {
+// printf("Input space flags = 0x%x\n",fl);
+// printf("Input space intent = %s\n",icx2str(icmRenderingIntent,li.in.intent));
+// printf("Input space pcs = %s\n",icx2str(icmColorSpaceSignature,li.pcsor));
+ if (li.in.vc_set || li.out.vc_set)
+ printf("Input space viewing conditions =\n"), xicc_dump_viewcond(&li.in.vc);
+// printf("Input space inking =\n"); xicc_dump_inking(&li.in.ink);
+ }
+
if ((li.in.luo = li.in.x->get_luobj(li.in.x, fl, icmFwd, li.in.intent,
li.pcsor, icmLuOrdNorm, &li.in.vc, &li.in.ink)) == NULL) {
error("get xlookup object failed: %d, %s",li.in.x->errc,li.in.x->err);
@@ -2151,6 +2870,7 @@ main(int argc, char *argv[]) {
&& (li.in.alg == icmMatrixFwdType || li.in.alg == icmMatrixBwdType
|| li.in.csp == icSigXYZData)) {
li.in.lcurve = 1; /* Use Y to L* and L* to Y for input */
+
if (li.in.csp == icSigXYZData) {
li.in.lcurve = 2; /* Use real Y to L* and L* to Y for input */
li.in.nocurve = 1; /* Don't trust the curve that comes with it */
@@ -2218,6 +2938,10 @@ main(int argc, char *argv[]) {
/* Get native PCS space */
li.out.luo->lutspaces(li.out.luo, &natpcs, NULL, NULL, NULL, NULL);
+ /* Get details of overall conversion */
+ li.out.luo->spaces(li.out.luo, NULL, NULL, &li.out.csp, &li.out.chan, &li.out.alg,
+ NULL, NULL, NULL);
+
} else { /* Using inverse A2B Lut for output conversion */
fl = flb;
@@ -2230,13 +2954,14 @@ main(int argc, char *argv[]) {
if (li.verb)
printf("Loading output inverse A2B table\n");
-#ifdef NEVER
- printf("~1 output space flags = 0x%x\n",fl);
- printf("~1 output space intent = %s\n",icx2str(icmRenderingIntent,li.out.intent));
- printf("~1 output space pcs = %s\n",icx2str(icmColorSpaceSignature,li.pcsor));
- printf("~1 output space viewing conditions =\n"); xicc_dump_viewcond(&li.out.vc);
- printf("~1 output space inking =\n"); xicc_dump_inking(&li.out.ink);
-#endif
+ if (li.verb) {
+// printf("Output space flags = 0x%x\n",fl);
+// printf("Output space intent = %s\n",icx2str(icmRenderingIntent,li.out.intent));
+// printf("Output space pcs = %s\n",icx2str(icmColorSpaceSignature,li.pcsor));
+ if (li.in.vc_set || li.out.vc_set)
+ printf("Output space viewing conditions =\n"), xicc_dump_viewcond(&li.out.vc);
+// printf("Output space inking =\n"); xicc_dump_inking(&li.out.ink);
+ }
if ((li.out.luo = li.out.x->get_luobj(li.out.x, fl, icmFwd,
li.out.intent, li.pcsor, icmLuOrdNorm, &li.out.vc,
@@ -2404,7 +3129,7 @@ main(int argc, char *argv[]) {
if (li.out.nocurve == 0 && natpcs == icSigXYZData
&& (li.out.alg == icmMatrixFwdType || li.out.alg == icmMatrixBwdType
|| li.out.csp == icSigXYZData)) {
- li.out.lcurve = 1; /* Use Y to L* and L* to Y for output */
+ li.out.lcurve = 1; /* Use Y to L* and L* to Y for output */
if (li.out.csp == icSigXYZData) {
li.out.lcurve = 2; /* Use real Y to L* and L* to Y for output */
li.out.nocurve = 1; /* Don't trust the curve that comes with it */
@@ -2415,6 +3140,138 @@ main(int argc, char *argv[]) {
}
/* - - - - - - - - - - - - - - - - - - - */
+ /* Sanity checking */
+
+ if (li.cal != NULL) {
+ if (li.cal->colspace != li.out.csp) {
+ error("Calibration space %s doesn't match output profile %s",
+ icm2str(icmColorSpaceSignature, li.cal->colspace),
+ icm2str(icmColorSpaceSignature, li.out.csp));
+ }
+ }
+
+ if (li.tdlut) {
+
+ /* eeColor format. */
+ if (li.tdlut == 1) {
+
+ if (li.in.csp != icSigRgbData)
+ error("Input profile must be RGB to output eeColor 3DLut");
+
+ if (li.out.csp != icSigRgbData)
+ error("Output profile must be RGB to output eeColor 3DLut");
+ }
+
+ /* MadVR format. */
+ else if (li.tdlut == 2) {
+
+ if (li.in.csp != icSigRgbData)
+ error("Input profile must be RGB to output MadVR 3DLut");
+
+ if (li.out.csp != icSigRgbData)
+ error("Output profile must be RGB to output MadVR 3DLut");
+ }
+
+ if (li.in.tvenc) {
+ if (li.in.csp != icSigRgbData)
+ error("Input profile must be RGB to use video encoding option");
+ }
+ if (li.out.tvenc) {
+ if (li.out.csp != icSigRgbData)
+ error("Output profile must be RGB to use video encoding option");
+ }
+ }
+
+ /* Do sanity check and setup for BT.1886 gamma mapping */
+ if (li.in.bt1886) {
+ bt1886_setnop(&li.in.bt);
+ }
+ if (li.in.bt1886 == 1 || li.in.bt1886 == 2) { /* If doing BT.1886 black point mapping */
+ icxLuBase *oluo; /* Output fwd lookup */
+ icxLuMatrix *lu; /* Input profile lookup */
+ icmLuMatrix *plu; /* Input profile lookup */
+ double bp[3], rgb[3];
+
+ /* Check input profile is an RGB matrix profile */
+ if (li.in.alg != icmMatrixFwdType
+ || li.in.csp != icSigRgbData)
+ error("BT.1886 mode only works with an RGB matrix input profile");
+
+ lu = (icxLuMatrix *)li.in.luo; /* Safe to coerce - we have checked it's matrix. */
+ plu = (icmLuMatrix *)lu->plu;
+
+ if ((oluo = li.out.x->get_luobj(li.out.x, ICX_CLIP_NEAREST, icmFwd, icRelativeColorimetric,
+ icSigXYZData, icmLuOrdNorm, &li.out.vc, &li.out.ink)) == NULL) {
+ error("get xlookup object failed: %d, %s",li.out.x->errc,li.out.x->err);
+ }
+ /* We're assuming that the input space has a perfect black point... */
+
+ /* Lookup the ouput black point in XYZ PCS.*/
+ bp[0] = bp[1] = bp[2] = 0.0;
+ oluo->lookup(oluo, bp, bp);
+
+ if (li.in.bt1886 == 1) { /* Using effective gamma */
+ li.in.tgamma = xicc_tech_gamma(li.in.egamma, bp[1]);
+ if (li.verb)
+ printf("Technical gamma %f used to achieve effective gamma %f\n",li.in.tgamma, li.in.egamma);
+ } else {
+ if (li.verb)
+ printf("Using technical gamma %f\n",li.in.tgamma);
+ }
+
+ bt1886_setup(&li.in.bt, bp, li.in.tgamma);
+
+ if (li.verb) {
+ printf("bt1886 target out black rel XYZ = %f %f %f, Lab %f %f %f\n",
+ bp[0],bp[1],bp[2], li.in.bt.outL, li.in.bt.tab[0], li.in.bt.tab[1]);
+ printf("bt1886 Y input offset = %f\n", li.in.bt.ingo);
+ printf("bt1886 Y output scale = %f\n", li.in.bt.outsc);
+ }
+
+ /* Check black point now produced by input profile with bt.1886 adjustment */
+ rgb[0] = rgb[1] = rgb[2] = 0.0;
+ lu->fwd_curve(lu, rgb, rgb);
+ lu->fwd_matrix(lu, rgb, rgb);
+ bt1886_apply(&li.in.bt, plu, rgb, rgb);
+ if (li.verb) printf("bt1886 check input black point rel. XYZ %f %f %f\n", rgb[0],rgb[1],rgb[2]);
+ /* Convert XYZ black point to gamut mapping space */
+ lu->fwd_abs(lu, li.in.bt_bk, bp);
+ if (li.verb) printf("bt1886 check input black point PCS %f %f %f\n", li.in.bt_bk[0],li.in.bt_bk[1],li.in.bt_bk[2]);
+
+ oluo->del(oluo);
+
+ if (li.verb) {
+ int no = 21;
+
+ /* Overral rendering curve from video in to output target */
+ printf("BT.1886 overall rendering\n");
+ for (i = 0; i < no; i++) {
+ double v = i/(no-1.0), vv;
+ double vi[3], vo[3], Lab[3];
+ double loglog = 0.0;
+
+ if (v <= 0.081)
+ vv = v/4.5;
+ else
+ vv = pow((0.099 + v)/1.099, 1.0/0.45);
+
+ vi[0] = vv * 0.9642; /* To D50 XYZ */
+ vi[1] = vv * 1.0000;
+ vi[2] = vv * 0.8249;
+
+ bt1886_apply(&li.in.bt, plu, vo, vi); /* BT.1886 mapping */
+
+ icmXYZ2Lab(&icmD50, Lab, vo);
+
+ if (v > 1e-9 && vo[1] > 1e-9 && fabs(v - 1.0) > 1e-9)
+ loglog = log(vo[1])/log(v);
+
+ printf(" In %5.1f%% -> XYZ in %f -> bt.1886 %f, log/log %.3f, Lab %f %f %f \n",v * 100.0,vi[1],vo[1], loglog, Lab[0], Lab[1], Lab[2]);
+ }
+ }
+ }
+
+ /* - - - - - - - - - - - - - - - - - - - */
/* Setup the gamut mapping */
// ~~~~ need to account for possible abstract profile after source !!!!
// ~~~~ also need to fix tiffgamut to allow for abstract profile !!!!
@@ -2466,6 +3323,14 @@ main(int argc, char *argv[]) {
if ((csgam = li.in.luo->get_gamut(li.in.luo, sgres)) == NULL)
error ("%d, %s",li.in.x->errc, li.in.x->err);
+ /* If BT.1886 has modified the effective input space black point, */
+ /* change the black point in the source gamut to match. (Note that */
+ /* this doesn't fix the source gamut surface itself, but should */
+ /* make sure that the gamut mapping black point mapping works properly.) */
+ if (li.in.bt1886 == 1) {
+ csgam->set_cs_bp_kp_ovrd(csgam, li.in.bt_bk, li.in.bt_bk);
+ }
+
/* Grab a given source image gamut. */
if (sgam_name[0] != '\000') { /* Optional source gamut - ie. from an images */
@@ -2579,8 +3444,16 @@ main(int argc, char *argv[]) {
/* Values that must be set before writing */
wh->deviceClass = icSigLinkClass; /* We are creating a link ! */
- wh->colorSpace = li.in.h->colorSpace; /* Input profile device space */
- wh->pcs = li.out.h->colorSpace; /* Output profile device space */
+ if (li.in.tvenc >= 2) {
+ wh->colorSpace = icSigYCbCrData; /* Use YCbCr encoding */
+ } else {
+ wh->colorSpace = li.in.h->colorSpace; /* Input profile device space */
+ }
+ if (li.out.tvenc >= 2) {
+ wh->pcs = icSigYCbCrData; /* Use YCbCr encoding */
+ } else {
+ wh->pcs = li.out.h->colorSpace; /* Output profile device space */
+ }
if (li.mode > 0) {
wh->renderingIntent = li.gmi.icci; /* Closest ICC intent */
} else {
@@ -2897,6 +3770,8 @@ main(int argc, char *argv[]) {
/* 16 bit input device -> output device lut: */
{
int inputEnt, outputEnt, clutPoints;
+ int *apxls_min = NULL, *apxls_max = NULL;
+ int tapxls_min[MAX_CHAN], tapxls_max[MAX_CHAN];
icmLut *wo;
/* Setup the cLUT resolutions */
@@ -2933,7 +3808,7 @@ main(int argc, char *argv[]) {
break;
case 3:
if (li.quality >= 3)
- clutPoints = 52;
+ clutPoints = 53;
else if (li.quality == 2)
clutPoints = 33;
else if (li.quality == 1)
@@ -2945,7 +3820,7 @@ main(int argc, char *argv[]) {
if (li.quality >= 3)
clutPoints = 33;
else if (li.quality == 2)
- clutPoints = 18;
+ clutPoints = 17;
else if (li.quality == 1)
clutPoints = 9;
else
@@ -2953,15 +3828,15 @@ main(int argc, char *argv[]) {
break;
case 5:
if (li.quality >= 3)
- clutPoints = 18;
+ clutPoints = 17;
else if (li.quality == 2)
- clutPoints = 16;
+ clutPoints = 15;
else
clutPoints = 9;
break;
case 6:
if (li.quality >= 3)
- clutPoints = 12;
+ clutPoints = 13;
else if (li.quality == 2)
clutPoints = 9;
else
@@ -2969,7 +3844,7 @@ main(int argc, char *argv[]) {
break;
case 7:
if (li.quality >= 3)
- clutPoints = 8;
+ clutPoints = 9;
else if (li.quality == 2)
clutPoints = 7;
else
@@ -3004,6 +3879,47 @@ main(int argc, char *argv[]) {
if (out_curve_res > outputEnt)
outputEnt = out_curve_res;
+ /* Sanity checking */
+ if (li.in.tvenc >= 2) { /* YCbCr encoded input */
+ if ((clutPoints & 1) == 0)
+ warning("Making grid resolution is even - this is not ideal for YCbCr input");
+ }
+ if (li.in.tvenc != 0 && clutPoints != 65 && clutPoints != 129 && clutPoints != 256)
+ warning("Video or YCbCr encoded inputs will work best with grid res. of 65 (got %d)",clutPoints);
+ if (li.tdlut == 1) { /* eeColor encoded input */
+ if (clutPoints != 65)
+ warning("eeColor 3DLut needs grid resolution of 65 (got %d)",clutPoints);
+ inputEnt = 1024;
+ outputEnt = 4096; /* Ideally 8192 */
+
+ } else if (li.tdlut == 2) {
+ if (clutPoints != 65 && clutPoints != 129 && clutPoints != 256)
+ warning("MadVR 3DLut will work best with grid resolution of 65 (got %d)",clutPoints);
+ inputEnt = 1024; /* Not used */
+ outputEnt = 1024; /* Not used */
+ }
+
+ /* Limits are grid indexes that should not be adjusted by SET_APXLS */
+ /* Grid index is not adjusted if it's within 10% of device value limits */
+ if (li.in.tvenc == 1) { /* Video encoded */
+ apxls_min = tapxls_min;
+ apxls_max = tapxls_max;
+ for (i = 0; i < li.in.chan; i++) {
+ apxls_min[i] = (int)(16.0/255.0 * (clutPoints-1.0) + 0.9);
+ apxls_max[i] = (int)(235.0/255.0 * (clutPoints-1.0) + 0.1);
+ }
+ } else if (li.in.tvenc >= 2) { /* YCbCr encoded */
+ apxls_min = tapxls_min;
+ apxls_max = tapxls_max;
+ for (i = 0; i < li.in.chan; i++) {
+ apxls_min[i] = (int)(16.0/255.0 * (clutPoints-1.0) + 0.9);
+ if (i == 0)
+ apxls_max[i] = (int)(235.0/255.0 * (clutPoints-1.0) + 0.1);
+ else
+ apxls_max[i] = (int)(240.0/255.0 * (clutPoints-1.0) + 0.1);
+ }
+ }
+
/* Link Lut = AToB0 */
if ((wo = (icmLut *)wr_icc->add_tag(
@@ -3018,6 +3934,11 @@ main(int argc, char *argv[]) {
wo->clutPoints = clutPoints;
wo->outputEnt = outputEnt;
+ if (clutPoints == 256) { /* MadVR special */
+ wr_icc->allowclutPoints256 = 1;
+ warning("Creating non-standard 256 res. cLUT ICC profile !!!!");
+ }
+
if (wo->allocate((icmBase *)wo) != 0) /* Allocate space */
error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err);
@@ -3033,29 +3954,55 @@ main(int argc, char *argv[]) {
if (li.verb)
printf("Filling in Lut table\n");
#ifdef DEBUG_ONE
-#define DBGNO 1 /* Up to 10 */
+#define DBGNO 3 /* Up to 10 */
#ifndef NEVER
/* Test a single given rgb/cmyk -> cmyk value */
{
double in[10][MAX_CHAN];
double out[MAX_CHAN];
- in[0][0] = 1.0;
- in[0][1] = 1.0;
- in[0][2] = 1.0;
+// in[0][0] = 0.2;
+// in[0][1] = 0.2;
+// in[0][2] = 0.8;
+
+// in[0][0] = 0.5;
+// in[0][1] = 0.5;
+// in[0][2] = 0.5;
+
+// in[0][0] = ((235-16)/255.0 * 0.5) + 16/255.0;
+// in[0][1] = ((235-16)/255.0 * 0.5) + 16/255.0;
+// in[0][2] = ((235-16)/255.0 * 0.5) + 16/255.0;
+
+// in[0][3] = 0.0;
+
+// in[0][0] = 16.0/255.0;
+// in[0][1] = 16.0/255.0;
+// in[0][2] = 16.0/255.0;
+// in[0][3] = 0.0;
+
+ in[0][0] = 3.0/64.0;
+ in[0][1] = 3.0/64.0;
+ in[0][2] = 3.0/64.0;
in[0][3] = 0.0;
- in[1][0] = 1.0;
- in[1][1] = 1.0;
- in[1][2] = 1.0;
+ in[1][0] = 4.0/64.0;
+ in[1][1] = 4.0/64.0;
+ in[1][2] = 4.0/64.0;
in[1][3] = 0.0;
+ in[2][0] = 5.0/64.0;
+ in[2][1] = 5.0/64.0;
+ in[2][2] = 5.0/64.0;
+ in[2][3] = 0.0;
+
for (i = 0; i < DBGNO; i++) {
- printf("Input %f %f %f %f\n",in[i][0], in[i][1], in[i][2], in[i][3]);
+ printf("Input %f %f %f %*\n",in[i][0], in[i][1], in[i][2], in[i][3]);
devi_devip((void *)&li, out, in[i]);
+ printf("Input' %f %f %f %*\n",out[0], out[1], out[2], out[3]);
devip_devop((void *)&li, out, out);
+ printf("Out'' %f %f %f %*\n",out[0], out[1], out[2], out[3]);
devop_devo((void *)&li, out, out);
- printf("Output %f %f %f %f\n\n",out[0], out[1], out[2], out[3]);
+ printf("Out %f %f %f %*\n\n",out[0], out[1], out[2], out[3]);
}
}
#endif /* NEVER */
@@ -3069,8 +4016,13 @@ main(int argc, char *argv[]) {
;
li.total = itotal;
/* Allow for extra lookups due to ICM_CLUT_SET_APXLS */
- for (itotal = 1, ui = 0; ui < li.in.chan; ui++, itotal *= (clutPoints-1))
- ;
+ if (apxls_min != NULL && apxls_max != NULL) {
+ for (itotal = 1, ui = 0; ui < li.in.chan; ui++)
+ itotal *= (apxls_max[ui] - apxls_min[ui]);
+ } else {
+ for (itotal = 1, ui = 0; ui < li.in.chan; ui++)
+ itotal *= (clutPoints-1);
+ }
li.total += itotal;
li.count = 0;
printf(" 0%%"); fflush(stdout);
@@ -3086,7 +4038,8 @@ main(int argc, char *argv[]) {
NULL, NULL, /* Use default input colorspace range */
devip_devop, /* devi' -> devo' transfer function */
NULL, NULL, /* Default output colorspace range */
- devop_devo /* Output transfer tables, devo'->devo */
+ devop_devo, /* Output transfer tables, devo'->devo */
+ apxls_min, apxls_max /* Limit APXLS to inside colorspace */
) != 0) {
error("Setting 16 bit Lut failed: %d, %s",wr_icc->errc,wr_icc->err);
}
@@ -3107,13 +4060,88 @@ main(int argc, char *argv[]) {
if (li.verb && li.wphack && li.wphacked > 1)
printf("Warning :- white point hack trigger more than once! (%d)\n",li.wphacked);
+ /* Special case black point correction when we are usng TV encoding */
+ /* and the black probably doesn't lie on a grid point. */
+ /* This probably only works if we can have "-ve" output values */
+ /* by virtue of the output being tv encoded too. */
+ if (li.in.tvenc) {
+ icmLut *lut;
+ double ival[MXDO]; /* Black input value */
+ double oval[MXDO]; /* Correct output value for black */
+
+ if ((lut = (icmLut *)wr_icc->read_tag(wr_icc, icSigAToB0Tag)) == NULL)
+ error("unableto locate A2B tag: %d, %s",wr_icc->errc,wr_icc->err);
+
+ ival[0] = ival[1] = ival[2] = 0.0; /* RGB black input */
+
+ /* Encode input value */
+ if (li.out.tvenc == 1) { /* Video 16-235 range */
+ icmRGB_2_VidRGB(ival, ival);
+ } else if (li.out.tvenc == 2) { /* Rec601 YCbCr */
+ icmRec601_RGBd_2_YPbPr(ival, ival);
+ icmRecXXX_YPbPr_2_YCbCr(ival, ival);
+ } else if (li.out.tvenc == 3) { /* Rec709 1150/60/2:1 YCbCr */
+ icmRec709_RGBd_2_YPbPr(ival, ival);
+ icmRecXXX_YPbPr_2_YCbCr(ival, ival);
+ } else if (li.out.tvenc == 4) { /* Rec709 1250/50/2:1 YCbCr */
+ icmRec709_50_RGBd_2_YPbPr(ival, ival);
+ icmRecXXX_YPbPr_2_YCbCr(ival, ival);
+ } else if (li.out.tvenc == 5) { /* Rec2020 Non-constant Luminance YCbCr encoding */
+ icmRec2020_NCL_RGBd_2_YPbPr(ival, ival);
+ icmRecXXX_YPbPr_2_YCbCr(ival, ival);
+ } else if (li.out.tvenc == 6) { /* Rec2020 Constant Luminance YCbCr encoding */
+ icmRec2020_CL_RGBd_2_YPbPr(ival, ival);
+ icmRecXXX_YPbPr_2_YCbCr(ival, ival);
+ }
+
+//printf("input value %f %f %f\n",ival[0], ival[1], ival[2]);
+ lut->lookup_clut_sx(lut, oval, ival);
+//printf("before tune out %f %f %f\n",oval[0], oval[1], oval[2]);
+
+ /* Lookup the cLUT input value */
+ devi_devip((void *)&li, ival, ival);
+
+ /* Look up black output value we want */
+ devip_devop((void *)&li, oval, ival);
+
+//printf("bp tune target %f %f %f\n",oval[0], oval[1], oval[2]);
+
+ // ~~~~9999 should do a lookup to set sx/nl type corrctly
+ rv = lut->tune_value(lut, oval, ival);
+// rv = icmLut_tune_value_sx(lut, oval, ival);
+// rv = icmLut_tune_value_nl(lut, oval, ival);
+
+ if (rv != 0)
+ warning("Fine tuning video black failed - clipping");
+
+// lut->lookup_clut_sx(lut, oval, ival);
+//printf("after sx out %f %f %f\n",oval[0], oval[1], oval[2]);
+
+// lut->lookup_clut_nl(lut, oval, ival);
+//printf("after nl out %f %f %f\n",oval[0], oval[1], oval[2]);
+ }
+
if (li.verb)
printf("Writing out file\n");
/* Write the file out */
if ((rv = wr_icc->write(wr_icc,wr_fp,0)) != 0)
error ("Write file: %d, %s",rv,wr_icc->err);
-
+
+ /* eeColor format */
+ if (li.tdlut == 1) {
+ write_eeColor1DinputLuts(&li, tdlut_name);
+ if (write_eeColor3DLut(wr_icc, tdlut_name))
+ error ("Write file '%s' failed",tdlut_name);
+ write_eeColor1DoutputLuts(&li, tdlut_name);
+ }
+
+ /* MadVR format */
+ else if (li.tdlut == 2) {
+ if (write_MadVR_3DLut(&li, wr_icc, tdlut_name))
+ error ("Write file '%s' failed",tdlut_name);
+ }
+
wr_icc->del(wr_icc);
wr_fp->del(wr_fp);
@@ -3276,6 +4304,9 @@ main(int argc, char *argv[]) {
li.abs_icc->del(li.abs_icc);
li.abs_fp->del(li.abs_fp);
}
+ if (li.cal != NULL) {
+ li.cal->del(li.cal);
+ }
li.in.luo->del(li.in.luo);
li.in.x->del(li.in.x);
@@ -3290,9 +4321,420 @@ main(int argc, char *argv[]) {
return 0;
}
+/* ===================================================================== */
+/* Write a eeColor 1DLut input LUT files */
+/* Return nz on error */
+int write_eeColor1DinputLuts(clink *li, char *tdlut_name) {
+ char fname[MAXNAMEL+1+20], *xl;
+ int i, j, k;
+
+ for (j = 0; j < 3; j++) {
+ icmFile *fp;
+ double in[3], out[3];
+
+ strncpy(fname,tdlut_name,MAXNAMEL-1); fname[MAXNAMEL-1] = '\000';
+ if ((xl = strrchr(fname, '.')) == NULL) /* Figure where extention is */
+ xl = fname + strlen(fname);
+
+ if (j == 0)
+ strcpy(xl,"-first1dred.txt");
+ else if (j == 1)
+ strcpy(xl,"-first1dgreen.txt");
+ else
+ strcpy(xl,"-first1dblue.txt");
+
+ if ((fp = new_icmFileStd_name(fname,"w")) == NULL)
+ error ("write_eeColor1DinputLuts: Can't open file '%s'",fname);
+
+ for (i = 0; i < 1024; i++) {
+ for (k = 0; k < 3; k++)
+ in[k] = i/(1024-1.0);
+ devi_devip((void *)li, out, in);
+ fp->gprintf(fp,"%.6f\n",out[j]);
+ }
+
+ if (fp->del(fp))
+ error ("write_eeColor1DinputLuts to '%s' failed",fname);
+ }
+ return 0;
+}
+
+#ifdef NEVER
+
+/* Write a eeColor 3DLut file */
+/* Return nz on error */
+/* (We delve inside the icc structure, so don't use this code) */
+int write_eeColor3DLut(icc *icc, char *fname) {
+ icmLut *lut;
+ icmFile *fp;
+
+ if ((lut = (icmLut *)icc->read_tag(icc, icSigAToB0Tag)) == NULL)
+ error("write_eeColor3DLut: unableto locate A2B tag: %d, %s",icc->errc,icc->err);
+
+ /* Open up the 3dlut file for writing */
+ if ((fp = new_icmFileStd_name(fname,"w")) == NULL)
+ error ("write_eeColor3DLut: Can't open file '%s'",fname);
+
+ {
+ int i, j, k;
+ DCOUNT(gc, MAX_CHAN, lut->inputChan, 0, 0, lut->clutPoints);
+ int ord[3] = { 1, 0, 2 }; /* Channel order, fastest to slowest, G, R, B */
+ DC_INIT(gc);
+ while (!DC_DONE(gc)) {
+ int ix;
+
+ ix = 0;
+ for (i = 0; i < lut->inputChan; i++) {
+ ix += lut->dinc[ord[i]] * gc[i];
+ }
+
+ /* Hmm. We're assuming that the eeColor is smart enough */
+ /* to map the floating point values to the maximum */
+ /* video encoded range, ie 255 * (bits - 8). */
+ /* It's not clear if "bits" is 8, 10 or 12, or if */
+ /* it actually works this way though. */
+
+ /* It's also not clear what it does for full range input, */
+ /* if anything. Ie. it may just map that to 255 * (bits - 8) too. */
+
+ /* There are two sets of RGB values. One is (suposedly)
+ the "calibrated white point" and one "the native white point".
+ It's hard to guess what this is.
+ */
+
+ for (i = 0; i < lut->inputChan; i++) {
+ fp->gprintf(fp," %.6f",lut->clutTable[ix + i]);
+ }
+ for (i = 0; i < lut->outputChan; i++) {
+ fp->gprintf(fp," %.6f",lut->clutTable[ix + i]);
+ }
+
+ fp->gprintf(fp,"\n");
+
+ DC_INC(gc);
+ }
+ }
+ if (fp->del(fp))
+ error ("write_eeColor3DLut: write to '%s' failed",fname);
+ return 0;
+}
+
+#else
+
+/* Write a eeColor 3DLut file by doing a lookup for each node. */
+/* Return nz on error */
+int write_eeColor3DLut(icc *icc, char *fname) {
+ icmLuBase *luo;
+ icmLuLut *lut;
+ int i, j, k;
+ DCOUNT(gc, MAX_CHAN, 3, 0, 0, 65);
+ int ord[3]; /* Input channel order, fastest to slowest */
+ icmFile *fp;
+
+ /* Get a conversion object. We assume it is of the right type, being a link */
+ if ((luo = icc->get_luobj(icc, icmFwd, icmDefaultIntent, icmSigDefaultData, icmLuOrdNorm))
+ == NULL)
+ error("write_eeColor3DLut: get luobj failed: %d, %s",icc->errc,icc->err);
+
+ /* Cast to Lut lookup - safe because that's all that collink does */
+ lut = (icmLuLut *)luo;
+
+ /* Open up the 3dlut file for writing */
+ if ((fp = new_icmFileStd_name(fname,"w")) == NULL)
+ error ("write_eeColor3DLut: Can't open file '%s'",fname);
+
+ DC_INIT(gc);
+
+ ord[0] = 1; ord[1] = 0; ord[2] = 2; /* Fastest to slowest G R B */
+
+ while (!DC_DONE(gc)) {
+ double in[3], out[3];
+
+ /* Hmm. If we knew how the eeColor quantized the incoming video values, */
+ /* we could do the same quantization here for better accuracy. */
+ /* It's hard to guess given the unknown 8/10/12 bit nature of it. */
+ for (i = 0; i < 3; i++)
+ in[ord[i]] = gc[i]/64.0;
+
+ if (lut->clut(lut, out, in) > 1)
+ error ("write_eeColor3DLut: %d, %s",icc->errc,icc->err);
+
+//printf("~1 %f %f %f -> %f %f %f\n", in[0], in[1], in[2], out[0], out[1], out[2]);
+
+ for (i = 0; i < 3; i++)
+ fp->gprintf(fp," %.6f",out[i]);
+ for (i = 0; i < 3; i++)
+ fp->gprintf(fp," %.6f",out[i]);
+
+ fp->gprintf(fp,"\n");
+
+ DC_INC(gc);
+ }
+ if (fp->del(fp))
+ error ("write_eeColor3DLut: write to '%s' failed",fname);
+ return 0;
+}
+#endif
+
+/* Write a eeColor 1DLut output LUT files */
+/* Return nz on error */
+int write_eeColor1DoutputLuts(clink *li, char *tdlut_name) {
+ char fname[MAXNAMEL+1+20], *xl;
+ int i, j, k;
+
+ for (j = 0; j < 3; j++) {
+ icmFile *fp;
+ double in[3], out[3];
+
+ strncpy(fname,tdlut_name,MAXNAMEL-1); fname[MAXNAMEL-1] = '\000';
+ if ((xl = strrchr(fname, '.')) == NULL) /* Figure where extention is */
+ xl = fname + strlen(fname);
+
+ if (j == 0)
+ strcpy(xl,"-second1dred.txt");
+ else if (j == 1)
+ strcpy(xl,"-second1dgreen.txt");
+ else
+ strcpy(xl,"-second1dblue.txt");
+
+ if ((fp = new_icmFileStd_name(fname,"w")) == NULL)
+ error ("write_eeColor1DoutputLuts: Can't open file '%s'",fname);
+
+ for (i = 0; i < 8192; i++) {
+ for (k = 0; k < 3; k++)
+ in[k] = i/(8192-1.0);
+ devop_devo((void *)li, out, in);
+ fp->gprintf(fp,"%.6f\n",out[j]);
+ }
+
+ if (fp->del(fp))
+ error ("write_eeColor1DoutputLuts to '%s' failed",fname);
+ }
+ return 0;
+}
+
+/* ===================================================================== */
+/* Write MadVR 3dlut file */
+
+/* Return nz on error */
+int write_MadVR_3DLut(clink *li, icc *icc, char *fname) {
+ icmFile *fp;
+ ORD8 *h;
+ int of, hoff, clutsize;
+ int dov2 = 0;
+ double rgbw[4][3] = { /* RGB + White Yxy */
+ { 1.0, 0.0, 0.0 },
+ { 0.0, 1.0, 0.0 },
+ { 0.0, 0.0, 1.0 },
+ { 1.0, 1.0, 1.0 }
+ };
+ int i;
+
+ icmLuBase *luo;
+
+ /* Get an absolute conversion object to lookup primaries */
+ if ((luo = li->in.c->get_luobj(li->in.c, icmFwd, icAbsoluteColorimetric, icmSigDefaultData, icmLuOrdNorm))
+ == NULL)
+ error ("write_MadVR_3DLut: %d, %s",icc->errc, icc->err);
+
+ for (i = 0; i < 4; i++) {
+ if (luo->lookup(luo, rgbw[i], rgbw[i]) > 1)
+ error ("write_MadVR_3DLut: %d, %s",icc->errc,icc->err);
+
+ icmXYZ2Yxy(rgbw[i], rgbw[i]);
+ }
+
+ luo->del(luo);
+
+ /* Get a conversion object. We assume it is of the right type */
+ if ((luo = icc->get_luobj(icc, icmFwd, icmDefaultIntent, icmSigDefaultData, icmLuOrdNorm))
+ == NULL)
+ error ("write_MadVR_3DLut: %d, %s",icc->errc, icc->err);
+
+ /* Open up the 3dlut file for writing */
+ if ((fp = new_icmFileStd_name(fname,"w")) == NULL)
+ error("write_MadVR_3DLut: Can't open file '%s'",fname);
+
+ /* Create the 3dlutheader */
+ if ((h = (ORD8 *)calloc(0x4000, sizeof(ORD8))) == NULL)
+ error("write_MadVR_3DLut: failed to calloc 16384 bytes");
+
+ of = 0;
+ if (dov2) {
+ h[0] = '3'; h[1] = 'D'; h[2] = 'L'; h[3] = '2'; of += 4; /* Signature */
+ } else {
+ h[0] = '3'; h[1] = 'D'; h[2] = 'L'; h[3] = 'T'; of += 4; /* Signature */
+ }
+ write_ORD32_le(1, h + of); of += 4; /* File format version */
+ strncpy((char *)h+of, "ArgyllCMS collink", 31); of += 32; /* Creation program */
+ write_ORD64_le(ARGYLL_VERSION, h + of); of += 8; /* Program version */
+ write_ORD32_le(8, h + of); of += 4; /* input bit depth */
+ write_ORD32_le(8, h + of); of += 4;
+ write_ORD32_le(8, h + of); of += 4;
+ write_ORD32_le(li->in.tvenc >= 2 ? 1 : 0, h + of); of += 4; /* Input BGR or cCbCr enc */
+ if (dov2)
+ write_ORD32_le(li->in.tvenc != 0 ? 1 : 0, h + of), of += 4; /* Range */
+ write_ORD32_le(16, h + of); of += 4; /* Output bit depth */
+ write_ORD32_le(li->out.tvenc >= 2 ? 1 : 0, h + of); of += 4; /* Output BGR or YCbCr encoding */
+ if (dov2)
+ write_ORD32_le(li->out.tvenc != 0 ? 1 : 0, h + of), of += 4; /* Range */
+ write_ORD32_le(0x200, h + of); of += 4; /* Bytes to parameters */
+ hoff = 0x200;
+ hoff += sprintf((char *)h+hoff, "Input_Primaries %f %f %f %f %f %f %f %f\r\n", /* For V0.66+ */
+ rgbw[0][1], rgbw[0][2], rgbw[1][1], rgbw[1][2],
+ rgbw[2][1], rgbw[2][2], rgbw[3][1], rgbw[3][2]);
+// hoff += sprintf((char *)h+hoff, "Input_Transfer_Function 1.0 0.0 0.45454545454545454545454545454545 0.0");
+
+ if (li->in.tvenc == 0)
+ hoff += sprintf((char *)h+hoff, "Input_Range 0 255\r\n");
+ else
+ hoff += sprintf((char *)h+hoff, "Input_Range 16 235\r\n");
+ if (li->out.tvenc == 0)
+ hoff += sprintf((char *)h+hoff, "Output_Range 0 255\r\n");
+ else
+ hoff += sprintf((char *)h+hoff, "Output_Range 16 235\r\n");
+ write_ORD32_le(hoff - 0x200 + 1, h + of); of += 4; /* Bytes of parameter data + nul */
+ write_ORD32_le(0x4000, h + of); of += 4; /* Bytes to clut data */
+ write_ORD32_le(0, h + of); of += 4; /* No compression */
+ clutsize = (1 << (3 * 8)) * 3 * 2;
+ write_ORD32_le(clutsize, h + of); of += 4; /* Compressed clut size */
+ write_ORD32_le(clutsize, h + of); of += 4; /* Uncompressed clut size */
+
+ if (li->verb)
+ printf("Writing 3dLut\n");
+
+ /* Write the 3dlutheader */
+ if (fp->write(fp, h, 1, 0x4000) != 0x4000)
+ error ("write_MadVR_3DLut: write header failed");
+
+ /* Write the clut data */
+ {
+ int i, j, k;
+ DCOUNT(gc, MAX_CHAN, 3, 0, 0, 256);
+ int ord[3]; /* Input channel order, fastest to slowest */
+ ORD8 buf[3 * 2];
+
+ DC_INIT(gc);
+
+ if (li->in.tvenc >= 2) { /* YCbCr fastest to slowest is Y Cb Cr */
+ ord[0] = 0; ord[1] = 1; ord[2] = 2; /* Y Cb Cr */
+
+ } else { /* RGB fastest to slowest is B G R */
+ ord[0] = 2; ord[1] = 1; ord[2] = 0; /* B G R */
+ }
+
+ while (!DC_DONE(gc)) {
+ double in[3], out[3];
+ int iout[3];
+
+ for (i = 0; i < 3; i++)
+ in[ord[i]] = gc[i]/255.0;
+
+ if (luo->lookup(luo, out, in) > 1)
+ error ("write_MadVR_3DLut: %d, %s",icc->errc,icc->err);
+
+//printf("~1 %f %f %f -> %f %f %f\n", in[0], in[1], in[2], out[0], out[1], out[2]);
+
+ if (li->in.tvenc == 7 || li->in.tvenc == 8) { /* xvYCC */
+ for (i = 1; i < 3; i++) { /* Force 'sync' entry values on CbCr*/
+ if (gc[i] == 0) {
+ out[i] = 0.0;
+ } else if (gc[i] == 255) {
+ out[i] = 1.0;
+ }
+ }
+ }
+
+ if (li->out.tvenc == 0) { /* Full range 16 bits */
+ iout[0] = (int)(out[0] * 0xffff + 0.5);
+ iout[1] = (int)(out[1] * 0xffff + 0.5);
+ iout[2] = (int)(out[2] * 0xffff + 0.5);
+
+ } else { /* TV encoding - shifted by 8 bits */
+ iout[0] = (int)(out[0] * 0xff00 + 0.5);
+ iout[1] = (int)(out[1] * 0xff00 + 0.5);
+ iout[2] = (int)(out[2] * 0xff00 + 0.5);
+
+ }
+
+ if (li->out.tvenc >= 2) { /* YCbCr order is YCbCr */
+ write_ORD16_le(iout[0], buf + 0);
+ write_ORD16_le(iout[1], buf + 2);
+ write_ORD16_le(iout[2], buf + 4);
+
+ } else { /* RGB order is BGR */
+ write_ORD16_le(iout[2], buf + 0);
+ write_ORD16_le(iout[1], buf + 2);
+ write_ORD16_le(iout[0], buf + 4);
+ }
+
+ if (fp->write(fp, buf, 1, 6) != 6)
+ error ("write_MadVR_3DLut: write clut data failed");
+
+ DC_INC(gc);
+ }
+ }
+
+ /* Append a cal1 table to the 3dlut. */
+ /* This can be used to ensure that the Graphics Card VideoLuts */
+ /* are correctly setup to match what the 3dLut is expecting. */
+
+ /* Note that the calibration is full range, never TV encoded output values */
+
+ /* Format is (little endian):
+ 4 byte magic number 'cal1'
+ 4 byte version = 1
+ 4 byte number per channel entries = 256
+ 4 byte bytes per entry = 2
+ [3][256] 2 byte entry values. Tables are in RGB order
+ */
+ if (li->cal != NULL) {
+ ORD8 buf[4 * 4 + 3 * 256 * 2], *of = buf;
+ ORD32 magic, vers, entries, depth;
+ unsigned int val;
+ int i, j;
+
+ if (li->verb)
+ printf("Appending %scalibration curves\n", li->addcal == 2 ? "" : "linear");
+
+ magic = ('c')
+ + ('a' << 8)
+ + ('l' << 16)
+ + ('1' << 24);
+ write_ORD32_le(magic, of); of += 4; /* Magic number */
+
+ vers = 1;
+ write_ORD32_le(vers, of); of += 4; /* Format version */
+
+ entries = 256;
+ write_ORD32_le(entries, of); of += 4; /* Number of entries per channel */
+
+ depth = 2;
+ write_ORD32_le(depth, of); of += 4; /* Depth per entry in bytes */
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 256; i++) {
+ double v = i/255.0;
+
+ if (li->addcal == 2)
+ v = li->cal->interp_ch(li->cal, j, v);
+ val = (int)(v * 65535.0 + 0.5);
+ write_ORD16_le(val, of); of += 2;
+ }
+ }
+ if (fp->write(fp, buf, 1, sizeof(buf)) != sizeof(buf))
+ error ("write_MadVR_3DLut: write cal1 data failed");
+ }
+
+ if (fp->del(fp))
+ error ("write_MadVR_3DLut: write to '%s' failed",fname);
+ luo->del(luo);
+
+ return 0;
+}