1/* $Id: tiff2bw.c 276 2010-06-30 12:18:30Z nijtmans $ */
2
3/*
4 * Copyright (c) 1988-1997 Sam Leffler
5 * Copyright (c) 1991-1997 Silicon Graphics, Inc.
6 *
7 * Permission to use, copy, modify, distribute, and sell this software and
8 * its documentation for any purpose is hereby granted without fee, provided
9 * that (i) the above copyright notices and this permission notice appear in
10 * all copies of the software and related documentation, and (ii) the names of
11 * Sam Leffler and Silicon Graphics may not be used in any advertising or
12 * publicity relating to the software without the specific, prior written
13 * permission of Sam Leffler and Silicon Graphics.
14 *
15 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
17 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
18 *
19 * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
20 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
21 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
22 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
23 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
24 * OF THIS SOFTWARE.
25 */
26
27#include "tif_config.h"
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <ctype.h>
33
34#ifdef HAVE_UNISTD_H
35# include <unistd.h>
36#endif
37
38#include "tiffio.h"
39
40#define	streq(a,b)	(strcmp((a),(b)) == 0)
41#define	strneq(a,b,n)	(strncmp(a,b,n) == 0)
42
43/* x% weighting -> fraction of full color */
44#define	PCT(x)	(((x)*255+127)/100)
45int	RED = PCT(30);		/* 30% */
46int	GREEN = PCT(59);	/* 59% */
47int	BLUE = PCT(11);		/* 11% */
48
49static	void usage(void);
50static	int processCompressOptions(char*);
51
52static void
53compresscontig(unsigned char* out, unsigned char* rgb, uint32 n)
54{
55	register int v, red = RED, green = GREEN, blue = BLUE;
56
57	while (n-- > 0) {
58		v = red*(*rgb++);
59		v += green*(*rgb++);
60		v += blue*(*rgb++);
61		*out++ = v>>8;
62	}
63}
64
65static void
66compresssep(unsigned char* out,
67	    unsigned char* r, unsigned char* g, unsigned char* b, uint32 n)
68{
69	register uint32 red = RED, green = GREEN, blue = BLUE;
70
71	while (n-- > 0)
72		*out++ = (unsigned char)
73			((red*(*r++) + green*(*g++) + blue*(*b++)) >> 8);
74}
75
76static int
77checkcmap(TIFF* tif, int n, uint16* r, uint16* g, uint16* b)
78{
79	while (n-- > 0)
80		if (*r++ >= 256 || *g++ >= 256 || *b++ >= 256)
81			return (16);
82	TIFFWarning(TIFFFileName(tif), "Assuming 8-bit colormap");
83	return (8);
84}
85
86static void
87compresspalette(unsigned char* out, unsigned char* data, uint32 n, uint16* rmap, uint16* gmap, uint16* bmap)
88{
89	register int v, red = RED, green = GREEN, blue = BLUE;
90
91	while (n-- > 0) {
92		unsigned int ix = *data++;
93		v = red*rmap[ix];
94		v += green*gmap[ix];
95		v += blue*bmap[ix];
96		*out++ = v>>8;
97	}
98}
99
100static	uint16 compression = (uint16) -1;
101static	uint16 predictor = 0;
102static	int jpegcolormode = JPEGCOLORMODE_RGB;
103static	int quality = 75;		/* JPEG quality */
104
105static	void cpTags(TIFF* in, TIFF* out);
106
107int
108main(int argc, char* argv[])
109{
110	uint32 rowsperstrip = (uint32) -1;
111	TIFF *in, *out;
112	uint32 w, h;
113	uint16 samplesperpixel;
114	uint16 bitspersample;
115	uint16 config;
116	uint16 photometric;
117	uint16* red;
118	uint16* green;
119	uint16* blue;
120	tsize_t rowsize;
121	register uint32 row;
122	register tsample_t s;
123	unsigned char *inbuf, *outbuf;
124	char thing[1024];
125	int c;
126	extern int optind;
127	extern char *optarg;
128
129	while ((c = getopt(argc, argv, "c:r:R:G:B:")) != -1)
130		switch (c) {
131		case 'c':		/* compression scheme */
132			if (!processCompressOptions(optarg))
133				usage();
134			break;
135		case 'r':		/* rows/strip */
136			rowsperstrip = atoi(optarg);
137			break;
138		case 'R':
139			RED = PCT(atoi(optarg));
140			break;
141		case 'G':
142			GREEN = PCT(atoi(optarg));
143			break;
144		case 'B':
145			BLUE = PCT(atoi(optarg));
146			break;
147		case '?':
148			usage();
149			/*NOTREACHED*/
150		}
151	if (argc - optind < 2)
152		usage();
153	in = TIFFOpen(argv[optind], "r");
154	if (in == NULL)
155		return (-1);
156	photometric = 0;
157	TIFFGetField(in, TIFFTAG_PHOTOMETRIC, &photometric);
158	if (photometric != PHOTOMETRIC_RGB && photometric != PHOTOMETRIC_PALETTE ) {
159		fprintf(stderr,
160	    "%s: Bad photometric; can only handle RGB and Palette images.\n",
161		    argv[optind]);
162		return (-1);
163	}
164	TIFFGetField(in, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel);
165	if (samplesperpixel != 1 && samplesperpixel != 3) {
166		fprintf(stderr, "%s: Bad samples/pixel %u.\n",
167		    argv[optind], samplesperpixel);
168		return (-1);
169	}
170	TIFFGetField(in, TIFFTAG_BITSPERSAMPLE, &bitspersample);
171	if (bitspersample != 8) {
172		fprintf(stderr,
173		    " %s: Sorry, only handle 8-bit samples.\n", argv[optind]);
174		return (-1);
175	}
176	TIFFGetField(in, TIFFTAG_IMAGEWIDTH, &w);
177	TIFFGetField(in, TIFFTAG_IMAGELENGTH, &h);
178	TIFFGetField(in, TIFFTAG_PLANARCONFIG, &config);
179
180	out = TIFFOpen(argv[optind+1], "w");
181	if (out == NULL)
182		return (-1);
183	TIFFSetField(out, TIFFTAG_IMAGEWIDTH, w);
184	TIFFSetField(out, TIFFTAG_IMAGELENGTH, h);
185	TIFFSetField(out, TIFFTAG_BITSPERSAMPLE, 8);
186	TIFFSetField(out, TIFFTAG_SAMPLESPERPIXEL, 1);
187	TIFFSetField(out, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
188	cpTags(in, out);
189	if (compression != (uint16) -1) {
190		TIFFSetField(out, TIFFTAG_COMPRESSION, compression);
191		switch (compression) {
192		case COMPRESSION_JPEG:
193			TIFFSetField(out, TIFFTAG_JPEGQUALITY, quality);
194			TIFFSetField(out, TIFFTAG_JPEGCOLORMODE, jpegcolormode);
195			break;
196		case COMPRESSION_LZW:
197		case COMPRESSION_DEFLATE:
198			if (predictor != 0)
199				TIFFSetField(out, TIFFTAG_PREDICTOR, predictor);
200			break;
201		}
202	}
203	TIFFSetField(out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
204	sprintf(thing, "B&W version of %s", argv[optind]);
205	TIFFSetField(out, TIFFTAG_IMAGEDESCRIPTION, thing);
206	TIFFSetField(out, TIFFTAG_SOFTWARE, "tiff2bw");
207	outbuf = (unsigned char *)_TIFFmalloc(TIFFScanlineSize(out));
208	TIFFSetField(out, TIFFTAG_ROWSPERSTRIP,
209	    TIFFDefaultStripSize(out, rowsperstrip));
210
211#define	pack(a,b)	((a)<<8 | (b))
212	switch (pack(photometric, config)) {
213	case pack(PHOTOMETRIC_PALETTE, PLANARCONFIG_CONTIG):
214	case pack(PHOTOMETRIC_PALETTE, PLANARCONFIG_SEPARATE):
215		TIFFGetField(in, TIFFTAG_COLORMAP, &red, &green, &blue);
216		/*
217		 * Convert 16-bit colormap to 8-bit (unless it looks
218		 * like an old-style 8-bit colormap).
219		 */
220		if (checkcmap(in, 1<<bitspersample, red, green, blue) == 16) {
221			int i;
222#define	CVT(x)		(((x) * 255L) / ((1L<<16)-1))
223			for (i = (1<<bitspersample)-1; i >= 0; i--) {
224				red[i] = CVT(red[i]);
225				green[i] = CVT(green[i]);
226				blue[i] = CVT(blue[i]);
227			}
228#undef CVT
229		}
230		inbuf = (unsigned char *)_TIFFmalloc(TIFFScanlineSize(in));
231		for (row = 0; row < h; row++) {
232			if (TIFFReadScanline(in, inbuf, row, 0) < 0)
233				break;
234			compresspalette(outbuf, inbuf, w, red, green, blue);
235			if (TIFFWriteScanline(out, outbuf, row, 0) < 0)
236				break;
237		}
238		break;
239	case pack(PHOTOMETRIC_RGB, PLANARCONFIG_CONTIG):
240		inbuf = (unsigned char *)_TIFFmalloc(TIFFScanlineSize(in));
241		for (row = 0; row < h; row++) {
242			if (TIFFReadScanline(in, inbuf, row, 0) < 0)
243				break;
244			compresscontig(outbuf, inbuf, w);
245			if (TIFFWriteScanline(out, outbuf, row, 0) < 0)
246				break;
247		}
248		break;
249	case pack(PHOTOMETRIC_RGB, PLANARCONFIG_SEPARATE):
250		rowsize = TIFFScanlineSize(in);
251		inbuf = (unsigned char *)_TIFFmalloc(3*rowsize);
252		for (row = 0; row < h; row++) {
253			for (s = 0; s < 3; s++)
254				if (TIFFReadScanline(in,
255				    inbuf+s*rowsize, row, s) < 0)
256					 return (-1);
257			compresssep(outbuf,
258			    inbuf, inbuf+rowsize, inbuf+2*rowsize, w);
259			if (TIFFWriteScanline(out, outbuf, row, 0) < 0)
260				break;
261		}
262		break;
263	}
264#undef pack
265	TIFFClose(out);
266	return (0);
267}
268
269static int
270processCompressOptions(char* opt)
271{
272	if (streq(opt, "none"))
273		compression = COMPRESSION_NONE;
274	else if (streq(opt, "packbits"))
275		compression = COMPRESSION_PACKBITS;
276	else if (strneq(opt, "jpeg", 4)) {
277		char* cp = strchr(opt, ':');
278
279                compression = COMPRESSION_JPEG;
280                while( cp )
281                {
282                    if (isdigit((int)cp[1]))
283			quality = atoi(cp+1);
284                    else if (cp[1] == 'r' )
285			jpegcolormode = JPEGCOLORMODE_RAW;
286                    else
287                        usage();
288
289                    cp = strchr(cp+1,':');
290                }
291	} else if (strneq(opt, "lzw", 3)) {
292		char* cp = strchr(opt, ':');
293		if (cp)
294			predictor = atoi(cp+1);
295		compression = COMPRESSION_LZW;
296	} else if (strneq(opt, "zip", 3)) {
297		char* cp = strchr(opt, ':');
298		if (cp)
299			predictor = atoi(cp+1);
300		compression = COMPRESSION_DEFLATE;
301	} else
302		return (0);
303	return (1);
304}
305
306#define	CopyField(tag, v) \
307    if (TIFFGetField(in, tag, &v)) TIFFSetField(out, tag, v)
308#define	CopyField2(tag, v1, v2) \
309    if (TIFFGetField(in, tag, &v1, &v2)) TIFFSetField(out, tag, v1, v2)
310#define	CopyField3(tag, v1, v2, v3) \
311    if (TIFFGetField(in, tag, &v1, &v2, &v3)) TIFFSetField(out, tag, v1, v2, v3)
312#define	CopyField4(tag, v1, v2, v3, v4) \
313    if (TIFFGetField(in, tag, &v1, &v2, &v3, &v4)) TIFFSetField(out, tag, v1, v2, v3, v4)
314
315static void
316cpTag(TIFF* in, TIFF* out, uint16 tag, uint16 count, TIFFDataType type)
317{
318	switch (type) {
319	case TIFF_SHORT:
320		if (count == 1) {
321			uint16 shortv;
322			CopyField(tag, shortv);
323		} else if (count == 2) {
324			uint16 shortv1, shortv2;
325			CopyField2(tag, shortv1, shortv2);
326		} else if (count == 4) {
327			uint16 *tr, *tg, *tb, *ta;
328			CopyField4(tag, tr, tg, tb, ta);
329		} else if (count == (uint16) -1) {
330			uint16 shortv1;
331			uint16* shortav;
332			CopyField2(tag, shortv1, shortav);
333		}
334		break;
335	case TIFF_LONG:
336		{ uint32 longv;
337		  CopyField(tag, longv);
338		}
339		break;
340	case TIFF_RATIONAL:
341		if (count == 1) {
342			float floatv;
343			CopyField(tag, floatv);
344		} else if (count == (uint16) -1) {
345			float* floatav;
346			CopyField(tag, floatav);
347		}
348		break;
349	case TIFF_ASCII:
350		{ char* stringv;
351		  CopyField(tag, stringv);
352		}
353		break;
354	case TIFF_DOUBLE:
355		if (count == 1) {
356			double doublev;
357			CopyField(tag, doublev);
358		} else if (count == (uint16) -1) {
359			double* doubleav;
360			CopyField(tag, doubleav);
361		}
362		break;
363          default:
364                TIFFError(TIFFFileName(in),
365                          "Data type %d is not supported, tag %d skipped.",
366                          tag, type);
367	}
368}
369
370#undef CopyField4
371#undef CopyField3
372#undef CopyField2
373#undef CopyField
374
375static struct cpTag {
376	uint16	tag;
377	uint16	count;
378	TIFFDataType type;
379} tags[] = {
380	{ TIFFTAG_SUBFILETYPE,		1, TIFF_LONG },
381	{ TIFFTAG_THRESHHOLDING,	1, TIFF_SHORT },
382	{ TIFFTAG_DOCUMENTNAME,		1, TIFF_ASCII },
383	{ TIFFTAG_IMAGEDESCRIPTION,	1, TIFF_ASCII },
384	{ TIFFTAG_MAKE,			1, TIFF_ASCII },
385	{ TIFFTAG_MODEL,		1, TIFF_ASCII },
386	{ TIFFTAG_MINSAMPLEVALUE,	1, TIFF_SHORT },
387	{ TIFFTAG_MAXSAMPLEVALUE,	1, TIFF_SHORT },
388	{ TIFFTAG_XRESOLUTION,		1, TIFF_RATIONAL },
389	{ TIFFTAG_YRESOLUTION,		1, TIFF_RATIONAL },
390	{ TIFFTAG_PAGENAME,		1, TIFF_ASCII },
391	{ TIFFTAG_XPOSITION,		1, TIFF_RATIONAL },
392	{ TIFFTAG_YPOSITION,		1, TIFF_RATIONAL },
393	{ TIFFTAG_RESOLUTIONUNIT,	1, TIFF_SHORT },
394	{ TIFFTAG_SOFTWARE,		1, TIFF_ASCII },
395	{ TIFFTAG_DATETIME,		1, TIFF_ASCII },
396	{ TIFFTAG_ARTIST,		1, TIFF_ASCII },
397	{ TIFFTAG_HOSTCOMPUTER,		1, TIFF_ASCII },
398	{ TIFFTAG_WHITEPOINT,		1, TIFF_RATIONAL },
399	{ TIFFTAG_PRIMARYCHROMATICITIES,(uint16) -1,TIFF_RATIONAL },
400	{ TIFFTAG_HALFTONEHINTS,	2, TIFF_SHORT },
401	{ TIFFTAG_INKSET,		1, TIFF_SHORT },
402	{ TIFFTAG_DOTRANGE,		2, TIFF_SHORT },
403	{ TIFFTAG_TARGETPRINTER,	1, TIFF_ASCII },
404	{ TIFFTAG_SAMPLEFORMAT,		1, TIFF_SHORT },
405	{ TIFFTAG_YCBCRCOEFFICIENTS,	(uint16) -1,TIFF_RATIONAL },
406	{ TIFFTAG_YCBCRSUBSAMPLING,	2, TIFF_SHORT },
407	{ TIFFTAG_YCBCRPOSITIONING,	1, TIFF_SHORT },
408	{ TIFFTAG_REFERENCEBLACKWHITE,	(uint16) -1,TIFF_RATIONAL },
409	{ TIFFTAG_EXTRASAMPLES,		(uint16) -1, TIFF_SHORT },
410	{ TIFFTAG_SMINSAMPLEVALUE,	1, TIFF_DOUBLE },
411	{ TIFFTAG_SMAXSAMPLEVALUE,	1, TIFF_DOUBLE },
412	{ TIFFTAG_STONITS,		1, TIFF_DOUBLE },
413};
414#define	NTAGS	(sizeof (tags) / sizeof (tags[0]))
415
416static void
417cpTags(TIFF* in, TIFF* out)
418{
419    struct cpTag *p;
420    for (p = tags; p < &tags[NTAGS]; p++)
421	cpTag(in, out, p->tag, p->count, p->type);
422}
423#undef NTAGS
424
425char* stuff[] = {
426"usage: tiff2bw [options] input.tif output.tif",
427"where options are:",
428" -R %		use #% from red channel",
429" -G %		use #% from green channel",
430" -B %		use #% from blue channel",
431"",
432" -r #		make each strip have no more than # rows",
433"",
434" -c lzw[:opts]	compress output with Lempel-Ziv & Welch encoding",
435" -c zip[:opts]	compress output with deflate encoding",
436" -c packbits	compress output with packbits encoding",
437" -c g3[:opts]	compress output with CCITT Group 3 encoding",
438" -c g4		compress output with CCITT Group 4 encoding",
439" -c none	use no compression algorithm on output",
440"",
441"LZW and deflate options:",
442" #		set predictor value",
443"For example, -c lzw:2 to get LZW-encoded data with horizontal differencing",
444NULL
445};
446
447static void
448usage(void)
449{
450	char buf[BUFSIZ];
451	int i;
452
453	setbuf(stderr, buf);
454        fprintf(stderr, "%s\n\n", TIFFGetVersion());
455	for (i = 0; stuff[i] != NULL; i++)
456		fprintf(stderr, "%s\n", stuff[i]);
457	exit(-1);
458}
459
460/* vim: set ts=8 sts=8 sw=8 noet: */
461/*
462 * Local Variables:
463 * mode: c
464 * c-basic-offset: 8
465 * fill-column: 78
466 * End:
467 */
468