/* $Id: thumbnail.c,v 1.9 2005-06-23 10:54:02 dron Exp $ */ /* * 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 "tif_config.h" #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include "tiffio.h" #ifndef HAVE_GETOPT extern int getopt(int, char**, char*); #endif #define streq(a,b) (strcmp(a,b) == 0) #ifndef TIFFhowmany8 # define TIFFhowmany8(x) (((x)&0x07)?((uint32)(x)>>3)+1:(uint32)(x)>>3) #endif typedef enum { EXP50, EXP60, EXP70, EXP80, EXP90, EXP, LINEAR } Contrast; static uint32 tnw = 216; /* thumbnail width */ static uint32 tnh = 274; /* thumbnail height */ static Contrast contrast = LINEAR; /* current contrast */ static uint8* thumbnail; static int cpIFD(TIFF*, TIFF*); static int generateThumbnail(TIFF*, TIFF*); static void initScale(); static void usage(void); extern char* optarg; extern int optind; 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(); } } if (argc-optind != 2) usage(); out = TIFFOpen(argv[optind+1], "w"); if (out == NULL) return 2; in = TIFFOpen(argv[optind], "r"); thumbnail = (uint8*) _TIFFmalloc(tnw * tnh); if (!thumbnail) { TIFFError(TIFFFileName(in), "Can't allocate space for thumbnail buffer."); return 1; } 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 0; bad: (void) TIFFClose(out); return 1; } #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 tag, uint16 count, TIFFDataType type) { switch (type) { case TIFF_SHORT: if (count == 1) { uint16 shortv; CopyField(tag, shortv); } else if (count == 2) { uint16 shortv1, shortv2; CopyField2(tag, shortv1, shortv2); } else if (count == 4) { uint16 *tr, *tg, *tb, *ta; CopyField4(tag, tr, tg, tb, ta); } else if (count == (uint16) -1) { uint16 shortv1; uint16* shortav; CopyField2(tag, shortv1, shortav); } break; case TIFF_LONG: { uint32 longv; CopyField(tag, longv); } break; case TIFF_RATIONAL: if (count == 1) { float floatv; CopyField(tag, floatv); } else if (count == (uint16) -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) -1) { double* doubleav; CopyField(tag, doubleav); } 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 struct cpTag { uint16 tag; uint16 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, 1, TIFF_RATIONAL }, { TIFFTAG_PRIMARYCHROMATICITIES, (uint16) -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 }, { TIFFTAG_DOTRANGE, 2, TIFF_SHORT }, { TIFFTAG_TARGETPRINTER, 1, TIFF_ASCII }, { TIFFTAG_SAMPLEFORMAT, 1, TIFF_SHORT }, { TIFFTAG_YCBCRCOEFFICIENTS, (uint16) -1,TIFF_RATIONAL }, { TIFFTAG_YCBCRSUBSAMPLING, 2, TIFF_SHORT }, { TIFFTAG_YCBCRPOSITIONING, 1, TIFF_SHORT }, { TIFFTAG_REFERENCEBLACKWHITE, (uint16) -1,TIFF_RATIONAL }, { TIFFTAG_EXTRASAMPLES, (uint16) -1, TIFF_SHORT }, }; #define NTAGS (sizeof (tags) / sizeof (tags[0])) static void cpTags(TIFF* in, TIFF* out) { struct cpTag *p; for (p = tags; p < &tags[NTAGS]; p++) 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); tsize_t *bytecounts; TIFFGetField(in, TIFFTAG_STRIPBYTECOUNTS, &bytecounts); for (s = 0; s < ns; s++) { if (bytecounts[s] > bufsize) { buf = (unsigned char *)_TIFFrealloc(buf, bytecounts[s]); if (!buf) goto bad; bufsize = bytecounts[s]; } if (TIFFReadRawStrip(in, s, buf, bytecounts[s]) < 0 || TIFFWriteRawStrip(out, s, buf, 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); tsize_t *bytecounts; TIFFGetField(in, TIFFTAG_TILEBYTECOUNTS, &bytecounts); for (t = 0; t < nt; t++) { if (bytecounts[t] > bufsize) { buf = (unsigned char *)_TIFFrealloc(buf, bytecounts[t]); if (!buf) goto bad; bufsize = bytecounts[t]; } if (TIFFReadRawTile(in, t, buf, bytecounts[t]) < 0 || TIFFWriteRawTile(out, t, buf, 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 photometric; /* current photometric of raster */ static uint16 filterWidth; /* filter width in pixels */ static uint32 stepSrcWidth; /* src image stepping width */ static uint32 stepDstWidth; /* dest stepping width */ static uint8* src0; /* horizontal bit stepping (start) */ static uint8* src1; /* horizontal bit stepping (middle) */ static uint8* src2; /* horizontal bit stepping (end) */ static uint32* rowoff; /* row offset for stepping */ static uint8 cmap[256]; /* colormap indexes */ static uint8 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 p, uint32 n) { uint32 i; uint32 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 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*) _TIFFmalloc(sizeof (uint8) * tnw); src1 = (uint8*) _TIFFmalloc(sizeof (uint8) * tnw); src2 = (uint8*) _TIFFmalloc(sizeof (uint8) * tnw); rowoff = (uint32*) _TIFFmalloc(sizeof (uint32) * 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 sw) { if (stepSrcWidth != sw || stepDstWidth != tnw) { int step = sw; int limit = tnw; int err = 0; uint32 sx = 0; uint32 x; int fw; uint8 b; for (x = 0; x < tnw; x++) { uint32 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* row, uint32 nrows, const uint8* rows[]) { uint32 x; uint32 area = nrows * filterWidth; for (x = 0; x < tnw; x++) { uint32 mask0 = src0[x]; uint32 fw = src1[x]; uint32 mask1 = src1[x]; uint32 off = rowoff[x]; uint32 acc = 0; uint32 y, i; for (y = 0; y < nrows; y++) { const uint8* src = rows[y] + off; acc += bits[*src++ & mask0]; switch (fw) { default: for (i = fw; i > 8; i--) acc += bits[*src++]; /* fall thru... */ case 8: acc += bits[*src++]; case 7: acc += bits[*src++]; case 6: acc += bits[*src++]; case 5: acc += bits[*src++]; case 4: acc += bits[*src++]; case 3: acc += bits[*src++]; case 2: acc += bits[*src++]; case 1: acc += bits[*src++]; 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* br, uint32 rw, uint32 rh) { int step = rh; int limit = tnh; int err = 0; int bpr = TIFFhowmany8(rw); int sy = 0; uint8* row = thumbnail; uint32 dy; for (dy = 0; dy < tnh; dy++) { const uint8* rows[256]; uint32 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) rows[nrows++] = br + bpr*sy; } setrow(row, nrows, rows); row += tnw; } } static void setImage(const uint8* br, uint32 rw, uint32 rh) { filterWidth = (uint16) 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 sw, sh, rps; uint16 bps, spp; tsize_t rowsize, rastersize; tstrip_t s, ns = TIFFNumberOfStrips(in); uint32 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); raster = (unsigned char*)_TIFFmalloc(rastersize); if (!raster) { TIFFError(TIFFFileName(in), "Can't allocate space for raster buffer."); return 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) tnw); TIFFSetField(out, TIFFTAG_IMAGELENGTH, (uint32) tnh); TIFFSetField(out, TIFFTAG_BITSPERSAMPLE, (uint16) 8); TIFFSetField(out, TIFFTAG_SAMPLESPERPIXEL, (uint16) 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) -1, TIFF_ASCII); cpTag(in, out, TIFFTAG_IMAGEDESCRIPTION, (uint16) -1, TIFF_ASCII); cpTag(in, out, TIFFTAG_DATETIME, (uint16) -1, TIFF_ASCII); cpTag(in, out, TIFFTAG_HOSTCOMPUTER, (uint16) -1, TIFF_ASCII); diroff[0] = 0; TIFFSetField(out, TIFFTAG_SUBIFD, 1, diroff); return (TIFFWriteEncodedStrip(out, 0, thumbnail, tnw*tnh) != -1 && TIFFWriteDirectory(out) != -1); } char* stuff[] = { "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(void) { char buf[BUFSIZ]; int i; setbuf(stderr, buf); fprintf(stderr, "%s\n\n", TIFFGetVersion()); for (i = 0; stuff[i] != NULL; i++) fprintf(stderr, "%s\n", stuff[i]); exit(-1); } /* vim: set ts=8 sts=8 sw=8 noet: */