singe/thirdparty/SDL2_image/external/libtiff/tools/thumbnail.c
2023-10-23 19:38:18 -05:00

761 lines
22 KiB
C

/*
* Copyright (c) 1994-1997 Sam Leffler
* Copyright (c) 1994-1997 Silicon Graphics, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
* that (i) the above copyright notices and this permission notice appear in
* all copies of the software and related documentation, and (ii) the names of
* Sam Leffler and Silicon Graphics may not be used in any advertising or
* publicity relating to the software without the specific, prior written
* permission of Sam Leffler and Silicon Graphics.
*
* THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
*
* IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
* ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THIS SOFTWARE.
*/
#include "libport.h"
#include "tif_config.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "tiffio.h"
#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif
#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif
#define streq(a, b) (strcmp(a, b) == 0)
#ifndef TIFFhowmany8
#define TIFFhowmany8(x) \
(((x)&0x07) ? ((uint32_t)(x) >> 3) + 1 : (uint32_t)(x) >> 3)
#endif
typedef enum
{
EXP50,
EXP60,
EXP70,
EXP80,
EXP90,
EXP,
LINEAR
} Contrast;
static uint32_t tnw = 216; /* thumbnail width */
static uint32_t tnh = 274; /* thumbnail height */
static Contrast contrast = LINEAR; /* current contrast */
static uint8_t *thumbnail;
static int cpIFD(TIFF *, TIFF *);
static int generateThumbnail(TIFF *, TIFF *);
static void initScale();
static void usage(int code);
#if !HAVE_DECL_OPTARG
extern char *optarg;
extern int optind;
#endif
int main(int argc, char *argv[])
{
TIFF *in;
TIFF *out;
int c;
while ((c = getopt(argc, argv, "w:h:c:")) != -1)
{
switch (c)
{
case 'w':
tnw = strtoul(optarg, NULL, 0);
break;
case 'h':
tnh = strtoul(optarg, NULL, 0);
break;
case 'c':
contrast = streq(optarg, "exp50") ? EXP50
: streq(optarg, "exp60") ? EXP60
: streq(optarg, "exp70") ? EXP70
: streq(optarg, "exp80") ? EXP80
: streq(optarg, "exp90") ? EXP90
: streq(optarg, "exp") ? EXP
: streq(optarg, "linear") ? LINEAR
: EXP;
break;
default:
usage(EXIT_FAILURE);
}
}
if (argc - optind != 2)
usage(EXIT_FAILURE);
out = TIFFOpen(argv[optind + 1], "w");
if (out == NULL)
return 2;
in = TIFFOpen(argv[optind], "r");
if (in == NULL)
return 2;
thumbnail = (uint8_t *)_TIFFmalloc(tnw * tnh);
if (!thumbnail)
{
TIFFError(TIFFFileName(in),
"Can't allocate space for thumbnail buffer.");
return EXIT_FAILURE;
}
if (in != NULL)
{
initScale();
do
{
if (!generateThumbnail(in, out))
goto bad;
if (!cpIFD(in, out) || !TIFFWriteDirectory(out))
goto bad;
} while (TIFFReadDirectory(in));
(void)TIFFClose(in);
}
(void)TIFFClose(out);
return EXIT_SUCCESS;
bad:
(void)TIFFClose(out);
return EXIT_FAILURE;
}
#define CopyField(tag, v) \
if (TIFFGetField(in, tag, &v)) \
TIFFSetField(out, tag, v)
#define CopyField2(tag, v1, v2) \
if (TIFFGetField(in, tag, &v1, &v2)) \
TIFFSetField(out, tag, v1, v2)
#define CopyField3(tag, v1, v2, v3) \
if (TIFFGetField(in, tag, &v1, &v2, &v3)) \
TIFFSetField(out, tag, v1, v2, v3)
#define CopyField4(tag, v1, v2, v3, v4) \
if (TIFFGetField(in, tag, &v1, &v2, &v3, &v4)) \
TIFFSetField(out, tag, v1, v2, v3, v4)
static void cpTag(TIFF *in, TIFF *out, uint16_t tag, uint16_t count,
TIFFDataType type)
{
switch (type)
{
case TIFF_SHORT:
if (count == 1)
{
uint16_t shortv;
CopyField(tag, shortv);
}
else if (count == 2)
{
uint16_t shortv1, shortv2;
CopyField2(tag, shortv1, shortv2);
}
else if (count == 4)
{
uint16_t *tr, *tg, *tb, *ta;
CopyField4(tag, tr, tg, tb, ta);
}
else if (count == (uint16_t)-1)
{
uint16_t shortv1;
uint16_t *shortav;
CopyField2(tag, shortv1, shortav);
}
break;
case TIFF_LONG:
{
uint32_t longv;
CopyField(tag, longv);
}
break;
case TIFF_LONG8:
{
uint64_t longv8;
CopyField(tag, longv8);
}
break;
case TIFF_SLONG8:
{
int64_t longv8;
CopyField(tag, longv8);
}
break;
case TIFF_RATIONAL:
if (count == 1)
{
float floatv;
CopyField(tag, floatv);
}
else if (count == (uint16_t)-1)
{
float *floatav;
CopyField(tag, floatav);
}
break;
case TIFF_ASCII:
{
char *stringv;
CopyField(tag, stringv);
}
break;
case TIFF_DOUBLE:
if (count == 1)
{
double doublev;
CopyField(tag, doublev);
}
else if (count == (uint16_t)-1)
{
double *doubleav;
CopyField(tag, doubleav);
}
break;
case TIFF_IFD8:
{
toff_t ifd8;
CopyField(tag, ifd8);
}
break;
default:
TIFFError(TIFFFileName(in),
"Data type %d is not supported, tag %d skipped.", tag,
type);
}
}
#undef CopyField4
#undef CopyField3
#undef CopyField2
#undef CopyField
static const struct cpTag
{
uint16_t tag;
uint16_t count;
TIFFDataType type;
} tags[] = {
{TIFFTAG_IMAGEWIDTH, 1, TIFF_LONG},
{TIFFTAG_IMAGELENGTH, 1, TIFF_LONG},
{TIFFTAG_BITSPERSAMPLE, 1, TIFF_SHORT},
{TIFFTAG_COMPRESSION, 1, TIFF_SHORT},
{TIFFTAG_FILLORDER, 1, TIFF_SHORT},
{TIFFTAG_SAMPLESPERPIXEL, 1, TIFF_SHORT},
{TIFFTAG_ROWSPERSTRIP, 1, TIFF_LONG},
{TIFFTAG_PLANARCONFIG, 1, TIFF_SHORT},
{TIFFTAG_GROUP3OPTIONS, 1, TIFF_LONG},
{TIFFTAG_SUBFILETYPE, 1, TIFF_LONG},
{TIFFTAG_PHOTOMETRIC, 1, TIFF_SHORT},
{TIFFTAG_THRESHHOLDING, 1, TIFF_SHORT},
{TIFFTAG_DOCUMENTNAME, 1, TIFF_ASCII},
{TIFFTAG_IMAGEDESCRIPTION, 1, TIFF_ASCII},
{TIFFTAG_MAKE, 1, TIFF_ASCII},
{TIFFTAG_MODEL, 1, TIFF_ASCII},
{TIFFTAG_ORIENTATION, 1, TIFF_SHORT},
{TIFFTAG_MINSAMPLEVALUE, 1, TIFF_SHORT},
{TIFFTAG_MAXSAMPLEVALUE, 1, TIFF_SHORT},
{TIFFTAG_XRESOLUTION, 1, TIFF_RATIONAL},
{TIFFTAG_YRESOLUTION, 1, TIFF_RATIONAL},
{TIFFTAG_PAGENAME, 1, TIFF_ASCII},
{TIFFTAG_XPOSITION, 1, TIFF_RATIONAL},
{TIFFTAG_YPOSITION, 1, TIFF_RATIONAL},
{TIFFTAG_GROUP4OPTIONS, 1, TIFF_LONG},
{TIFFTAG_RESOLUTIONUNIT, 1, TIFF_SHORT},
{TIFFTAG_PAGENUMBER, 2, TIFF_SHORT},
{TIFFTAG_SOFTWARE, 1, TIFF_ASCII},
{TIFFTAG_DATETIME, 1, TIFF_ASCII},
{TIFFTAG_ARTIST, 1, TIFF_ASCII},
{TIFFTAG_HOSTCOMPUTER, 1, TIFF_ASCII},
{TIFFTAG_WHITEPOINT, 2, TIFF_RATIONAL},
{TIFFTAG_PRIMARYCHROMATICITIES, (uint16_t)-1, TIFF_RATIONAL},
{TIFFTAG_HALFTONEHINTS, 2, TIFF_SHORT},
{TIFFTAG_BADFAXLINES, 1, TIFF_LONG},
{TIFFTAG_CLEANFAXDATA, 1, TIFF_SHORT},
{TIFFTAG_CONSECUTIVEBADFAXLINES, 1, TIFF_LONG},
{TIFFTAG_INKSET, 1, TIFF_SHORT},
/*{ TIFFTAG_INKNAMES, 1, TIFF_ASCII },*/ /* Needs much
more
complicated
logic. See
tiffcp */
{TIFFTAG_DOTRANGE, 2, TIFF_SHORT},
{TIFFTAG_TARGETPRINTER, 1, TIFF_ASCII},
{TIFFTAG_SAMPLEFORMAT, 1, TIFF_SHORT},
{TIFFTAG_YCBCRCOEFFICIENTS, (uint16_t)-1, TIFF_RATIONAL},
{TIFFTAG_YCBCRSUBSAMPLING, 2, TIFF_SHORT},
{TIFFTAG_YCBCRPOSITIONING, 1, TIFF_SHORT},
{TIFFTAG_REFERENCEBLACKWHITE, (uint16_t)-1, TIFF_RATIONAL},
{TIFFTAG_EXTRASAMPLES, (uint16_t)-1, TIFF_SHORT},
};
#define NTAGS (sizeof(tags) / sizeof(tags[0]))
static void cpTags(TIFF *in, TIFF *out)
{
const struct cpTag *p;
for (p = tags; p < &tags[NTAGS]; p++)
{
/* Horrible: but TIFFGetField() expects 2 arguments to be passed */
/* if we request a tag that is defined in a codec, but that codec */
/* isn't used */
if (p->tag == TIFFTAG_GROUP3OPTIONS)
{
uint16_t compression;
if (!TIFFGetField(in, TIFFTAG_COMPRESSION, &compression) ||
compression != COMPRESSION_CCITTFAX3)
continue;
}
if (p->tag == TIFFTAG_GROUP4OPTIONS)
{
uint16_t compression;
if (!TIFFGetField(in, TIFFTAG_COMPRESSION, &compression) ||
compression != COMPRESSION_CCITTFAX4)
continue;
}
cpTag(in, out, p->tag, p->count, p->type);
}
}
#undef NTAGS
static int cpStrips(TIFF *in, TIFF *out)
{
tsize_t bufsize = TIFFStripSize(in);
unsigned char *buf = (unsigned char *)_TIFFmalloc(bufsize);
if (buf)
{
tstrip_t s, ns = TIFFNumberOfStrips(in);
uint64_t *bytecounts;
TIFFGetField(in, TIFFTAG_STRIPBYTECOUNTS, &bytecounts);
for (s = 0; s < ns; s++)
{
if (bytecounts[s] > (uint64_t)bufsize)
{
buf =
(unsigned char *)_TIFFrealloc(buf, (tmsize_t)bytecounts[s]);
if (!buf)
goto bad;
bufsize = (tmsize_t)bytecounts[s];
}
if (TIFFReadRawStrip(in, s, buf, (tmsize_t)bytecounts[s]) < 0 ||
TIFFWriteRawStrip(out, s, buf, (tmsize_t)bytecounts[s]) < 0)
{
_TIFFfree(buf);
return 0;
}
}
_TIFFfree(buf);
return 1;
}
bad:
TIFFError(TIFFFileName(in), "Can't allocate space for strip buffer.");
return 0;
}
static int cpTiles(TIFF *in, TIFF *out)
{
tsize_t bufsize = TIFFTileSize(in);
unsigned char *buf = (unsigned char *)_TIFFmalloc(bufsize);
if (buf)
{
ttile_t t, nt = TIFFNumberOfTiles(in);
uint64_t *bytecounts;
TIFFGetField(in, TIFFTAG_TILEBYTECOUNTS, &bytecounts);
for (t = 0; t < nt; t++)
{
if (bytecounts[t] > (uint64_t)bufsize)
{
buf =
(unsigned char *)_TIFFrealloc(buf, (tmsize_t)bytecounts[t]);
if (!buf)
goto bad;
bufsize = (tmsize_t)bytecounts[t];
}
if (TIFFReadRawTile(in, t, buf, (tmsize_t)bytecounts[t]) < 0 ||
TIFFWriteRawTile(out, t, buf, (tmsize_t)bytecounts[t]) < 0)
{
_TIFFfree(buf);
return 0;
}
}
_TIFFfree(buf);
return 1;
}
bad:
TIFFError(TIFFFileName(in), "Can't allocate space for tile buffer.");
return (0);
}
static int cpIFD(TIFF *in, TIFF *out)
{
cpTags(in, out);
if (TIFFIsTiled(in))
{
if (!cpTiles(in, out))
return (0);
}
else
{
if (!cpStrips(in, out))
return (0);
}
return (1);
}
static uint16_t photometric; /* current photometric of raster */
static uint16_t filterWidth; /* filter width in pixels */
static uint32_t stepSrcWidth; /* src image stepping width */
static uint32_t stepDstWidth; /* dest stepping width */
static uint8_t *src0; /* horizontal bit stepping (start) */
static uint8_t *src1; /* horizontal bit stepping (middle) */
static uint8_t *src2; /* horizontal bit stepping (end) */
static uint32_t *rowoff; /* row offset for stepping */
static uint8_t cmap[256]; /* colormap indexes */
static uint8_t bits[256]; /* count of bits set */
static void setupBitsTables()
{
int i;
for (i = 0; i < 256; i++)
{
int n = 0;
if (i & 0x01)
n++;
if (i & 0x02)
n++;
if (i & 0x04)
n++;
if (i & 0x08)
n++;
if (i & 0x10)
n++;
if (i & 0x20)
n++;
if (i & 0x40)
n++;
if (i & 0x80)
n++;
bits[i] = n;
}
}
static int clamp(float v, int low, int high)
{
return (v < low ? low : v > high ? high : (int)v);
}
#ifndef M_E
#define M_E 2.7182818284590452354
#endif
static void expFill(float pct[], uint32_t p, uint32_t n)
{
uint32_t i;
uint32_t c = (p * n) / 100;
for (i = 1; i < c; i++)
pct[i] = (float)(1 - exp(i / ((double)(n - 1))) / M_E);
for (; i < n; i++)
pct[i] = 0.;
}
static void setupCmap()
{
float pct[256]; /* known to be large enough */
uint32_t i;
pct[0] = 1; /* force white */
switch (contrast)
{
case EXP50:
expFill(pct, 50, 256);
break;
case EXP60:
expFill(pct, 60, 256);
break;
case EXP70:
expFill(pct, 70, 256);
break;
case EXP80:
expFill(pct, 80, 256);
break;
case EXP90:
expFill(pct, 90, 256);
break;
case EXP:
expFill(pct, 100, 256);
break;
case LINEAR:
for (i = 1; i < 256; i++)
pct[i] = 1 - ((float)i) / (256 - 1);
break;
}
switch (photometric)
{
case PHOTOMETRIC_MINISWHITE:
for (i = 0; i < 256; i++)
cmap[i] = clamp(255 * pct[(256 - 1) - i], 0, 255);
break;
case PHOTOMETRIC_MINISBLACK:
for (i = 0; i < 256; i++)
cmap[i] = clamp(255 * pct[i], 0, 255);
break;
}
}
static void initScale()
{
src0 = (uint8_t *)_TIFFmalloc(sizeof(uint8_t) * tnw);
src1 = (uint8_t *)_TIFFmalloc(sizeof(uint8_t) * tnw);
src2 = (uint8_t *)_TIFFmalloc(sizeof(uint8_t) * tnw);
rowoff = (uint32_t *)_TIFFmalloc(sizeof(uint32_t) * tnw);
filterWidth = 0;
stepDstWidth = stepSrcWidth = 0;
setupBitsTables();
}
/*
* Calculate the horizontal accumulation parameteres
* according to the widths of the src and dst images.
*/
static void setupStepTables(uint32_t sw)
{
if (stepSrcWidth != sw || stepDstWidth != tnw)
{
int step = sw;
int limit = tnw;
int err = 0;
uint32_t sx = 0;
uint32_t x;
int fw;
uint8_t b;
for (x = 0; x < tnw; x++)
{
uint32_t sx0 = sx;
err += step;
while (err >= limit)
{
err -= limit;
sx++;
}
rowoff[x] = sx0 >> 3;
fw = sx - sx0; /* width */
b = (fw < 8) ? 0xff << (8 - fw) : 0xff;
src0[x] = b >> (sx0 & 7);
fw -= 8 - (sx0 & 7);
if (fw < 0)
fw = 0;
src1[x] = fw >> 3;
fw -= (fw >> 3) << 3;
src2[x] = 0xff << (8 - fw);
}
stepSrcWidth = sw;
stepDstWidth = tnw;
}
}
static void setrow(uint8_t *row, uint32_t nrows, const uint8_t *rows[])
{
uint32_t x;
uint32_t area = nrows * filterWidth;
for (x = 0; x < tnw; x++)
{
uint32_t mask0 = src0[x];
uint32_t fw = src1[x];
uint32_t mask1 = src1[x];
uint32_t off = rowoff[x];
uint32_t acc = 0;
uint32_t y, i;
for (y = 0; y < nrows; y++)
{
const uint8_t *src = rows[y] + off;
acc += bits[*src++ & mask0];
switch (fw)
{
default:
for (i = fw; i > 8; i--)
acc += bits[*src++];
/* fall through... */
case 8:
acc += bits[*src++]; /* fall through */
case 7:
acc += bits[*src++]; /* fall through */
case 6:
acc += bits[*src++]; /* fall through */
case 5:
acc += bits[*src++]; /* fall through */
case 4:
acc += bits[*src++]; /* fall through */
case 3:
acc += bits[*src++]; /* fall through */
case 2:
acc += bits[*src++]; /* fall through */
case 1:
acc += bits[*src++]; /* fall through */
case 0:
break;
}
acc += bits[*src & mask1];
}
*row++ = cmap[(255 * acc) / area];
}
}
/*
* Install the specified image. The
* image is resized to fit the display page using
* a box filter. The resultant pixels are mapped
* with a user-selectable contrast curve.
*/
static void setImage1(const uint8_t *br, uint32_t rw, uint32_t rh)
{
int step = rh;
int limit = tnh;
int err = 0;
int bpr = TIFFhowmany8(rw);
int sy = 0;
uint8_t *row = thumbnail;
uint32_t dy;
for (dy = 0; dy < tnh; dy++)
{
const uint8_t *rows[256];
uint32_t nrows = 1;
fprintf(stderr, "bpr=%d, sy=%d, bpr*sy=%d\n", bpr, sy, bpr * sy);
rows[0] = br + bpr * sy;
err += step;
while (err >= limit)
{
err -= limit;
sy++;
if (err >= limit)
{
/* We should perhaps error loudly, but I can't make sense of
* that */
/* code... */
if (nrows == 256)
break;
rows[nrows++] = br + bpr * sy;
}
}
setrow(row, nrows, rows);
row += tnw;
}
}
static void setImage(const uint8_t *br, uint32_t rw, uint32_t rh)
{
filterWidth = (uint16_t)ceil((double)rw / (double)tnw);
setupStepTables(rw);
setImage1(br, rw, rh);
}
static int generateThumbnail(TIFF *in, TIFF *out)
{
unsigned char *raster;
unsigned char *rp;
uint32_t sw, sh, rps;
uint16_t bps, spp;
tsize_t rowsize, rastersize;
tstrip_t s, ns = TIFFNumberOfStrips(in);
toff_t diroff[1];
TIFFGetField(in, TIFFTAG_IMAGEWIDTH, &sw);
TIFFGetField(in, TIFFTAG_IMAGELENGTH, &sh);
TIFFGetFieldDefaulted(in, TIFFTAG_BITSPERSAMPLE, &bps);
TIFFGetFieldDefaulted(in, TIFFTAG_SAMPLESPERPIXEL, &spp);
TIFFGetFieldDefaulted(in, TIFFTAG_ROWSPERSTRIP, &rps);
if (spp != 1 || bps != 1)
return 0;
rowsize = TIFFScanlineSize(in);
rastersize = sh * rowsize;
fprintf(stderr, "rastersize=%u\n", (unsigned int)rastersize);
/* +3 : add a few guard bytes since setrow() can read a bit */
/* outside buffer */
raster = (unsigned char *)_TIFFmalloc(rastersize + 3);
if (!raster)
{
TIFFError(TIFFFileName(in), "Can't allocate space for raster buffer.");
return 0;
}
raster[rastersize] = 0;
raster[rastersize + 1] = 0;
raster[rastersize + 2] = 0;
rp = raster;
for (s = 0; s < ns; s++)
{
(void)TIFFReadEncodedStrip(in, s, rp, -1);
rp += rps * rowsize;
}
TIFFGetField(in, TIFFTAG_PHOTOMETRIC, &photometric);
setupCmap();
setImage(raster, sw, sh);
_TIFFfree(raster);
TIFFSetField(out, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE);
TIFFSetField(out, TIFFTAG_IMAGEWIDTH, (uint32_t)tnw);
TIFFSetField(out, TIFFTAG_IMAGELENGTH, (uint32_t)tnh);
TIFFSetField(out, TIFFTAG_BITSPERSAMPLE, (uint16_t)8);
TIFFSetField(out, TIFFTAG_SAMPLESPERPIXEL, (uint16_t)1);
TIFFSetField(out, TIFFTAG_COMPRESSION, COMPRESSION_PACKBITS);
TIFFSetField(out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);
TIFFSetField(out, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
TIFFSetField(out, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
cpTag(in, out, TIFFTAG_SOFTWARE, (uint16_t)-1, TIFF_ASCII);
cpTag(in, out, TIFFTAG_IMAGEDESCRIPTION, (uint16_t)-1, TIFF_ASCII);
cpTag(in, out, TIFFTAG_DATETIME, (uint16_t)-1, TIFF_ASCII);
cpTag(in, out, TIFFTAG_HOSTCOMPUTER, (uint16_t)-1, TIFF_ASCII);
diroff[0] = 0UL;
TIFFSetField(out, TIFFTAG_SUBIFD, 1, diroff);
return (TIFFWriteEncodedStrip(out, 0, thumbnail, tnw * tnh) != -1 &&
TIFFWriteDirectory(out) != -1);
}
const char *usage_info[] = {
"Create a TIFF file with thumbnail images\n\n"
"usage: thumbnail [options] input.tif output.tif",
"where options are:",
" -h # specify thumbnail image height (default is 274)",
" -w # specify thumbnail image width (default is 216)",
"",
" -c linear use linear contrast curve",
" -c exp50 use 50% exponential contrast curve",
" -c exp60 use 60% exponential contrast curve",
" -c exp70 use 70% exponential contrast curve",
" -c exp80 use 80% exponential contrast curve",
" -c exp90 use 90% exponential contrast curve",
" -c exp use pure exponential contrast curve",
NULL};
static void usage(int code)
{
int i;
FILE *out = (code == EXIT_SUCCESS) ? stdout : stderr;
fprintf(out, "%s\n\n", TIFFGetVersion());
for (i = 0; usage_info[i] != NULL; i++)
fprintf(out, "%s\n", usage_info[i]);
exit(code);
}