Files
argyllcms/imdi/greytiff.c
2026-08-20 20:28:40 +01:00

583 lines
15 KiB
C

/*
* Convert a TIFF to monochrome in a colorimetrically correct way.
*
* Author: Graeme W. Gill
* Date: 01/8/29
* Version: 1.00
*
* Copyright 2000, 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.
*/
/*
* Thanks to Neil Okamoto for the 16 bit TIFF mods.
*/
/* TTBD:
*
*/
/*
This program is a framework that exercises the
IMDI code. It can also do the conversion using the
floating point code in ICCLIB as a reference.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <string.h>
#include <math.h>
#include "copyright.h"
#include "aconfig.h"
#include "tiffio.h"
#include "icc.h"
#include "numlib.h"
#include "conv.h"
#include "xicc.h"
#include "imdi.h"
#undef DO_CHECK /* Do floating point check */
void usage(void) {
fprintf(stderr,"Convert a TIFF file to monochrome using an ICC device profile, V%s\n",ARGYLL_VERSION_STR);
fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
fprintf(stderr,"usage: greytiff [-v level] profile.icm infile.tif outfile.tif\n");
fprintf(stderr," -v Verbose\n");
fprintf(stderr," -p Use slow precise correction\n");
fprintf(stderr," -j Use CIECAM02\n");
exit(1);
}
/* Convert an ICC colorspace to the corresponding TIFF Photometric tag */
/* return 0xffff if not possible. */
int
ColorSpaceSignature2TiffPhotometric(
icColorSpaceSignature cspace
) {
switch(cspace) {
case icSigGrayData:
return PHOTOMETRIC_MINISBLACK;
case icSigRgbData:
return PHOTOMETRIC_RGB;
case icSigCmykData:
return PHOTOMETRIC_SEPARATED;
case icSigYCbCrData:
return PHOTOMETRIC_YCBCR;
case icSigLabData:
return PHOTOMETRIC_CIELAB;
case icSigXYZData:
case icSigLuvData:
case icSigYxyData:
case icSigHsvData:
case icSigHlsData:
case icSigCmyData:
case icSig2colorData:
case icSig3colorData:
case icSig4colorData:
case icSig5colorData:
case icSigMch5Data:
case icSig6colorData:
case icSigMch6Data:
case icSig7colorData:
case icSigMch7Data:
case icSig8colorData:
case icSigMch8Data:
case icSig9colorData:
case icSig10colorData:
case icSig11colorData:
case icSig12colorData:
case icSig13colorData:
case icSig14colorData:
case icSig15colorData:
default:
return 0xffff;
}
return 0xffff;
}
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 "CMYK";
case PHOTOMETRIC_YCBCR:
return "YCbCr";
case PHOTOMETRIC_CIELAB:
return "CIELab";
case PHOTOMETRIC_LOGL:
return "CIELog2L";
case PHOTOMETRIC_LOGLUV:
return "CIELog2Luv";
}
sprintf(buf,"Unknown Tag %d",pmtc);
return buf;
}
/* Callbacks used to initialise imdi */
/* Context for imdi setup callbacks */
typedef struct {
int id, od;
icxLuBase *flu; /* Device -> Jab/Lab */
icxLuBase *blu; /* Jab/Lab -> Device */
} sucntx;
/* Input curve function */
void input_curve(
void *cntx,
double *out_vals,
double *in_vals
) {
sucntx *rx = (sucntx *)cntx;
int e;
for (e = 0; e < rx->id; e++)
out_vals[e] = in_vals[e];
}
/* Multi-dim table function */
void md_table(
void *cntx,
double *out_vals,
double *in_vals
) {
sucntx *rx = (sucntx *)cntx;
double Lab[3];
rx->flu->lookup(rx->flu, Lab, in_vals);
Lab[1] = Lab[2] = 0.0;
rx->blu->lookup(rx->blu, out_vals, Lab);
}
/* Output curve function */
void output_curve(
void *cntx,
double *out_vals,
double *in_vals
) {
sucntx *rx = (sucntx *)cntx;
int e;
for (e = 0; e < rx->od; e++)
out_vals[e] = in_vals[e];
}
int
main(int argc, char *argv[]) {
int fa,nfa; /* argument we're looking at */
static char prof_name[MAXNAMEL+1];
static char in_name[MAXNAMEL+1];
static char out_name[MAXNAMEL+1];
icmFile *p_fp;
icmErr err = { 0, { '\000'} };
icc *icco;
xicc *xicco;
int verb = 0;
int slow = 0;
int rv = 0;
icColorSpaceSignature pcsor = icSigLabData;
TIFF *rh = NULL, *wh = NULL;
int x, y, width, height; /* Size of image */
uint16 samplesperpixel, bitspersample;
uint16 pconfig, photometric, pmtc;
uint16 resunits;
float resx, resy;
tdata_t *inbuf, *outbuf, *checkbuf;
icColorSpaceSignature ins; /* Type of input spaces */
int inn; /* Number of device components */
/* IMDI */
imdi *s = NULL;
sucntx su; /* Setup context */
unsigned char *inp[MAX_CHAN];
unsigned char *outp[MAX_CHAN];
#ifdef DO_CHECK
/* Error check */
int mxerr = 0;
double avgerr = 0.0;
double avgcount = 0.0;
#endif /* DO_CHECK */
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();
/* Slow, Precise */
else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') {
slow = 1;
}
/* Use CIECAM02 */
else if (argv[fa][1] == 'j' || argv[fa][1] == 'J') {
pcsor = icxSigJabData;
}
/* Verbosity */
else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') {
verb = 1;
}
else
usage();
} else
break;
}
if (fa >= argc || argv[fa][0] == '-') usage();
strncpy(prof_name,argv[fa++],MAXNAMEL); prof_name[MAXNAMEL] = '\000';
if (fa >= argc || argv[fa][0] == '-') usage();
strncpy(in_name,argv[fa++],MAXNAMEL); in_name[MAXNAMEL] = '\000';
if (fa >= argc || argv[fa][0] == '-') usage();
strncpy(out_name,argv[fa++],MAXNAMEL); out_name[MAXNAMEL] = '\000';
/* - - - - - - - - - - - - - - - - */
/* Open up the profile for reading */
if ((p_fp = new_icmFileStd_name(&err,prof_name,"r")) == NULL)
error ("Can't open file '%s' (0x%x, '%s')",prof_name,err.c,err.m);
if ((icco = new_icc(&err)) == NULL)
error ("Creation of ICC object failed (0x%x, '%s')",err.c,err.m);
/* Wrap with an expanded icc */
if ((xicco = new_xicc(icco)) == NULL)
error ("Creation of xicc failed");
if ((rv = icco->read(icco,p_fp,0)) != 0)
error ("%d, %s",rv,icco->e.m);
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) /* For sRGB etc. */
error("Profile isn't a device profile");
/* Get a expanded color conversion object */
if ((su.flu = xicco->get_luobj(xicco, ICX_CLIP_NEAREST, icmFwd, icRelativeColorimetric, pcsor, icmLuOrdNorm, NULL, NULL)) == NULL)
error ("%d, %s",xicco->e.c, xicco->e.m);
/* Get details of conversion (Arguments may be NULL if info not needed) */
su.flu->spaces(su.flu, &ins, &inn, NULL, NULL, NULL, NULL, NULL, NULL);
su.id = inn;
su.od = inn;
/* Get a bwd conversion object */
if ((su.blu = xicco->get_luobj(xicco, ICX_CLIP_NEAREST, icmBwd, icRelativeColorimetric, pcsor, icmLuOrdNorm, NULL, NULL)) == NULL)
error ("%d, %s",xicco->e.c, xicco->e.m);
/* - - - - - - - - - - - - - - - */
/* Open up input tiff file ready for reading */
/* Got arguments, so setup to process the file */
if ((rh = TIFFOpen(in_name, "r")) == NULL)
error("error opening read file '%s'",in_name);
TIFFGetField(rh, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetField(rh, TIFFTAG_IMAGELENGTH, &height);
TIFFGetField(rh, TIFFTAG_BITSPERSAMPLE, &bitspersample);
if (bitspersample != 8 && bitspersample != 16) {
error("TIFF Input file must be 8 or 16 bit/channel");
}
TIFFGetField(rh, TIFFTAG_PHOTOMETRIC, &photometric);
if ((pmtc = ColorSpaceSignature2TiffPhotometric(ins)) == 0xffff)
error("ICC input colorspace '%s' can't be handled by a TIFF file!",
icm2str(icmColorSpaceSig, ins));
if (pmtc != photometric)
error("ICC input colorspace '%s' doesn't match TIFF photometric '%s'!",
icm2str(icmColorSpaceSig, ins), Photometric2str(photometric));
TIFFGetField(rh, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel);
if (inn != samplesperpixel)
error ("TIFF Input file has %d input channels mismatched to colorspace '%s'",
samplesperpixel, icm2str(icmColorSpaceSig, ins));
TIFFGetField(rh, TIFFTAG_PLANARCONFIG, &pconfig);
if (pconfig != PLANARCONFIG_CONTIG)
error ("TIFF Input file must be planar");
TIFFGetField(rh, TIFFTAG_RESOLUTIONUNIT, &resunits);
TIFFGetField(rh, TIFFTAG_XRESOLUTION, &resx);
TIFFGetField(rh, TIFFTAG_YRESOLUTION, &resy);
/* - - - - - - - - - - - - - - - */
if ((wh = TIFFOpen(out_name, "w")) == NULL)
error("Can\'t create TIFF file '%s'!",out_name);
TIFFSetField(wh, TIFFTAG_IMAGEWIDTH, width);
TIFFSetField(wh, TIFFTAG_IMAGELENGTH, height);
TIFFSetField(wh, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
TIFFSetField(wh, TIFFTAG_SAMPLESPERPIXEL, inn);
TIFFSetField(wh, TIFFTAG_BITSPERSAMPLE, bitspersample);
TIFFSetField(wh, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
if ((pmtc = ColorSpaceSignature2TiffPhotometric(ins)) == 0xffff)
error("TIFF file can't handle output colorspace '%s'!",
icm2str(icmColorSpaceSig, ins));
TIFFSetField(wh, TIFFTAG_PHOTOMETRIC, pmtc);
TIFFSetField(wh, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
if (resunits) {
TIFFSetField(wh, TIFFTAG_RESOLUTIONUNIT, resunits);
TIFFSetField(wh, TIFFTAG_XRESOLUTION, resx);
TIFFSetField(wh, TIFFTAG_YRESOLUTION, resy);
}
TIFFSetField(wh, TIFFTAG_IMAGEDESCRIPTION, "Color corrected by Argyll");
/* - - - - - - - - - - - - - - - */
/* Setup the imdi */
if (!slow) {
s = new_imdi(
inn, /* Number of input dimensions */
inn, /* Number of output dimensions */
bitspersample == 8 ? pixint8 : pixint16,
/* Output pixel representation */
0x0, /* Treat every channel as unsigned */
NULL, /* No raster to callback channel mapping */
prec_min, /* Minimum of input and output precision */
bitspersample == 8 ? pixint8 : pixint16,
0x0, /* Treat every channel as unsigned */
NULL, /* No raster to callback channel mapping */
17, /* Desired table resolution. 33 is also a good number */
oopts_none, /* Desired per channel output options */
NULL, /* Output channel check values */
opts_none, /* Desired processing direction and stride support */
input_curve, /* Callback functions */
md_table,
output_curve,
(void *)&su /* Context to callbacks */
);
if (s == NULL)
error("new_imdi failed");
}
/* - - - - - - - - - - - - - - - */
/* Process colors to translate */
/* (Should fix this to process a group of lines at a time ?) */
inbuf = _TIFFmalloc(TIFFScanlineSize(rh));
outbuf = _TIFFmalloc(TIFFScanlineSize(wh));
checkbuf = _TIFFmalloc(TIFFScanlineSize(wh));
inp[0] = (unsigned char *)inbuf;
outp[0] = (unsigned char *)outbuf;
if (!slow) { /* Fast */
for (y = 0; y < height; y++) {
/* Read in the next line */
if (TIFFReadScanline(rh, inbuf, y, 0) < 0)
error ("Failed to read TIFF line %d",y);
/* Do fast conversion */
s->interp(s, (void **)outp, 0, (void **)inp, 0, width);
#ifdef DO_CHECK
/* Do floating point conversion */
for (x = 0; x < width; x++) {
int i;
double in[MAX_CHAN], out[MAX_CHAN];
double Lab[3];
if (bitspersample == 8)
for (i = 0; i < inn; i++)
in[i] = ((unsigned char *)inbuf)[x * inn + i]/255.0;
else
for (i = 0; i < inn; i++)
in[i] = ((unsigned short *)inbuf)[x * inn + i]/65535.0;
if ((rv = su.flu->lookup(su.flu, Lab, in)) > 1)
error ("%d, %s",icco->e.c,icco->e.m);
Lab[1] = Lab[2] = 0.0;
if ((rv = su.blu->lookup(su.blu, out, Lab)) > 1)
error ("%d, %s",icco->e.c,icco->e.m);
if (bitspersample == 8)
for (i = 0; i < inn; i++)
((unsigned char *)checkbuf)[x * inn + i] = (int)(out[i] * 255.0 + 0.5);
else
for (i = 0; i < inn; i++)
((unsigned short *)checkbuf)[x * inn + i] = (int)(out[i] * 65535.0 + 0.5);
}
/* Compute the errors */
for (x = 0; x < (width * inn); x++) {
int err;
if (bitspersample == 8)
err = ((unsigned char *)outbuf)[x] - ((unsigned char *)checkbuf)[x];
else
err = ((unsigned short *)outbuf)[x] - ((unsigned short *)checkbuf)[x];
if (err < 0)
err = -err;
if (err > mxerr)
mxerr = err;
avgerr += (double)err;
avgcount++;
}
#endif /* DO_CHECK */
if (TIFFWriteScanline(wh, outbuf, y, 0) < 0)
error ("Failed to write TIFF line %d",y);
}
} else { /* Slow but precise */
if (bitspersample == 8) {
for (y = 0; y < height; y++) {
/* Read in the next line */
if (TIFFReadScanline(rh, inbuf, y, 0) < 0)
error ("Failed to read TIFF line %d",y);
/* Do floating point conversion */
for (x = 0; x < width; x++) {
int i;
double in[MAX_CHAN], out[MAX_CHAN];
double Lab[3];
for (i = 0; i < inn; i++) {
in[i] = ((unsigned char *)inbuf)[x * inn + i]/255.0;
}
if ((rv = su.flu->lookup(su.flu, Lab, in)) > 1)
error ("%d, %s",icco->e.c,icco->e.m);
Lab[1] = Lab[2] = 0.0;
if ((rv = su.blu->lookup(su.blu, out, Lab)) > 1)
error ("%d, %s",icco->e.c,icco->e.m);
for (i = 0; i < inn; i++) {
((unsigned char *)outbuf)[x * inn + i] = (int)(out[i] * 255.0 + 0.5);
}
}
if (TIFFWriteScanline(wh, outbuf, y, 0) < 0)
error ("Failed to write TIFF line %d",y);
}
} else if (bitspersample == 16) {
for (y = 0; y < height; y++) {
/* Read in the next line */
if (TIFFReadScanline(rh, inbuf, y, 0) < 0)
error ("Failed to read TIFF line %d",y);
/* Do floating point conversion */
for (x = 0; x < width; x++) {
int i;
double in[MAX_CHAN], out[MAX_CHAN];
double Lab[3];
for (i = 0; i < inn; i++) {
in[i] = ((unsigned short *)inbuf)[x * inn + i]/65535.0;
}
if ((rv = su.flu->lookup(su.flu, Lab, in)) > 1)
error ("%d, %s",icco->e.c,icco->e.m);
Lab[1] = Lab[2] = 0.0;
if ((rv = su.blu->lookup(su.blu, out, Lab)) > 1)
error ("%d, %s",icco->e.c,icco->e.m);
for (i = 0; i < inn; i++) {
((unsigned short *)outbuf)[x * inn + i] = (int)(out[i] * 65535.0 + 0.5);
}
}
if (TIFFWriteScanline(wh, outbuf, y, 0) < 0)
error ("Failed to write TIFF line %d",y);
}
}
}
#ifdef DO_CHECK
printf("Worst error = %d bits, average error = %f bits\n", mxerr, avgerr/avgcount);
if (bitspersample == 8)
printf("Worst error = %f%%, average error = %f%%\n",
mxerr/2.55, avgerr/(2.55 * avgcount));
else
printf("Worst error = %f%%, average error = %f%%\n",
mxerr/655.35, avgerr/(655.35 * avgcount));
#endif /* DO_CHECK */
/* Done with lookup object */
if (s != NULL)
s->del(s);
su.flu->del(su.flu);
su.blu->del(su.blu);
xicco->del(xicco);
icco->del(icco);
p_fp->del(p_fp);
TIFFClose(rh); /* Close Input file */
TIFFClose(wh); /* Close Output file */
return 0;
}