1486 lines
48 KiB
C
1486 lines
48 KiB
C
|
|
/*
|
|
* Argyll Color Correction System
|
|
*
|
|
* Scanin: Input the scan of a test chart, and output cgats data
|
|
* Uses scanrd to do the hard work.
|
|
*
|
|
* Author: Graeme W. Gill
|
|
* Date: 29/1/97
|
|
*
|
|
* Copyright 1995 - 2002 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
|
|
|
|
Add "single pixel patch" mode, for pure digital processing for
|
|
abstract profile creation.
|
|
|
|
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <fcntl.h> /* In case DOS binary stuff is needed */
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include <time.h>
|
|
#include <stdlib.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "copyright.h"
|
|
#include "aconfig.h"
|
|
#include "numlib.h"
|
|
#include "cgats.h"
|
|
#include "icc.h"
|
|
#include "conv.h"
|
|
#include "xicc.h"
|
|
#include "scanrd.h"
|
|
#include "tiffio.h"
|
|
#include "ui.h"
|
|
|
|
/* Utilities */
|
|
|
|
void fix_it8(char *o, char *i);
|
|
|
|
#ifdef NT /* You'd think there might be some standards.... */
|
|
# ifndef __BORLANDC__
|
|
# define stricmp _stricmp
|
|
# endif
|
|
#else
|
|
# define stricmp strcasecmp
|
|
#endif
|
|
|
|
#define TXBUF (256*1024L)
|
|
|
|
/* NOTE: We aren't handling the libtiff error/warning messages !! */
|
|
|
|
/* Read a line of data from the input Grey, RGB or CMYK tiff file */
|
|
/* return non-zero on error */
|
|
int read_line(
|
|
void *fdata,
|
|
int y,
|
|
char *dst
|
|
) {
|
|
if (TIFFReadScanline((TIFF *)fdata, (tdata_t)dst, y, 0) < 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* Write a line of data to the diagnostic RGB tiff file */
|
|
/* return non-zero on error */
|
|
static int
|
|
write_line(
|
|
void *ddata,
|
|
int y,
|
|
char *src
|
|
) {
|
|
if (TIFFWriteScanline((TIFF *)ddata, (tdata_t)src, y, 0) < 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* Compute a simple string hash value */
|
|
unsigned int shash(char *c) {
|
|
unsigned int hash = 0;
|
|
|
|
for (; *c != '\000'; c++)
|
|
hash = hash * 67 + (unsigned char)(*c);
|
|
|
|
return hash;
|
|
}
|
|
|
|
void
|
|
usage(void) {
|
|
fprintf(stderr,"Scanin, Version %s\n",ARGYLL_VERSION_STR);
|
|
fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
|
|
fprintf(stderr,"\n");
|
|
fprintf(stderr,"usage: scanin [options] input.tif recogin.cht valin.cie [diag.tif]\n");
|
|
fprintf(stderr," :- inputs 'input.tif' and outputs scanner 'input.ti3', or\n");
|
|
fprintf(stderr,"\n");
|
|
fprintf(stderr,"usage: scanin -g [options] input.tif recogout.cht [diag.tif]\n");
|
|
fprintf(stderr," :- outputs file 'recogout.cht', or\n");
|
|
fprintf(stderr,"\n");
|
|
fprintf(stderr,"usage: scanin -o [options] input.tif recogin.cht [diag.tif]\n");
|
|
fprintf(stderr," :- outputs file 'input.val', or\n");
|
|
fprintf(stderr,"\n");
|
|
fprintf(stderr,"usage: scanin -c [options] input.tif recogin.cht scanprofile.[%s|mpp] pbase [diag.tif]\n",ICC_FILE_EXT_ND);
|
|
fprintf(stderr," :- inputs pbase.ti2 and outputs printer pbase.ti3, or\n");
|
|
fprintf(stderr,"\n");
|
|
fprintf(stderr,"usage: scanin -r [options] input.tif recogin.cht pbase [diag.tif]\n");
|
|
fprintf(stderr," :- inputs pbase.ti2+.ti3 and outputs pbase.ti3\n");
|
|
fprintf(stderr,"\n");
|
|
fprintf(stderr," -g Generate a chart reference (.cht) file\n");
|
|
fprintf(stderr," -o Output patch values in .val file\n");
|
|
fprintf(stderr," -c Use image to measure color to convert printer pbase .ti2 to .ti3\n");
|
|
fprintf(stderr," -ca Same as -c, but accumulates more values to pbase .ti3\n");
|
|
fprintf(stderr," from subsequent pages\n");
|
|
fprintf(stderr," -r Replace device values in pbase .ti2/.ti3\n");
|
|
fprintf(stderr," Default is to create a scanner .ti3 file\n");
|
|
fprintf(stderr," -F x1,y1,x2,y2,x3,y3,x4,y4\n");
|
|
fprintf(stderr," Don't auto recognize, locate using four fiducual marks\n");
|
|
fprintf(stderr," -p Compensate for perspective distortion\n");
|
|
fprintf(stderr," -a Recognise chart in normal orientation only (-A fallback as is)\n");
|
|
fprintf(stderr," Default is to recognise all possible chart angles\n");
|
|
fprintf(stderr," -m Return true mean (default is robust mean)\n");
|
|
fprintf(stderr," -G gamma Approximate gamma encoding of image\n");
|
|
fprintf(stderr," -v [n] Verbosity level 0-9\n");
|
|
fprintf(stderr," -d [ihvglLIcrsonap] Generate diagnostic output (try -dipn)\n");
|
|
fprintf(stderr," i diag - B&W of input image\n");
|
|
fprintf(stderr," h diag - Horizontal edge/tick detection\n");
|
|
fprintf(stderr," v diag - Vertical edge/tick detection\n");
|
|
fprintf(stderr," g diag - Groups detected\n");
|
|
fprintf(stderr," l diag - Lines detected\n");
|
|
fprintf(stderr," L diag - All lines detected\n");
|
|
fprintf(stderr," I diag - lines used to improve fit\n");
|
|
fprintf(stderr," c diag - lines perspective corrected\n");
|
|
fprintf(stderr," r diag - lines rotated\n");
|
|
fprintf(stderr," s diag - diagnostic sample boxes rotated\n");
|
|
fprintf(stderr," o diag - sample box outlines\n");
|
|
fprintf(stderr," n diag - sample box names\n");
|
|
fprintf(stderr," a diag - sample box areas\n");
|
|
fprintf(stderr," p diag - pixel areas sampled\n");
|
|
fprintf(stderr," -O outputfile Override the default output filename & extension.\n");
|
|
exit(1);
|
|
}
|
|
|
|
|
|
int main(int argc, char *argv[]) {
|
|
int fa,nfa; /* current argument we're looking at */
|
|
static char tiffin_name[MAXNAMEL+1] = { 0 }; /* TIFF Input file name (.tif) */
|
|
static char datin_name[MAXNAMEL+10+1] = { 0 }; /* Data input name (.cie/.q60) */
|
|
static char datout_name[MAXNAMEL+10+1] = { 0 }; /* Data output name (.ti3/.val) */
|
|
static char recog_name[MAXNAMEL+1] = { 0 }; /* Reference chart name (.cht) */
|
|
static char prof_name[MAXNAMEL+1] = { 0 }; /* scanner profile name (.cht) */
|
|
static char diag_name[MAXNAMEL+1] = { 0 }; /* Diagnostic Output (.tif) name, if used */
|
|
int verb = 1;
|
|
int tmean = 0; /* Return true mean, rather than robust mean */
|
|
int repl = 0; /* Replace .ti3 device values from raster file */
|
|
int outo = 0; /* Output the values read, rather than creating scanner .ti3 */
|
|
int colm = 0; /* Use inage values to measure color for print profile. > 1 == append */
|
|
int flags = SI_GENERAL_ROT; /* Default allow all rotations */
|
|
|
|
TIFF *rh = NULL, *wh = NULL;
|
|
uint16 depth, bps; /* Useful depth, bits per sample */
|
|
uint16 tdepth; /* Total depth including alpha */
|
|
uint16 pconfig, photometric;
|
|
uint16 rextrasamples; /* Extra "alpha" samples */
|
|
uint16 *rextrainfo; /* Info about extra samples */
|
|
int gotres = 0;
|
|
uint16 resunits;
|
|
float resx, resy;
|
|
|
|
icColorSpaceSignature tiffs = 0; /* Type of tiff color space */
|
|
|
|
int i, j;
|
|
double gamma = 0.0; /* default */
|
|
double _sfid[8], *sfid = NULL; /* Specified fiducials */
|
|
int width, height; /* x and y size */
|
|
|
|
scanrd *sr; /* Scanrd object */
|
|
int err;
|
|
char *errm;
|
|
int pnotscan = 0; /* Number of patches that wern't scanned */
|
|
|
|
|
|
error_program = argv[0];
|
|
|
|
if (argc <= 1)
|
|
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();
|
|
} else if (argv[fa][1] == 'v') {
|
|
verb = 2;
|
|
if (na != NULL && isdigit(na[0])) {
|
|
verb = atoi(na);
|
|
}
|
|
} else if (argv[fa][1] == 'm') {
|
|
tmean = 1;
|
|
|
|
} else if (argv[fa][1] == 'g') {
|
|
flags |= SI_BUILD_REF;
|
|
repl = 0;
|
|
outo = 0;
|
|
colm = 0;
|
|
|
|
} else if (argv[fa][1] == 'r') {
|
|
repl = 1;
|
|
outo = 0;
|
|
colm = 0;
|
|
|
|
} else if (argv[fa][1] == 'o') {
|
|
repl = 0;
|
|
outo = 1;
|
|
colm = 0;
|
|
|
|
} else if (argv[fa][1] == 'c') {
|
|
repl = 0;
|
|
outo = 0;
|
|
colm = 1;
|
|
if (argv[fa][2] != '\000' && argv[fa][2] == 'a')
|
|
colm = 2;
|
|
|
|
/* Approximate gamma encoding of image */
|
|
} else if (argv[fa][1] == 'G') {
|
|
fa = nfa;
|
|
if (na == NULL) usage();
|
|
gamma = atof(na);
|
|
if (gamma < 0.0 || gamma > 5.0)
|
|
usage();
|
|
|
|
/* Use specified fiducials instead of auto recognition */
|
|
} else if (argv[fa][1] == 'F') {
|
|
fa = nfa;
|
|
if (na == NULL) usage();
|
|
if (sscanf(na, " %lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf ",
|
|
&_sfid[0], &_sfid[1], &_sfid[2], &_sfid[3],
|
|
&_sfid[4], &_sfid[5], &_sfid[6], &_sfid[7]) != 8) {
|
|
usage();
|
|
}
|
|
|
|
sfid = _sfid;
|
|
|
|
/* Compensate for perspective */
|
|
} else if (argv[fa][1] == 'p') {
|
|
flags |= SI_PERSPECTIVE;
|
|
|
|
/* Don't recognise rotations */
|
|
} else if (argv[fa][1] == 'a') {
|
|
flags &= ~SI_GENERAL_ROT;
|
|
|
|
/* Don't recognise rotations, and read patches */
|
|
/* anyway "as is", if everything else failes */
|
|
} else if (argv[fa][1] == 'A') {
|
|
flags &= ~SI_GENERAL_ROT;
|
|
flags |= SI_ASISIFFAIL;
|
|
|
|
} else if (argv[fa][1] == 'd' || argv[fa][1] == 'D') {
|
|
while (na != NULL && *na != '\000') {
|
|
switch (*na) {
|
|
case 'i':
|
|
flags |= SI_SHOW_IMAGE;
|
|
break;
|
|
case 'h':
|
|
flags |= SI_SHOW_DIFFSH;
|
|
break;
|
|
case 'v':
|
|
flags |= SI_SHOW_DIFFSV;
|
|
break;
|
|
case 'g':
|
|
flags |= SI_SHOW_GROUPS;
|
|
break;
|
|
case 'l':
|
|
flags |= SI_SHOW_LINES;
|
|
break;
|
|
case 'L':
|
|
flags |= SI_SHOW_ALL_LINES;
|
|
break;
|
|
case 'I':
|
|
flags |= SI_SHOW_IMPL;
|
|
break;
|
|
case 'c':
|
|
flags |= SI_SHOW_PERS;
|
|
break;
|
|
case 'r':
|
|
flags |= SI_SHOW_ROT;
|
|
break;
|
|
case 's':
|
|
flags |= SI_SHOW_SBOX;
|
|
break;
|
|
case 'o':
|
|
flags |= SI_SHOW_SBOX_OUTLINES;
|
|
break;
|
|
case 'n':
|
|
flags |= SI_SHOW_SBOX_NAMES;
|
|
break;
|
|
case 'a':
|
|
flags |= SI_SHOW_SBOX_AREAS;
|
|
break;
|
|
case 'p':
|
|
flags |= SI_SHOW_SAMPLED_AREA;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
na++;
|
|
}
|
|
|
|
/* Output file name */
|
|
} else if (argv[fa][1] == 'O') {
|
|
fa = nfa;
|
|
if (na == NULL) usage();
|
|
strncpy(datout_name,na,MAXNAMEL); datout_name[MAXNAMEL] = '\000';
|
|
|
|
} else
|
|
usage();
|
|
} else
|
|
break;
|
|
}
|
|
|
|
/* TIFF Raster input file name */
|
|
if (fa >= argc || argv[fa][0] == '-') usage();
|
|
strncpy(tiffin_name,argv[fa],MAXNAMEL); tiffin_name[MAXNAMEL] = '\000';
|
|
|
|
|
|
/* Create a desination file path and name */
|
|
if (datout_name[0] == '\000' /* Not been overridden */
|
|
&& (flags & SI_BUILD_REF) == 0
|
|
&& repl == 0 && colm == 0) { /* Not generate ref or replacing .ti3 dev */
|
|
// ~~~99 Hmm. Should we honour -O ??
|
|
char *xl;
|
|
strncpy(datout_name,argv[fa],MAXNAMEL); datout_name[MAXNAMEL] = '\000';
|
|
if ((xl = strrchr(datout_name, '.')) == NULL) /* Figure where extention is */
|
|
xl = datout_name + strlen(datout_name);
|
|
if (outo == 0) /* Creating scan calib data */
|
|
strcpy(xl,".ti3");
|
|
else /* Just outputing values for some other purpose */
|
|
strcpy(xl,".val");
|
|
}
|
|
|
|
/* .cht Reference file in or out */
|
|
if (++fa >= argc || argv[fa][0] == '-') usage();
|
|
strncpy(recog_name,argv[fa],MAXNAMEL); recog_name[MAXNAMEL] = '\000';
|
|
|
|
if (colm > 0) {
|
|
if (++fa >= argc || argv[fa][0] == '-') usage();
|
|
strncpy(prof_name,argv[fa],MAXNAMEL); prof_name[MAXNAMEL] = '\000';
|
|
}
|
|
|
|
/* CGATS Data file input/output */
|
|
if ((flags & SI_BUILD_REF) == 0 && outo == 0) { /* Not generate ref or just outputing */
|
|
if (++fa >= argc || argv[fa][0] == '-') usage();
|
|
if (outo == 0) { /* Creating scan calib data */
|
|
/* Data file */
|
|
strncpy(datin_name,argv[fa],MAXNAMEL); datin_name[MAXNAMEL] = '\000';
|
|
}
|
|
if (repl != 0 || colm > 0) { /* Color from image or replacing .ti3 device data */
|
|
strcpy(datin_name,argv[fa]);
|
|
strcat(datin_name,".ti2");
|
|
strcpy(datout_name,argv[fa]); // ~~~99 Hmm. Should we honour -O ??
|
|
strcat(datout_name,".ti3");
|
|
}
|
|
}
|
|
|
|
/* optional diagnostic file */
|
|
if (++fa < argc) {
|
|
if (argv[fa][0] == '-')
|
|
usage();
|
|
strncpy(diag_name,argv[fa],MAXNAMEL); diag_name[MAXNAMEL] = '\000';
|
|
} else { /* Provide a default name */
|
|
strcpy(diag_name,"diag.tif");
|
|
}
|
|
|
|
if (stricmp(diag_name, tiffin_name) == 0) {
|
|
error("Diagnostic output '%s' might overwrite the input '%s'!",diag_name,tiffin_name);
|
|
}
|
|
|
|
/* ----------------------------------------- */
|
|
/* Open up input tiff file ready for reading */
|
|
/* Got arguments, so setup to process the file */
|
|
if ((rh = TIFFOpen(tiffin_name, "r")) == NULL)
|
|
error("error opening read file '%s'",tiffin_name);
|
|
|
|
TIFFGetField(rh, TIFFTAG_IMAGEWIDTH, &width);
|
|
TIFFGetField(rh, TIFFTAG_IMAGELENGTH, &height);
|
|
|
|
TIFFGetField(rh, TIFFTAG_BITSPERSAMPLE, &bps);
|
|
if (bps != 8 && bps != 16)
|
|
error("TIFF Input file '%s' must be 8 or 16 bits/channel",tiffin_name);
|
|
|
|
/* See if there are alpha planes */
|
|
TIFFGetFieldDefaulted(rh, TIFFTAG_EXTRASAMPLES, &rextrasamples, &rextrainfo);
|
|
|
|
TIFFGetField(rh, TIFFTAG_SAMPLESPERPIXEL, &depth);
|
|
|
|
if (rextrasamples > 0 && verb)
|
|
printf("%d extra (alpha ?) samples will be ignored\n",rextrasamples);
|
|
|
|
tdepth = depth;
|
|
depth = tdepth - rextrasamples;
|
|
|
|
if (depth != 1 && depth != 3 && depth != 4)
|
|
error("Input '%s' must be a Grey, RGB or CMYK tiff file",tiffin_name);
|
|
|
|
TIFFGetField(rh, TIFFTAG_PHOTOMETRIC, &photometric);
|
|
if (depth == 1 && photometric != PHOTOMETRIC_MINISBLACK
|
|
&& photometric != PHOTOMETRIC_MINISWHITE)
|
|
error("1 chanel input '%s' must be a Grey tiff file",tiffin_name);
|
|
else if (depth == 3 && photometric != PHOTOMETRIC_RGB)
|
|
error("3 chanel input '%s' must be an RGB tiff file",tiffin_name);
|
|
else if (depth == 4 && photometric != PHOTOMETRIC_SEPARATED)
|
|
error("4 chanel input '%s' must be a CMYK tiff file",tiffin_name);
|
|
|
|
if (depth == 1)
|
|
tiffs = icSigGrayData;
|
|
else if (depth == 3)
|
|
tiffs = icSigRgbData;
|
|
else if (depth == 4)
|
|
tiffs = icSigCmykData;
|
|
|
|
TIFFGetField(rh, TIFFTAG_PLANARCONFIG, &pconfig);
|
|
if (pconfig != PLANARCONFIG_CONTIG)
|
|
error("TIFF Input file '%s' must be planar",tiffin_name);
|
|
|
|
|
|
if (TIFFGetField(rh, TIFFTAG_RESOLUTIONUNIT, &resunits) != 0) {
|
|
TIFFGetField(rh, TIFFTAG_XRESOLUTION, &resx);
|
|
TIFFGetField(rh, TIFFTAG_YRESOLUTION, &resy);
|
|
|
|
if (resunits == RESUNIT_NONE /* If it looks valid */
|
|
|| resunits == RESUNIT_INCH
|
|
|| resunits == RESUNIT_CENTIMETER)
|
|
gotres = 1;
|
|
}
|
|
|
|
/* -------------------------- */
|
|
/* setup the diag output file */
|
|
if (flags & SI_SHOW_FLAGS) {
|
|
if ((wh = TIFFOpen(diag_name, "w")) == NULL)
|
|
error("Can\'t create TIFF file '%s'!",diag_name);
|
|
|
|
TIFFSetField(wh, TIFFTAG_IMAGEWIDTH, width);
|
|
TIFFSetField(wh, TIFFTAG_IMAGELENGTH, height);
|
|
TIFFSetField(wh, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
|
|
TIFFSetField(wh, TIFFTAG_SAMPLESPERPIXEL, 3);
|
|
TIFFSetField(wh, TIFFTAG_BITSPERSAMPLE, 8);
|
|
TIFFSetField(wh, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
|
|
TIFFSetField(wh, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
|
|
TIFFSetField(wh, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
|
|
if (gotres) {
|
|
TIFFSetField(wh, TIFFTAG_RESOLUTIONUNIT, resunits);
|
|
TIFFSetField(wh, TIFFTAG_XRESOLUTION, resx);
|
|
TIFFSetField(wh, TIFFTAG_YRESOLUTION, resy);
|
|
}
|
|
TIFFSetField(wh, TIFFTAG_IMAGEDESCRIPTION, "Scanin diagnosis output");
|
|
}
|
|
|
|
/* -------------------------- */
|
|
if (verb >= 2) {
|
|
printf("Input file '%s': w=%d, h=%d, d = %d, bpp = %d\n",
|
|
tiffin_name, width, height, depth, bps);
|
|
if (flags & SI_BUILD_REF)
|
|
printf("Build Scan Chart reference file '%s'\n",recog_name);
|
|
else {
|
|
printf("Data input file '%s'\n",datin_name);
|
|
printf("Data output file '%s'\n",datout_name);
|
|
printf("Chart reference file '%s'\n",recog_name);
|
|
}
|
|
if (flags & SI_SHOW_FLAGS)
|
|
printf("Creating diagnostic tiff file '%s'\n",diag_name);
|
|
}
|
|
|
|
/* -------------------------- */
|
|
/* Do the operation */
|
|
|
|
|
|
if ((sr = do_scanrd(
|
|
flags, /* option flags */
|
|
verb, /* verbosity level */
|
|
|
|
gamma,
|
|
sfid, /* Specified fiducuals, if any */
|
|
width, height, depth, tdepth, bps, /* Width, Height and Depth of input in pixels */
|
|
read_line, /* Read line function */
|
|
(void *)rh, /* Opaque data for read_line */
|
|
|
|
recog_name, /* reference file name */
|
|
|
|
write_line, /* Write line function */
|
|
(void *)wh /* Opaque data for write_line */
|
|
)) == NULL) {
|
|
if (flags & SI_SHOW_FLAGS)
|
|
TIFFClose(wh);
|
|
error("Unable to allocate scanrd object");
|
|
}
|
|
|
|
if ((err = sr->error(sr, &errm)) != 0) {
|
|
if ((flags & SI_SHOW_FLAGS) && err != SI_DIAG_WRITE_ERR)
|
|
TIFFClose(wh); /* Close diagnostic file */
|
|
error("Scanin failed with code 0x%x, %s",err,errm);
|
|
}
|
|
|
|
/* Read an output the values */
|
|
if ((flags & SI_BUILD_REF) == 0) { /* Not generate ref */
|
|
|
|
/* -------------------------------------------------- */
|
|
if (outo != 0) { /* Just output the values */
|
|
/* Note value range is raw 0..255, */
|
|
/* while all others output formats are out of 100 */
|
|
cgats *ocg; /* output cgats structure */
|
|
time_t clk = time(0);
|
|
struct tm *tsp = localtime(&clk);
|
|
char *atm = asctime(tsp); /* Ascii time */
|
|
|
|
/* Setup output cgats file */
|
|
ocg = new_cgats(); /* Create a CGATS structure */
|
|
ocg->add_other(ocg, "VALS"); /* Dummy type */
|
|
ocg->add_table(ocg, tt_other, 0); /* Start the first table */
|
|
|
|
ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration raster values",NULL);
|
|
ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll scanin", NULL);
|
|
atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
|
|
ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
|
|
|
|
ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t);
|
|
if (depth == 1) {
|
|
ocg->add_field(ocg, 0, "GREY", r_t);
|
|
} else if (depth == 3) {
|
|
ocg->add_field(ocg, 0, "RGB_R", r_t);
|
|
ocg->add_field(ocg, 0, "RGB_G", r_t);
|
|
ocg->add_field(ocg, 0, "RGB_B", r_t);
|
|
} else if (depth == 4) {
|
|
ocg->add_field(ocg, 0, "CMYK_C", r_t);
|
|
ocg->add_field(ocg, 0, "CMYK_M", r_t);
|
|
ocg->add_field(ocg, 0, "CMYK_Y", r_t);
|
|
ocg->add_field(ocg, 0, "CMYK_K", r_t);
|
|
}
|
|
|
|
/* Initialise, ready to read out all the values */
|
|
for (j = 0; ; j++) {
|
|
char id[100]; /* Input patch id */
|
|
double P[4]; /* Robust/true mean values */
|
|
int pixcnt; /* PIxel count */
|
|
|
|
if (tmean) {
|
|
if (sr->read(sr, id, NULL, P, NULL, &pixcnt) != 0)
|
|
break;
|
|
} else {
|
|
if (sr->read(sr, id, P, NULL, NULL, &pixcnt) != 0)
|
|
break;
|
|
}
|
|
|
|
if (pixcnt == 0)
|
|
pnotscan++;
|
|
|
|
if (depth == 1) {
|
|
ocg->add_set( ocg, 0, id, P[0]);
|
|
} else if (depth == 3) {
|
|
ocg->add_set( ocg, 0, id, P[0], P[1], P[2]);
|
|
} else if (depth == 4) {
|
|
ocg->add_set( ocg, 0, id, P[0], P[1], P[2], P[3]);
|
|
}
|
|
}
|
|
|
|
if (verb)
|
|
printf("Writing output values to file '%s'\n",datout_name);
|
|
|
|
if (ocg->write_name(ocg, datout_name))
|
|
error("Write error to '%s' : %s",datout_name,ocg->e.m);
|
|
|
|
ocg->del(ocg); /* Clean up */
|
|
|
|
/* -------------------------------------------------- */
|
|
} else if (repl != 0) { /* Replace .ti3 device values */
|
|
cgats *icg; /* input .ti2 cgats structure */
|
|
cgats *ocg; /* input/output .ti3 cgats structure */
|
|
int npat; /* Number of test patches */
|
|
int dim = 0; /* Dimenstionality of device space */
|
|
int fi; /* Field index */
|
|
int isi, ili; /* Input file sample and location indexes */
|
|
char *dfnames[5][4] = { /* Device colorspace names */
|
|
{ "" },
|
|
{ "GRAY_W" },
|
|
{ "" },
|
|
{ "RGB_R", "RGB_G", "RGB_B" },
|
|
{ "CMYK_C", "CMYK_M", "CMYK_Y", "CMYK_K" }
|
|
};
|
|
int odim = 0; /* Output file device dimensionality */
|
|
int dfi[5][4]; /* Output file device colorspace indexes */
|
|
int osi; /* Output file sample id index */
|
|
|
|
/* Setup input .ti2 file */
|
|
icg = new_cgats(); /* Create a CGATS structure */
|
|
icg->add_other(icg, "CTI2"); /* Calibration Target Information 2 */
|
|
if (icg->read_name(icg, datin_name))
|
|
error("CGATS file '%s' read error : %s",datin_name,icg->e.m);
|
|
|
|
if (icg->t[0].tt != tt_other || icg->t[0].oi != 0)
|
|
error("Input file '%s' isn't a CTI2 format file",datin_name);
|
|
|
|
if (icg->ntables < 1)
|
|
error("Input file '%s' doesn't contain at least one table",datin_name);
|
|
|
|
if ((npat = icg->t[0].nsets) <= 0)
|
|
error("Input file '%s' doesn't contain any data sets",datin_name);
|
|
|
|
/* Figure out the color space */
|
|
if ((fi = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
|
|
error("Input file '%s' doesn't contain keyword COLOR_REP",datin_name);
|
|
|
|
if (strcmp(icg->t[0].kdata[fi],"CMYK") == 0) {
|
|
dim = 4;
|
|
} else if (strcmp(icg->t[0].kdata[fi],"RGB") == 0) {
|
|
dim = 3;
|
|
} else if (strcmp(icg->t[0].kdata[fi],"W") == 0) {
|
|
dim = 1;
|
|
} else
|
|
error("Input file '%s' keyword COLOR_REP has unknown value",datin_name);
|
|
|
|
/* Find fields we want in the input file */
|
|
if ((isi = icg->find_field(icg, 0, "SAMPLE_ID")) < 0)
|
|
error("Input file '%s' doesn't contain field SAMPLE_ID",datin_name);
|
|
if (icg->t[0].ftype[isi] != nqcs_t)
|
|
error("Input file '%s' Field SAMPLE_ID is wrong type",datin_name);
|
|
|
|
if ((ili = icg->find_field(icg, 0, "SAMPLE_LOC")) < 0)
|
|
error("Input file '%s' doesn't contain field SAMPLE_LOC",datin_name);
|
|
if (icg->t[0].ftype[ili] != cs_t
|
|
&& icg->t[0].ftype[ili] != nqcs_t)
|
|
error("Input file '%s' Field SAMPLE_LOC is wrong type",datin_name);
|
|
|
|
/* Setup input/output .ti3 file */
|
|
ocg = new_cgats(); /* Create a CGATS structure */
|
|
ocg->add_other(ocg, "CTI3"); /* Calibration Target Information 3 */
|
|
if (ocg->read_name(ocg, datout_name))
|
|
error("CGATS file '%s' read error : %s",datout_name,ocg->e.m);
|
|
|
|
if (ocg->t[0].tt != tt_other || ocg->t[0].oi != 0)
|
|
error("Input file '%s' isn't a CTI3 format file",datout_name);
|
|
|
|
if (ocg->ntables < 1)
|
|
error("Input file '%s' doesn't contain at least one table",datout_name);
|
|
|
|
if (npat != ocg->t[0].nsets)
|
|
error("Input file '%s' doesn't contain same number of data sets",datout_name);
|
|
|
|
|
|
/* Find the fields we want in the output file */
|
|
|
|
/* Figure out the color space */
|
|
if ((fi = ocg->find_kword(ocg, 0, "COLOR_REP")) < 0)
|
|
error("Input file '%s' doesn't contain keyword COLOR_REP",datout_name);
|
|
|
|
if (strncmp(ocg->t[0].kdata[fi],"CMYK",4) == 0) {
|
|
odim = 4;
|
|
} else if (strncmp(ocg->t[0].kdata[fi],"RGB",3) == 0) {
|
|
odim = 3;
|
|
} else if (strncmp(ocg->t[0].kdata[fi],"W",1) == 0) {
|
|
odim = 1;
|
|
} else
|
|
error("Input file '%s' keyword COLOR_REP has unknown value",datout_name);
|
|
|
|
if (odim != dim)
|
|
error("File '%s' has different device space to '%s'",datin_name, datout_name);
|
|
|
|
if ((osi = ocg->find_field(ocg, 0, "SAMPLE_ID")) < 0)
|
|
error("Input file '%s' doesn't contain field SAMPLE_ID",datout_name);
|
|
if (ocg->t[0].ftype[osi] != nqcs_t)
|
|
error("Input file '%s' Field SAMPLE_ID is wrong type",datout_name);
|
|
|
|
for (i = 0; i < dim; i++) {
|
|
if ((dfi[dim][i] = ocg->find_field(ocg, 0, dfnames[dim][i])) < 0)
|
|
error("Input '%s' file doesn't contain field %s", datout_name, dfnames[dim][i]);
|
|
if (ocg->t[0].ftype[dfi[dim][i]] != r_t)
|
|
error("Input '%s' Field %s is wrong type",datout_name,dfnames[dim][i]);
|
|
}
|
|
|
|
/* Initialise, ready to read out all the values */
|
|
for (i = sr->reset(sr); i > 0; i--) { /* For all samples in .tiff file */
|
|
char loc[100]; /* Target patch location */
|
|
double P[4]; /* Robust/raw mean values */
|
|
int pixcnt; /* Pixel count */
|
|
int k, e;
|
|
|
|
if (tmean)
|
|
sr->read(sr, loc, NULL, P, NULL, &pixcnt);
|
|
else
|
|
sr->read(sr, loc, P, NULL, NULL, &pixcnt);
|
|
|
|
if (pixcnt == 0)
|
|
pnotscan++;
|
|
|
|
/* Search for this location in the .ti2 file */
|
|
for (j = 0; j < npat; j++) {
|
|
if (strcmp(loc, (char *)icg->t[0].fdata[j][ili]) == 0) {
|
|
char *sidp = (char *)icg->t[0].fdata[j][isi];
|
|
|
|
/* Search for this sample id in .ti3 file */
|
|
for (k = 0; k < npat; k++) {
|
|
if (strcmp(sidp, (char *)ocg->t[0].fdata[k][osi]) == 0) {
|
|
/* Update the device values */
|
|
for (e = 0; e < dim; e++) {
|
|
double vv = 100.0 * P[e]/255.0;
|
|
*((double *)ocg->t[0].fdata[k][dfi[dim][e]]) = vv;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (k >= npat && verb >= 1)
|
|
printf("Warning: Couldn't find sample '%s' in '%s'\n",sidp,datout_name);
|
|
break;
|
|
}
|
|
}
|
|
if (j >= npat && verb >= 1)
|
|
printf("Warning: Couldn't find location '%s' in '%s'\n",loc,datin_name);
|
|
}
|
|
|
|
/* Flush our changes */
|
|
if (verb)
|
|
printf("Writing output values to file '%s'\n",datout_name);
|
|
|
|
if (ocg->write_name(ocg, datout_name))
|
|
error("Write error to file '%s' : %s",datout_name,ocg->e.m);
|
|
|
|
ocg->del(ocg); /* Clean up */
|
|
icg->del(icg); /* Clean up */
|
|
|
|
/* ---------------------------------------------------------- */
|
|
} else if (colm > 0) { /* Using the image to measure color */
|
|
/* All this needs to track the code in spectro/printread.c */
|
|
cgats *icg; /* input .ti2 cgats structure */
|
|
cgats *ocg; /* input/output .ti3 cgats structure */
|
|
icmFile *rd_fp = NULL; /* Image to CIE lookup */
|
|
icmErr err = { 0, { '\000'} };
|
|
icc *rd_icco = NULL;
|
|
icmLuSpace *luo;
|
|
mpp *mlu = NULL;
|
|
time_t clk = time(0);
|
|
struct tm *tsp = localtime(&clk);
|
|
char *atm = asctime(tsp); /* Ascii time */
|
|
int nmask = 0; /* Device colorant mask */
|
|
int nchan = 0; /* Number of device chanels */
|
|
int npat; /* Number of input patches (inc. padding) */
|
|
int nopat = 0; /* Number of output patches */
|
|
int si; /* Sample id index */
|
|
int li; /* Location id index */
|
|
int ti; /* Temp index */
|
|
int fi; /* Colorspace index */
|
|
|
|
icg = new_cgats(); /* Create a CGATS structure */
|
|
icg->add_other(icg, "CTI2"); /* special type Calibration Target Information 2 */
|
|
icg->add_other(icg, "CAL"); /* There may be a calibration too */
|
|
|
|
if (icg->read_name(icg, datin_name))
|
|
error("CGATS file '%s' read error : %s",datin_name,icg->e.m);
|
|
|
|
if (icg->t[0].tt != tt_other || icg->t[0].oi != 0)
|
|
error("Input file '%s' isn't a CTI2 format file",datin_name);
|
|
if (icg->ntables < 1)
|
|
error("Input file '%s' doesn't contain at least one table",datin_name);
|
|
|
|
if ((npat = icg->t[0].nsets) <= 0)
|
|
error("Input file '%s' has no sets of data",datin_name);
|
|
|
|
/* Setup output cgats file */
|
|
ocg = new_cgats(); /* Create a CGATS structure */
|
|
ocg->add_other(ocg, "CTI3"); /* special type Calibration Target Information 3 */
|
|
|
|
if (colm > 1) { /* Appending information to .ti3 */
|
|
|
|
if (ocg->read_name(ocg, datout_name))
|
|
error("CGATS file read error on '%s': %s",datout_name, ocg->e.m);
|
|
|
|
if (ocg->t[0].tt != tt_other || ocg->t[0].oi != 0)
|
|
error("Input file '%s' isn't a CTI3 format file",datout_name);
|
|
if (ocg->ntables < 1)
|
|
error("Input file '%s' doesn't at least exactly one table",datout_name);
|
|
if ((nopat = ocg->t[0].nsets) <= 0)
|
|
error("Input file '%s' has no existing sets of data",datout_name);
|
|
|
|
} else { /* Creating .ti3 */
|
|
|
|
ocg->add_table(ocg, tt_other, 0); /* Start the first table */
|
|
|
|
ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 3",NULL);
|
|
ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll printread", NULL);
|
|
atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
|
|
ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
|
|
ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL); /* What sort of device this is */
|
|
if ((ti = icg->find_kword(icg, 0, "SINGLE_DIM_STEPS")) >= 0)
|
|
ocg->add_kword(ocg, 0, "SINGLE_DIM_STEPS",icg->t[0].kdata[ti], NULL);
|
|
|
|
if ((ti = icg->find_kword(icg, 0, "COMP_GREY_STEPS")) >= 0)
|
|
ocg->add_kword(ocg, 0, "COMP_GREY_STEPS",icg->t[0].kdata[ti], NULL);
|
|
|
|
if ((ti = icg->find_kword(icg, 0, "MULTI_DIM_STEPS")) >= 0)
|
|
ocg->add_kword(ocg, 0, "MULTI_DIM_STEPS",icg->t[0].kdata[ti], NULL);
|
|
|
|
if ((ti = icg->find_kword(icg, 0, "FULL_SPREAD_PATCHES")) >= 0)
|
|
ocg->add_kword(ocg, 0, "FULL_SPREAD_PATCHES",icg->t[0].kdata[ti], NULL);
|
|
|
|
if ((ti = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0)
|
|
ocg->add_kword(ocg, 0, "TOTAL_INK_LIMIT",icg->t[0].kdata[ti], NULL);
|
|
|
|
/* See if there is a calibration in the .ti2, and copy it if there is */
|
|
{
|
|
int oi, tab;
|
|
|
|
oi = icg->get_oi(icg, "CAL");
|
|
|
|
for (tab = 0; tab < icg->ntables; tab++) {
|
|
if (icg->t[tab].tt == tt_other && icg->t[tab].oi == oi) {
|
|
break;
|
|
}
|
|
}
|
|
if (tab < icg->ntables) {
|
|
xcal *cal = NULL;
|
|
|
|
if (verb)
|
|
printf("Copying .cal from '%s' to '%s'\n",datin_name,datout_name);
|
|
|
|
if ((cal = new_xcal()) == NULL) {
|
|
error("new_xcal failed");
|
|
}
|
|
if (cal->read_cgats(cal, icg, tab, datin_name) != 0) {
|
|
error("%s",cal->e.m);
|
|
}
|
|
|
|
if (cal->write_cgats(cal, ocg)) {
|
|
error("%s",cal->e.m);
|
|
}
|
|
|
|
cal->del(cal);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((si = icg->find_field(icg, 0, "SAMPLE_ID")) < 0)
|
|
error("Input file '%s' doesn't contain field SAMPLE_ID",datin_name);
|
|
if (icg->t[0].ftype[si] != nqcs_t)
|
|
error("Input file '%s' Field SAMPLE_ID is wrong type",datin_name);
|
|
|
|
/* Fields we want */
|
|
if (colm > 1) { /* Appending information to .ti3 */
|
|
if ((ti = ocg->find_field(ocg, 0, "SAMPLE_ID")) != 0)
|
|
error("Input file '%s' field SAMPLE_ID (%d) not in expected location (%d)",
|
|
datout_name, ti, 0);
|
|
} else {
|
|
ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t);
|
|
}
|
|
|
|
if ((li = icg->find_field(icg, 0, "SAMPLE_LOC")) < 0)
|
|
error("Input file '%s' doesn't contain field SAMPLE_LOC",datin_name);
|
|
if (icg->t[0].ftype[li] != cs_t)
|
|
error("Input file '%s' field SAMPLE_LOC is wrong type",datin_name);
|
|
|
|
/* Figure out the color space */
|
|
if ((fi = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
|
|
error("Input file '%s' doesn't contain keyword COLOR_REPS",datin_name);
|
|
|
|
if ((nmask = icx_char2inkmask(icg->t[0].kdata[fi])) != 0) {
|
|
int i, j, ii;
|
|
int chix[ICX_MXINKS]; /* Device chanel indexes */
|
|
int xyzix[3]; /* XYZ chanel indexes */
|
|
char *ident, *bident;
|
|
char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" };
|
|
|
|
nchan = icx_noofinks(nmask);
|
|
ident = icx_inkmask2char(nmask, 1);
|
|
bident = icx_inkmask2char(nmask, 0);
|
|
|
|
/* Device channels */
|
|
for (j = 0; j < nchan; j++) {
|
|
int imask;
|
|
char fname[100];
|
|
|
|
imask = icx_index2ink(nmask, j);
|
|
sprintf(fname,"%s_%s",nmask == ICX_W || nmask == ICX_K ? "GRAY" : bident,
|
|
icx_ink2char(imask));
|
|
|
|
if ((ii = icg->find_field(icg, 0, fname)) < 0)
|
|
error("Input file '%s' doesn't contain field %s",datin_name,fname);
|
|
if (icg->t[0].ftype[ii] != r_t)
|
|
error("Field %s is wrong type",fname);
|
|
|
|
if (colm > 1) { /* Appending information to .ti3 */
|
|
if (ocg->find_field(ocg, 0, fname) != 1 + j)
|
|
error("Input file '%s' field %s not in expected location",datout_name,fname);
|
|
} else {
|
|
ocg->add_field(ocg, 0, fname, r_t);
|
|
}
|
|
chix[j] = ii;
|
|
}
|
|
|
|
/* Approximate XYZ and real XYZ */
|
|
for (j = 0; j < 3; j++) {
|
|
if ((ii = icg->find_field(icg, 0, xyzfname[j])) >= 0) {
|
|
|
|
if (icg->t[0].ftype[ii] != r_t)
|
|
error("Input file '%s' field %s is wrong type",datin_name,xyzfname[j]);
|
|
}
|
|
|
|
if (colm > 1) { /* Appending information to .ti3 */
|
|
if (ocg->find_field(ocg, 0, xyzfname[j]) != 1 + nchan + j)
|
|
error("Input file '%s' field %s not in expected location",
|
|
datout_name,xyzfname[j]);
|
|
} else {
|
|
ocg->add_field(ocg, 0, xyzfname[j], r_t);
|
|
}
|
|
xyzix[j] = ii;
|
|
}
|
|
|
|
if (colm <= 1) { /* Creating .ti3 */
|
|
char fname[100];
|
|
sprintf(fname, "%s_XYZ", ident);
|
|
ocg->add_kword(ocg, 0, "COLOR_REP", fname, NULL);
|
|
}
|
|
|
|
if (colm > 1) { /* Appending .ti3 data */
|
|
|
|
/* Check that all the patches match */
|
|
for (ii = i = 0; i < npat; i++) {
|
|
|
|
if (strcmp(((char *)icg->t[0].fdata[i][si]), "0") == 0)
|
|
continue; /* Padding, so skip it */
|
|
|
|
/* Id's */
|
|
if (strcmp (((char *)icg->t[0].fdata[i][si]),
|
|
((char *)ocg->t[0].fdata[ii][si])) != 0)
|
|
error("'%s' and '%s' field id's don't match at patch %d\n",datin_name,datout_name,i+1);
|
|
|
|
/* device values */
|
|
for (j = 0; j < nchan; j++) {
|
|
double ival, oval;
|
|
ival = *((double *)icg->t[0].fdata[i][chix[j]]);
|
|
oval = *((double *)ocg->t[0].fdata[ii][1 + j]);
|
|
if (fabs(ival - oval) > 0.001)
|
|
error("'%s' and '%s' device values (%f %f) don't match at patch %d %d\n",datin_name,datout_name,ival, oval, i+1, ii+1);
|
|
}
|
|
ii++;
|
|
}
|
|
if (ii != nopat)
|
|
error("Different number of patches in '%s' (%d) to expected(%d)",datout_name,nopat,ii);
|
|
|
|
} else { /* Read all the test patches in, and create output slots */
|
|
cgats_set_elem *setel; /* Array of set value elements */
|
|
|
|
if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * (1 + nchan + 3))) == NULL)
|
|
error("Malloc failed!");
|
|
|
|
|
|
for (ii = i = 0; i < npat; i++) {
|
|
int k = 0;
|
|
|
|
if (strcmp(((char *)icg->t[0].fdata[i][si]), "0") == 0)
|
|
continue; /* Padding, so skip it */
|
|
|
|
/* Id */
|
|
setel[k++].c = ((char *)icg->t[0].fdata[i][si]);
|
|
|
|
/* device values */
|
|
for (j = 0; j < nchan; j++) {
|
|
setel[k++].d = *((double *)icg->t[0].fdata[i][chix[j]]);
|
|
}
|
|
|
|
/* Unset XYZ values */
|
|
setel[k++].d = -1.0;
|
|
setel[k++].d = -1.0;
|
|
setel[k++].d = -1.0;
|
|
|
|
ocg->add_setarr(ocg, 0, setel);
|
|
|
|
ii++;
|
|
}
|
|
nopat = ii;
|
|
free(setel);
|
|
}
|
|
free(ident);
|
|
free(bident);
|
|
|
|
} else
|
|
error("Input file '%s' keyword COLOR_REPS has unknown value",datin_name);
|
|
|
|
/* Setup RGB to XYZ conversion */
|
|
{
|
|
int inn, outn; /* Chanels for input and output spaces */
|
|
icColorSpaceSignature ins, outs; /* Type of input and output spaces */
|
|
int rv;
|
|
|
|
/* Open up the file for reading */
|
|
if ((rd_fp = new_icmFileStd_name(&err,prof_name,"r")) == NULL)
|
|
error("Write: Can't open file '%s' (0x%x, '%s')",prof_name,err.c,err.m);
|
|
|
|
if ((rd_icco = new_icc(&err)) == NULL)
|
|
error("Read: Creation of ICC object failed (0x%x, '%s')",err.c,err.m);
|
|
|
|
/* Read the header and tag list */
|
|
if ((rv = rd_icco->read(rd_icco,rd_fp,0)) == 0) {
|
|
icmCSInfo ini, outi;
|
|
|
|
/* Get the Fwd table, absolute with XYZ override */
|
|
if ((luo = (icmLuSpace *)rd_icco->get_luobj(rd_icco, icmFwd, icAbsoluteColorimetric,
|
|
icSigXYZData, icmLuOrdNorm)) == NULL) {
|
|
error("%d, %s",rd_icco->e.c, rd_icco->e.m);
|
|
}
|
|
|
|
/* Get details of conversion */
|
|
luo->spaces(luo, &ini, &outi, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
|
ins = ini.sig;
|
|
inn = ini.nch;
|
|
outs = outi.sig;
|
|
outn = outi.nch;
|
|
|
|
/* Check that it matches what we expect */
|
|
|
|
} else { /* Not a valid ICC */
|
|
inkmask cnv_nmask = 0; /* Conversion input nmask */
|
|
|
|
/* Close out the ICC profile */
|
|
rd_icco->del(rd_icco);
|
|
rd_icco = NULL;
|
|
rd_fp->del(rd_fp);
|
|
rd_fp = NULL;
|
|
|
|
/* If we don't have an ICC lookup object, look for an MPP */
|
|
|
|
if ((mlu = new_mpp()) == NULL)
|
|
error ("Creation of MPP object failed");
|
|
|
|
if ((rv = mlu->read_mpp(mlu, prof_name)) == 0) {
|
|
|
|
/* mlu defaults to absolute XYZ lookup */
|
|
mlu->get_info(mlu, &cnv_nmask, &inn, NULL, NULL, NULL, NULL, NULL, NULL);
|
|
|
|
outn = 3;
|
|
outs = icSigXYZData;
|
|
|
|
if ((ins = icx_colorant_comb_to_icc(cnv_nmask)) == 0)
|
|
error ("Couldn't match MPP mask to valid ICC colorspace");
|
|
|
|
} else {
|
|
mlu->del(mlu);
|
|
mlu = NULL;
|
|
error("File '%s' failed to read as ICC or MPP profile",prof_name);
|
|
}
|
|
}
|
|
if (inn != depth || tiffs != ins)
|
|
error("%s profile '%s' doesn't match TIFF file type",luo != NULL ? "ICC" : "MPP", prof_name);
|
|
}
|
|
|
|
/* Initialise, ready to read out all the values */
|
|
for (i = sr->reset(sr); i > 0; i--) { /* For all samples in .tiff file */
|
|
char loc[100]; /* Target patch location */
|
|
double P[ICX_MXINKS]; /* Robust/true mean values */
|
|
double xyz[3]; /* profile XYZ value */
|
|
int pixcnt; /* Pixel count */
|
|
int k, e;
|
|
|
|
if (tmean)
|
|
sr->read(sr, loc, NULL, P, NULL, &pixcnt);
|
|
else
|
|
sr->read(sr, loc, P, NULL, NULL, &pixcnt);
|
|
|
|
if (pixcnt == 0)
|
|
pnotscan++;
|
|
|
|
/* Search for this location in the .ti2 file */
|
|
for (j = 0; j < npat; j++) {
|
|
if (strcmp(loc, (char *)icg->t[0].fdata[j][li]) == 0) { /* Got location */
|
|
char *sidp = (char *)icg->t[0].fdata[j][si]; /* Get id */
|
|
|
|
if (strcmp(sidp, "0") == 0)
|
|
break; /* Padding, so ignore it */
|
|
|
|
/* Search for this sample id in .ti3 file */
|
|
for (k = 0; k < nopat; k++) {
|
|
if (strcmp(sidp, (char *)ocg->t[0].fdata[k][si]) == 0) {
|
|
|
|
//printf("Loc %s, ID %s got RGB value %f %f %f\n",sidp, loc, P[0], P[1], P[2]);
|
|
|
|
/* Convert RGB to XYZ */
|
|
for (e = 0; e < depth; e++)
|
|
P[e] /= 255.0; /* Convert to 0.0 .. 1.0 range */
|
|
|
|
/* Convert to XYZ */
|
|
if (luo != NULL)
|
|
luo->lookup_fwd(luo, xyz, P);
|
|
else
|
|
mlu->lookup(mlu, xyz, P);
|
|
|
|
/* Sanity check XYZ ? */
|
|
// ~~~99
|
|
|
|
/* Update the XYZ values */
|
|
for (e = 0; e < 3; e++) {
|
|
double ev = *((double *)ocg->t[0].fdata[k][1 + nchan + e]);
|
|
|
|
if (ev != -1.0)
|
|
error("Found an existing value in '%s' file (%f)",datout_name,ev);
|
|
|
|
*((double *)ocg->t[0].fdata[k][1 + nchan + e]) = 100.0 * xyz[e];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (k >= nopat)
|
|
error("Couldn't find sample '%s' in '%s'\n",sidp,datout_name);
|
|
break;
|
|
}
|
|
}
|
|
if (j >= npat && verb >= 1)
|
|
error("Couldn't find location '%s' in '%s'\n",loc, datin_name);
|
|
}
|
|
|
|
/* Warn if not all patch values have been filled */
|
|
if (verb) {
|
|
int e, k;
|
|
for (k = 0; k < nopat; k++) {
|
|
for (e = 0; e < 3; e++) {
|
|
double ev = *((double *)ocg->t[0].fdata[k][1 + nchan + e]);
|
|
|
|
if (ev == -1.0)
|
|
break;
|
|
}
|
|
if (e < 3)
|
|
break;
|
|
}
|
|
if (k < nopat)
|
|
printf("Not all sample values have been filled\n");
|
|
else
|
|
printf("All sample values have been filled\n");
|
|
}
|
|
|
|
if (verb)
|
|
printf("Writing output values to file '%s'\n",datout_name);
|
|
|
|
if (ocg->write_name(ocg, datout_name))
|
|
error("File '%s' write error : %s",datout_name,ocg->e.m);
|
|
|
|
if (luo != NULL)
|
|
luo->del(luo);
|
|
if (rd_icco != NULL)
|
|
rd_icco->del(rd_icco);
|
|
if (rd_fp != NULL)
|
|
rd_fp->del(rd_fp);
|
|
if (mlu != NULL)
|
|
mlu->del(mlu);
|
|
|
|
ocg->del(ocg); /* Clean up */
|
|
icg->del(icg); /* Clean up */
|
|
|
|
/* ----------------------------------- */
|
|
} else { /* Normal scan calibration */
|
|
cgats *icg; /* input cgats structure */
|
|
cgats *ocg; /* output cgats structure */
|
|
time_t clk = time(0);
|
|
struct tm *tsp = localtime(&clk);
|
|
char *atm = asctime(tsp); /* Ascii time */
|
|
int ti; /* Temp index */
|
|
int sx; /* Sample id index */
|
|
int isLab = 0; /* D50 Lab reference */
|
|
int Xx, Yx, Zx; /* XYZ_X, XYZ_Y, XYZ_Z index */
|
|
int spec_n = 0; /* Number of spectral bands */
|
|
double spec_wl_short;/* First reading wavelength in nm (shortest) */
|
|
double spec_wl_long; /* Last reading wavelength in nm (longest) */
|
|
int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
|
|
int npat; /* Number of test patches in it8 chart */
|
|
int nsetel = 0; /* Number of output set elements */
|
|
cgats_set_elem *setel; /* Array of set value elements */
|
|
unsigned int *idhash; /* Array of reference id hashes */
|
|
|
|
icg = new_cgats(); /* Create a CGATS structure */
|
|
icg->add_other(icg, ""); /* Accept any type */
|
|
if (icg->read_name(icg, datin_name))
|
|
error("CGATS file '%s' read error : %s",datin_name,icg->e.m);
|
|
|
|
/* ~~ should accept ti2 file and convert RGB to XYZ using */
|
|
/* device cal., to make W/RGB/CMYK ->XYZ reading chart ~~ */
|
|
if (icg->ntables < 1)
|
|
error("Input file '%s' doesn't contain at least one table",datin_name);
|
|
|
|
if ((npat = icg->t[0].nsets) <= 0)
|
|
error("File '%s' no sets of data in first table",datin_name);
|
|
|
|
/* Setup output cgats file */
|
|
ocg = new_cgats(); /* Create a CGATS structure */
|
|
ocg->add_other(ocg, "CTI3"); /* our special type is Calibration Target Information 3 */
|
|
ocg->add_table(ocg, tt_other, 0); /* Start the first table */
|
|
|
|
ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 3",NULL);
|
|
ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll target", NULL);
|
|
atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
|
|
ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
|
|
|
|
ocg->add_kword(ocg, 0, "DEVICE_CLASS","INPUT", NULL); /* What sort of device this is */
|
|
ocg->add_kword(ocg, 0, "COLOR_REP","XYZ_RGB", NULL);
|
|
|
|
/* Fields we want from input chart reference file */
|
|
if ((sx = icg->find_field(icg, 0, "Sample_Name")) < 0) {
|
|
if ((sx = icg->find_field(icg, 0, "SAMPLE_NAME")) < 0) {
|
|
if ((sx = icg->find_field(icg, 0, "SAMPLE_LOC")) < 0) {
|
|
if ((sx = icg->find_field(icg, 0, "SAMPLE_ID")) < 0) {
|
|
error("Input file '%s' doesn't contain field SAMPLE_ID, Sample_Name or SAMPLE_NAME",datin_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (icg->t[0].ftype[sx] != nqcs_t && icg->t[0].ftype[sx] != cs_t)
|
|
error("Input file '%s' field %s is wrong type", datin_name, icg->t[0].fsym[sx]);
|
|
|
|
if ((Xx = icg->find_field(icg, 0, "XYZ_X")) < 0) {
|
|
if ((Xx = icg->find_field(icg, 0, "LAB_L")) < 0)
|
|
error("Input file '%s' doesn't contain field XYZ_X or LAB_L",datin_name);
|
|
|
|
isLab = 1;
|
|
if (icg->t[0].ftype[Xx] != r_t)
|
|
error("Input file '%s' field LAB_L is wrong type",datin_name);
|
|
if ((Yx = icg->find_field(icg, 0, "LAB_A")) < 0)
|
|
error("Input file doesn't contain field LAB_A",datin_name);
|
|
if (icg->t[0].ftype[Yx] != r_t)
|
|
error("Input file '%s' field LAB_A is wrong type",datin_name);
|
|
if ((Zx = icg->find_field(icg, 0, "LAB_B")) < 0)
|
|
error("Input file '%s' doesn't contain field LAB_B",datin_name);
|
|
if (icg->t[0].ftype[Zx] != r_t)
|
|
error("Input file '%s' field LAB_B is wrong type",datin_name);
|
|
} else {
|
|
if (icg->t[0].ftype[Xx] != r_t)
|
|
error("Input file '%s' field XYZ_X is wrong type",datin_name);
|
|
if ((Yx = icg->find_field(icg, 0, "XYZ_Y")) < 0)
|
|
error("Input file '%s' doesn't contain field XYZ_Y",datin_name);
|
|
if (icg->t[0].ftype[Yx] != r_t)
|
|
error("Input file '%s' field XYZ_Y is wrong type",datin_name);
|
|
if ((Zx = icg->find_field(icg, 0, "XYZ_Z")) < 0)
|
|
error("Input file '%s' doesn't contain field XYZ_Z",datin_name);
|
|
if (icg->t[0].ftype[Zx] != r_t)
|
|
error("Input file '%s' field XYZ_Z is wrong type",datin_name);
|
|
}
|
|
|
|
/* Find possible spectral fields in reference */
|
|
if ((ti = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) >= 0) {
|
|
spec_n = atoi(icg->t[0].kdata[ti]);
|
|
if ((ti = icg->find_kword(icg, 0, "SPECTRAL_START_NM")) < 0)
|
|
error ("Input file '%s' doesn't contain keyword SPECTRAL_START_NM",datin_name);
|
|
spec_wl_short = atof(icg->t[0].kdata[ti]);
|
|
if ((ti = icg->find_kword(icg, 0, "SPECTRAL_END_NM")) < 0)
|
|
error ("Input file '%s' doesn't contain keyword SPECTRAL_END_NM",datin_name);
|
|
spec_wl_long = atof(icg->t[0].kdata[ti]);
|
|
|
|
/* Find the fields for spectral values */
|
|
for (i = 0; i < spec_n; i++) {
|
|
char buf[100];
|
|
int nm;
|
|
|
|
/* Compute nearest integer wavelength */
|
|
nm = (int)(spec_wl_short + ((double)i/(spec_n-1.0))
|
|
* (spec_wl_long - spec_wl_short) + 0.5);
|
|
|
|
sprintf(buf,"SPEC_%03d",nm);
|
|
|
|
if ((spi[i] = icg->find_field(icg, 0, buf)) < 0)
|
|
error("Input file doesn't contain field %s",datin_name);
|
|
}
|
|
}
|
|
|
|
ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t);
|
|
nsetel += 1;
|
|
ocg->add_field(ocg, 0, "XYZ_X", r_t);
|
|
ocg->add_field(ocg, 0, "XYZ_Y", r_t);
|
|
ocg->add_field(ocg, 0, "XYZ_Z", r_t);
|
|
nsetel += 3;
|
|
|
|
/* If we have spectral information, output it too */
|
|
if (spec_n > 0) {
|
|
char buf[100];
|
|
|
|
nsetel += spec_n; /* Spectral values */
|
|
sprintf(buf,"%d", spec_n);
|
|
ocg->add_kword(ocg, 0, "SPECTRAL_BANDS",buf, NULL);
|
|
sprintf(buf,"%f", spec_wl_short);
|
|
ocg->add_kword(ocg, 0, "SPECTRAL_START_NM",buf, NULL);
|
|
sprintf(buf,"%f", spec_wl_long);
|
|
ocg->add_kword(ocg, 0, "SPECTRAL_END_NM",buf, NULL);
|
|
|
|
/* Generate fields for spectral values */
|
|
for (i = 0; i < spec_n; i++) {
|
|
int nm;
|
|
|
|
/* Compute nearest integer wavelength */
|
|
nm = (int)(spec_wl_short + ((double)i/(spec_n-1.0))
|
|
* (spec_wl_long - spec_wl_short) + 0.5);
|
|
|
|
sprintf(buf,"SPEC_%03d",nm);
|
|
ocg->add_field(ocg, 0, buf, r_t);
|
|
}
|
|
}
|
|
|
|
if (depth == 1) {
|
|
ocg->add_field(ocg, 0, "GREY", r_t);
|
|
ocg->add_field(ocg, 0, "STDEV_GREY", r_t);
|
|
} else if (depth == 3) {
|
|
ocg->add_field(ocg, 0, "RGB_R", r_t);
|
|
ocg->add_field(ocg, 0, "RGB_G", r_t);
|
|
ocg->add_field(ocg, 0, "RGB_B", r_t);
|
|
ocg->add_field(ocg, 0, "STDEV_R", r_t);
|
|
ocg->add_field(ocg, 0, "STDEV_G", r_t);
|
|
ocg->add_field(ocg, 0, "STDEV_B", r_t);
|
|
} else if (depth == 4) {
|
|
ocg->add_field(ocg, 0, "CMYK_C", r_t);
|
|
ocg->add_field(ocg, 0, "CMYK_M", r_t);
|
|
ocg->add_field(ocg, 0, "CMYK_Y", r_t);
|
|
ocg->add_field(ocg, 0, "CMYK_K", r_t);
|
|
ocg->add_field(ocg, 0, "STDEV_C", r_t);
|
|
ocg->add_field(ocg, 0, "STDEV_M", r_t);
|
|
ocg->add_field(ocg, 0, "STDEV_Y", r_t);
|
|
ocg->add_field(ocg, 0, "STDEV_K", r_t);
|
|
}
|
|
nsetel += 2 * depth;
|
|
|
|
if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
|
|
error("Malloc failed!");
|
|
|
|
if ((idhash = (unsigned int *)malloc(sizeof(unsigned int) * npat)) == NULL)
|
|
error("Malloc failed!");
|
|
|
|
/* Setup hash list of reference labels to speed comparisons */
|
|
for (j = 0; j < npat; j++) {
|
|
char id[100]; /* Reference patch id */
|
|
|
|
/* Normalise reference labels */
|
|
fix_it8(id, ((char *)icg->t[0].fdata[j][sx])); /* Copy and fix */
|
|
|
|
idhash[j] = shash(id);
|
|
}
|
|
|
|
/* Initialise, ready to read out all the values */
|
|
for (i = sr->reset(sr); i > 0; i--) {
|
|
char tod[100]; /* Temp output patch id */
|
|
char od[100]; /* Output patch id */
|
|
unsigned int odhash; /* Chart id hashes */
|
|
double P[4]; /* Robust/true mean values */
|
|
double sdP[4]; /* Standard deviation */
|
|
int pixcnt; /* Pixel count */
|
|
|
|
if (tmean)
|
|
sr->read(sr, tod, NULL, P, sdP, &pixcnt);
|
|
else
|
|
sr->read(sr, tod, P, NULL, sdP, &pixcnt);
|
|
|
|
fix_it8(od, tod);
|
|
|
|
odhash = shash(od);
|
|
|
|
if (pixcnt == 0)
|
|
pnotscan++;
|
|
|
|
/* Search for matching id in reference */
|
|
for (j = 0; j < npat; j++) {
|
|
char id[100]; /* Reference patch id */
|
|
|
|
if (odhash != idhash[j]) { /* Fast reject */
|
|
continue;
|
|
}
|
|
|
|
/* Normalise reference labels */
|
|
fix_it8(id, ((char *)icg->t[0].fdata[j][sx])); /* Copy and fix */
|
|
|
|
if (strcmp(id, od) == 0) {
|
|
int k = 0, m;
|
|
double XYZ[3];
|
|
|
|
setel[k++].c = id;
|
|
|
|
XYZ[0] = *((double *)icg->t[0].fdata[j][Xx]);
|
|
XYZ[1] = *((double *)icg->t[0].fdata[j][Yx]);
|
|
XYZ[2] = *((double *)icg->t[0].fdata[j][Zx]);
|
|
if (isLab) {
|
|
icmLab2XYZ(&icmD50, XYZ, XYZ);
|
|
XYZ[0] *= 100.0;
|
|
XYZ[1] *= 100.0;
|
|
XYZ[2] *= 100.0;
|
|
}
|
|
|
|
setel[k++].d = XYZ[0];
|
|
setel[k++].d = XYZ[1];
|
|
setel[k++].d = XYZ[2];
|
|
|
|
if (spec_n > 0) {
|
|
for (m = 0; m < spec_n; m++) {
|
|
setel[k++].d = *((double *)icg->t[0].fdata[j][spi[m]]);
|
|
}
|
|
}
|
|
|
|
for (m = 0; m < depth; m++)
|
|
setel[k++].d = P[m] * 100.0/255.0;
|
|
for (m = 0; m < depth; m++)
|
|
setel[k++].d = sdP[m] * 100.0/255.0;
|
|
|
|
ocg->add_setarr(ocg, 0, setel);
|
|
|
|
break;
|
|
}
|
|
|
|
if (j >= npat && verb >= 1)
|
|
printf("Warning: Couldn't match field '%s'\n",od);
|
|
}
|
|
}
|
|
|
|
if (verb)
|
|
printf("Writing output values to file '%s'\n",datout_name);
|
|
|
|
if (ocg->write_name(ocg, datout_name))
|
|
error("Output file '%s' write error : %s",datout_name, ocg->e.m);
|
|
|
|
free(idhash);
|
|
free(setel);
|
|
|
|
ocg->del(ocg); /* Clean up */
|
|
icg->del(icg); /* Clean up */
|
|
|
|
}
|
|
}
|
|
|
|
if (pnotscan > 0)
|
|
warning("A total of %d patches had no value set!",pnotscan);
|
|
|
|
/* Clean up */
|
|
sr->free(sr);
|
|
|
|
TIFFClose(rh);
|
|
|
|
if (flags & SI_SHOW_FLAGS)
|
|
TIFFClose(wh);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Fix IT8 chart labels */
|
|
void fix_it8(char *o, char *i) {
|
|
/* Some hacks for it8 */
|
|
if (strcmp(i,"Dmin")==0) {
|
|
strcpy(o,"GS00");
|
|
return;
|
|
}
|
|
if (strcmp(i,"Dmax")==0) {
|
|
strcpy(o,"GS23");
|
|
return;
|
|
}
|
|
|
|
while (!isdigit(*i) && *i != '\000') /* Skip non-numbers */
|
|
*o++ = *i++;
|
|
if (i[0] != '\000' && i[1] == '\000') /* Single last digit */
|
|
*o++ = '0'; /* Add leading zero */
|
|
strcpy(o, i); /* Copy remainder */
|
|
}
|
|
|
|
/********************************************************************************/
|
|
|
|
|