Fixed i_transform2() so malloc(0) doesn't happen. Also corrected pod errors and
[imager.git] / tiff.c
CommitLineData
02d1d628
AMH
1#include "image.h"
2#include "tiffio.h"
3#include "iolayer.h"
4
02d1d628
AMH
5/*
6=head1 NAME
7
8tiff.c - implements reading and writing tiff files, uses io layer.
9
10=head1 SYNOPSIS
11
12 io_glue *ig = io_new_fd( fd );
13 i_img *im = i_readtiff_wiol(ig, -1); // no limit on how much is read
14 // or
15 io_glue *ig = io_new_fd( fd );
16 return_code = i_writetiff_wiol(im, ig);
17
18=head1 DESCRIPTION
19
20tiff.c implements the basic functions to read and write tiff files.
21It uses the iolayer and needs either a seekable source or an entire
22memory mapped buffer.
23
24=head1 FUNCTION REFERENCE
25
26Some of these functions are internal.
27
b8c2033e 28=over
02d1d628
AMH
29
30=cut
31*/
32
02d1d628
AMH
33/*
34=item comp_seek(h, o, w)
35
36Compatability for 64 bit systems like latest freebsd (internal)
37
38 h - tiff handle, cast an io_glue object
39 o - offset
40 w - whence
41
42=cut
43*/
44
45static
46toff_t
47comp_seek(thandle_t h, toff_t o, int w) {
48 io_glue *ig = (io_glue*)h;
49 return (toff_t) ig->seekcb(ig, o, w);
50}
51
52
53/*
54=item i_readtiff_wiol(ig, length)
55
56Retrieve an image and stores in the iolayer object. Returns NULL on fatal error.
57
58 ig - io_glue object
59 length - maximum length to read from data source, before closing it
60
61=cut
62*/
63
64i_img*
65i_readtiff_wiol(io_glue *ig, int length) {
66 i_img *im;
67 uint32 width, height;
68 uint16 channels;
69 uint32* raster;
70 int tiled, error;
71 TIFF* tif;
faa9b3e7
TC
72 float xres, yres;
73 uint16 resunit;
74 int gotXres, gotYres;
02d1d628
AMH
75
76 error = 0;
77
78 /* Add code to get the filename info from the iolayer */
79 /* Also add code to check for mmapped code */
80
81 io_glue_commit_types(ig);
895dbd34 82 mm_log((1, "i_readtiff_wiol(ig %p, length %d)\n", ig, length));
02d1d628
AMH
83
84 tif = TIFFClientOpen("Iolayer: FIXME",
85 "rm",
86 (thandle_t) ig,
87 (TIFFReadWriteProc) ig->readcb,
88 (TIFFReadWriteProc) ig->writecb,
89 (TIFFSeekProc) comp_seek,
90 (TIFFCloseProc) ig->closecb,
91 (TIFFSizeProc) ig->sizecb,
92 (TIFFMapFileProc) NULL,
93 (TIFFUnmapFileProc) NULL);
94
95 if (!tif) {
96 mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
97 return NULL;
98 }
99
100 TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
101 TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);
102 TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &channels);
103 tiled = TIFFIsTiled(tif);
104
105 mm_log((1, "i_readtiff_wiol: width=%d, height=%d, channels=%d\n", width, height, channels));
106 mm_log((1, "i_readtiff_wiol: %stiled\n", tiled?"":"not "));
107 mm_log((1, "i_readtiff_wiol: %sbyte swapped\n", TIFFIsByteSwapped(tif)?"":"not "));
108
109 im = i_img_empty_ch(NULL, width, height, channels);
faa9b3e7
TC
110
111 if (!TIFFGetField(tif, TIFFTAG_RESOLUTIONUNIT, &resunit))
112 resunit = RESUNIT_INCH;
113 gotXres = TIFFGetField(tif, TIFFTAG_XRESOLUTION, &xres);
114 gotYres = TIFFGetField(tif, TIFFTAG_YRESOLUTION, &yres);
115 if (gotXres || gotYres) {
116 if (!gotXres)
117 xres = yres;
118 else if (!gotYres)
119 yres = xres;
120 if (resunit == RESUNIT_CENTIMETER) {
121 /* from dots per cm to dpi */
122 xres *= 2.54;
123 yres *= 2.54;
124 }
125 i_tags_addn(&im->tags, "tiff_resolutionunit", 0, resunit);
126 if (resunit == RESUNIT_NONE)
127 i_tags_addn(&im->tags, "i_aspect_only", 0, 1);
128 i_tags_set_float(&im->tags, "i_xres", 0, xres);
129 i_tags_set_float(&im->tags, "i_yres", 0, yres);
130 }
02d1d628
AMH
131
132 /* TIFFPrintDirectory(tif, stdout, 0); good for debugging */
133
134 if (tiled) {
135 int ok = 1;
136 uint32 row, col;
137 uint32 tile_width, tile_height;
138
139 TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width);
140 TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height);
141 mm_log((1, "i_readtiff_wiol: tile_width=%d, tile_height=%d\n", tile_width, tile_height));
142
143 raster = (uint32*)_TIFFmalloc(tile_width * tile_height * sizeof (uint32));
144 if (!raster) {
145 TIFFError(TIFFFileName(tif), "No space for raster buffer");
146 return NULL;
147 }
148
149 for( row = 0; row < height; row += tile_height ) {
150 for( col = 0; ok && col < width; col += tile_width ) {
151 uint32 i_row, x, newrows, newcols;
152
153 /* Read the tile into an RGBA array */
154 if (!TIFFReadRGBATile(tif, col, row, raster)) {
155 ok = 0;
156 break;
157 }
158 newrows = (row+tile_height > height) ? height-row : tile_height;
159 mm_log((1, "i_readtiff_wiol: newrows=%d\n", newrows));
160 newcols = (col+tile_width > width ) ? width-row : tile_width;
161 for( i_row = 0; i_row < tile_height; i_row++ ) {
162 for(x = 0; x < newcols; x++) {
163 i_color val; /* FIXME: Make sure this works everywhere */
164 val.ui = raster[x+tile_width*(tile_height-i_row-1)];
165 i_ppix(im, col+x, row+i_row, &val);
166 }
167 }
168 }
169 }
170 } else {
171 uint32 rowsperstrip, row;
172 TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
173 mm_log((1, "i_readtiff_wiol: rowsperstrip=%d\n", rowsperstrip));
174
175 raster = (uint32*)_TIFFmalloc(width * rowsperstrip * sizeof (uint32));
176 if (!raster) {
177 TIFFError(TIFFFileName(tif), "No space for raster buffer");
178 return NULL;
179 }
180
181 for( row = 0; row < height; row += rowsperstrip ) {
182 uint32 newrows, i_row;
183
184 if (!TIFFReadRGBAStrip(tif, row, raster)) {
185 error++;
186 break;
187 }
188
189 newrows = (row+rowsperstrip > height) ? height-row : rowsperstrip;
190 mm_log((1, "newrows=%d\n", newrows));
191
192 for( i_row = 0; i_row < newrows; i_row++ ) {
193 uint32 x;
194 for(x = 0; x<width; x++) {
195 i_color val; /* FIXME: Make sure this works everywhere */
196 val.ui = raster[x+width*(newrows-i_row-1)];
197 i_ppix(im, x, i_row+row, &val);
198 }
199 }
200 }
201
202 }
203 if (error) {
204 mm_log((1, "i_readtiff_wiol: error during reading\n"));
205 }
206 _TIFFfree( raster );
207 if (TIFFLastDirectory(tif)) mm_log((1, "Last directory of tiff file\n"));
208 return im;
209}
210
211
212
213/*
214=item i_writetif_wiol(im, ig)
215
216Stores an image in the iolayer object.
217
218 im - image object to write out
219 ig - io_object that defines source to write to
220
221=cut
222*/
223
224
225/* FIXME: Add an options array in here soonish */
226
227undef_int
228i_writetiff_wiol(i_img *im, io_glue *ig) {
229 uint32 width, height;
230 uint16 channels;
231 uint16 predictor = 0;
232 int quality = 75;
233 int jpegcolormode = JPEGCOLORMODE_RGB;
234 uint16 compression = COMPRESSION_PACKBITS;
235 i_color val;
236 uint16 photometric;
237 uint32 rowsperstrip = (uint32) -1; /* Let library pick default */
02d1d628
AMH
238 unsigned char *linebuf = NULL;
239 uint32 y;
240 tsize_t linebytes;
241 int ch, ci, rc;
242 uint32 x;
243 TIFF* tif;
faa9b3e7
TC
244 int got_xres, got_yres, got_aspectonly, aspect_only, resunit;
245 double xres, yres;
02d1d628
AMH
246
247 char *cc = mymalloc( 123 );
248 myfree(cc);
249
250
251 width = im->xsize;
252 height = im->ysize;
253 channels = im->channels;
254
255 switch (channels) {
256 case 1:
257 photometric = PHOTOMETRIC_MINISBLACK;
258 break;
259 case 3:
260 photometric = PHOTOMETRIC_RGB;
261 if (compression == COMPRESSION_JPEG && jpegcolormode == JPEGCOLORMODE_RGB) photometric = PHOTOMETRIC_YCBCR;
262 break;
263 default:
264 /* This means a colorspace we don't handle yet */
265 mm_log((1, "i_writetiff_wiol: don't handle %d channel images.\n", channels));
266 return 0;
267 }
268
269 /* Add code to get the filename info from the iolayer */
270 /* Also add code to check for mmapped code */
271
272 io_glue_commit_types(ig);
273 mm_log((1, "i_writetiff_wiol(im 0x%p, ig 0x%p)\n", im, ig));
274
275 /* FIXME: Enable the mmap interface */
276
277 tif = TIFFClientOpen("No name",
278 "wm",
279 (thandle_t) ig,
280 (TIFFReadWriteProc) ig->readcb,
281 (TIFFReadWriteProc) ig->writecb,
282 (TIFFSeekProc) comp_seek,
283 (TIFFCloseProc) ig->closecb,
284 (TIFFSizeProc) ig->sizecb,
285 (TIFFMapFileProc) NULL,
286 (TIFFUnmapFileProc) NULL);
287
288
289
290 if (!tif) {
291 mm_log((1, "i_writetiff_wiol: Unable to open tif file for writing\n"));
292 return 0;
293 }
294
295 mm_log((1, "i_writetiff_wiol: width=%d, height=%d, channels=%d\n", width, height, channels));
296
297 if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width) ) { mm_log((1, "i_writetiff_wiol: TIFFSetField width=%d failed\n", width)); return 0; }
298 if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height) ) { mm_log((1, "i_writetiff_wiol: TIFFSetField length=%d failed\n", height)); return 0; }
299 if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, channels)) { mm_log((1, "i_writetiff_wiol: TIFFSetField samplesperpixel=%d failed\n", channels)); return 0; }
300 if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT)) { mm_log((1, "i_writetiff_wiol: TIFFSetField Orientation=topleft\n")); return 0; }
301 if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8) ) { mm_log((1, "i_writetiff_wiol: TIFFSetField bitpersample=8\n")); return 0; }
302 if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) { mm_log((1, "i_writetiff_wiol: TIFFSetField planarconfig\n")); return 0; }
303 if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric)) { mm_log((1, "i_writetiff_wiol: TIFFSetField photometric=%d\n", photometric)); return 0; }
304 if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, compression)) { mm_log((1, "i_writetiff_wiol: TIFFSetField compression=%d\n", compression)); return 0; }
305
306 switch (compression) {
307 case COMPRESSION_JPEG:
308 mm_log((1, "i_writetiff_wiol: jpeg compression\n"));
309 if (!TIFFSetField(tif, TIFFTAG_JPEGQUALITY, quality) ) { mm_log((1, "i_writetiff_wiol: TIFFSetField jpegquality=%d\n", quality)); return 0; }
310 if (!TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, jpegcolormode)) { mm_log((1, "i_writetiff_wiol: TIFFSetField jpegcolormode=%d\n", jpegcolormode)); return 0; }
311 break;
312 case COMPRESSION_LZW:
313 mm_log((1, "i_writetiff_wiol: lzw compression\n"));
314 break;
315 case COMPRESSION_DEFLATE:
316 mm_log((1, "i_writetiff_wiol: deflate compression\n"));
317 if (predictor != 0)
318 if (!TIFFSetField(tif, TIFFTAG_PREDICTOR, predictor)) { mm_log((1, "i_writetiff_wiol: TIFFSetField predictor=%d\n", predictor)); return 0; }
319 break;
320 case COMPRESSION_PACKBITS:
321 mm_log((1, "i_writetiff_wiol: packbits compression\n"));
322 break;
323 default:
324 mm_log((1, "i_writetiff_wiol: unknown compression %d\n", compression));
325 return 0;
326 }
327
328 linebytes = channels * width;
329 linebuf = (unsigned char *)_TIFFmalloc( TIFFScanlineSize(tif) > linebytes ?
330 linebytes : TIFFScanlineSize(tif) );
331
332 if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, rowsperstrip))) {
333 mm_log((1, "i_writetiff_wiol: TIFFSetField rowsperstrip=%d\n", rowsperstrip)); return 0; }
334
335 TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
336 TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rc);
337
338 mm_log((1, "i_writetiff_wiol: TIFFGetField rowsperstrip=%d\n", rowsperstrip));
339 mm_log((1, "i_writetiff_wiol: TIFFGetField scanlinesize=%d\n", TIFFScanlineSize(tif) ));
340 mm_log((1, "i_writetiff_wiol: TIFFGetField planarconfig=%d == %d\n", rc, PLANARCONFIG_CONTIG));
341
faa9b3e7
TC
342 got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres);
343 got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres);
344 if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only))
345 aspect_only = 0;
346 if (!i_tags_get_int(&im->tags, "tiff_resolutionunit", 0, &resunit))
347 resunit = RESUNIT_INCH;
348 if (got_xres || got_yres) {
349 if (!got_xres)
350 xres = yres;
351 else if (!got_yres)
352 yres = xres;
353 if (aspect_only) {
354 resunit = RESUNIT_NONE;
355 }
356 else {
357 if (resunit == RESUNIT_CENTIMETER) {
358 xres /= 2.54;
359 yres /= 2.54;
360 }
361 else {
362 resunit = RESUNIT_INCH;
363 }
364 }
365 if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)xres)) {
366 TIFFClose(tif);
367 i_img_destroy(im);
368 i_push_error(0, "cannot set TIFFTAG_XRESOLUTION tag");
369 return 0;
370 }
371 if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, (float)yres)) {
372 TIFFClose(tif);
373 i_img_destroy(im);
374 i_push_error(0, "cannot set TIFFTAG_YRESOLUTION tag");
375 return 0;
376 }
377 if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, (uint16)resunit)) {
378 TIFFClose(tif);
379 i_img_destroy(im);
380 i_push_error(0, "cannot set TIFFTAG_RESOLUTIONUNIT tag");
381 return 0;
02d1d628
AMH
382 }
383 }
384
385 for (y=0; y<height; y++) {
386 ci = 0;
387 for(x=0; x<width; x++) {
388 (void) i_gpix(im, x, y,&val);
389 for(ch=0; ch<channels; ch++) linebuf[ci++] = val.channel[ch];
390 }
391 if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
392 mm_log((1, "i_writetiff_wiol: TIFFWriteScanline failed.\n"));
393 break;
394 }
395 }
396 (void) TIFFClose(tif);
397 if (linebuf) _TIFFfree(linebuf);
398 return 1;
399}
400
d2dfdcc9
TC
401/*
402=item i_writetiff_wiol_faxable(i_img *, io_glue *)
403
404Stores an image in the iolayer object in faxable tiff format.
405
406 im - image object to write out
407 ig - io_object that defines source to write to
408
409Note, this may be rewritten to use to simply be a call to a
410lower-level function that gives more options for writing tiff at some
411point.
412
413=cut
414*/
415
416undef_int
4c2d6970 417i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine) {
d2dfdcc9 418 uint32 width, height;
d2dfdcc9
TC
419 unsigned char *linebuf = NULL;
420 uint32 y;
a743c0a6 421 int rc;
d2dfdcc9
TC
422 uint32 x;
423 TIFF* tif;
faa9b3e7 424 int luma_mask;
d2dfdcc9 425 uint32 rowsperstrip;
4c2d6970 426 float vres = fine ? 196 : 98;
faa9b3e7 427 int luma_chan;
d2dfdcc9
TC
428
429 width = im->xsize;
430 height = im->ysize;
431
432 switch (im->channels) {
433 case 1:
faa9b3e7
TC
434 case 2:
435 luma_chan = 0;
d2dfdcc9
TC
436 break;
437 case 3:
faa9b3e7
TC
438 case 4:
439 luma_chan = 1;
d2dfdcc9
TC
440 break;
441 default:
442 /* This means a colorspace we don't handle yet */
443 mm_log((1, "i_writetiff_wiol_faxable: don't handle %d channel images.\n", im->channels));
444 return 0;
445 }
446
447 /* Add code to get the filename info from the iolayer */
448 /* Also add code to check for mmapped code */
449
450 io_glue_commit_types(ig);
451 mm_log((1, "i_writetiff_wiol_faxable(im 0x%p, ig 0x%p)\n", im, ig));
452
453 /* FIXME: Enable the mmap interface */
454
455 tif = TIFFClientOpen("No name",
456 "wm",
457 (thandle_t) ig,
458 (TIFFReadWriteProc) ig->readcb,
459 (TIFFReadWriteProc) ig->writecb,
460 (TIFFSeekProc) comp_seek,
461 (TIFFCloseProc) ig->closecb,
462 (TIFFSizeProc) ig->sizecb,
463 (TIFFMapFileProc) NULL,
464 (TIFFUnmapFileProc) NULL);
465
466 if (!tif) {
467 mm_log((1, "i_writetiff_wiol_faxable: Unable to open tif file for writing\n"));
468 return 0;
469 }
470
471 mm_log((1, "i_writetiff_wiol_faxable: width=%d, height=%d, channels=%d\n", width, height, im->channels));
472
473 if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width) )
474 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField width=%d failed\n", width)); return 0; }
475 if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height) )
476 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField length=%d failed\n", height)); return 0; }
477 if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1))
478 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField samplesperpixel=1 failed\n")); return 0; }
479 if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT))
480 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Orientation=topleft\n")); return 0; }
481 if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 1) )
482 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField bitpersample=1\n")); return 0; }
483 if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG))
484 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField planarconfig\n")); return 0; }
485 if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK))
486 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField photometric=%d\n", PHOTOMETRIC_MINISBLACK)); return 0; }
487 if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, 3))
488 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField compression=3\n")); return 0; }
489
490 linebuf = (unsigned char *)_TIFFmalloc( TIFFScanlineSize(tif) );
491
492 if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) {
493 mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField rowsperstrip=-1\n")); return 0; }
494
495 TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
496 TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rc);
497
498 mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField rowsperstrip=%d\n", rowsperstrip));
499 mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField scanlinesize=%d\n", TIFFScanlineSize(tif) ));
500 mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField planarconfig=%d == %d\n", rc, PLANARCONFIG_CONTIG));
501
4c2d6970 502 if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)204))
d2dfdcc9 503 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Xresolution=204\n")); return 0; }
4c2d6970 504 if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, vres))
d2dfdcc9
TC
505 { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Yresolution=196\n")); return 0; }
506 if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)) {
507 mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField ResolutionUnit=%d\n", RESUNIT_INCH)); return 0;
508 }
d2dfdcc9
TC
509
510 for (y=0; y<height; y++) {
511 int linebufpos=0;
512 for(x=0; x<width; x+=8) {
513 int bits;
514 int bitpos;
faa9b3e7 515 i_sample_t luma[8];
d2dfdcc9
TC
516 uint8 bitval = 128;
517 linebuf[linebufpos]=0;
518 bits = width-x; if(bits>8) bits=8;
faa9b3e7 519 i_gsamp(im, x, x+8, y, luma, &luma_chan, 1);
d2dfdcc9 520 for(bitpos=0;bitpos<bits;bitpos++) {
faa9b3e7 521 linebuf[linebufpos] |= ((luma[bitpos]>=128)?bitval:0);
d2dfdcc9
TC
522 bitval >>= 1;
523 }
524 linebufpos++;
525 }
526 if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
527 mm_log((1, "i_writetiff_wiol_faxable: TIFFWriteScanline failed.\n"));
528 break;
529 }
530 }
531 (void) TIFFClose(tif);
532 if (linebuf) _TIFFfree(linebuf);
533 return 1;
534}
535
b8c2033e
AMH
536
537/*
538=back
539
540=head1 AUTHOR
541
542Arnar M. Hrafnkelsson <addi@umich.edu>
543
544=head1 SEE ALSO
545
546Imager(3)
547
548=cut
549*/