1419 lines
41 KiB
C
1419 lines
41 KiB
C
|
|
/*
|
|
* Create a visualisation of the gamut of a TIFF or JPEG file.
|
|
*
|
|
* Author: Graeme W. Gill
|
|
* Date: 00/9/20
|
|
* Version: 1.00
|
|
*
|
|
* Copyright 2000, 2002, 2012 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.
|
|
*/
|
|
|
|
/*
|
|
* TTBD:
|
|
*
|
|
* Should record abs/relative intent, PCS space used together
|
|
* with viewing conditions, so that a mismatch within
|
|
* icclink can be detected or allowed for.
|
|
*
|
|
* Need to cope with profile not having black point.
|
|
*
|
|
* How to cope with no profile, therefore no white or black point ?
|
|
*
|
|
* Should we have a median filter option, to ignore small groups
|
|
* of extreme pixel values, rathar than total small numbers using -f ?
|
|
*/
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <fcntl.h>
|
|
#include <setjmp.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include "copyright.h"
|
|
#include "aconfig.h"
|
|
#include "numlib.h"
|
|
#include "tiffio.h"
|
|
#include "jpeglib.h"
|
|
#include "iccjpeg.h"
|
|
#include "icc.h"
|
|
#include "cgats.h"
|
|
#include "gamut.h"
|
|
#include "conv.h"
|
|
#include "xicc.h"
|
|
#include "sort.h"
|
|
#include "vrml.h"
|
|
|
|
#undef NOCAMGAM_CLIP /* No clip to CAM gamut before CAM lookup */
|
|
#undef DEBUG /* Dump filter cell contents */
|
|
|
|
#if !defined(O_CREAT) && !defined(_O_CREAT)
|
|
# error "Need to #include fcntl.h!"
|
|
#endif
|
|
|
|
|
|
void set_fminmax(double min[3], double max[3]);
|
|
void reset_filter();
|
|
void add_fpixel(double val[3]);
|
|
void flush_filter(int verb, gamut *gam, double filtperc);
|
|
void del_filter();
|
|
|
|
void usage(void) {
|
|
int i;
|
|
fprintf(stderr,"Create gamut surface of a TIFF or JPEG, Version %s\n",ARGYLL_VERSION_STR);
|
|
fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
|
|
fprintf(stderr,"usage: tiffgamut [-v level] [profile.icm | embedded.tif/jpg] infile1.tif/jpg [infile2.tif/jpg ...] \n");
|
|
fprintf(stderr," -v Verbose\n");
|
|
fprintf(stderr," -d sres Surface resolution details 1.0 - 50.0\n");
|
|
fprintf(stderr," -w emit %s %s file as well as CGATS .gam file\n",vrml_format(),vrml_ext());
|
|
fprintf(stderr," -n Don't add %s axes or white/black point\n",vrml_format());
|
|
fprintf(stderr," -k Add %s markers for prim. & sec. \"cusp\" points\n",vrml_format());
|
|
fprintf(stderr," (set env. ARGYLL_3D_DISP_FORMAT to VRML, X3D or X3DOM to change format)\n");
|
|
fprintf(stderr," -f perc Filter by popularity, perc = percent to use\n");
|
|
fprintf(stderr," -i intent p = perceptual, r = relative colorimetric,\n");
|
|
fprintf(stderr," s = saturation, a = absolute (default), d = profile default\n");
|
|
// fprintf(stderr," P = absolute perceptual, S = absolute saturation\n");
|
|
fprintf(stderr," -p oride l = Lab_PCS (default), j = %s Appearance Jab\n",icxcam_description(cam_default));
|
|
fprintf(stderr," -o order n = normal (priority: lut > matrix > monochrome)\n");
|
|
fprintf(stderr," r = reverse (priority: monochrome > matrix > lut)\n");
|
|
fprintf(stderr," -c viewcond set appearance mode and viewing conditions for %s,\n",icxcam_description(cam_default));
|
|
fprintf(stderr," either an enumerated choice, or a parameter:value changes\n");
|
|
for (i = 0; ; i++) {
|
|
icxViewCond vc;
|
|
if (xicc_enum_viewcond(NULL, &vc, i, NULL, 1, NULL) == -999)
|
|
break;
|
|
|
|
fprintf(stderr," %s\n",vc.desc);
|
|
}
|
|
fprintf(stderr," s:surround n = auto, a = average, m = dim, d = dark,\n");
|
|
fprintf(stderr," c = transparency (default average)\n");
|
|
fprintf(stderr," w:X:Y:Z Adapted white point as XYZ (default media white)\n");
|
|
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: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 %d)\n",XICC_DEFAULT_GLARE);
|
|
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," h:hkscale Helmholtz-Kohlrausch effect scale factor (default 1.0)\n");
|
|
fprintf(stderr," m:mtaf Mid-tone partial adaptation factor (default 0.0)\n");
|
|
fprintf(stderr," m:X:Y:Z Mid-tone Adaptation white as XYZ (default D50)\n");
|
|
fprintf(stderr," m:x:y Mid-tone Adaptation white as x, y\n");
|
|
fprintf(stderr," -x pcent Expand/compress gamut cylindrically by percent\n");
|
|
fprintf(stderr," -O outputfile Override the default output filename.\n");
|
|
fprintf(stderr," [profile.icm | embedded.tif/jpg] File specifying device to CIE conversion\n");
|
|
fprintf(stderr," infile1.tif/jpg ... One or more raster files to create gamut from\n");
|
|
exit(1);
|
|
}
|
|
|
|
#define GAMRES 10.0 /* Default surface resolution */
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
/* Conversion functions from direct binary 0..n^2-1 == 0.0 .. 1.0 range */
|
|
/* to ICC luo input range. */
|
|
/* It is assumed that the binary has been sign corrected to be */
|
|
/* contiguous (ie CIELab). */
|
|
|
|
/* TIFF 8 bit CIELAB to standard L*a*b* */
|
|
/* Assume that a & b have been converted from signed to offset */
|
|
static void cvt_CIELAB8_to_Lab(double *out, double *in) {
|
|
out[0] = in[0] * 100.0;
|
|
out[1] = in[1] * 255.0 - 128.0;
|
|
out[2] = in[2] * 255.0 - 128.0;
|
|
}
|
|
|
|
/* TIFF 16 bit CIELAB to standard L*a*b* */
|
|
/* Assume that a & b have been converted from signed to offset */
|
|
static void cvt_CIELAB16_to_Lab(double *out, double *in) {
|
|
out[0] = in[0] * 100.0;
|
|
out[1] = (in[1] - 32768.0/65535.0) * 256.0;
|
|
out[2] = (in[2] - 32768.0/65535.0) * 256.0;
|
|
}
|
|
|
|
/* TIFF 8 bit ICCLAB to standard L*a*b* */
|
|
static void cvt_ICCLAB8_to_Lab(double *out, double *in) {
|
|
out[0] = in[0] * 100.0;
|
|
out[1] = (in[1] * 255.0) - 128.0;
|
|
out[2] = (in[2] * 255.0) - 128.0;
|
|
}
|
|
|
|
/* TIFF 16 bit ICCLAB to standard L*a*b* */
|
|
static void cvt_ICCLAB16_to_Lab(double *out, double *in) {
|
|
out[0] = in[0] * (100.0 * 65535.0)/65280.0;
|
|
out[1] = (in[1] * (255.0 * 65535.0)/65280) - 128.0;
|
|
out[2] = (in[2] * (255.0 * 65535.0)/65280) - 128.0;
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
/* Convert a TIFF Photometric tag to an ICC colorspace. */
|
|
/* return 0 if not possible or applicable. */
|
|
icColorSpaceSignature
|
|
TiffPhotometric2ColorSpaceSignature(
|
|
void (**icvt)(double *out, double *in), /* Return read conversion function, NULL if none */
|
|
int *smsk, /* Return signed handling mask, 0x0 if none */
|
|
int pmtc, /* Input TIFF photometric */
|
|
int bps, /* Input Bits per sample */
|
|
int spp, /* Input Samples per pixel */
|
|
int extra /* Extra Samples per pixel, if any */
|
|
) {
|
|
if (icvt != NULL)
|
|
*icvt = NULL; /* Default return values */
|
|
if (smsk != NULL)
|
|
*smsk = 0x0;
|
|
|
|
switch (pmtc) {
|
|
case PHOTOMETRIC_MINISWHITE: /* Subtractive Gray */
|
|
return icSigGrayData;
|
|
|
|
case PHOTOMETRIC_MINISBLACK: /* Additive Gray */
|
|
return icSigGrayData;
|
|
|
|
case PHOTOMETRIC_RGB:
|
|
return icSigRgbData;
|
|
|
|
case PHOTOMETRIC_PALETTE:
|
|
return 0x0;
|
|
|
|
case PHOTOMETRIC_MASK:
|
|
return 0x0;
|
|
|
|
case PHOTOMETRIC_SEPARATED:
|
|
/* Should look at the colorant names to figure out if this is CMY, CMYK */
|
|
/* Should at least return both Cmy/3 or Cmyk/4 ! */
|
|
switch(spp) {
|
|
case 2:
|
|
return icSig2colorData;
|
|
case 3:
|
|
// return icSig3colorData;
|
|
return icSigCmyData;
|
|
case 4:
|
|
// return icSig4colorData;
|
|
return icSigCmykData;
|
|
case 5:
|
|
return icSig5colorData;
|
|
case 6:
|
|
return icSig6colorData;
|
|
case 7:
|
|
return icSig7colorData;
|
|
case 8:
|
|
return icSig8colorData;
|
|
case 9:
|
|
return icSig9colorData;
|
|
case 10:
|
|
return icSig10colorData;
|
|
case 11:
|
|
return icSig11colorData;
|
|
case 12:
|
|
return icSig12colorData;
|
|
case 13:
|
|
return icSig13colorData;
|
|
case 14:
|
|
return icSig14colorData;
|
|
case 15:
|
|
return icSig15colorData;
|
|
}
|
|
|
|
case PHOTOMETRIC_YCBCR:
|
|
return icSigYCbCrData;
|
|
|
|
case PHOTOMETRIC_CIELAB:
|
|
if (bps == 8) {
|
|
if (icvt != NULL)
|
|
*icvt = cvt_CIELAB8_to_Lab;
|
|
} else {
|
|
if (icvt != NULL)
|
|
*icvt = cvt_CIELAB16_to_Lab;
|
|
}
|
|
*smsk = 0x6; /* Treat a & b as signed */
|
|
return icSigLabData;
|
|
|
|
case PHOTOMETRIC_ICCLAB:
|
|
if (bps == 8) {
|
|
if (icvt != NULL)
|
|
*icvt = cvt_ICCLAB8_to_Lab;
|
|
} else {
|
|
if (icvt != NULL)
|
|
*icvt = cvt_ICCLAB16_to_Lab;
|
|
}
|
|
return icSigLabData;
|
|
|
|
case PHOTOMETRIC_ITULAB:
|
|
return 0x0; /* Could add this with a conversion function */
|
|
/* but have to allow for variable ITU gamut */
|
|
/* (Tag 433, "Decode") */
|
|
|
|
case PHOTOMETRIC_LOGL:
|
|
return 0x0; /* Could add this with a conversion function */
|
|
|
|
case PHOTOMETRIC_LOGLUV:
|
|
return 0x0; /* Could add this with a conversion function */
|
|
}
|
|
return 0x0;
|
|
}
|
|
|
|
|
|
char *
|
|
Photometric2str(
|
|
int pmtc
|
|
) {
|
|
static char buf[80];
|
|
switch (pmtc) {
|
|
case PHOTOMETRIC_MINISWHITE:
|
|
return "Subtractive Gray";
|
|
case PHOTOMETRIC_MINISBLACK:
|
|
return "Additive Gray";
|
|
case PHOTOMETRIC_RGB:
|
|
return "RGB";
|
|
case PHOTOMETRIC_PALETTE:
|
|
return "Indexed";
|
|
case PHOTOMETRIC_MASK:
|
|
return "Transparency Mask";
|
|
case PHOTOMETRIC_SEPARATED:
|
|
return "Separated";
|
|
case PHOTOMETRIC_YCBCR:
|
|
return "YCbCr";
|
|
case PHOTOMETRIC_CIELAB:
|
|
return "CIELab";
|
|
case PHOTOMETRIC_ICCLAB:
|
|
return "ICCLab";
|
|
case PHOTOMETRIC_ITULAB:
|
|
return "ITULab";
|
|
case PHOTOMETRIC_LOGL:
|
|
return "CIELog2L";
|
|
case PHOTOMETRIC_LOGLUV:
|
|
return "CIELog2Luv";
|
|
}
|
|
sprintf(buf,"Unknown Photometric Tag %d",pmtc);
|
|
return buf;
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
/* JPEG error information */
|
|
typedef struct {
|
|
jmp_buf env; /* setjmp/longjmp environment */
|
|
char message[JMSG_LENGTH_MAX];
|
|
} jpegerrorinfo;
|
|
|
|
/* JPEG error handler */
|
|
static void jpeg_error(j_common_ptr cinfo) {
|
|
jpegerrorinfo *p = (jpegerrorinfo *)cinfo->client_data;
|
|
(*cinfo->err->format_message) (cinfo, p->message);
|
|
longjmp(p->env, 1);
|
|
}
|
|
|
|
static char *
|
|
JPEG_cspace2str(
|
|
J_COLOR_SPACE cspace
|
|
) {
|
|
static char buf[80];
|
|
switch (cspace) {
|
|
case JCS_UNKNOWN:
|
|
return "Unknown";
|
|
case JCS_GRAYSCALE:
|
|
return "Monochrome";
|
|
case JCS_RGB:
|
|
return "RGB";
|
|
case JCS_YCbCr:
|
|
return "YCbCr";
|
|
case JCS_CMYK:
|
|
return "CMYK";
|
|
case JCS_YCCK:
|
|
return "YCCK";
|
|
}
|
|
sprintf(buf,"Unknown JPEG colorspace %d",cspace);
|
|
return buf;
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
int
|
|
main(int argc, char *argv[]) {
|
|
int fa,nfa; /* argument we're looking at */
|
|
int ffa, lfa; /* First, last input file argument */
|
|
static char prof_name[MAXNAMEL+1] = { '\000' }; /* ICC profile name, "" if none */
|
|
static char in_name[MAXNAMEL+1]; /* TIFF input file */
|
|
static char *xl = NULL, out_name[MAXNAMEL+4+1] = { '\000' }; /* VRML/X3D output file */
|
|
int verb = 0;
|
|
int vrml = 0;
|
|
int doaxes = 1;
|
|
int docusps = 0;
|
|
int filter = 0;
|
|
double filtperc = 100.0;
|
|
int rv = 0;
|
|
|
|
icc *icco = NULL;
|
|
icmErr err = { 0, { '\000'} };
|
|
xicc *xicco = NULL;
|
|
icxcam *cam = NULL; /* Separate CAM used for Lab TIFF files */
|
|
icxViewCond vc; /* Viewing Condition for CIECAM */
|
|
int vc_e = -1; /* Enumerated viewing condition */
|
|
int vc_s = -1; /* Surround override */
|
|
double vc_wXYZ[3] = {-1.0, -1.0, -1.0}; /* Adapted white override in XYZ */
|
|
double vc_wxy[2] = {-1.0, -1.0}; /* Adapted white override in x,y */
|
|
double vc_a = -1.0; /* Adapted luminance */
|
|
double vc_b = -1.0; /* Background % overid */
|
|
double vc_l = -1.0; /* Scene luminance override */
|
|
double vc_f = -1.0; /* Flare % overide */
|
|
double vc_g = -1.0; /* Glare % overide */
|
|
double vc_gXYZ[3] = {-1.0, -1.0, -1.0}; /* Glare color override in XYZ */
|
|
double vc_gxy[2] = {-1.0, -1.0}; /* Glare color override in x,y */
|
|
double vc_hkscale = -1.0; /* HK scaling factor */
|
|
double vc_mtaf = -1.0; /* Mid tone partial adapation factor from Wxyz to Wxyz2 */
|
|
double vc_Wxyz2[3] = {-1.0, -1.0, -1.0}; /* Adapted white override in XYZ */
|
|
double expand = 1.0; /* Expand gamut cylindrically by ratio */
|
|
icxLuBase *luo = NULL; /* Generic lookup object */
|
|
icColorSpaceSignature ins = icSigLabData, outs; /* Type of input and output spaces */
|
|
int inn, outn; /* Number of components */
|
|
icmLuAlgType alg; /* Type of lookup algorithm */
|
|
icmLookupFunc func = icmFwd; /* Must be */
|
|
icRenderingIntent intent = -1; /* Default */
|
|
icColorSpaceSignature pcsor = icSigLabData; /* Default */
|
|
icmLookupOrder order = icmLuOrdNorm; /* Default */
|
|
|
|
/* TIFF */
|
|
TIFFErrorHandler olderrh, oldwarnh;
|
|
TIFFErrorHandlerExt olderrhx, oldwarnhx;
|
|
TIFF *rh = NULL;
|
|
int x, y, width, height; /* Size of image */
|
|
uint16 samplesperpixel, bitspersample;
|
|
uint16 pconfig, photometric, pmtc;
|
|
tdata_t *inbuf;
|
|
int inbpix = 0; /* Number of pixels in jpeg in buf */
|
|
void (*cvt)(double *out, double *in) = NULL; /* TIFF conversion function, NULL if none */
|
|
icColorSpaceSignature tcs = 0; /* TIFF colorspace */
|
|
uint16 extrasamples = 0; /* Extra "alpha" samples */
|
|
uint16 *extrainfo = NULL; /* Info about extra samples */
|
|
int sign_mask = 0; /* Handling of encoding sign */
|
|
|
|
/* JPEG */
|
|
jpegerrorinfo jpeg_rerr;
|
|
FILE *rf = NULL;
|
|
struct jpeg_decompress_struct rj;
|
|
struct jpeg_error_mgr jerr;
|
|
int iinv; /* Invert the input */
|
|
|
|
double gamres = GAMRES; /* Surface resolution */
|
|
gamut *gam;
|
|
|
|
double apcsmin[3], apcsmax[3]; /* Actual PCS range */
|
|
|
|
|
|
error_program = argv[0];
|
|
|
|
if (argc < 2)
|
|
usage();
|
|
|
|
/* Process the arguments */
|
|
for (fa = 1;fa < argc;fa++) {
|
|
nfa = fa; /* skip to nfa if next argument is used */
|
|
if (argv[fa][0] == '-') { /* Look for any flags */
|
|
char *na = NULL; /* next argument after flag, null if none */
|
|
|
|
if (argv[fa][2] != '\000')
|
|
na = &argv[fa][2]; /* next is directly after flag */
|
|
else {
|
|
if ((fa+1) < argc) {
|
|
if (argv[fa+1][0] != '-') {
|
|
nfa = fa + 1;
|
|
na = argv[nfa]; /* next is seperate non-flag argument */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (argv[fa][1] == '?')
|
|
usage();
|
|
|
|
/* Verbosity */
|
|
else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') {
|
|
verb = 1;
|
|
}
|
|
|
|
/* Intent */
|
|
else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') {
|
|
fa = nfa;
|
|
if (na == NULL) usage();
|
|
switch (na[0]) {
|
|
case 'd':
|
|
intent = icmDefaultIntent;
|
|
break;
|
|
case 'a':
|
|
intent = icAbsoluteColorimetric;
|
|
break;
|
|
case 'p':
|
|
intent = icPerceptual;
|
|
break;
|
|
case 'r':
|
|
intent = icRelativeColorimetric;
|
|
break;
|
|
case 's':
|
|
intent = icSaturation;
|
|
break;
|
|
/* Argyll special intents to check spaces underlying */
|
|
/* icxPerceptualAppearance & icxSaturationAppearance */
|
|
case 'P':
|
|
intent = icmAbsolutePerceptual;
|
|
break;
|
|
case 'S':
|
|
intent = icmAbsoluteSaturation;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
|
|
/* Search order */
|
|
else if (argv[fa][1] == 'o') {
|
|
fa = nfa;
|
|
if (na == NULL) usage();
|
|
switch (na[0]) {
|
|
case 'n':
|
|
case 'N':
|
|
order = icmLuOrdNorm;
|
|
break;
|
|
case 'r':
|
|
case 'R':
|
|
order = icmLuOrdRev;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
|
|
/* PCS override */
|
|
else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') {
|
|
fa = nfa;
|
|
if (na == NULL) usage();
|
|
switch (na[0]) {
|
|
case 'l':
|
|
pcsor = icSigLabData;
|
|
break;
|
|
case 'j':
|
|
pcsor = icxSigJabData;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
|
|
/* Viewing conditions */
|
|
else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') {
|
|
fa = nfa;
|
|
if (na == NULL) usage();
|
|
|
|
/* Switch to Jab automatically */
|
|
pcsor = icxSigJabData;
|
|
|
|
/* Set the viewing conditions */
|
|
if (na[1] != ':') {
|
|
if ((vc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999)
|
|
usage();
|
|
} else if (na[0] == 's' || na[0] == 'S') {
|
|
if (na[1] != ':')
|
|
usage();
|
|
if (na[2] == 'n' || na[2] == 'N') {
|
|
vc_s = vc_none; /* Automatic */
|
|
} else if (na[2] == 'a' || na[2] == 'A') {
|
|
vc_s = vc_average;
|
|
} else if (na[2] == 'm' || na[2] == 'M') {
|
|
vc_s = vc_dim;
|
|
} else if (na[2] == 'd' || na[2] == 'D') {
|
|
vc_s = vc_dark;
|
|
} else if (na[2] == 'c' || na[2] == 'C') {
|
|
vc_s = vc_cut_sheet;
|
|
} else
|
|
usage();
|
|
} else if (na[0] == 'w' || na[0] == 'W') {
|
|
double x, y, z;
|
|
if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) {
|
|
vc_wXYZ[0] = x; vc_wXYZ[1] = y; vc_wXYZ[2] = z;
|
|
} else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) {
|
|
vc_wxy[0] = x; vc_wxy[1] = y;
|
|
} else
|
|
usage();
|
|
} else if (na[0] == 'a' || na[0] == 'A') {
|
|
if (na[1] != ':')
|
|
usage();
|
|
vc_a = atof(na+2);
|
|
} else if (na[0] == 'b' || na[0] == 'B') {
|
|
if (na[1] != ':')
|
|
usage();
|
|
vc_b = atof(na+2);
|
|
} else if (na[0] == 'l' || na[0] == 'L') {
|
|
if (na[1] != ':')
|
|
usage();
|
|
vc_l = atof(na+2);
|
|
} else if (na[0] == 'f' || na[0] == 'F') {
|
|
if (na[1] != ':')
|
|
usage();
|
|
vc_f = 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_gXYZ[0] = x; vc_gXYZ[1] = y; vc_gXYZ[2] = z;
|
|
} else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) {
|
|
vc_gxy[0] = x; vc_gxy[1] = y;
|
|
} else if (sscanf(na+1,":%lf",&x) == 1) {
|
|
vc_g = x;
|
|
} else
|
|
usage();
|
|
} else if (na[0] == 'h' || na[0] == 'H') {
|
|
if (na[1] != ':')
|
|
usage();
|
|
vc_hkscale = atof(na+2);
|
|
} else if (na[0] == 'm' || na[0] == 'M') {
|
|
double x, y, z;
|
|
if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) {
|
|
vc_Wxyz2[0] = x; vc_Wxyz2[1] = y; vc_Wxyz2[2] = z;
|
|
} else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) {
|
|
vc_Wxyz2[0] = x; vc_Wxyz2[1] = y; vc_Wxyz2[2] = -1;
|
|
} else if (sscanf(na+1,":%lf",&x) == 1) {
|
|
vc_mtaf = x;
|
|
} else
|
|
usage();
|
|
} else
|
|
usage();
|
|
}
|
|
|
|
/* VRML/X3D output */
|
|
else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') {
|
|
vrml = 1;
|
|
}
|
|
/* No axis output */
|
|
else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') {
|
|
doaxes = 0;
|
|
}
|
|
/* Do cusp markers */
|
|
else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') {
|
|
docusps = 1;
|
|
}
|
|
/* Surface Detail */
|
|
else if (argv[fa][1] == 'd' || argv[fa][1] == 'D') {
|
|
fa = nfa;
|
|
if (na == NULL) usage();
|
|
gamres = atof(na);
|
|
}
|
|
/* Filtering */
|
|
else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') {
|
|
fa = nfa;
|
|
if (na == NULL) usage();
|
|
filtperc = atof(na);
|
|
if (filtperc < 0.0 || filtperc> 100.0)
|
|
usage();
|
|
filter = 1;
|
|
}
|
|
|
|
/* Expand gamut cylindrically */
|
|
else if (argv[fa][1] == 'x') {
|
|
double rr;
|
|
fa = nfa;
|
|
if (na == NULL) usage();
|
|
rr = atof(na)/100.0;
|
|
|
|
if (rr < 0.01 || rr > 100.0)
|
|
usage();
|
|
expand = rr;
|
|
}
|
|
|
|
/* Output file name */
|
|
else if (argv[fa][1] == 'O') {
|
|
fa = nfa;
|
|
if (na == NULL) usage();
|
|
strncpy(out_name,na,MAXNAMEL); out_name[MAXNAMEL] = '\000';
|
|
}
|
|
|
|
else
|
|
usage();
|
|
} else
|
|
break;
|
|
}
|
|
|
|
if (fa >= argc || argv[fa][0] == '-') usage();
|
|
if (fa < (argc-1))
|
|
strncpy(prof_name,argv[fa++],MAXNAMEL); prof_name[MAXNAMEL] = '\000';
|
|
|
|
|
|
for (ffa = fa; fa < argc; fa++)
|
|
if (fa >= argc || argv[fa][0] == '-') usage();
|
|
lfa = fa-1;
|
|
if (verb)
|
|
printf("There are %d raster files to process\n",lfa - ffa + 1);
|
|
|
|
if (out_name[0] == '\000') {
|
|
strncpy(out_name,argv[lfa],MAXNAMEL); out_name[MAXNAMEL] = '\000';
|
|
if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */
|
|
xl = out_name + strlen(out_name);
|
|
strcpy(xl,".gam");
|
|
} else {
|
|
if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */
|
|
xl = out_name + strlen(out_name);
|
|
}
|
|
|
|
if (verb)
|
|
printf("Gamut output files is '%s'\n",out_name);
|
|
|
|
if (intent == -1) {
|
|
if (pcsor == icxSigJabData)
|
|
intent = icRelativeColorimetric; /* Default to icxAppearance */
|
|
else
|
|
intent = icAbsoluteColorimetric; /* Default to icAbsoluteColorimetric */
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - */
|
|
/* If we were provided an ICC profile to use */
|
|
if (prof_name[0] != '\000') {
|
|
|
|
/* Open up the profile or TIFF embedded profile for reading */
|
|
if ((icco = read_embedded_icc(prof_name)) == NULL)
|
|
error ("Can't open profile in file '%s'",prof_name);
|
|
|
|
if (verb) {
|
|
icmFile *op;
|
|
if ((op = new_icmFileStd_fp(&err, stdout)) == NULL)
|
|
error ("Can't open stdout (0x%x, '%s')",err.c,err.m);
|
|
icco->header->dump(icco->header, op, 1);
|
|
op->del(op);
|
|
}
|
|
|
|
/* Check that the profile is appropriate */
|
|
if (icco->header->deviceClass != icSigInputClass
|
|
&& icco->header->deviceClass != icSigDisplayClass
|
|
&& icco->header->deviceClass != icSigOutputClass
|
|
&& icco->header->deviceClass != icSigColorSpaceClass)
|
|
error("Profile type isn't device or colorspace");
|
|
|
|
/* Wrap with an expanded icc */
|
|
if ((xicco = new_xicc(icco)) == NULL)
|
|
error ("Creation of xicc failed");
|
|
|
|
/* Setup the default viewing conditions */
|
|
if (xicc_enum_viewcond(xicco, &vc, -1, NULL, 0, NULL) == -999)
|
|
error ("%d, %s",xicco->e.c, xicco->e.m);
|
|
|
|
if (vc_e != -1)
|
|
if (xicc_enum_viewcond(xicco, &vc, vc_e, NULL, 0, NULL) == -999)
|
|
error ("%d, %s",xicco->e.c, xicco->e.m);
|
|
if (vc_s >= 0)
|
|
vc.Ev = vc_s;
|
|
if (vc_wXYZ[1] > 0.0) {
|
|
/* Normalise it to current media white */
|
|
vc.Wxyz[0] = vc_wXYZ[0]/vc_wXYZ[1] * vc.Wxyz[1];
|
|
vc.Wxyz[2] = vc_wXYZ[2]/vc_wXYZ[1] * vc.Wxyz[1];
|
|
}
|
|
if (vc_wxy[0] >= 0.0) {
|
|
double x = vc_wxy[0];
|
|
double y = vc_wxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */
|
|
double z = 1.0 - x - y;
|
|
vc.Wxyz[0] = x/y * vc.Wxyz[1];
|
|
vc.Wxyz[2] = z/y * vc.Wxyz[1];
|
|
}
|
|
if (vc_a >= 0.0)
|
|
vc.La = vc_a;
|
|
if (vc_b >= 0.0)
|
|
vc.Yb = vc_b/100.0;
|
|
if (vc_l >= 0.0)
|
|
vc.Lv = vc_l;
|
|
|
|
if (vc_f >= 0.0)
|
|
vc.Yf = vc_f/100.0;
|
|
if (vc_g >= 0.0)
|
|
vc.Yg = vc_g/100.0;
|
|
if (vc_gXYZ[1] > 0.0) {
|
|
/* Normalise it to current media white */
|
|
vc.Gxyz[0] = vc_gXYZ[0]/vc_gXYZ[1] * vc.Gxyz[1];
|
|
vc.Gxyz[2] = vc_gXYZ[2]/vc_gXYZ[1] * vc.Gxyz[1];
|
|
}
|
|
if (vc_gxy[0] >= 0.0) {
|
|
double x = vc_gxy[0];
|
|
double y = vc_gxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */
|
|
double z = 1.0 - x - y;
|
|
vc.Gxyz[0] = x/y * vc.Gxyz[1];
|
|
vc.Gxyz[2] = z/y * vc.Gxyz[1];
|
|
}
|
|
if (vc_hkscale >= 0.0)
|
|
vc.hkscale = vc_hkscale;
|
|
if (vc_mtaf >= 0.0)
|
|
vc.mtaf = vc_mtaf;
|
|
if (vc_Wxyz2[0] >= 0.0 && vc_Wxyz2[1] > 0.0 && vc_Wxyz2[2] >= 0.0) {
|
|
/* Normalise XYZ */
|
|
vc.Wxyz2[0] = vc_Wxyz2[0]/vc_Wxyz2[1] * vc.Wxyz2[1];
|
|
vc.Wxyz2[2] = vc_Wxyz2[2]/vc_Wxyz2[1] * vc.Wxyz2[1];
|
|
}
|
|
if (vc_Wxyz2[0] >= 0.0 && vc_Wxyz2[1] >= 0.0 && vc_Wxyz2[2] < 0.0) {
|
|
/* Convert Yxy to XYZ */
|
|
double x = vc_Wxyz2[0];
|
|
double y = vc_Wxyz2[1]; /* If Y == 1.0, then X+Y+Z = 1/y */
|
|
double z = 1.0 - x - y;
|
|
vc.Wxyz2[0] = x/y * vc.Wxyz2[1];
|
|
vc.Wxyz2[2] = z/y * vc.Wxyz2[1];
|
|
}
|
|
|
|
/* Get a expanded color conversion object */
|
|
if ((luo = xicco->get_luobj(xicco, ICX_CLIP_NEAREST
|
|
#ifdef NOCAMGAM_CLIP
|
|
| ICX_CAM_NOGAMCLIP
|
|
#endif
|
|
, func, intent, pcsor, order, &vc, NULL)) == NULL)
|
|
error ("%d, %s",xicco->e.c, xicco->e.m);
|
|
|
|
luo->spaces(luo, &ins, &inn, &outs, &outn, &alg, NULL, NULL, NULL);
|
|
|
|
/* Else if the image is in Lab space, and we want Jab */
|
|
} else if (pcsor == icxSigJabData) {
|
|
double wp[3]; /* D50 white point */
|
|
|
|
wp[0] = icmD50.X;
|
|
wp[1] = icmD50.Y;
|
|
wp[2] = icmD50.Z;
|
|
|
|
/* Setup the default viewing conditions */
|
|
if (xicc_enum_viewcond(NULL, &vc, -1, NULL, 0, wp) == -999)
|
|
error("Failed to locate a default viewing condition");
|
|
|
|
if (vc_e != -1)
|
|
if (xicc_enum_viewcond(NULL, &vc, vc_e, NULL, 0, wp) == -999)
|
|
error("Failed to enumerate viewing condition %d",vc_e);
|
|
if (vc_s >= 0)
|
|
vc.Ev = vc_s;
|
|
if (vc_wXYZ[1] > 0.0) {
|
|
/* Normalise it to current media white */
|
|
vc.Wxyz[0] = vc_wXYZ[0]/vc_wXYZ[1] * vc.Wxyz[1];
|
|
vc.Wxyz[2] = vc_wXYZ[2]/vc_wXYZ[1] * vc.Wxyz[1];
|
|
}
|
|
if (vc_wxy[0] >= 0.0) {
|
|
double x = vc_wxy[0];
|
|
double y = vc_wxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */
|
|
double z = 1.0 - x - y;
|
|
vc.Wxyz[0] = x/y * vc.Wxyz[1];
|
|
vc.Wxyz[2] = z/y * vc.Wxyz[1];
|
|
}
|
|
if (vc_a >= 0.0)
|
|
vc.La = vc_a;
|
|
if (vc_b >= 0.0)
|
|
vc.Yb = vc_b/100.0;
|
|
if (vc_f >= 0.0)
|
|
vc.Yf = vc_f/100.0;
|
|
if (vc_g >= 0.0)
|
|
vc.Yg = vc_g/100.0;
|
|
if (vc_gXYZ[1] > 0.0) {
|
|
/* Normalise it to current media white */
|
|
vc.Gxyz[0] = vc_gXYZ[0]/vc_gXYZ[1] * vc.Gxyz[1];
|
|
vc.Gxyz[2] = vc_gXYZ[2]/vc_gXYZ[1] * vc.Gxyz[1];
|
|
}
|
|
if (vc_gxy[0] >= 0.0) {
|
|
double x = vc_gxy[0];
|
|
double y = vc_gxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */
|
|
double z = 1.0 - x - y;
|
|
vc.Gxyz[0] = x/y * vc.Gxyz[1];
|
|
vc.Gxyz[2] = z/y * vc.Gxyz[1];
|
|
}
|
|
if (vc_hkscale >= 0.0)
|
|
vc.hkscale = vc_hkscale;
|
|
if (vc_mtaf >= 0.0)
|
|
vc.mtaf = vc_mtaf;
|
|
if (vc_Wxyz2[0] >= 0.0 && vc_Wxyz2[1] > 0.0 && vc_Wxyz2[2] >= 0.0) {
|
|
/* Normalise XYZ */
|
|
vc.Wxyz2[0] = vc_Wxyz2[0]/vc_Wxyz2[1] * vc.Wxyz2[1];
|
|
vc.Wxyz2[2] = vc_Wxyz2[2]/vc_Wxyz2[1] * vc.Wxyz2[1];
|
|
}
|
|
if (vc_Wxyz2[0] >= 0.0 && vc_Wxyz2[1] >= 0.0 && vc_Wxyz2[2] < 0.0) {
|
|
/* Convert Yxy to XYZ */
|
|
double x = vc_Wxyz2[0];
|
|
double y = vc_Wxyz2[1]; /* If Y == 1.0, then X+Y+Z = 1/y */
|
|
double z = 1.0 - x - y;
|
|
vc.Wxyz2[0] = x/y * vc.Wxyz2[1];
|
|
vc.Wxyz2[2] = z/y * vc.Wxyz2[1];
|
|
}
|
|
|
|
if ((cam = new_icxcam(cam_default)) == NULL)
|
|
error("new_icxcam failed");
|
|
|
|
cam->set_view_vc(cam, &vc);
|
|
}
|
|
|
|
/* Establish the PCS range if we are filtering */
|
|
if (filter) {
|
|
double pcsmin[3], pcsmax[3]; /* PCS range for filter stats array */
|
|
|
|
if (luo) {
|
|
gamut *csgam;
|
|
|
|
if ((csgam = luo->get_gamut(luo, 20.0)) == NULL)
|
|
error("Getting the gamut of the source colorspace failed");
|
|
|
|
csgam->getrange(csgam, pcsmin, pcsmax);
|
|
csgam->del(csgam);
|
|
} else {
|
|
pcsmin[0] = -10.0;
|
|
pcsmax[0] = 110.0;
|
|
pcsmin[1] = -138.0;
|
|
pcsmax[1] = 138.0;
|
|
pcsmin[2] = -138.0;
|
|
pcsmax[2] = 138.0;
|
|
|
|
#ifdef NEVER /* Does this make any sense ?? */
|
|
if (cam) {
|
|
icmLab2XYZ(&icmD50, pcsmin, pcsmin);
|
|
cam->XYZ_to_cam(cam, pcsmin, pcsmin);
|
|
|
|
icmLab2XYZ(&icmD50, pcsmax, pcsmax);
|
|
cam->XYZ_to_cam(cam, pcsmax, pcsmax);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (verb)
|
|
printf("PCS range = %f..%f, %f..%f. %f..%f\n\n", pcsmin[0], pcsmax[0], pcsmin[1], pcsmax[1], pcsmin[2], pcsmax[2]);
|
|
|
|
/* Allocate and initialize the filter */
|
|
set_fminmax(pcsmin, pcsmax);
|
|
}
|
|
|
|
|
|
/* - - - - - - - - - - - - - - - */
|
|
/* Creat a raster gamut surface */
|
|
gam = new_gamut(gamres, pcsor == icxSigJabData, 1);
|
|
|
|
apcsmin[0] = apcsmin[1] = apcsmin[2] = 1e6;
|
|
apcsmax[0] = apcsmax[1] = apcsmax[2] = -1e6;
|
|
|
|
/* Process all the tiff files */
|
|
for (fa = ffa; fa <= lfa; fa++) {
|
|
|
|
/* - - - - - - - - - - - - - - - */
|
|
/* Open up input tiff/jpeg file ready for reading */
|
|
/* Got arguments, so setup to process the file */
|
|
strncpy(in_name,argv[fa],MAXNAMEL); in_name[MAXNAMEL] = '\000';
|
|
|
|
olderrh = TIFFSetErrorHandler(NULL);
|
|
oldwarnh = TIFFSetWarningHandler(NULL);
|
|
olderrhx = TIFFSetErrorHandlerExt(NULL);
|
|
oldwarnhx = TIFFSetWarningHandlerExt(NULL);
|
|
|
|
if ((rh = TIFFOpen(in_name, "r")) != NULL) {
|
|
TIFFSetErrorHandler(olderrh);
|
|
TIFFSetWarningHandler(oldwarnh);
|
|
TIFFSetErrorHandlerExt(olderrhx);
|
|
TIFFSetWarningHandlerExt(oldwarnhx);
|
|
|
|
TIFFGetField(rh, TIFFTAG_IMAGEWIDTH, &width);
|
|
TIFFGetField(rh, TIFFTAG_IMAGELENGTH, &height);
|
|
|
|
TIFFGetField(rh, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel);
|
|
TIFFGetField(rh, TIFFTAG_BITSPERSAMPLE, &bitspersample);
|
|
if (bitspersample != 8 && bitspersample != 16)
|
|
error("TIFF Input file must be 8 or 16 bits/channel");
|
|
|
|
TIFFGetFieldDefaulted(rh, TIFFTAG_EXTRASAMPLES, &extrasamples, &extrainfo);
|
|
TIFFGetField(rh, TIFFTAG_PHOTOMETRIC, &photometric);
|
|
|
|
if (luo != NULL) {
|
|
if (inn != (samplesperpixel-extrasamples))
|
|
error("TIFF Input file has %d input chanels and is mismatched to colorspace '%s'",
|
|
samplesperpixel, icm2str(icmColorSpaceSig, ins));
|
|
}
|
|
|
|
if ((tcs = TiffPhotometric2ColorSpaceSignature(&cvt, &sign_mask, photometric,
|
|
bitspersample, samplesperpixel, extrasamples)) == 0)
|
|
error("Can't handle TIFF file photometric %s", Photometric2str(photometric));
|
|
|
|
if (tcs != ins) {
|
|
if (luo != NULL)
|
|
error("TIFF photometric '%s' doesn't match ICC input colorspace '%s' !",
|
|
Photometric2str(photometric), icm2str(icmColorSpaceSig,ins));
|
|
else
|
|
error("No profile provided and TIFF photometric '%s' isn't Lab !",
|
|
Photometric2str(photometric));
|
|
}
|
|
|
|
TIFFGetField(rh, TIFFTAG_PLANARCONFIG, &pconfig);
|
|
if (pconfig != PLANARCONFIG_CONTIG)
|
|
error("TIFF Input file must be planar");
|
|
|
|
if (verb) {
|
|
printf("Input TIFF file '%s'\n",in_name);
|
|
printf("TIFF file photometric is %s\n",Photometric2str(photometric));
|
|
printf("TIFF file colorspace is %s\n",icm2str(icmColorSpaceSig,tcs));
|
|
printf("File size %d x %d pixels\n",width,height);
|
|
printf("\n");
|
|
}
|
|
|
|
} else {
|
|
TIFFSetErrorHandler(olderrh);
|
|
TIFFSetWarningHandler(oldwarnh);
|
|
TIFFSetErrorHandlerExt(olderrhx);
|
|
TIFFSetWarningHandlerExt(oldwarnhx);
|
|
|
|
/* We cope with the horrible ijg jpeg library error handling */
|
|
/* by using a setjmp/longjmp. */
|
|
jpeg_std_error(&jerr);
|
|
jerr.error_exit = jpeg_error;
|
|
if (setjmp(jpeg_rerr.env)) {
|
|
jpeg_destroy_decompress(&rj);
|
|
fclose(rf);
|
|
error("Unable to read JPEG file '%s'\n",in_name);
|
|
}
|
|
|
|
rj.err = &jerr;
|
|
rj.client_data = &jpeg_rerr;
|
|
jpeg_create_decompress(&rj);
|
|
|
|
#if defined(O_BINARY) || defined(_O_BINARY)
|
|
if ((rf = fopen(in_name,"rb")) == NULL)
|
|
#else
|
|
if ((rf = fopen(in_name,"r")) == NULL)
|
|
#endif
|
|
{
|
|
jpeg_destroy_decompress(&rj);
|
|
error("Unable to open input file '%s'\n",in_name);
|
|
}
|
|
|
|
jpeg_stdio_src(&rj, rf);
|
|
jpeg_read_header(&rj, TRUE); /* we'll longjmp on error */
|
|
|
|
bitspersample = rj.data_precision;
|
|
if (bitspersample != 8 && bitspersample != 16) {
|
|
error("JPEG Input file must be 8 or 16 bit/channel");
|
|
}
|
|
|
|
/* No extra samples */
|
|
extrasamples = 0;
|
|
iinv = 0;
|
|
|
|
switch (rj.jpeg_color_space) {
|
|
case JCS_GRAYSCALE:
|
|
rj.out_color_space = JCS_GRAYSCALE;
|
|
tcs = icSigGrayData;
|
|
samplesperpixel = 1;
|
|
break;
|
|
|
|
case JCS_YCbCr: /* get libjpg to convert to RGB */
|
|
rj.out_color_space = JCS_RGB;
|
|
tcs = icSigRgbData;
|
|
samplesperpixel = 3;
|
|
break;
|
|
|
|
case JCS_RGB:
|
|
rj.out_color_space = JCS_RGB;
|
|
tcs = icSigRgbData;
|
|
samplesperpixel = 3;
|
|
break;
|
|
|
|
case JCS_YCCK: /* libjpg to convert to CMYK */
|
|
rj.out_color_space = JCS_CMYK;
|
|
tcs = icSigCmykData;
|
|
samplesperpixel = 4;
|
|
if (rj.saw_Adobe_marker)
|
|
iinv = 1;
|
|
break;
|
|
|
|
case JCS_CMYK:
|
|
rj.out_color_space = JCS_CMYK;
|
|
tcs = icSigCmykData;
|
|
samplesperpixel = 4;
|
|
if (rj.saw_Adobe_marker) /* Adobe inverts CMYK */
|
|
iinv = 1;
|
|
break;
|
|
|
|
default:
|
|
error("Can't handle JPEG file '%s' colorspace 0x%x", in_name, rj.jpeg_color_space);
|
|
}
|
|
|
|
if (luo != NULL) {
|
|
if (inn != samplesperpixel)
|
|
error ("JPEG Input file has %d input chanels and is mismatched to colorspace '%s'",
|
|
samplesperpixel, icm2str(icmColorSpaceSig, ins));
|
|
}
|
|
|
|
if (tcs != ins) {
|
|
if (luo != NULL)
|
|
error("JPEG colorspace '%s' doesn't match ICC input colorspace '%s' !",
|
|
icm2str(icmColorSpaceSig, tcs), icm2str(icmColorSpaceSig,ins));
|
|
else
|
|
error("No profile provided and JPEG colorspace '%s' isn't Lab !",
|
|
icm2str(icmColorSpaceSig, tcs));
|
|
}
|
|
jpeg_calc_output_dimensions(&rj);
|
|
width = rj.output_width;
|
|
height = rj.output_height;
|
|
|
|
if (verb) {
|
|
printf("Input JPEG file '%s'\n",in_name);
|
|
printf("JPEG file original colorspace is %s\n",JPEG_cspace2str(rj.jpeg_color_space));
|
|
printf("JPEG file colorspace is %s\n",icm2str(icmColorSpaceSig,tcs));
|
|
printf("File size %d x %d pixels\n",width,height);
|
|
printf("\n");
|
|
}
|
|
jpeg_start_decompress(&rj);
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - */
|
|
/* Process colors to translate */
|
|
/* (Should fix this to process a group of lines at a time ?) */
|
|
|
|
if (rh != NULL)
|
|
inbuf = _TIFFmalloc(TIFFScanlineSize(rh));
|
|
else {
|
|
inbpix = rj.output_width * rj.num_components;
|
|
if ((inbuf = (tdata_t *)malloc(inbpix)) == NULL)
|
|
error("Malloc failed on input line buffer");
|
|
|
|
if (setjmp(jpeg_rerr.env)) {
|
|
/* Something went wrong with reading the file */
|
|
jpeg_destroy_decompress(&rj);
|
|
error("failed to read JPEG line of file '%s' [%s]",in_name, jpeg_rerr.message);
|
|
}
|
|
}
|
|
|
|
for (y = 0; y < height; y++) {
|
|
|
|
/* Read in the next line */
|
|
if (rh) {
|
|
if (TIFFReadScanline(rh, inbuf, y, 0) < 0)
|
|
error ("Failed to read TIFF line %d",y);
|
|
} else {
|
|
jpeg_read_scanlines(&rj, (JSAMPARRAY)&inbuf, 1);
|
|
if (iinv) {
|
|
unsigned char *cp, *ep = (unsigned char *)inbuf + inbpix;
|
|
for (cp = (unsigned char *)inbuf; cp < ep; cp++)
|
|
*cp = ~*cp;
|
|
}
|
|
}
|
|
|
|
/* Do floating point conversion */
|
|
for (x = 0; x < width; x++) {
|
|
int i;
|
|
double in[MAX_CHAN], out[MAX_CHAN];
|
|
|
|
//printf("~1 location %d,%d\n",x,y);
|
|
if (bitspersample == 8) {
|
|
for (i = 0; i < samplesperpixel; i++) {
|
|
int v = ((unsigned char *)inbuf)[x * samplesperpixel + i];
|
|
//printf("~1 v[%d] = %d\n",i,v);
|
|
if (sign_mask & (1 << i)) /* Treat input as signed */
|
|
v = (v & 0x80) ? v - 0x80 : v + 0x80;
|
|
//printf("~1 signed v[%d] = %d\n",i,v);
|
|
in[i] = v/255.0;
|
|
//printf("~1 in[%d] = %f\n",i,in[i]);
|
|
}
|
|
} else {
|
|
for (i = 0; i < samplesperpixel; i++) {
|
|
int v = ((unsigned short *)inbuf)[x * samplesperpixel + i];
|
|
//printf("~1 v[%d] = %d\n",i,v);
|
|
if (sign_mask & (1 << i)) /* Treat input as signed */
|
|
v = (v & 0x8000) ? v - 0x8000 : v + 0x8000;
|
|
//printf("~1 signed v[%d] = %d\n",i,v);
|
|
in[i] = v/65535.0;
|
|
//printf("~1 in[%d] = %f\n",i,in[i]);
|
|
}
|
|
}
|
|
if (cvt != NULL) { /* Undo TIFF encoding */
|
|
cvt(in, in);
|
|
}
|
|
/* ICC profile to convert RGB to Lab or Jab */
|
|
if (luo != NULL) {
|
|
//printf("~1 RGB in value = %f %f %f\n",in[0],in[1],in[2]);
|
|
if ((rv = luo->lookup(luo, out, in)) > 1)
|
|
error ("%d, %s",icco->e.c,icco->e.m);
|
|
//printf("~1 after luo = %f %f %f\n",out[0],out[1],out[2]);
|
|
|
|
if (outs == icSigXYZData) { /* Convert to Lab */
|
|
icmXYZ2Lab(&icco->header->illuminant, out, out);
|
|
//printf("~1 after XYZ2Lab = %f %f %f\n",out[0],out[1],out[2]);
|
|
}
|
|
/* Lab TIFF - may need to convert to Jab */
|
|
} else if (cam != NULL) {
|
|
//printf("~1 Lab in value = %f %f %f\n",in[0],in[1],in[2]);
|
|
icmLab2XYZ(&icmD50, out, in);
|
|
//printf("~1 XYZ = %f %f %f\n",out[0],out[1],out[2]);
|
|
cam->XYZ_to_cam(cam, out, out);
|
|
//printf("~1 Jab = %f %f %f\n",out[0],out[1],out[2]);
|
|
|
|
} else {
|
|
//printf("~1 Lab value = %f %f %f\n",in[0],in[1],in[2]);
|
|
for (i = 0; i < samplesperpixel; i++)
|
|
out[i] = in[i];
|
|
}
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
if (out[i] < apcsmin[i]) {
|
|
//printf("~1 new min %f\n",out[i]);
|
|
apcsmin[i] = out[i];
|
|
}
|
|
if (out[i] > apcsmax[i]) {
|
|
//printf("~1 new max %f\n",out[i]);
|
|
apcsmax[i] = out[i];
|
|
}
|
|
}
|
|
if (filter)
|
|
add_fpixel(out);
|
|
else
|
|
gam->expand(gam, out);
|
|
}
|
|
}
|
|
|
|
/* Release buffers and close files */
|
|
if (rh != NULL) {
|
|
if (inbuf != NULL)
|
|
_TIFFfree(inbuf);
|
|
TIFFClose(rh); /* Close Input file */
|
|
} else {
|
|
jpeg_finish_decompress(&rj);
|
|
jpeg_destroy_decompress(&rj);
|
|
if (inbuf != NULL)
|
|
free(inbuf);
|
|
if (fclose(rf))
|
|
error("Error closing JPEG input file '%s'\n",in_name);
|
|
}
|
|
|
|
/* If filtering, flush filtered points to the gamut */
|
|
if (filter) {
|
|
flush_filter(verb, gam, filtperc);
|
|
}
|
|
}
|
|
|
|
if (verb)
|
|
printf("Actual PCS range = %f..%f, %f..%f. %f..%f\n\n", apcsmin[0], apcsmax[0], apcsmin[1], apcsmax[1], apcsmin[2], apcsmax[2]);
|
|
|
|
if (filter)
|
|
del_filter();
|
|
|
|
/* Get White and Black points from the profile, and set them in the gamut. */
|
|
if (luo != NULL) {
|
|
double wp[3], bp[3], kp[3];
|
|
|
|
luo->efv_wh_bk_points(luo, wp, bp,kp);
|
|
gam->setwb(gam, wp, bp, kp);
|
|
} else {
|
|
double wp[3] = { 100.0, 0.0, 0.0 };
|
|
double bp[3] = { 0.0, 0.0, 0.0 };
|
|
gam->setwb(gam, wp, bp, bp);
|
|
}
|
|
|
|
/* Done with lookup object */
|
|
if (luo != NULL) {
|
|
luo->del(luo);
|
|
xicco->del(xicco); /* Expansion wrapper */
|
|
icco->del(icco); /* Icc */
|
|
}
|
|
if (cam != NULL) {
|
|
cam->del(cam);
|
|
}
|
|
|
|
if (verb)
|
|
printf("Output Gamut file '%s'\n",out_name);
|
|
|
|
/* Expand gamut cylindrically */
|
|
if (expand != 1.0) {
|
|
gamut *xgam;
|
|
|
|
if ((xgam = new_gamut(1.0, 0, 0)) == NULL
|
|
|| xgam->exp_cyl(xgam, gam, expand)) {
|
|
error ("Creating expanded gamut failed");
|
|
}
|
|
|
|
gam->del(gam);
|
|
gam = xgam;
|
|
}
|
|
|
|
/* Create the VRML/X3D file */
|
|
if (gam->write_gam(gam,out_name))
|
|
error ("write gamut failed on '%s'",out_name);
|
|
|
|
if (vrml) {
|
|
xl[0] = '\000';
|
|
printf("Output %s file '%s%s'\n",vrml_format(),out_name,vrml_ext());
|
|
if (gam->write_vrml(gam,out_name, doaxes, docusps))
|
|
error ("write vrml failed on '%s'",out_name);
|
|
}
|
|
|
|
if (verb) {
|
|
printf("Total volume of gamut is %f cubic colorspace units\n",gam->volume(gam));
|
|
}
|
|
gam->del(gam);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* ============================================================================= */
|
|
/* A pixel value filter module. We quantize the pixel values and keep statistics */
|
|
/* on them, so as to filter out low frequency colors. */
|
|
|
|
#define FILTBITS 6 /* Total = 2 ^ (3 * FILTBITS) entries = 33Mbytes*/
|
|
#define FILTSIZE (1 << FILTBITS)
|
|
|
|
/* A filtered cell entry */
|
|
typedef struct {
|
|
int count; /* Count of pixels that fall in this cell */
|
|
float pcs[3]; /* Most extreme PCS value that fell in this cell */
|
|
} fent;
|
|
|
|
struct _ffilter {
|
|
double min[3], max[3]; /* PCS range */
|
|
|
|
fent cells[FILTSIZE][FILTSIZE][FILTSIZE]; /* Quantized pixels stats */
|
|
fent *scells[FILTSIZE * FILTSIZE * FILTSIZE]; /* Sorted order */
|
|
|
|
}; typedef struct _ffilter ffilter;
|
|
|
|
|
|
|
|
/* Use a global object */
|
|
ffilter *ff = NULL;
|
|
|
|
/* Set the min and max values and init the filter */
|
|
void set_fminmax(double min[3], double max[3]) {
|
|
|
|
if (ff == NULL) {
|
|
if ((ff = (ffilter *) calloc(1,sizeof(ffilter))) == NULL)
|
|
error("ffilter: calloc failed");
|
|
}
|
|
|
|
ff->min[0] = min[0];
|
|
ff->min[1] = min[1];
|
|
ff->min[2] = min[2];
|
|
ff->max[0] = max[0];
|
|
ff->max[1] = max[1];
|
|
ff->max[2] = max[2];
|
|
}
|
|
|
|
/* Add another pixel to the filter */
|
|
void add_fpixel(double val[3]) {
|
|
int j;
|
|
int qv[3];
|
|
fent *fe;
|
|
double cent[3] = { 50.0, 0.0, 0.0 }; /* Assumed center */
|
|
double tt, cdist, ndist;
|
|
|
|
if (ff == NULL)
|
|
error("ffilter not initialized");
|
|
|
|
/* Quantize the values */
|
|
for (j = 0; j < 3; j++) {
|
|
double vv;
|
|
|
|
vv = (val[j] - ff->min[j])/(ff->max[j] - ff->min[j]);
|
|
qv[j] = (int)(vv * (FILTSIZE - 1) + 0.5);
|
|
if (qv[j] < 0)
|
|
qv[j] = 0;
|
|
else if (qv[j] >= FILTSIZE)
|
|
qv[j] = FILTSIZE-1;
|
|
}
|
|
|
|
//printf("~1 color %f %f %f -> Cell %d %d %d\n", val[0], val[1], val[2], qv[0], qv[1], qv[2]);
|
|
|
|
/* Find the appropriate cell */
|
|
fe = &ff->cells[qv[0]][qv[1]][qv[2]];
|
|
|
|
/* See if this is the new most distant value in this cell */
|
|
for (cdist = ndist = 0.0, j = 0; j < 3; j++) {
|
|
tt = fe->pcs[j] - cent[j];
|
|
cdist += tt * tt;
|
|
tt = val[j] - cent[j];
|
|
ndist += tt * tt;
|
|
}
|
|
if (fe->count == 0 || ndist > cdist) {
|
|
fe->pcs[0] = val[0];
|
|
fe->pcs[1] = val[1];
|
|
fe->pcs[2] = val[2];
|
|
//printf("Updated pcs to %f %f %f\n", val[0],val[1],val[2]);
|
|
}
|
|
fe->count++;
|
|
//printf("Cell count = %d\n",fe->count);
|
|
}
|
|
|
|
/* Flush the filter contents to the gamut, and then reset it */
|
|
void flush_filter(int verb, gamut *gam, double filtperc) {
|
|
int i, j;
|
|
int totcells = FILTSIZE * FILTSIZE * FILTSIZE;
|
|
int used, hasone;
|
|
double cuml, avgcnt;
|
|
|
|
if (ff == NULL)
|
|
error("ffilter not initialized");
|
|
|
|
/* Sort the cells by popularity from most to least */
|
|
for (used = hasone = avgcnt = i = 0; i < totcells; i++) {
|
|
ff->scells[i] = (fent *)ff->cells + i;
|
|
if (ff->scells[i]->count > 0) {
|
|
used++;
|
|
if (ff->scells[i]->count == 1)
|
|
hasone++;
|
|
avgcnt += ff->scells[i]->count;
|
|
}
|
|
}
|
|
avgcnt /= used;
|
|
|
|
#define HEAP_COMPARE(A,B) A->count > B->count
|
|
HEAPSORT(fent *,ff->scells, totcells)
|
|
|
|
#ifdef DEBUG
|
|
for (i = 0; i < totcells; i++) {
|
|
if (ff->scells[i]->count > 0) {
|
|
printf("Cell %d at %f %f %f count %d\n",
|
|
i,
|
|
ff->scells[i]->pcs[0],
|
|
ff->scells[i]->pcs[1],
|
|
ff->scells[i]->pcs[2],
|
|
ff->scells[i]->count);
|
|
}
|
|
}
|
|
#endif
|
|
if (verb) {
|
|
printf("Total of %d cells out of %d were hit (%.1f%%)\n",used,totcells,used * 100.0/totcells);
|
|
printf("%.1f%% have a count of 1\n",hasone * 100.0/used);
|
|
printf("Average cell count = %f\n",avgcnt);
|
|
printf("\n");
|
|
}
|
|
|
|
/* Add the populated cells in order */
|
|
filtperc /= 100.0;
|
|
for (cuml = 0.0, i = j = 0; cuml < filtperc && i < totcells; i++) {
|
|
|
|
if (ff->scells[i]->count > 0) {
|
|
double val[3];
|
|
val[0] = ff->scells[i]->pcs[0]; /* float -> double */
|
|
val[1] = ff->scells[i]->pcs[1];
|
|
val[2] = ff->scells[i]->pcs[2];
|
|
#ifdef DEBUG
|
|
printf("Adding %d: %f %f %f count %d to gamut\n", i, val[0], val[1], val[2],ff->scells[i]->count);
|
|
#endif
|
|
gam->expand(gam, val);
|
|
j++;
|
|
cuml = j/(used-1.0);
|
|
}
|
|
|
|
}
|
|
|
|
/* Reset it to empty */
|
|
{
|
|
double min[3], max[3];
|
|
|
|
for (j = 0; j < 3; j++) {
|
|
min[j] = ff->min[j];
|
|
max[j] = ff->max[j];
|
|
}
|
|
memset(ff, 0, sizeof(ffilter));
|
|
for (j = 0; j < 3; j++) {
|
|
ff->min[j] = min[j];
|
|
ff->max[j] = max[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Free up the filter structure */
|
|
void del_filter() {
|
|
|
|
free(ff);
|
|
}
|
|
|
|
|