- start of external Imager API access:
[imager.git] / tiff.c
CommitLineData
92bda632 1#include "imager.h"
02d1d628
AMH
2#include "tiffio.h"
3#include "iolayer.h"
92bda632 4#include "imageri.h"
5c829fcf 5
02d1d628
AMH
6/*
7=head1 NAME
8
9tiff.c - implements reading and writing tiff files, uses io layer.
10
11=head1 SYNOPSIS
12
13 io_glue *ig = io_new_fd( fd );
14 i_img *im = i_readtiff_wiol(ig, -1); // no limit on how much is read
15 // or
16 io_glue *ig = io_new_fd( fd );
17 return_code = i_writetiff_wiol(im, ig);
18
19=head1 DESCRIPTION
20
21tiff.c implements the basic functions to read and write tiff files.
22It uses the iolayer and needs either a seekable source or an entire
23memory mapped buffer.
24
25=head1 FUNCTION REFERENCE
26
27Some of these functions are internal.
28
b8c2033e 29=over
02d1d628
AMH
30
31=cut
32*/
33
5c829fcf
AMH
34#define byteswap_macro(x) \
35 ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
36 (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
37
fd9a31d2
TC
38struct tag_name {
39 char *name;
40 uint32 tag;
41};
42
43static struct tag_name text_tag_names[] =
44{
45 { "tiff_documentname", TIFFTAG_DOCUMENTNAME, },
46 { "tiff_imagedescription", TIFFTAG_IMAGEDESCRIPTION, },
47 { "tiff_make", TIFFTAG_MAKE, },
48 { "tiff_model", TIFFTAG_MODEL, },
49 { "tiff_pagename", TIFFTAG_PAGENAME, },
50 { "tiff_software", TIFFTAG_SOFTWARE, },
51 { "tiff_datetime", TIFFTAG_DATETIME, },
52 { "tiff_artist", TIFFTAG_ARTIST, },
53 { "tiff_hostcomputer", TIFFTAG_HOSTCOMPUTER, },
54};
55
56static const int text_tag_count =
57 sizeof(text_tag_names) / sizeof(*text_tag_names);
5c829fcf 58
5bb828f1
TC
59static void error_handler(char const *module, char const *fmt, va_list ap) {
60 i_push_errorvf(0, fmt, ap);
61}
62
ffeb4a67
TC
63#define WARN_BUFFER_LIMIT 10000
64static char *warn_buffer = NULL;
65static int warn_buffer_size = 0;
66
be371490 67static void warn_handler(char const *module, char const *fmt, va_list ap) {
ffeb4a67
TC
68 char buf[1000];
69
70 buf[0] = '\0';
71#ifdef HAVE_SNPRINTF
72 vsnprintf(buf, sizeof(buf), fmt, ap);
73#else
74 vsprintf(buf, fmt, ap);
75#endif
76 if (!warn_buffer || strlen(warn_buffer)+strlen(buf)+2 > warn_buffer_size) {
77 int new_size = warn_buffer_size + strlen(buf) + 2;
78 char *old_buffer = warn_buffer;
79 if (new_size > WARN_BUFFER_LIMIT) {
80 new_size = WARN_BUFFER_LIMIT;
81 }
82 warn_buffer = myrealloc(warn_buffer, new_size);
83 if (!old_buffer) *warn_buffer = '\0';
84 warn_buffer_size = new_size;
85 }
86 if (strlen(warn_buffer)+strlen(buf)+2 <= warn_buffer_size) {
87 strcat(warn_buffer, buf);
88 strcat(warn_buffer, "\n");
89 }
be371490
TC
90}
91
5bb828f1
TC
92static int save_tiff_tags(TIFF *tif, i_img *im);
93
94static void expand_4bit_hl(unsigned char *buf, int count);
95
f62b2d84
TC
96static void pack_4bit_hl(unsigned char *buf, int count);
97
caa833d5
AMH
98
99static toff_t sizeproc(thandle_t x) {
100 return 0;
101}
102
103
02d1d628
AMH
104/*
105=item comp_seek(h, o, w)
106
107Compatability for 64 bit systems like latest freebsd (internal)
108
109 h - tiff handle, cast an io_glue object
110 o - offset
111 w - whence
112
113=cut
114*/
115
116static
117toff_t
118comp_seek(thandle_t h, toff_t o, int w) {
119 io_glue *ig = (io_glue*)h;
120 return (toff_t) ig->seekcb(ig, o, w);
121}
122
e18f39b3
TC
123/*
124=item comp_mmap(thandle_t, tdata_t*, toff_t*)
125
126Dummy mmap stub.
127
128This shouldn't ever be called but newer tifflibs want it anyway.
129
130=cut
131*/
132
133static
134int
135comp_mmap(thandle_t h, tdata_t*p, toff_t*off) {
136 return -1;
137}
138
139/*
140=item comp_munmap(thandle_t h, tdata_t p, toff_t off)
141
142Dummy munmap stub.
143
144This shouldn't ever be called but newer tifflibs want it anyway.
145
146=cut
147*/
148
149static void
150comp_munmap(thandle_t h, tdata_t p, toff_t off) {
151 /* do nothing */
152}
153
10461f9a 154static i_img *read_one_tiff(TIFF *tif) {
02d1d628
AMH
155 i_img *im;
156 uint32 width, height;
157 uint16 channels;
5bb828f1 158 uint32* raster = NULL;
02d1d628 159 int tiled, error;
faa9b3e7
TC
160 float xres, yres;
161 uint16 resunit;
162 int gotXres, gotYres;
fd9a31d2 163 uint16 photometric;
5bb828f1 164 uint16 bits_per_sample;
fd9a31d2 165 int i;
5bb828f1 166 int ch;
02d1d628
AMH
167
168 error = 0;
169
02d1d628
AMH
170 TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
171 TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);
5bb828f1 172 TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &channels);
02d1d628 173 tiled = TIFFIsTiled(tif);
5bb828f1
TC
174 TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric);
175 TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
02d1d628
AMH
176
177 mm_log((1, "i_readtiff_wiol: width=%d, height=%d, channels=%d\n", width, height, channels));
178 mm_log((1, "i_readtiff_wiol: %stiled\n", tiled?"":"not "));
179 mm_log((1, "i_readtiff_wiol: %sbyte swapped\n", TIFFIsByteSwapped(tif)?"":"not "));
faa9b3e7 180
5bb828f1
TC
181 if (photometric == PHOTOMETRIC_PALETTE && bits_per_sample <= 8) {
182 channels = 3;
183 im = i_img_pal_new(width, height, channels, 256);
184 }
185 else {
186 im = i_img_empty_ch(NULL, width, height, channels);
187 }
8c3af7b3
TC
188
189 if (!im)
190 return NULL;
f00e06a0
TC
191
192 /* general metadata */
193 i_tags_addn(&im->tags, "tiff_bitspersample", 0, bits_per_sample);
194 i_tags_addn(&im->tags, "tiff_photometric", 0, photometric);
5bb828f1 195
fd9a31d2 196 /* resolution tags */
5bb828f1 197 TIFFGetFieldDefaulted(tif, TIFFTAG_RESOLUTIONUNIT, &resunit);
faa9b3e7
TC
198 gotXres = TIFFGetField(tif, TIFFTAG_XRESOLUTION, &xres);
199 gotYres = TIFFGetField(tif, TIFFTAG_YRESOLUTION, &yres);
200 if (gotXres || gotYres) {
201 if (!gotXres)
202 xres = yres;
203 else if (!gotYres)
204 yres = xres;
3cff89e2 205 i_tags_addn(&im->tags, "tiff_resolutionunit", 0, resunit);
faa9b3e7
TC
206 if (resunit == RESUNIT_CENTIMETER) {
207 /* from dots per cm to dpi */
208 xres *= 2.54;
209 yres *= 2.54;
616b2541 210 i_tags_add(&im->tags, "tiff_resolutionunit_name", 0, "centimeter", -1, 0);
faa9b3e7 211 }
3cff89e2 212 else if (resunit == RESUNIT_NONE) {
faa9b3e7 213 i_tags_addn(&im->tags, "i_aspect_only", 0, 1);
3cff89e2
TC
214 i_tags_add(&im->tags, "tiff_resolutionunit_name", 0, "none", -1, 0);
215 }
216 else if (resunit == RESUNIT_INCH) {
217 i_tags_add(&im->tags, "tiff_resolutionunit_name", 0, "inch", -1, 0);
218 }
219 else {
220 i_tags_add(&im->tags, "tiff_resolutionunit_name", 0, "unknown", -1, 0);
221 }
2e41e30b
TC
222 /* tifflib doesn't seem to provide a way to get to the original rational
223 value of these, which would let me provide a more reasonable
224 precision. So make up a number. */
225 i_tags_set_float2(&im->tags, "i_xres", 0, xres, 6);
226 i_tags_set_float2(&im->tags, "i_yres", 0, yres, 6);
faa9b3e7 227 }
fd9a31d2
TC
228
229 /* Text tags */
230 for (i = 0; i < text_tag_count; ++i) {
231 char *data;
232 if (TIFFGetField(tif, text_tag_names[i].tag, &data)) {
233 mm_log((1, "i_readtiff_wiol: tag %d has value %s\n",
234 text_tag_names[i].tag, data));
235 i_tags_add(&im->tags, text_tag_names[i].name, 0, data,
236 strlen(data), 0);
237 }
238 }
8c3af7b3
TC
239
240 i_tags_add(&im->tags, "i_format", 0, "tiff", -1, 0);
ffeb4a67
TC
241 if (warn_buffer && *warn_buffer) {
242 i_tags_add(&im->tags, "i_warning", 0, warn_buffer, -1, 0);
243 *warn_buffer = '\0';
244 }
02d1d628
AMH
245
246 /* TIFFPrintDirectory(tif, stdout, 0); good for debugging */
02d1d628 247
5bb828f1
TC
248 if (photometric == PHOTOMETRIC_PALETTE &&
249 (bits_per_sample == 4 || bits_per_sample == 8)) {
250 uint16 *maps[3];
251 char used[256];
252 int maxused;
253 uint32 row, col;
254 unsigned char *buffer;
02d1d628 255
5bb828f1
TC
256 if (!TIFFGetField(tif, TIFFTAG_COLORMAP, maps+0, maps+1, maps+2)) {
257 i_push_error(0, "Cannot get colormap for paletted image");
5bb828f1 258 i_img_destroy(im);
02d1d628
AMH
259 return NULL;
260 }
5bb828f1
TC
261 buffer = (unsigned char *)_TIFFmalloc(width+2);
262 if (!buffer) {
263 i_push_error(0, "out of memory");
5bb828f1 264 i_img_destroy(im);
5bb828f1
TC
265 return NULL;
266 }
267 row = 0;
268 memset(used, 0, sizeof(used));
269 while (row < height && TIFFReadScanline(tif, buffer, row, 0) > 0) {
270 if (bits_per_sample == 4)
271 expand_4bit_hl(buffer, (width+1)/2);
272 for (col = 0; col < width; ++col) {
273 used[buffer[col]] = 1;
02d1d628 274 }
5bb828f1
TC
275 i_ppal(im, 0, width, row, buffer);
276 ++row;
02d1d628 277 }
5bb828f1
TC
278 if (row < height) {
279 error = 1;
02d1d628 280 }
5bb828f1
TC
281 /* Ideally we'd optimize the palette, but that could be expensive
282 since we'd have to re-index every pixel.
283
284 Optimizing the palette (even at this level) might not
285 be what the user wants, so I don't do it.
286
287 We'll add a function to optimize a paletted image instead.
288 */
289 maxused = (1 << bits_per_sample)-1;
290 if (!error) {
291 while (maxused >= 0 && !used[maxused])
292 --maxused;
293 }
294 for (i = 0; i < 1 << bits_per_sample; ++i) {
295 i_color c;
296 for (ch = 0; ch < 3; ++ch) {
297 c.channel[ch] = Sample16To8(maps[ch][i]);
298 }
299 i_addcolors(im, &c, 1);
300 }
301 _TIFFfree(buffer);
302 }
303 else {
304 if (tiled) {
305 int ok = 1;
306 uint32 row, col;
307 uint32 tile_width, tile_height;
308
309 TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width);
310 TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height);
311 mm_log((1, "i_readtiff_wiol: tile_width=%d, tile_height=%d\n", tile_width, tile_height));
02d1d628 312
5bb828f1
TC
313 raster = (uint32*)_TIFFmalloc(tile_width * tile_height * sizeof (uint32));
314 if (!raster) {
315 i_img_destroy(im);
316 i_push_error(0, "No space for raster buffer");
5bb828f1 317 return NULL;
02d1d628
AMH
318 }
319
5bb828f1
TC
320 for( row = 0; row < height; row += tile_height ) {
321 for( col = 0; ok && col < width; col += tile_width ) {
322 uint32 i_row, x, newrows, newcols;
323
324 /* Read the tile into an RGBA array */
325 if (!TIFFReadRGBATile(tif, col, row, raster)) {
326 ok = 0;
327 break;
328 }
329 newrows = (row+tile_height > height) ? height-row : tile_height;
330 mm_log((1, "i_readtiff_wiol: newrows=%d\n", newrows));
331 newcols = (col+tile_width > width ) ? width-row : tile_width;
332 for( i_row = 0; i_row < tile_height; i_row++ ) {
333 for(x = 0; x < newcols; x++) {
334 i_color val;
335 uint32 temp = raster[x+tile_width*(tile_height-i_row-1)];
336 val.rgba.r = TIFFGetR(temp);
337 val.rgba.g = TIFFGetG(temp);
338 val.rgba.b = TIFFGetB(temp);
339 val.rgba.a = TIFFGetA(temp);
340 i_ppix(im, col+x, row+i_row, &val);
341 }
342 }
343 }
344 }
345 } else {
346 uint32 rowsperstrip, row;
1b0554d1
AMH
347 int rc = TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
348 mm_log((1, "i_readtiff_wiol: rowsperstrip=%d rc = %d\n", rowsperstrip, rc));
349
350 if (rc != 1 || rowsperstrip==-1) {
351 rowsperstrip = height;
352 }
353
5bb828f1
TC
354 raster = (uint32*)_TIFFmalloc(width * rowsperstrip * sizeof (uint32));
355 if (!raster) {
356 i_img_destroy(im);
357 i_push_error(0, "No space for raster buffer");
5bb828f1
TC
358 return NULL;
359 }
02d1d628 360
5bb828f1
TC
361 for( row = 0; row < height; row += rowsperstrip ) {
362 uint32 newrows, i_row;
363
364 if (!TIFFReadRGBAStrip(tif, row, raster)) {
365 error++;
366 break;
367 }
368
369 newrows = (row+rowsperstrip > height) ? height-row : rowsperstrip;
370 mm_log((1, "newrows=%d\n", newrows));
371
372 for( i_row = 0; i_row < newrows; i_row++ ) {
373 uint32 x;
374 for(x = 0; x<width; x++) {
375 i_color val;
376 uint32 temp = raster[x+width*(newrows-i_row-1)];
377 val.rgba.r = TIFFGetR(temp);
378 val.rgba.g = TIFFGetG(temp);
379 val.rgba.b = TIFFGetB(temp);
380 val.rgba.a = TIFFGetA(temp);
381 i_ppix(im, x, i_row+row, &val);
382 }
383 }
02d1d628
AMH
384 }
385 }
02d1d628
AMH
386 }
387 if (error) {
388 mm_log((1, "i_readtiff_wiol: error during reading\n"));
5bb828f1 389 i_tags_addn(&im->tags, "i_incomplete", 0, 1);
02d1d628 390 }
5bb828f1
TC
391 if (raster)
392 _TIFFfree( raster );
10461f9a
TC
393
394 return im;
395}
396
397/*
398=item i_readtiff_wiol(im, ig)
399
400=cut
401*/
402i_img*
8f8bd9aa 403i_readtiff_wiol(io_glue *ig, int length, int page) {
10461f9a
TC
404 TIFF* tif;
405 TIFFErrorHandler old_handler;
be371490 406 TIFFErrorHandler old_warn_handler;
10461f9a
TC
407 i_img *im;
408
409 i_clear_error();
410 old_handler = TIFFSetErrorHandler(error_handler);
be371490 411 old_warn_handler = TIFFSetWarningHandler(warn_handler);
ffeb4a67
TC
412 if (warn_buffer)
413 *warn_buffer = '\0';
10461f9a
TC
414
415 /* Add code to get the filename info from the iolayer */
416 /* Also add code to check for mmapped code */
417
418 io_glue_commit_types(ig);
419 mm_log((1, "i_readtiff_wiol(ig %p, length %d)\n", ig, length));
420
421 tif = TIFFClientOpen("(Iolayer)",
422 "rm",
423 (thandle_t) ig,
424 (TIFFReadWriteProc) ig->readcb,
425 (TIFFReadWriteProc) ig->writecb,
426 (TIFFSeekProc) comp_seek,
427 (TIFFCloseProc) ig->closecb,
caa833d5 428 ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
e18f39b3
TC
429 (TIFFMapFileProc) comp_mmap,
430 (TIFFUnmapFileProc) comp_munmap);
10461f9a
TC
431
432 if (!tif) {
433 mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
434 i_push_error(0, "opening file");
435 TIFFSetErrorHandler(old_handler);
be371490 436 TIFFSetWarningHandler(old_warn_handler);
10461f9a
TC
437 return NULL;
438 }
439
8f8bd9aa
TC
440 if (page != 0) {
441 if (!TIFFSetDirectory(tif, page)) {
442 mm_log((1, "i_readtiff_wiol: Unable to switch to directory %d\n", page));
443 i_push_errorf(0, "could not switch to page %d", page);
444 TIFFSetErrorHandler(old_handler);
445 TIFFSetWarningHandler(old_warn_handler);
446 return NULL;
447 }
448 }
449
10461f9a
TC
450 im = read_one_tiff(tif);
451
02d1d628 452 if (TIFFLastDirectory(tif)) mm_log((1, "Last directory of tiff file\n"));
5bb828f1 453 TIFFSetErrorHandler(old_handler);
be371490 454 TIFFSetWarningHandler(old_warn_handler);
5bb828f1 455 TIFFClose(tif);
02d1d628
AMH
456 return im;
457}
458
10461f9a
TC
459/*
460=item i_readtiff_multi_wiol(ig, length, *count)
02d1d628 461
10461f9a 462Reads multiple images from a TIFF.
02d1d628 463
10461f9a
TC
464=cut
465*/
466i_img**
467i_readtiff_multi_wiol(io_glue *ig, int length, int *count) {
468 TIFF* tif;
469 TIFFErrorHandler old_handler;
be371490 470 TIFFErrorHandler old_warn_handler;
10461f9a
TC
471 i_img **results = NULL;
472 int result_alloc = 0;
473 int dirnum = 0;
02d1d628 474
10461f9a
TC
475 i_clear_error();
476 old_handler = TIFFSetErrorHandler(error_handler);
be371490 477 old_warn_handler = TIFFSetWarningHandler(warn_handler);
ffeb4a67
TC
478 if (warn_buffer)
479 *warn_buffer = '\0';
02d1d628 480
10461f9a
TC
481 /* Add code to get the filename info from the iolayer */
482 /* Also add code to check for mmapped code */
02d1d628 483
10461f9a
TC
484 io_glue_commit_types(ig);
485 mm_log((1, "i_readtiff_wiol(ig %p, length %d)\n", ig, length));
486
487 tif = TIFFClientOpen("(Iolayer)",
488 "rm",
489 (thandle_t) ig,
490 (TIFFReadWriteProc) ig->readcb,
491 (TIFFReadWriteProc) ig->writecb,
492 (TIFFSeekProc) comp_seek,
493 (TIFFCloseProc) ig->closecb,
e18f39b3
TC
494 ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
495 (TIFFMapFileProc) comp_mmap,
496 (TIFFUnmapFileProc) comp_munmap);
10461f9a
TC
497
498 if (!tif) {
499 mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
500 i_push_error(0, "opening file");
501 TIFFSetErrorHandler(old_handler);
be371490 502 TIFFSetWarningHandler(old_warn_handler);
10461f9a
TC
503 return NULL;
504 }
02d1d628 505
10461f9a
TC
506 *count = 0;
507 do {
508 i_img *im = read_one_tiff(tif);
509 if (!im)
510 break;
511 if (++*count > result_alloc) {
512 if (result_alloc == 0) {
513 result_alloc = 5;
514 results = mymalloc(result_alloc * sizeof(i_img *));
515 }
516 else {
517 i_img **newresults;
518 result_alloc *= 2;
519 newresults = myrealloc(results, result_alloc * sizeof(i_img *));
be371490
TC
520 if (!newresults) {
521 i_img_destroy(im); /* don't leak it */
522 break;
523 }
524 results = newresults;
10461f9a
TC
525 }
526 }
527 results[*count-1] = im;
528 } while (TIFFSetDirectory(tif, ++dirnum));
529
be371490 530 TIFFSetWarningHandler(old_warn_handler);
10461f9a
TC
531 TIFFSetErrorHandler(old_handler);
532 TIFFClose(tif);
533 return results;
534}
02d1d628
AMH
535
536undef_int
10461f9a
TC
537i_writetiff_low_faxable(TIFF *tif, i_img *im, int fine) {
538 uint32 width, height;
539 unsigned char *linebuf = NULL;
540 uint32 y;
541 int rc;
542 uint32 x;
10461f9a
TC
543 uint32 rowsperstrip;
544 float vres = fine ? 196 : 98;
545 int luma_chan;
546
547 width = im->xsize;
548 height = im->ysize;
549
550 switch (im->channels) {
551 case 1:
552 case 2:
553 luma_chan = 0;
554 break;
555 case 3:
556 case 4:
557 luma_chan = 1;
558 break;
559 default:
560 /* This means a colorspace we don't handle yet */
561 mm_log((1, "i_writetiff_wiol_faxable: don't handle %d channel images.\n", im->channels));
562 return 0;
563 }
564
565 /* Add code to get the filename info from the iolayer */
566 /* Also add code to check for mmapped code */
567
568
569 mm_log((1, "i_writetiff_wiol_faxable: width=%d, height=%d, channels=%d\n", width, height, im->channels));
570
571 if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width) )
572 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField width=%d failed\n", width)); return 0; }
573 if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height) )
574 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField length=%d failed\n", height)); return 0; }
575 if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1))
576 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField samplesperpixel=1 failed\n")); return 0; }
577 if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT))
578 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Orientation=topleft\n")); return 0; }
579 if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 1) )
580 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField bitpersample=1\n")); return 0; }
581 if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG))
582 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField planarconfig\n")); return 0; }
583 if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK))
584 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField photometric=%d\n", PHOTOMETRIC_MINISBLACK)); return 0; }
585 if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, 3))
586 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField compression=3\n")); return 0; }
587
588 linebuf = (unsigned char *)_TIFFmalloc( TIFFScanlineSize(tif) );
589
590 if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) {
591 mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField rowsperstrip=-1\n")); return 0; }
592
593 TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
594 TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rc);
595
596 mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField rowsperstrip=%d\n", rowsperstrip));
597 mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField scanlinesize=%d\n", TIFFScanlineSize(tif) ));
598 mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField planarconfig=%d == %d\n", rc, PLANARCONFIG_CONTIG));
599
600 if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)204))
601 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Xresolution=204\n")); return 0; }
602 if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, vres))
603 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Yresolution=196\n")); return 0; }
604 if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)) {
605 mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField ResolutionUnit=%d\n", RESUNIT_INCH)); return 0;
606 }
607
608 if (!save_tiff_tags(tif, im)) {
609 return 0;
610 }
611
612 for (y=0; y<height; y++) {
613 int linebufpos=0;
614 for(x=0; x<width; x+=8) {
615 int bits;
616 int bitpos;
617 i_sample_t luma[8];
618 uint8 bitval = 128;
619 linebuf[linebufpos]=0;
620 bits = width-x; if(bits>8) bits=8;
621 i_gsamp(im, x, x+8, y, luma, &luma_chan, 1);
622 for(bitpos=0;bitpos<bits;bitpos++) {
623 linebuf[linebufpos] |= ((luma[bitpos]>=128)?bitval:0);
624 bitval >>= 1;
625 }
626 linebufpos++;
627 }
628 if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
629 mm_log((1, "i_writetiff_wiol_faxable: TIFFWriteScanline failed.\n"));
630 break;
631 }
632 }
633 if (linebuf) _TIFFfree(linebuf);
634
635 return 1;
636}
637
638undef_int
639i_writetiff_low(TIFF *tif, i_img *im) {
02d1d628
AMH
640 uint32 width, height;
641 uint16 channels;
642 uint16 predictor = 0;
643 int quality = 75;
644 int jpegcolormode = JPEGCOLORMODE_RGB;
645 uint16 compression = COMPRESSION_PACKBITS;
646 i_color val;
647 uint16 photometric;
648 uint32 rowsperstrip = (uint32) -1; /* Let library pick default */
02d1d628
AMH
649 unsigned char *linebuf = NULL;
650 uint32 y;
651 tsize_t linebytes;
652 int ch, ci, rc;
653 uint32 x;
a659442a 654 int got_xres, got_yres, aspect_only, resunit;
faa9b3e7 655 double xres, yres;
f62b2d84
TC
656 uint16 bitspersample = 8;
657 uint16 samplesperpixel;
658 uint16 *colors = NULL;
02d1d628 659
02d1d628
AMH
660 width = im->xsize;
661 height = im->ysize;
662 channels = im->channels;
663
664 switch (channels) {
665 case 1:
666 photometric = PHOTOMETRIC_MINISBLACK;
667 break;
668 case 3:
669 photometric = PHOTOMETRIC_RGB;
670 if (compression == COMPRESSION_JPEG && jpegcolormode == JPEGCOLORMODE_RGB) photometric = PHOTOMETRIC_YCBCR;
f62b2d84
TC
671 else if (im->type == i_palette_type) {
672 photometric = PHOTOMETRIC_PALETTE;
673 }
02d1d628
AMH
674 break;
675 default:
676 /* This means a colorspace we don't handle yet */
677 mm_log((1, "i_writetiff_wiol: don't handle %d channel images.\n", channels));
678 return 0;
679 }
680
681 /* Add code to get the filename info from the iolayer */
682 /* Also add code to check for mmapped code */
683
10461f9a
TC
684 /*io_glue_commit_types(ig);*/
685 /*mm_log((1, "i_writetiff_wiol(im 0x%p, ig 0x%p)\n", im, ig));*/
02d1d628 686
10461f9a 687 mm_log((1, "i_writetiff_low: width=%d, height=%d, channels=%d\n", width, height, channels));
02d1d628 688
10461f9a
TC
689 if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width) ) {
690 mm_log((1, "i_writetiff_wiol: TIFFSetField width=%d failed\n", width));
691 return 0;
692 }
693 if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height) ) {
694 mm_log((1, "i_writetiff_wiol: TIFFSetField length=%d failed\n", height));
695 return 0;
696 }
697 if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT)) {
698 mm_log((1, "i_writetiff_wiol: TIFFSetField Orientation=topleft\n"));
699 return 0;
700 }
701 if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) {
702 mm_log((1, "i_writetiff_wiol: TIFFSetField planarconfig\n"));
703 return 0;
704 }
705 if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric)) {
706 mm_log((1, "i_writetiff_wiol: TIFFSetField photometric=%d\n", photometric));
707 return 0;
708 }
709 if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, compression)) {
710 mm_log((1, "i_writetiff_wiol: TIFFSetField compression=%d\n", compression));
711 return 0;
02d1d628 712 }
f62b2d84
TC
713 samplesperpixel = channels;
714 if (photometric == PHOTOMETRIC_PALETTE) {
715 uint16 *out[3];
716 i_color c;
717 int count = i_colorcount(im);
718 int size;
f62b2d84
TC
719 int ch, i;
720
721 samplesperpixel = 1;
722 if (count > 16)
723 bitspersample = 8;
724 else
725 bitspersample = 4;
726 size = 1 << bitspersample;
727 colors = (uint16 *)_TIFFmalloc(sizeof(uint16) * 3 * size);
728 out[0] = colors;
729 out[1] = colors + size;
730 out[2] = colors + 2 * size;
731
732 for (i = 0; i < count; ++i) {
733 i_getcolors(im, i, &c, 1);
734 for (ch = 0; ch < 3; ++ch)
735 out[ch][i] = c.channel[ch] * 257;
736 }
737 for (; i < size; ++i) {
738 for (ch = 0; ch < 3; ++ch)
739 out[ch][i] = 0;
740 }
741 if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bitspersample)) {
742 mm_log((1, "i_writetiff_wiol: TIFFSetField bitpersample=%d\n",
743 bitspersample));
744 return 0;
745 }
746 if (!TIFFSetField(tif, TIFFTAG_COLORMAP, out[0], out[1], out[2])) {
747 mm_log((1, "i_writetiff_wiol: TIFFSetField colormap\n"));
748 return 0;
749 }
750 }
751 else {
752 if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bitspersample)) {
753 mm_log((1, "i_writetiff_wiol: TIFFSetField bitpersample=%d\n",
754 bitspersample));
755 return 0;
756 }
757 }
758 if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel)) {
759 mm_log((1, "i_writetiff_wiol: TIFFSetField samplesperpixel=%d failed\n", samplesperpixel));
760 return 0;
761 }
02d1d628
AMH
762
763 switch (compression) {
764 case COMPRESSION_JPEG:
765 mm_log((1, "i_writetiff_wiol: jpeg compression\n"));
10461f9a
TC
766 if (!TIFFSetField(tif, TIFFTAG_JPEGQUALITY, quality) ) {
767 mm_log((1, "i_writetiff_wiol: TIFFSetField jpegquality=%d\n", quality));
768 return 0;
769 }
770 if (!TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, jpegcolormode)) {
771 mm_log((1, "i_writetiff_wiol: TIFFSetField jpegcolormode=%d\n", jpegcolormode));
772 return 0;
773 }
02d1d628
AMH
774 break;
775 case COMPRESSION_LZW:
776 mm_log((1, "i_writetiff_wiol: lzw compression\n"));
777 break;
778 case COMPRESSION_DEFLATE:
779 mm_log((1, "i_writetiff_wiol: deflate compression\n"));
780 if (predictor != 0)
10461f9a
TC
781 if (!TIFFSetField(tif, TIFFTAG_PREDICTOR, predictor)) {
782 mm_log((1, "i_writetiff_wiol: TIFFSetField predictor=%d\n", predictor));
783 return 0;
784 }
02d1d628
AMH
785 break;
786 case COMPRESSION_PACKBITS:
787 mm_log((1, "i_writetiff_wiol: packbits compression\n"));
788 break;
789 default:
790 mm_log((1, "i_writetiff_wiol: unknown compression %d\n", compression));
791 return 0;
792 }
793
794 linebytes = channels * width;
f62b2d84
TC
795 linebytes = TIFFScanlineSize(tif) > linebytes ? linebytes
796 : TIFFScanlineSize(tif);
797 /* working space for the scanlines - we go from 8-bit/pixel to 4 */
798 if (photometric == PHOTOMETRIC_PALETTE && bitspersample == 4)
799 linebytes += linebytes + 1;
800 linebuf = (unsigned char *)_TIFFmalloc(linebytes);
02d1d628
AMH
801
802 if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, rowsperstrip))) {
803 mm_log((1, "i_writetiff_wiol: TIFFSetField rowsperstrip=%d\n", rowsperstrip)); return 0; }
804
805 TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
806 TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rc);
807
808 mm_log((1, "i_writetiff_wiol: TIFFGetField rowsperstrip=%d\n", rowsperstrip));
809 mm_log((1, "i_writetiff_wiol: TIFFGetField scanlinesize=%d\n", TIFFScanlineSize(tif) ));
810 mm_log((1, "i_writetiff_wiol: TIFFGetField planarconfig=%d == %d\n", rc, PLANARCONFIG_CONTIG));
230e675b 811 mm_log((1, "i_writetiff_wiol: bitspersample = %d\n", bitspersample));
02d1d628 812
faa9b3e7
TC
813 got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres);
814 got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres);
815 if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only))
816 aspect_only = 0;
817 if (!i_tags_get_int(&im->tags, "tiff_resolutionunit", 0, &resunit))
818 resunit = RESUNIT_INCH;
819 if (got_xres || got_yres) {
820 if (!got_xres)
821 xres = yres;
822 else if (!got_yres)
823 yres = xres;
824 if (aspect_only) {
825 resunit = RESUNIT_NONE;
826 }
827 else {
828 if (resunit == RESUNIT_CENTIMETER) {
829 xres /= 2.54;
830 yres /= 2.54;
831 }
832 else {
833 resunit = RESUNIT_INCH;
834 }
835 }
836 if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)xres)) {
faa9b3e7
TC
837 i_push_error(0, "cannot set TIFFTAG_XRESOLUTION tag");
838 return 0;
839 }
840 if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, (float)yres)) {
faa9b3e7
TC
841 i_push_error(0, "cannot set TIFFTAG_YRESOLUTION tag");
842 return 0;
843 }
844 if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, (uint16)resunit)) {
faa9b3e7
TC
845 i_push_error(0, "cannot set TIFFTAG_RESOLUTIONUNIT tag");
846 return 0;
02d1d628
AMH
847 }
848 }
fd9a31d2
TC
849
850 if (!save_tiff_tags(tif, im)) {
fd9a31d2
TC
851 return 0;
852 }
853
f62b2d84
TC
854 if (photometric == PHOTOMETRIC_PALETTE) {
855 for (y = 0; y < height; ++y) {
856 i_gpal(im, 0, width, y, linebuf);
857 if (bitspersample == 4)
858 pack_4bit_hl(linebuf, width);
859 if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
860 mm_log((1, "i_writetiff_wiol: TIFFWriteScanline failed.\n"));
10461f9a
TC
861 if (linebuf) _TIFFfree(linebuf);
862 if (colors) _TIFFfree(colors);
863 return 0;
f62b2d84 864 }
02d1d628 865 }
f62b2d84
TC
866 }
867 else {
868 for (y=0; y<height; y++) {
869 ci = 0;
870 for(x=0; x<width; x++) {
871 (void) i_gpix(im, x, y,&val);
872 for(ch=0; ch<channels; ch++)
873 linebuf[ci++] = val.channel[ch];
874 }
875 if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
876 mm_log((1, "i_writetiff_wiol: TIFFWriteScanline failed.\n"));
10461f9a
TC
877 if (linebuf) _TIFFfree(linebuf);
878 if (colors) _TIFFfree(colors);
879 return 0;
f62b2d84 880 }
02d1d628
AMH
881 }
882 }
02d1d628 883 if (linebuf) _TIFFfree(linebuf);
f62b2d84 884 if (colors) _TIFFfree(colors);
02d1d628
AMH
885 return 1;
886}
887
d2dfdcc9 888/*
10461f9a 889=item i_writetiff_multi_wiol(ig, imgs, count, fine_mode)
d2dfdcc9 890
10461f9a 891Stores an image in the iolayer object.
d2dfdcc9 892
d2dfdcc9 893 ig - io_object that defines source to write to
10461f9a 894 imgs,count - the images to write
d2dfdcc9 895
10461f9a 896=cut
d2dfdcc9
TC
897*/
898
899undef_int
10461f9a 900i_writetiff_multi_wiol(io_glue *ig, i_img **imgs, int count) {
d2dfdcc9 901 TIFF* tif;
10461f9a 902 int i;
d2dfdcc9 903
10461f9a
TC
904 io_glue_commit_types(ig);
905 i_clear_error();
906 mm_log((1, "i_writetiff_multi_wiol(ig 0x%p, imgs 0x%p, count %d)\n",
907 ig, imgs, count));
d2dfdcc9 908
10461f9a
TC
909 /* FIXME: Enable the mmap interface */
910
911 tif = TIFFClientOpen("No name",
912 "wm",
913 (thandle_t) ig,
914 (TIFFReadWriteProc) ig->readcb,
915 (TIFFReadWriteProc) ig->writecb,
916 (TIFFSeekProc) comp_seek,
917 (TIFFCloseProc) ig->closecb,
e18f39b3
TC
918 ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
919 (TIFFMapFileProc) comp_mmap,
920 (TIFFUnmapFileProc) comp_munmap);
10461f9a
TC
921
922
923
924 if (!tif) {
925 mm_log((1, "i_writetiff_mulit_wiol: Unable to open tif file for writing\n"));
d2dfdcc9
TC
926 return 0;
927 }
928
10461f9a
TC
929 for (i = 0; i < count; ++i) {
930 if (!i_writetiff_low(tif, imgs[i])) {
931 TIFFClose(tif);
932 return 0;
933 }
934
935 if (!TIFFWriteDirectory(tif)) {
936 i_push_error(0, "Cannot write TIFF directory");
937 TIFFClose(tif);
938 return 0;
939 }
940 }
941
942 (void) TIFFClose(tif);
943 return 1;
944}
945
946/*
947=item i_writetiff_multi_wiol_faxable(ig, imgs, count, fine_mode)
948
949Stores an image in the iolayer object.
950
951 ig - io_object that defines source to write to
952 imgs,count - the images to write
953 fine_mode - select fine or normal mode fax images
954
955=cut
956*/
957
958
959undef_int
960i_writetiff_multi_wiol_faxable(io_glue *ig, i_img **imgs, int count, int fine) {
961 TIFF* tif;
962 int i;
d2dfdcc9
TC
963
964 io_glue_commit_types(ig);
10461f9a
TC
965 i_clear_error();
966 mm_log((1, "i_writetiff_multi_wiol(ig 0x%p, imgs 0x%p, count %d)\n",
967 ig, imgs, count));
d2dfdcc9
TC
968
969 /* FIXME: Enable the mmap interface */
970
971 tif = TIFFClientOpen("No name",
972 "wm",
973 (thandle_t) ig,
974 (TIFFReadWriteProc) ig->readcb,
975 (TIFFReadWriteProc) ig->writecb,
976 (TIFFSeekProc) comp_seek,
977 (TIFFCloseProc) ig->closecb,
e18f39b3
TC
978 ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
979 (TIFFMapFileProc) comp_mmap,
980 (TIFFUnmapFileProc) comp_munmap);
10461f9a
TC
981
982
d2dfdcc9
TC
983
984 if (!tif) {
10461f9a 985 mm_log((1, "i_writetiff_mulit_wiol: Unable to open tif file for writing\n"));
d2dfdcc9
TC
986 return 0;
987 }
988
10461f9a
TC
989 for (i = 0; i < count; ++i) {
990 if (!i_writetiff_low_faxable(tif, imgs[i], fine)) {
991 TIFFClose(tif);
992 return 0;
993 }
d2dfdcc9 994
10461f9a
TC
995 if (!TIFFWriteDirectory(tif)) {
996 i_push_error(0, "Cannot write TIFF directory");
997 TIFFClose(tif);
998 return 0;
999 }
1000 }
d2dfdcc9 1001
10461f9a
TC
1002 (void) TIFFClose(tif);
1003 return 1;
1004}
d2dfdcc9 1005
10461f9a
TC
1006/*
1007=item i_writetiff_wiol(im, ig)
d2dfdcc9 1008
10461f9a
TC
1009Stores an image in the iolayer object.
1010
1011 im - image object to write out
1012 ig - io_object that defines source to write to
1013
1014=cut
1015*/
1016undef_int
1017i_writetiff_wiol(i_img *img, io_glue *ig) {
1018 TIFF* tif;
10461f9a
TC
1019
1020 io_glue_commit_types(ig);
1021 i_clear_error();
1022 mm_log((1, "i_writetiff_wiol(img %p, ig 0x%p)\n", img, ig));
1023
1024 /* FIXME: Enable the mmap interface */
1025
1026 tif = TIFFClientOpen("No name",
1027 "wm",
1028 (thandle_t) ig,
1029 (TIFFReadWriteProc) ig->readcb,
1030 (TIFFReadWriteProc) ig->writecb,
1031 (TIFFSeekProc) comp_seek,
1032 (TIFFCloseProc) ig->closecb,
e18f39b3
TC
1033 ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
1034 (TIFFMapFileProc) comp_mmap,
1035 (TIFFUnmapFileProc) comp_munmap);
10461f9a
TC
1036
1037
1038
1039 if (!tif) {
1040 mm_log((1, "i_writetiff_wiol: Unable to open tif file for writing\n"));
1041 return 0;
d2dfdcc9 1042 }
d2dfdcc9 1043
10461f9a 1044 if (!i_writetiff_low(tif, img)) {
fd9a31d2
TC
1045 TIFFClose(tif);
1046 return 0;
1047 }
1048
10461f9a
TC
1049 (void) TIFFClose(tif);
1050 return 1;
1051}
1052
1053
1054
1055/*
1056=item i_writetiff_wiol_faxable(i_img *, io_glue *)
1057
1058Stores an image in the iolayer object in faxable tiff format.
1059
1060 im - image object to write out
1061 ig - io_object that defines source to write to
1062
1063Note, this may be rewritten to use to simply be a call to a
1064lower-level function that gives more options for writing tiff at some
1065point.
1066
1067=cut
1068*/
1069
1070undef_int
1071i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine) {
1072 TIFF* tif;
10461f9a
TC
1073
1074 io_glue_commit_types(ig);
1075 i_clear_error();
1076 mm_log((1, "i_writetiff_wiol(img %p, ig 0x%p)\n", im, ig));
1077
1078 /* FIXME: Enable the mmap interface */
1079
1080 tif = TIFFClientOpen("No name",
1081 "wm",
1082 (thandle_t) ig,
1083 (TIFFReadWriteProc) ig->readcb,
1084 (TIFFReadWriteProc) ig->writecb,
1085 (TIFFSeekProc) comp_seek,
1086 (TIFFCloseProc) ig->closecb,
e18f39b3
TC
1087 ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
1088 (TIFFMapFileProc) comp_mmap,
1089 (TIFFUnmapFileProc) comp_munmap);
10461f9a
TC
1090
1091
1092
1093 if (!tif) {
1094 mm_log((1, "i_writetiff_wiol: Unable to open tif file for writing\n"));
1095 return 0;
d2dfdcc9 1096 }
10461f9a
TC
1097
1098 if (!i_writetiff_low_faxable(tif, im, fine)) {
1099 TIFFClose(tif);
1100 return 0;
1101 }
1102
d2dfdcc9 1103 (void) TIFFClose(tif);
d2dfdcc9
TC
1104 return 1;
1105}
1106
fd9a31d2
TC
1107static int save_tiff_tags(TIFF *tif, i_img *im) {
1108 int i;
10461f9a 1109
fd9a31d2
TC
1110 for (i = 0; i < text_tag_count; ++i) {
1111 int entry;
1112 if (i_tags_find(&im->tags, text_tag_names[i].name, 0, &entry)) {
1113 if (!TIFFSetField(tif, text_tag_names[i].tag,
10461f9a
TC
1114 im->tags.tags[entry].data)) {
1115 i_push_errorf(0, "cannot save %s to TIFF", text_tag_names[i].name);
1116 return 0;
fd9a31d2
TC
1117 }
1118 }
1119 }
10461f9a 1120
fd9a31d2
TC
1121 return 1;
1122}
b8c2033e 1123
10461f9a 1124
5bb828f1
TC
1125/*
1126=item expand_4bit_hl(buf, count)
1127
1128Expands 4-bit/entry packed data into 1 byte/entry.
1129
1130buf must contain count bytes to be expanded and have 2*count bytes total
1131space.
1132
1133The data is expanded in place.
1134
1135=cut
1136*/
1137
1138static void expand_4bit_hl(unsigned char *buf, int count) {
1139 while (--count >= 0) {
1140 buf[count*2+1] = buf[count] & 0xF;
1141 buf[count*2] = buf[count] >> 4;
1142 }
1143}
1144
f62b2d84 1145static void pack_4bit_hl(unsigned char *buf, int count) {
230e675b 1146 int i = 0;
f62b2d84
TC
1147 while (i < count) {
1148 buf[i/2] = (buf[i] << 4) + buf[i+1];
1149 i += 2;
1150 }
1151}
5bb828f1 1152
b8c2033e
AMH
1153/*
1154=back
1155
1156=head1 AUTHOR
1157
1158Arnar M. Hrafnkelsson <addi@umich.edu>
1159
1160=head1 SEE ALSO
1161
1162Imager(3)
1163
1164=cut
1165*/