3 * Copyright (c) 1994-1997 Sam Leffler
4 * Copyright (c) 1994-1997 Silicon Graphics, Inc.
6 * Permission to use, copy, modify, distribute, and sell this software and
7 * its documentation for any purpose is hereby granted without fee, provided
8 * that (i) the above copyright notices and this permission notice appear in
9 * all copies of the software and related documentation, and (ii) the names of
10 * Sam Leffler and Silicon Graphics may not be used in any advertising or
11 * publicity relating to the software without the specific, prior written
12 * permission of Sam Leffler and Silicon Graphics.
14 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
15 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
16 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
18 * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
19 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
20 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
21 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
22 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
26 #include "tif_config.h"
44 extern int getopt(int, char**, char*);
47 #define streq(a,b) (strcmp(a,b) == 0)
50 # define TIFFhowmany8(x) (((x)&0x07)?((uint32)(x)>>3)+1:(uint32)(x)>>3)
63 static uint32 tnw
= 216; /* thumbnail width */
64 static uint32 tnh
= 274; /* thumbnail height */
65 static Contrast contrast
= LINEAR
; /* current contrast */
66 static uint8
* thumbnail
;
68 static int cpIFD(TIFF
*, TIFF
*);
69 static int generateThumbnail(TIFF
*, TIFF
*);
70 static void initScale();
71 static void usage(void);
77 main(int argc
, char* argv
[])
83 while ((c
= getopt(argc
, argv
, "w:h:c:")) != -1) {
85 case 'w': tnw
= strtoul(optarg
, NULL
, 0); break;
86 case 'h': tnh
= strtoul(optarg
, NULL
, 0); break;
87 case 'c': contrast
= streq(optarg
, "exp50") ? EXP50
:
88 streq(optarg
, "exp60") ? EXP60
:
89 streq(optarg
, "exp70") ? EXP70
:
90 streq(optarg
, "exp80") ? EXP80
:
91 streq(optarg
, "exp90") ? EXP90
:
92 streq(optarg
, "exp") ? EXP
:
93 streq(optarg
, "linear")? LINEAR
:
102 out
= TIFFOpen(argv
[optind
+1], "w");
105 in
= TIFFOpen(argv
[optind
], "r");
109 thumbnail
= (uint8
*) _TIFFmalloc(tnw
* tnh
);
111 TIFFError(TIFFFileName(in
),
112 "Can't allocate space for thumbnail buffer.");
119 if (!generateThumbnail(in
, out
))
121 if (!cpIFD(in
, out
) || !TIFFWriteDirectory(out
))
123 } while (TIFFReadDirectory(in
));
124 (void) TIFFClose(in
);
126 (void) TIFFClose(out
);
129 (void) TIFFClose(out
);
133 #define CopyField(tag, v) \
134 if (TIFFGetField(in, tag, &v)) TIFFSetField(out, tag, v)
135 #define CopyField2(tag, v1, v2) \
136 if (TIFFGetField(in, tag, &v1, &v2)) TIFFSetField(out, tag, v1, v2)
137 #define CopyField3(tag, v1, v2, v3) \
138 if (TIFFGetField(in, tag, &v1, &v2, &v3)) TIFFSetField(out, tag, v1, v2, v3)
139 #define CopyField4(tag, v1, v2, v3, v4) \
140 if (TIFFGetField(in, tag, &v1, &v2, &v3, &v4)) TIFFSetField(out, tag, v1, v2, v3, v4)
143 cpTag(TIFF
* in
, TIFF
* out
, uint16 tag
, uint16 count
, TIFFDataType type
)
149 CopyField(tag
, shortv
);
150 } else if (count
== 2) {
151 uint16 shortv1
, shortv2
;
152 CopyField2(tag
, shortv1
, shortv2
);
153 } else if (count
== 4) {
154 uint16
*tr
, *tg
, *tb
, *ta
;
155 CopyField4(tag
, tr
, tg
, tb
, ta
);
156 } else if (count
== (uint16
) -1) {
159 CopyField2(tag
, shortv1
, shortav
);
164 CopyField(tag
, longv
);
169 CopyField(tag
, longv8
);
174 CopyField(tag
, longv8
);
180 CopyField(tag
, floatv
);
181 } else if (count
== (uint16
) -1) {
183 CopyField(tag
, floatav
);
188 CopyField(tag
, stringv
);
194 CopyField(tag
, doublev
);
195 } else if (count
== (uint16
) -1) {
197 CopyField(tag
, doubleav
);
202 CopyField(tag
, ifd8
);
205 TIFFError(TIFFFileName(in
),
206 "Data type %d is not supported, tag %d skipped.",
216 static struct cpTag
{
221 { TIFFTAG_IMAGEWIDTH
, 1, TIFF_LONG
},
222 { TIFFTAG_IMAGELENGTH
, 1, TIFF_LONG
},
223 { TIFFTAG_BITSPERSAMPLE
, 1, TIFF_SHORT
},
224 { TIFFTAG_COMPRESSION
, 1, TIFF_SHORT
},
225 { TIFFTAG_FILLORDER
, 1, TIFF_SHORT
},
226 { TIFFTAG_SAMPLESPERPIXEL
, 1, TIFF_SHORT
},
227 { TIFFTAG_ROWSPERSTRIP
, 1, TIFF_LONG
},
228 { TIFFTAG_PLANARCONFIG
, 1, TIFF_SHORT
},
229 { TIFFTAG_GROUP3OPTIONS
, 1, TIFF_LONG
},
230 { TIFFTAG_SUBFILETYPE
, 1, TIFF_LONG
},
231 { TIFFTAG_PHOTOMETRIC
, 1, TIFF_SHORT
},
232 { TIFFTAG_THRESHHOLDING
, 1, TIFF_SHORT
},
233 { TIFFTAG_DOCUMENTNAME
, 1, TIFF_ASCII
},
234 { TIFFTAG_IMAGEDESCRIPTION
, 1, TIFF_ASCII
},
235 { TIFFTAG_MAKE
, 1, TIFF_ASCII
},
236 { TIFFTAG_MODEL
, 1, TIFF_ASCII
},
237 { TIFFTAG_ORIENTATION
, 1, TIFF_SHORT
},
238 { TIFFTAG_MINSAMPLEVALUE
, 1, TIFF_SHORT
},
239 { TIFFTAG_MAXSAMPLEVALUE
, 1, TIFF_SHORT
},
240 { TIFFTAG_XRESOLUTION
, 1, TIFF_RATIONAL
},
241 { TIFFTAG_YRESOLUTION
, 1, TIFF_RATIONAL
},
242 { TIFFTAG_PAGENAME
, 1, TIFF_ASCII
},
243 { TIFFTAG_XPOSITION
, 1, TIFF_RATIONAL
},
244 { TIFFTAG_YPOSITION
, 1, TIFF_RATIONAL
},
245 { TIFFTAG_GROUP4OPTIONS
, 1, TIFF_LONG
},
246 { TIFFTAG_RESOLUTIONUNIT
, 1, TIFF_SHORT
},
247 { TIFFTAG_PAGENUMBER
, 2, TIFF_SHORT
},
248 { TIFFTAG_SOFTWARE
, 1, TIFF_ASCII
},
249 { TIFFTAG_DATETIME
, 1, TIFF_ASCII
},
250 { TIFFTAG_ARTIST
, 1, TIFF_ASCII
},
251 { TIFFTAG_HOSTCOMPUTER
, 1, TIFF_ASCII
},
252 { TIFFTAG_WHITEPOINT
, 2, TIFF_RATIONAL
},
253 { TIFFTAG_PRIMARYCHROMATICITIES
, (uint16
) -1,TIFF_RATIONAL
},
254 { TIFFTAG_HALFTONEHINTS
, 2, TIFF_SHORT
},
255 { TIFFTAG_BADFAXLINES
, 1, TIFF_LONG
},
256 { TIFFTAG_CLEANFAXDATA
, 1, TIFF_SHORT
},
257 { TIFFTAG_CONSECUTIVEBADFAXLINES
, 1, TIFF_LONG
},
258 { TIFFTAG_INKSET
, 1, TIFF_SHORT
},
259 { TIFFTAG_INKNAMES
, 1, TIFF_ASCII
},
260 { TIFFTAG_DOTRANGE
, 2, TIFF_SHORT
},
261 { TIFFTAG_TARGETPRINTER
, 1, TIFF_ASCII
},
262 { TIFFTAG_SAMPLEFORMAT
, 1, TIFF_SHORT
},
263 { TIFFTAG_YCBCRCOEFFICIENTS
, (uint16
) -1,TIFF_RATIONAL
},
264 { TIFFTAG_YCBCRSUBSAMPLING
, 2, TIFF_SHORT
},
265 { TIFFTAG_YCBCRPOSITIONING
, 1, TIFF_SHORT
},
266 { TIFFTAG_REFERENCEBLACKWHITE
, (uint16
) -1,TIFF_RATIONAL
},
267 { TIFFTAG_EXTRASAMPLES
, (uint16
) -1, TIFF_SHORT
},
269 #define NTAGS (sizeof (tags) / sizeof (tags[0]))
272 cpTags(TIFF
* in
, TIFF
* out
)
275 for (p
= tags
; p
< &tags
[NTAGS
]; p
++)
276 cpTag(in
, out
, p
->tag
, p
->count
, p
->type
);
281 cpStrips(TIFF
* in
, TIFF
* out
)
283 tsize_t bufsize
= TIFFStripSize(in
);
284 unsigned char *buf
= (unsigned char *)_TIFFmalloc(bufsize
);
287 tstrip_t s
, ns
= TIFFNumberOfStrips(in
);
290 TIFFGetField(in
, TIFFTAG_STRIPBYTECOUNTS
, &bytecounts
);
291 for (s
= 0; s
< ns
; s
++) {
292 if (bytecounts
[s
] > (uint64
) bufsize
) {
293 buf
= (unsigned char *)_TIFFrealloc(buf
, (tmsize_t
)bytecounts
[s
]);
296 bufsize
= (tmsize_t
)bytecounts
[s
];
298 if (TIFFReadRawStrip(in
, s
, buf
, (tmsize_t
)bytecounts
[s
]) < 0 ||
299 TIFFWriteRawStrip(out
, s
, buf
, (tmsize_t
)bytecounts
[s
]) < 0) {
309 TIFFError(TIFFFileName(in
),
310 "Can't allocate space for strip buffer.");
315 cpTiles(TIFF
* in
, TIFF
* out
)
317 tsize_t bufsize
= TIFFTileSize(in
);
318 unsigned char *buf
= (unsigned char *)_TIFFmalloc(bufsize
);
321 ttile_t t
, nt
= TIFFNumberOfTiles(in
);
324 TIFFGetField(in
, TIFFTAG_TILEBYTECOUNTS
, &bytecounts
);
325 for (t
= 0; t
< nt
; t
++) {
326 if (bytecounts
[t
] > (uint64
) bufsize
) {
327 buf
= (unsigned char *)_TIFFrealloc(buf
, (tmsize_t
)bytecounts
[t
]);
330 bufsize
= (tmsize_t
)bytecounts
[t
];
332 if (TIFFReadRawTile(in
, t
, buf
, (tmsize_t
)bytecounts
[t
]) < 0 ||
333 TIFFWriteRawTile(out
, t
, buf
, (tmsize_t
)bytecounts
[t
]) < 0) {
343 TIFFError(TIFFFileName(in
),
344 "Can't allocate space for tile buffer.");
349 cpIFD(TIFF
* in
, TIFF
* out
)
352 if (TIFFIsTiled(in
)) {
353 if (!cpTiles(in
, out
))
356 if (!cpStrips(in
, out
))
362 static uint16 photometric
; /* current photometric of raster */
363 static uint16 filterWidth
; /* filter width in pixels */
364 static uint32 stepSrcWidth
; /* src image stepping width */
365 static uint32 stepDstWidth
; /* dest stepping width */
366 static uint8
* src0
; /* horizontal bit stepping (start) */
367 static uint8
* src1
; /* horizontal bit stepping (middle) */
368 static uint8
* src2
; /* horizontal bit stepping (end) */
369 static uint32
* rowoff
; /* row offset for stepping */
370 static uint8 cmap
[256]; /* colormap indexes */
371 static uint8 bits
[256]; /* count of bits set */
377 for (i
= 0; i
< 256; i
++) {
391 static int clamp(float v
, int low
, int high
)
392 { return (v
< low
? low
: v
> high
? high
: (int)v
); }
395 #define M_E 2.7182818284590452354
399 expFill(float pct
[], uint32 p
, uint32 n
)
402 uint32 c
= (p
* n
) / 100;
403 for (i
= 1; i
< c
; i
++)
404 pct
[i
] = (float) (1-exp(i
/((double)(n
-1)))/ M_E
);
412 float pct
[256]; /* known to be large enough */
414 pct
[0] = 1; /* force white */
416 case EXP50
: expFill(pct
, 50, 256); break;
417 case EXP60
: expFill(pct
, 60, 256); break;
418 case EXP70
: expFill(pct
, 70, 256); break;
419 case EXP80
: expFill(pct
, 80, 256); break;
420 case EXP90
: expFill(pct
, 90, 256); break;
421 case EXP
: expFill(pct
, 100, 256); break;
423 for (i
= 1; i
< 256; i
++)
424 pct
[i
] = 1-((float)i
)/(256-1);
427 switch (photometric
) {
428 case PHOTOMETRIC_MINISWHITE
:
429 for (i
= 0; i
< 256; i
++)
430 cmap
[i
] = clamp(255*pct
[(256-1)-i
], 0, 255);
432 case PHOTOMETRIC_MINISBLACK
:
433 for (i
= 0; i
< 256; i
++)
434 cmap
[i
] = clamp(255*pct
[i
], 0, 255);
442 src0
= (uint8
*) _TIFFmalloc(sizeof (uint8
) * tnw
);
443 src1
= (uint8
*) _TIFFmalloc(sizeof (uint8
) * tnw
);
444 src2
= (uint8
*) _TIFFmalloc(sizeof (uint8
) * tnw
);
445 rowoff
= (uint32
*) _TIFFmalloc(sizeof (uint32
) * tnw
);
447 stepDstWidth
= stepSrcWidth
= 0;
452 * Calculate the horizontal accumulation parameteres
453 * according to the widths of the src and dst images.
456 setupStepTables(uint32 sw
)
458 if (stepSrcWidth
!= sw
|| stepDstWidth
!= tnw
) {
466 for (x
= 0; x
< tnw
; x
++) {
469 while (err
>= limit
) {
473 rowoff
[x
] = sx0
>> 3;
474 fw
= sx
- sx0
; /* width */
475 b
= (fw
< 8) ? 0xff<<(8-fw
) : 0xff;
476 src0
[x
] = b
>> (sx0
&7);
482 src2
[x
] = 0xff << (8-fw
);
490 setrow(uint8
* row
, uint32 nrows
, const uint8
* rows
[])
493 uint32 area
= nrows
* filterWidth
;
494 for (x
= 0; x
< tnw
; x
++) {
495 uint32 mask0
= src0
[x
];
497 uint32 mask1
= src1
[x
];
498 uint32 off
= rowoff
[x
];
501 for (y
= 0; y
< nrows
; y
++) {
502 const uint8
* src
= rows
[y
] + off
;
503 acc
+= bits
[*src
++ & mask0
];
506 for (i
= fw
; i
> 8; i
--)
509 case 8: acc
+= bits
[*src
++];
510 case 7: acc
+= bits
[*src
++];
511 case 6: acc
+= bits
[*src
++];
512 case 5: acc
+= bits
[*src
++];
513 case 4: acc
+= bits
[*src
++];
514 case 3: acc
+= bits
[*src
++];
515 case 2: acc
+= bits
[*src
++];
516 case 1: acc
+= bits
[*src
++];
519 acc
+= bits
[*src
& mask1
];
521 *row
++ = cmap
[(255*acc
)/area
];
526 * Install the specified image. The
527 * image is resized to fit the display page using
528 * a box filter. The resultant pixels are mapped
529 * with a user-selectable contrast curve.
532 setImage1(const uint8
* br
, uint32 rw
, uint32 rh
)
537 int bpr
= TIFFhowmany8(rw
);
539 uint8
* row
= thumbnail
;
541 for (dy
= 0; dy
< tnh
; dy
++) {
542 const uint8
* rows
[256];
544 fprintf(stderr
, "bpr=%d, sy=%d, bpr*sy=%d\n", bpr
, sy
, bpr
*sy
);
545 rows
[0] = br
+ bpr
*sy
;
547 while (err
>= limit
) {
551 rows
[nrows
++] = br
+ bpr
*sy
;
553 setrow(row
, nrows
, rows
);
559 setImage(const uint8
* br
, uint32 rw
, uint32 rh
)
561 filterWidth
= (uint16
) ceil((double) rw
/ (double) tnw
);
563 setImage1(br
, rw
, rh
);
567 generateThumbnail(TIFF
* in
, TIFF
* out
)
569 unsigned char* raster
;
573 tsize_t rowsize
, rastersize
;
574 tstrip_t s
, ns
= TIFFNumberOfStrips(in
);
577 TIFFGetField(in
, TIFFTAG_IMAGEWIDTH
, &sw
);
578 TIFFGetField(in
, TIFFTAG_IMAGELENGTH
, &sh
);
579 TIFFGetFieldDefaulted(in
, TIFFTAG_BITSPERSAMPLE
, &bps
);
580 TIFFGetFieldDefaulted(in
, TIFFTAG_SAMPLESPERPIXEL
, &spp
);
581 TIFFGetFieldDefaulted(in
, TIFFTAG_ROWSPERSTRIP
, &rps
);
582 if (spp
!= 1 || bps
!= 1)
584 rowsize
= TIFFScanlineSize(in
);
585 rastersize
= sh
* rowsize
;
586 fprintf(stderr
, "rastersize=%u\n", (unsigned int)rastersize
);
587 raster
= (unsigned char*)_TIFFmalloc(rastersize
);
589 TIFFError(TIFFFileName(in
),
590 "Can't allocate space for raster buffer.");
594 for (s
= 0; s
< ns
; s
++) {
595 (void) TIFFReadEncodedStrip(in
, s
, rp
, -1);
598 TIFFGetField(in
, TIFFTAG_PHOTOMETRIC
, &photometric
);
600 setImage(raster
, sw
, sh
);
603 TIFFSetField(out
, TIFFTAG_SUBFILETYPE
, FILETYPE_REDUCEDIMAGE
);
604 TIFFSetField(out
, TIFFTAG_IMAGEWIDTH
, (uint32
) tnw
);
605 TIFFSetField(out
, TIFFTAG_IMAGELENGTH
, (uint32
) tnh
);
606 TIFFSetField(out
, TIFFTAG_BITSPERSAMPLE
, (uint16
) 8);
607 TIFFSetField(out
, TIFFTAG_SAMPLESPERPIXEL
, (uint16
) 1);
608 TIFFSetField(out
, TIFFTAG_COMPRESSION
, COMPRESSION_PACKBITS
);
609 TIFFSetField(out
, TIFFTAG_PHOTOMETRIC
, PHOTOMETRIC_MINISWHITE
);
610 TIFFSetField(out
, TIFFTAG_PLANARCONFIG
, PLANARCONFIG_CONTIG
);
611 TIFFSetField(out
, TIFFTAG_ORIENTATION
, ORIENTATION_TOPLEFT
);
612 cpTag(in
, out
, TIFFTAG_SOFTWARE
, (uint16
) -1, TIFF_ASCII
);
613 cpTag(in
, out
, TIFFTAG_IMAGEDESCRIPTION
, (uint16
) -1, TIFF_ASCII
);
614 cpTag(in
, out
, TIFFTAG_DATETIME
, (uint16
) -1, TIFF_ASCII
);
615 cpTag(in
, out
, TIFFTAG_HOSTCOMPUTER
, (uint16
) -1, TIFF_ASCII
);
617 TIFFSetField(out
, TIFFTAG_SUBIFD
, 1, diroff
);
618 return (TIFFWriteEncodedStrip(out
, 0, thumbnail
, tnw
*tnh
) != -1 &&
619 TIFFWriteDirectory(out
) != -1);
623 "usage: thumbnail [options] input.tif output.tif",
624 "where options are:",
625 " -h # specify thumbnail image height (default is 274)",
626 " -w # specify thumbnail image width (default is 216)",
628 " -c linear use linear contrast curve",
629 " -c exp50 use 50% exponential contrast curve",
630 " -c exp60 use 60% exponential contrast curve",
631 " -c exp70 use 70% exponential contrast curve",
632 " -c exp80 use 80% exponential contrast curve",
633 " -c exp90 use 90% exponential contrast curve",
634 " -c exp use pure exponential contrast curve",
645 fprintf(stderr
, "%s\n\n", TIFFGetVersion());
646 for (i
= 0; stuff
[i
] != NULL
; i
++)
647 fprintf(stderr
, "%s\n", stuff
[i
]);
651 /* vim: set ts=8 sts=8 sw=8 noet: */