1/* $Id: fax2tiff.c 276 2010-06-30 12:18:30Z nijtmans $ */
2
3/*
4 * Copyright (c) 1990-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/*
28 * Convert a CCITT Group 3 or 4 FAX file to TIFF Group 3 or 4 format.
29 */
30#include "tif_config.h"
31
32#include <stdio.h>
33#include <stdlib.h>		/* should have atof & getopt */
34
35#ifdef HAVE_UNISTD_H
36# include <unistd.h>
37#endif
38
39#ifdef HAVE_FCNTL_H
40# include <fcntl.h>
41#endif
42
43#ifdef HAVE_IO_H
44# include <io.h>
45#endif
46
47#include "tiffiop.h"
48
49#ifndef EXIT_SUCCESS
50# define EXIT_SUCCESS	0
51#endif
52#ifndef EXIT_FAILURE
53# define EXIT_FAILURE	1
54#endif
55
56#define TIFFhowmany8(x) (((x)&0x07)?((uint32)(x)>>3)+1:(uint32)(x)>>3)
57
58TIFF	*faxTIFF;
59char	*rowbuf;
60char	*refbuf;
61
62uint32	xsize = 1728;
63int	verbose;
64int	stretch;
65uint16	badfaxrun;
66uint32	badfaxlines;
67
68int	copyFaxFile(TIFF* tifin, TIFF* tifout);
69static	void usage(void);
70
71int
72main(int argc, char* argv[])
73{
74	FILE *in;
75	TIFF *out = NULL;
76	TIFFErrorHandler whandler = NULL;
77	int compression_in = COMPRESSION_CCITTFAX3;
78	int compression_out = COMPRESSION_CCITTFAX3;
79	int fillorder_in = FILLORDER_LSB2MSB;
80	int fillorder_out = FILLORDER_LSB2MSB;
81	uint32 group3options_in = 0;	/* 1d-encoded */
82	uint32 group3options_out = 0;	/* 1d-encoded */
83	uint32 group4options_in = 0;	/* compressed */
84	uint32 group4options_out = 0;	/* compressed */
85	uint32 defrowsperstrip = (uint32) 0;
86	uint32 rowsperstrip;
87	int photometric_in = PHOTOMETRIC_MINISWHITE;
88	int photometric_out = PHOTOMETRIC_MINISWHITE;
89	int mode = FAXMODE_CLASSF;
90	int rows;
91	int c;
92	int pn, npages;
93	float resY = 196.0;
94	extern int optind;
95	extern char* optarg;
96
97
98	while ((c = getopt(argc, argv, "R:X:o:1234ABLMPUW5678abcflmprsuvwz?")) != -1)
99		switch (c) {
100			/* input-related options */
101		case '3':		/* input is g3-encoded */
102			compression_in = COMPRESSION_CCITTFAX3;
103			break;
104		case '4':		/* input is g4-encoded */
105			compression_in = COMPRESSION_CCITTFAX4;
106			break;
107		case 'U':		/* input is uncompressed (g3 and g4) */
108			group3options_in |= GROUP3OPT_UNCOMPRESSED;
109			group4options_in |= GROUP4OPT_UNCOMPRESSED;
110			break;
111		case '1':		/* input is 1d-encoded (g3 only) */
112			group3options_in &= ~GROUP3OPT_2DENCODING;
113			break;
114		case '2':		/* input is 2d-encoded (g3 only) */
115			group3options_in |= GROUP3OPT_2DENCODING;
116			break;
117		case 'P':	/* input has not-aligned EOL (g3 only) */
118			group3options_in &= ~GROUP3OPT_FILLBITS;
119			break;
120		case 'A':		/* input has aligned EOL (g3 only) */
121			group3options_in |= GROUP3OPT_FILLBITS;
122			break;
123		case 'W':		/* input has 0 mean white */
124			photometric_in = PHOTOMETRIC_MINISWHITE;
125			break;
126		case 'B':		/* input has 0 mean black */
127			photometric_in = PHOTOMETRIC_MINISBLACK;
128			break;
129		case 'L':		/* input has lsb-to-msb fillorder */
130			fillorder_in = FILLORDER_LSB2MSB;
131			break;
132		case 'M':		/* input has msb-to-lsb fillorder */
133			fillorder_in = FILLORDER_MSB2LSB;
134			break;
135		case 'R':		/* input resolution */
136			resY = (float) atof(optarg);
137			break;
138		case 'X':		/* input width */
139			xsize = (uint32) atoi(optarg);
140			break;
141
142			/* output-related options */
143		case '7':		/* generate g3-encoded output */
144			compression_out = COMPRESSION_CCITTFAX3;
145			break;
146		case '8':		/* generate g4-encoded output */
147			compression_out = COMPRESSION_CCITTFAX4;
148			break;
149		case 'u':	/* generate uncompressed output (g3 and g4) */
150			group3options_out |= GROUP3OPT_UNCOMPRESSED;
151			group4options_out |= GROUP4OPT_UNCOMPRESSED;
152			break;
153		case '5':	/* generate 1d-encoded output (g3 only) */
154			group3options_out &= ~GROUP3OPT_2DENCODING;
155			break;
156		case '6':	/* generate 2d-encoded output (g3 only) */
157			group3options_out |= GROUP3OPT_2DENCODING;
158			break;
159		case 'c':		/* generate "classic" g3 format */
160			mode = FAXMODE_CLASSIC;
161			break;
162		case 'f':		/* generate Class F format */
163			mode = FAXMODE_CLASSF;
164			break;
165		case 'm':		/* output's fillorder is msb-to-lsb */
166			fillorder_out = FILLORDER_MSB2LSB;
167			break;
168		case 'l':		/* output's fillorder is lsb-to-msb */
169			fillorder_out = FILLORDER_LSB2MSB;
170			break;
171		case 'o':
172			out = TIFFOpen(optarg, "w");
173			if (out == NULL) {
174				fprintf(stderr,
175				    "%s: Can not create or open %s\n",
176				    argv[0], optarg);
177				return EXIT_FAILURE;
178			}
179			break;
180		case 'a':	/* generate EOL-aligned output (g3 only) */
181			group3options_out |= GROUP3OPT_FILLBITS;
182			break;
183		case 'p':	/* generate not EOL-aligned output (g3 only) */
184			group3options_out &= ~GROUP3OPT_FILLBITS;
185			break;
186		case 'r':		/* rows/strip */
187			defrowsperstrip = atol(optarg);
188			break;
189		case 's':		/* stretch image by dup'ng scanlines */
190			stretch = 1;
191			break;
192		case 'w':		/* undocumented -- for testing */
193			photometric_out = PHOTOMETRIC_MINISWHITE;
194			break;
195		case 'b':		/* undocumented -- for testing */
196			photometric_out = PHOTOMETRIC_MINISBLACK;
197			break;
198		case 'z':		/* undocumented -- for testing */
199			compression_out = COMPRESSION_LZW;
200			break;
201		case 'v':		/* -v for info */
202			verbose++;
203			break;
204		case '?':
205			usage();
206			/*NOTREACHED*/
207		}
208	npages = argc - optind;
209	if (npages < 1)
210		usage();
211
212	rowbuf = _TIFFmalloc(TIFFhowmany8(xsize));
213	refbuf = _TIFFmalloc(TIFFhowmany8(xsize));
214	if (rowbuf == NULL || refbuf == NULL) {
215		fprintf(stderr, "%s: Not enough memory\n", argv[0]);
216		return (EXIT_FAILURE);
217	}
218
219	if (out == NULL) {
220		out = TIFFOpen("fax.tif", "w");
221		if (out == NULL) {
222			fprintf(stderr, "%s: Can not create fax.tif\n",
223			    argv[0]);
224			return (EXIT_FAILURE);
225		}
226	}
227
228	faxTIFF = TIFFClientOpen("(FakeInput)", "w",
229	/* TIFFClientOpen() fails if we don't set existing value here */
230				 TIFFClientdata(out),
231				 TIFFGetReadProc(out), TIFFGetWriteProc(out),
232				 TIFFGetSeekProc(out), TIFFGetCloseProc(out),
233				 TIFFGetSizeProc(out), TIFFGetMapFileProc(out),
234				 TIFFGetUnmapFileProc(out));
235	if (faxTIFF == NULL) {
236		fprintf(stderr, "%s: Can not create fake input file\n",
237		    argv[0]);
238		return (EXIT_FAILURE);
239	}
240	TIFFSetMode(faxTIFF, O_RDONLY);
241	TIFFSetField(faxTIFF, TIFFTAG_IMAGEWIDTH,	xsize);
242	TIFFSetField(faxTIFF, TIFFTAG_SAMPLESPERPIXEL,	1);
243	TIFFSetField(faxTIFF, TIFFTAG_BITSPERSAMPLE,	1);
244	TIFFSetField(faxTIFF, TIFFTAG_FILLORDER,	fillorder_in);
245	TIFFSetField(faxTIFF, TIFFTAG_PLANARCONFIG,	PLANARCONFIG_CONTIG);
246	TIFFSetField(faxTIFF, TIFFTAG_PHOTOMETRIC,	photometric_in);
247	TIFFSetField(faxTIFF, TIFFTAG_YRESOLUTION,	resY);
248	TIFFSetField(faxTIFF, TIFFTAG_RESOLUTIONUNIT,	RESUNIT_INCH);
249
250	/* NB: this must be done after directory info is setup */
251	TIFFSetField(faxTIFF, TIFFTAG_COMPRESSION, compression_in);
252	if (compression_in == COMPRESSION_CCITTFAX3)
253		TIFFSetField(faxTIFF, TIFFTAG_GROUP3OPTIONS, group3options_in);
254	else if (compression_in == COMPRESSION_CCITTFAX4)
255		TIFFSetField(faxTIFF, TIFFTAG_GROUP4OPTIONS, group4options_in);
256	for (pn = 0; optind < argc; pn++, optind++) {
257		in = fopen(argv[optind], "rb");
258		if (in == NULL) {
259			fprintf(stderr,
260			    "%s: %s: Can not open\n", argv[0], argv[optind]);
261			continue;
262		}
263#if defined(_WIN32) && defined(USE_WIN32_FILEIO)
264                TIFFSetClientdata(faxTIFF, (thandle_t)_get_osfhandle(fileno(in)));
265#else
266                TIFFSetClientdata(faxTIFF, (thandle_t)fileno(in));
267#endif
268		TIFFSetFileName(faxTIFF, (const char*)argv[optind]);
269		TIFFSetField(out, TIFFTAG_IMAGEWIDTH, xsize);
270		TIFFSetField(out, TIFFTAG_BITSPERSAMPLE, 1);
271		TIFFSetField(out, TIFFTAG_COMPRESSION, compression_out);
272		TIFFSetField(out, TIFFTAG_PHOTOMETRIC, photometric_out);
273		TIFFSetField(out, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
274		TIFFSetField(out, TIFFTAG_SAMPLESPERPIXEL, 1);
275		switch (compression_out) {
276			/* g3 */
277			case COMPRESSION_CCITTFAX3:
278			TIFFSetField(out, TIFFTAG_GROUP3OPTIONS,
279				     group3options_out);
280			TIFFSetField(out, TIFFTAG_FAXMODE, mode);
281			rowsperstrip =
282				(defrowsperstrip)?defrowsperstrip:(uint32)-1L;
283			break;
284
285			/* g4 */
286			case COMPRESSION_CCITTFAX4:
287			TIFFSetField(out, TIFFTAG_GROUP4OPTIONS,
288				     group4options_out);
289			TIFFSetField(out, TIFFTAG_FAXMODE, mode);
290			rowsperstrip =
291				(defrowsperstrip)?defrowsperstrip:(uint32)-1L;
292			break;
293
294			default:
295			rowsperstrip = (defrowsperstrip) ?
296				defrowsperstrip : TIFFDefaultStripSize(out, 0);
297		}
298		TIFFSetField(out, TIFFTAG_ROWSPERSTRIP, rowsperstrip);
299		TIFFSetField(out, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
300		TIFFSetField(out, TIFFTAG_FILLORDER, fillorder_out);
301		TIFFSetField(out, TIFFTAG_SOFTWARE, "fax2tiff");
302		TIFFSetField(out, TIFFTAG_XRESOLUTION, 204.0);
303		if (!stretch) {
304			TIFFGetField(faxTIFF, TIFFTAG_YRESOLUTION, &resY);
305			TIFFSetField(out, TIFFTAG_YRESOLUTION, resY);
306		} else
307			TIFFSetField(out, TIFFTAG_YRESOLUTION, 196.);
308		TIFFSetField(out, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);
309		TIFFSetField(out, TIFFTAG_PAGENUMBER, pn, npages);
310
311		if (!verbose)
312		    whandler = TIFFSetWarningHandler(NULL);
313		rows = copyFaxFile(faxTIFF, out);
314		fclose(in);
315		if (!verbose)
316		    (void) TIFFSetWarningHandler(whandler);
317
318		TIFFSetField(out, TIFFTAG_IMAGELENGTH, rows);
319
320		if (verbose) {
321			fprintf(stderr, "%s:\n", argv[optind]);
322			fprintf(stderr, "%d rows in input\n", rows);
323			fprintf(stderr, "%ld total bad rows\n",
324			    (long) badfaxlines);
325			fprintf(stderr, "%d max consecutive bad rows\n", badfaxrun);
326		}
327		if (compression_out == COMPRESSION_CCITTFAX3 &&
328		    mode == FAXMODE_CLASSF) {
329			TIFFSetField(out, TIFFTAG_BADFAXLINES, badfaxlines);
330			TIFFSetField(out, TIFFTAG_CLEANFAXDATA, badfaxlines ?
331			    CLEANFAXDATA_REGENERATED : CLEANFAXDATA_CLEAN);
332			TIFFSetField(out, TIFFTAG_CONSECUTIVEBADFAXLINES, badfaxrun);
333		}
334		TIFFWriteDirectory(out);
335	}
336	TIFFClose(out);
337	_TIFFfree(rowbuf);
338	_TIFFfree(refbuf);
339	return (EXIT_SUCCESS);
340}
341
342int
343copyFaxFile(TIFF* tifin, TIFF* tifout)
344{
345	uint32 row;
346	uint32 linesize = TIFFhowmany8(xsize);
347	uint16 badrun;
348	int ok;
349
350	tifin->tif_rawdatasize = TIFFGetFileSize(tifin);
351	tifin->tif_rawdata = _TIFFmalloc(tifin->tif_rawdatasize);
352	if (tifin->tif_rawdata == NULL) {
353		TIFFError(tifin->tif_name, "Not enough memory");
354		return (0);
355	}
356	if (!ReadOK(tifin, tifin->tif_rawdata, tifin->tif_rawdatasize)) {
357		TIFFError(tifin->tif_name, "Read error at scanline 0");
358		return (0);
359	}
360	tifin->tif_rawcp = tifin->tif_rawdata;
361	tifin->tif_rawcc = tifin->tif_rawdatasize;
362
363	(*tifin->tif_setupdecode)(tifin);
364	(*tifin->tif_predecode)(tifin, (tsample_t) 0);
365	tifin->tif_row = 0;
366	badfaxlines = 0;
367	badfaxrun = 0;
368
369	_TIFFmemset(refbuf, 0, linesize);
370	row = 0;
371	badrun = 0;		/* current run of bad lines */
372	while (tifin->tif_rawcc > 0) {
373		ok = (*tifin->tif_decoderow)(tifin, (tdata_t) rowbuf,
374					     linesize, 0);
375		if (!ok) {
376			badfaxlines++;
377			badrun++;
378			/* regenerate line from previous good line */
379			_TIFFmemcpy(rowbuf, refbuf, linesize);
380		} else {
381			if (badrun > badfaxrun)
382				badfaxrun = badrun;
383			badrun = 0;
384			_TIFFmemcpy(refbuf, rowbuf, linesize);
385		}
386		tifin->tif_row++;
387
388		if (TIFFWriteScanline(tifout, rowbuf, row, 0) < 0) {
389			fprintf(stderr, "%s: Write error at row %ld.\n",
390			    tifout->tif_name, (long) row);
391			break;
392		}
393		row++;
394		if (stretch) {
395			if (TIFFWriteScanline(tifout, rowbuf, row, 0) < 0) {
396				fprintf(stderr, "%s: Write error at row %ld.\n",
397				    tifout->tif_name, (long) row);
398				break;
399			}
400			row++;
401		}
402	}
403	if (badrun > badfaxrun)
404		badfaxrun = badrun;
405	_TIFFfree(tifin->tif_rawdata);
406	return (row);
407}
408
409char* stuff[] = {
410"usage: fax2tiff [options] input.raw...",
411"where options are:",
412" -3		input data is G3-encoded		[default]",
413" -4		input data is G4-encoded",
414" -U		input data is uncompressed (G3 or G4)",
415" -1		input data is 1D-encoded (G3 only)	[default]",
416" -2		input data is 2D-encoded (G3 only)",
417" -P		input is not EOL-aligned (G3 only)	[default]",
418" -A		input is EOL-aligned (G3 only)",
419" -M		input data has MSB2LSB bit order",
420" -L		input data has LSB2MSB bit order	[default]",
421" -B		input data has min 0 means black",
422" -W		input data has min 0 means white	[default]",
423" -R #		input data has # resolution (lines/inch) [default is 196]",
424" -X #		input data has # width			[default is 1728]",
425"",
426" -o out.tif	write output to out.tif",
427" -7		generate G3-encoded output		[default]",
428" -8		generate G4-encoded output",
429" -u		generate uncompressed output (G3 or G4)",
430" -5		generate 1D-encoded output (G3 only)",
431" -6		generate 2D-encoded output (G3 only)	[default]",
432" -p		generate not EOL-aligned output (G3 only)",
433" -a		generate EOL-aligned output (G3 only)	[default]",
434" -c		generate \"classic\" TIFF format",
435" -f		generate TIFF Class F (TIFF/F) format	[default]",
436" -m		output fill order is MSB2LSB",
437" -l		output fill order is LSB2MSB		[default]",
438" -r #		make each strip have no more than # rows",
439" -s		stretch image by duplicating scanlines",
440" -v		print information about conversion work",
441" -z		generate LZW compressed output",
442NULL
443};
444
445static void
446usage(void)
447{
448	char buf[BUFSIZ];
449	int i;
450
451	setbuf(stderr, buf);
452        fprintf(stderr, "%s\n\n", TIFFGetVersion());
453	for (i = 0; stuff[i] != NULL; i++)
454		fprintf(stderr, "%s\n", stuff[i]);
455	exit(EXIT_FAILURE);
456}
457
458/* vim: set ts=8 sts=8 sw=8 noet: */
459/*
460 * Local Variables:
461 * mode: c
462 * c-basic-offset: 8
463 * fill-column: 78
464 * End:
465 */
466