--- /dev/null
+#include <tiffio.h>
+#include <string.h>
+#include "imtiff.h"
+#include "imext.h"
+
+/* needed to implement our substitute TIFFIsCODECConfigured */
+#if TIFFLIB_VERSION < 20031121
+static int TIFFIsCODECConfigured(uint16 scheme);
+#endif
+
+/*
+=head1 NAME
+
+tiff.c - implements reading and writing tiff files, uses io layer.
+
+=head1 SYNOPSIS
+
+ io_glue *ig = io_new_fd( fd );
+ i_img *im = i_readtiff_wiol(ig, -1); // no limit on how much is read
+ // or
+ io_glue *ig = io_new_fd( fd );
+ return_code = i_writetiff_wiol(im, ig);
+
+=head1 DESCRIPTION
+
+tiff.c implements the basic functions to read and write tiff files.
+It uses the iolayer and needs either a seekable source or an entire
+memory mapped buffer.
+
+=head1 FUNCTION REFERENCE
+
+Some of these functions are internal.
+
+=over
+
+=cut
+*/
+
+#define byteswap_macro(x) \
+ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
+ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
+
+#define CLAMP8(x) ((x) < 0 ? 0 : (x) > 255 ? 255 : (x))
+#define CLAMP16(x) ((x) < 0 ? 0 : (x) > 65535 ? 65535 : (x))
+
+#define Sample16To8(num) ((num) / 257)
+
+struct tag_name {
+ char *name;
+ uint32 tag;
+};
+
+static i_img *read_one_rgb_tiled(TIFF *tif, int width, int height, int allow_incomplete);
+static i_img *read_one_rgb_lines(TIFF *tif, int width, int height, int allow_incomplete);
+
+static struct tag_name text_tag_names[] =
+{
+ { "tiff_documentname", TIFFTAG_DOCUMENTNAME, },
+ { "tiff_imagedescription", TIFFTAG_IMAGEDESCRIPTION, },
+ { "tiff_make", TIFFTAG_MAKE, },
+ { "tiff_model", TIFFTAG_MODEL, },
+ { "tiff_pagename", TIFFTAG_PAGENAME, },
+ { "tiff_software", TIFFTAG_SOFTWARE, },
+ { "tiff_datetime", TIFFTAG_DATETIME, },
+ { "tiff_artist", TIFFTAG_ARTIST, },
+ { "tiff_hostcomputer", TIFFTAG_HOSTCOMPUTER, },
+};
+
+static struct tag_name
+compress_values[] =
+ {
+ { "none", COMPRESSION_NONE },
+ { "ccittrle", COMPRESSION_CCITTRLE },
+ { "fax3", COMPRESSION_CCITTFAX3 },
+ { "t4", COMPRESSION_CCITTFAX3 },
+ { "fax4", COMPRESSION_CCITTFAX4 },
+ { "t6", COMPRESSION_CCITTFAX4 },
+ { "lzw", COMPRESSION_LZW },
+ { "jpeg", COMPRESSION_JPEG },
+ { "packbits", COMPRESSION_PACKBITS },
+ { "deflate", COMPRESSION_ADOBE_DEFLATE },
+ { "zip", COMPRESSION_ADOBE_DEFLATE },
+ { "oldzip", COMPRESSION_DEFLATE },
+ { "ccittrlew", COMPRESSION_CCITTRLEW },
+ };
+
+static const int compress_value_count =
+ sizeof(compress_values) / sizeof(*compress_values);
+
+static int
+myTIFFIsCODECConfigured(uint16 scheme);
+
+typedef struct read_state_tag read_state_t;
+/* the setup function creates the image object, allocates the line buffer */
+typedef int (*read_setup_t)(read_state_t *state);
+
+/* the putter writes the image data provided by the getter to the
+ image, x, y, width, height describe the target area of the image,
+ extras is the extra number of pixels stored for each scanline in
+ the raster buffer, (for tiles against the right side of the
+ image) */
+
+typedef int (*read_putter_t)(read_state_t *state, int x, int y, int width,
+ int height, int extras);
+
+/* reads from a tiled or strip image and calls the putter.
+ This may need a second type for handling non-contiguous images
+ at some point */
+typedef int (*read_getter_t)(read_state_t *state, read_putter_t putter);
+
+struct read_state_tag {
+ TIFF *tif;
+ i_img *img;
+ void *raster;
+ unsigned long pixels_read;
+ int allow_incomplete;
+ void *line_buf;
+ uint32 width, height;
+ uint16 bits_per_sample;
+ uint16 photometric;
+
+ /* the total number of channels (samples per pixel) */
+ int samples_per_pixel;
+
+ /* if non-zero, which channel is the alpha channel, typically 3 for rgb */
+ int alpha_chan;
+
+ /* whether or not to scale the color channels based on the alpha
+ channel. TIFF has 2 types of alpha channel, if the alpha channel
+ we use is EXTRASAMPLE_ASSOCALPHA then the color data will need to
+ be scaled to match Imager's conventions */
+ int scale_alpha;
+};
+
+static int tile_contig_getter(read_state_t *state, read_putter_t putter);
+static int strip_contig_getter(read_state_t *state, read_putter_t putter);
+
+static int setup_paletted(read_state_t *state);
+static int paletted_putter8(read_state_t *, int, int, int, int, int);
+static int paletted_putter4(read_state_t *, int, int, int, int, int);
+
+static int setup_16_rgb(read_state_t *state);
+static int setup_16_grey(read_state_t *state);
+static int putter_16(read_state_t *, int, int, int, int, int);
+
+static int setup_8_rgb(read_state_t *state);
+static int setup_8_grey(read_state_t *state);
+static int putter_8(read_state_t *, int, int, int, int, int);
+
+static int setup_32_rgb(read_state_t *state);
+static int setup_32_grey(read_state_t *state);
+static int putter_32(read_state_t *, int, int, int, int, int);
+
+static int setup_bilevel(read_state_t *state);
+static int putter_bilevel(read_state_t *, int, int, int, int, int);
+
+static int setup_cmyk8(read_state_t *state);
+static int putter_cmyk8(read_state_t *, int, int, int, int, int);
+
+static int setup_cmyk16(read_state_t *state);
+static int putter_cmyk16(read_state_t *, int, int, int, int, int);
+
+static const int text_tag_count =
+ sizeof(text_tag_names) / sizeof(*text_tag_names);
+
+static void error_handler(char const *module, char const *fmt, va_list ap) {
+ mm_log((1, "tiff error fmt %s\n", fmt));
+ i_push_errorvf(0, fmt, ap);
+}
+
+#define WARN_BUFFER_LIMIT 10000
+static char *warn_buffer = NULL;
+static int warn_buffer_size = 0;
+
+static void warn_handler(char const *module, char const *fmt, va_list ap) {
+ char buf[1000];
+
+ buf[0] = '\0';
+#ifdef HAVE_SNPRINTF
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+#else
+ vsprintf(buf, fmt, ap);
+#endif
+ mm_log((1, "tiff warning %s\n", buf));
+
+ if (!warn_buffer || strlen(warn_buffer)+strlen(buf)+2 > warn_buffer_size) {
+ int new_size = warn_buffer_size + strlen(buf) + 2;
+ char *old_buffer = warn_buffer;
+ if (new_size > WARN_BUFFER_LIMIT) {
+ new_size = WARN_BUFFER_LIMIT;
+ }
+ warn_buffer = myrealloc(warn_buffer, new_size);
+ if (!old_buffer) *warn_buffer = '\0';
+ warn_buffer_size = new_size;
+ }
+ if (strlen(warn_buffer)+strlen(buf)+2 <= warn_buffer_size) {
+ strcat(warn_buffer, buf);
+ strcat(warn_buffer, "\n");
+ }
+}
+
+static int save_tiff_tags(TIFF *tif, i_img *im);
+
+static void
+pack_4bit_to(unsigned char *dest, const unsigned char *src, int count);
+
+
+static toff_t sizeproc(thandle_t x) {
+ return 0;
+}
+
+
+/*
+=item comp_seek(h, o, w)
+
+Compatability for 64 bit systems like latest freebsd (internal)
+
+ h - tiff handle, cast an io_glue object
+ o - offset
+ w - whence
+
+=cut
+*/
+
+static
+toff_t
+comp_seek(thandle_t h, toff_t o, int w) {
+ io_glue *ig = (io_glue*)h;
+ return (toff_t) ig->seekcb(ig, o, w);
+}
+
+/*
+=item comp_mmap(thandle_t, tdata_t*, toff_t*)
+
+Dummy mmap stub.
+
+This shouldn't ever be called but newer tifflibs want it anyway.
+
+=cut
+*/
+
+static
+int
+comp_mmap(thandle_t h, tdata_t*p, toff_t*off) {
+ return -1;
+}
+
+/*
+=item comp_munmap(thandle_t h, tdata_t p, toff_t off)
+
+Dummy munmap stub.
+
+This shouldn't ever be called but newer tifflibs want it anyway.
+
+=cut
+*/
+
+static void
+comp_munmap(thandle_t h, tdata_t p, toff_t off) {
+ /* do nothing */
+}
+
+static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) {
+ i_img *im;
+ uint32 width, height;
+ uint16 samples_per_pixel;
+ int tiled, error;
+ float xres, yres;
+ uint16 resunit;
+ int gotXres, gotYres;
+ uint16 photometric;
+ uint16 bits_per_sample;
+ uint16 planar_config;
+ uint16 inkset;
+ uint16 compress;
+ int i;
+ read_state_t state;
+ read_setup_t setupf = NULL;
+ read_getter_t getterf = NULL;
+ read_putter_t putterf = NULL;
+
+ error = 0;
+
+ TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
+ TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);
+ TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel);
+ tiled = TIFFIsTiled(tif);
+ TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric);
+ TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
+ TIFFGetFieldDefaulted(tif, TIFFTAG_PLANARCONFIG, &planar_config);
+ TIFFGetFieldDefaulted(tif, TIFFTAG_INKSET, &inkset);
+
+ mm_log((1, "i_readtiff_wiol: width=%d, height=%d, channels=%d\n", width, height, samples_per_pixel));
+ mm_log((1, "i_readtiff_wiol: %stiled\n", tiled?"":"not "));
+ mm_log((1, "i_readtiff_wiol: %sbyte swapped\n", TIFFIsByteSwapped(tif)?"":"not "));
+
+ /* yes, this if() is horrible */
+ if (photometric == PHOTOMETRIC_PALETTE && bits_per_sample <= 8) {
+ setupf = setup_paletted;
+ if (bits_per_sample == 8)
+ putterf = paletted_putter8;
+ else if (bits_per_sample == 4)
+ putterf = paletted_putter4;
+ else
+ mm_log((1, "unsupported paletted bits_per_sample %d\n", bits_per_sample));
+ }
+ else if (bits_per_sample == 16
+ && photometric == PHOTOMETRIC_RGB
+ && samples_per_pixel >= 3) {
+ setupf = setup_16_rgb;
+ putterf = putter_16;
+ }
+ else if (bits_per_sample == 16
+ && photometric == PHOTOMETRIC_MINISBLACK) {
+ setupf = setup_16_grey;
+ putterf = putter_16;
+ }
+ else if (bits_per_sample == 8
+ && photometric == PHOTOMETRIC_MINISBLACK) {
+ setupf = setup_8_grey;
+ putterf = putter_8;
+ }
+ else if (bits_per_sample == 8
+ && photometric == PHOTOMETRIC_RGB) {
+ setupf = setup_8_rgb;
+ putterf = putter_8;
+ }
+ else if (bits_per_sample == 32
+ && photometric == PHOTOMETRIC_RGB
+ && samples_per_pixel >= 3) {
+ setupf = setup_32_rgb;
+ putterf = putter_32;
+ }
+ else if (bits_per_sample == 32
+ && photometric == PHOTOMETRIC_MINISBLACK) {
+ setupf = setup_32_grey;
+ putterf = putter_32;
+ }
+ else if (bits_per_sample == 1
+ && (photometric == PHOTOMETRIC_MINISBLACK
+ || photometric == PHOTOMETRIC_MINISWHITE)
+ && samples_per_pixel == 1) {
+ setupf = setup_bilevel;
+ putterf = putter_bilevel;
+ }
+ else if (bits_per_sample == 8
+ && photometric == PHOTOMETRIC_SEPARATED
+ && inkset == INKSET_CMYK
+ && samples_per_pixel >= 4) {
+ setupf = setup_cmyk8;
+ putterf = putter_cmyk8;
+ }
+ else if (bits_per_sample == 16
+ && photometric == PHOTOMETRIC_SEPARATED
+ && inkset == INKSET_CMYK
+ && samples_per_pixel >= 4) {
+ setupf = setup_cmyk16;
+ putterf = putter_cmyk16;
+ }
+ if (tiled) {
+ if (planar_config == PLANARCONFIG_CONTIG)
+ getterf = tile_contig_getter;
+ }
+ else {
+ if (planar_config == PLANARCONFIG_CONTIG)
+ getterf = strip_contig_getter;
+ }
+ if (setupf && getterf && putterf) {
+ unsigned long total_pixels = (unsigned long)width * height;
+ memset(&state, 0, sizeof(state));
+ state.tif = tif;
+ state.allow_incomplete = allow_incomplete;
+ state.width = width;
+ state.height = height;
+ state.bits_per_sample = bits_per_sample;
+ state.samples_per_pixel = samples_per_pixel;
+ state.photometric = photometric;
+
+ if (!setupf(&state))
+ return NULL;
+ if (!getterf(&state, putterf) || !state.pixels_read) {
+ if (state.img)
+ i_img_destroy(state.img);
+ if (state.raster)
+ _TIFFfree(state.raster);
+ if (state.line_buf)
+ myfree(state.line_buf);
+
+ return NULL;
+ }
+
+ if (allow_incomplete && state.pixels_read < total_pixels) {
+ i_tags_setn(&(state.img->tags), "i_incomplete", 1);
+ i_tags_setn(&(state.img->tags), "i_lines_read",
+ state.pixels_read / width);
+ }
+ im = state.img;
+
+ if (state.raster)
+ _TIFFfree(state.raster);
+ if (state.line_buf)
+ myfree(state.line_buf);
+ }
+ else {
+ if (tiled) {
+ im = read_one_rgb_tiled(tif, width, height, allow_incomplete);
+ }
+ else {
+ im = read_one_rgb_lines(tif, width, height, allow_incomplete);
+ }
+ }
+
+ if (!im)
+ return NULL;
+
+ /* general metadata */
+ i_tags_setn(&im->tags, "tiff_bitspersample", bits_per_sample);
+ i_tags_setn(&im->tags, "tiff_photometric", photometric);
+ TIFFGetFieldDefaulted(tif, TIFFTAG_COMPRESSION, &compress);
+
+ /* resolution tags */
+ TIFFGetFieldDefaulted(tif, TIFFTAG_RESOLUTIONUNIT, &resunit);
+ gotXres = TIFFGetField(tif, TIFFTAG_XRESOLUTION, &xres);
+ gotYres = TIFFGetField(tif, TIFFTAG_YRESOLUTION, &yres);
+ if (gotXres || gotYres) {
+ if (!gotXres)
+ xres = yres;
+ else if (!gotYres)
+ yres = xres;
+ i_tags_setn(&im->tags, "tiff_resolutionunit", resunit);
+ if (resunit == RESUNIT_CENTIMETER) {
+ /* from dots per cm to dpi */
+ xres *= 2.54;
+ yres *= 2.54;
+ i_tags_set(&im->tags, "tiff_resolutionunit_name", "centimeter", -1);
+ }
+ else if (resunit == RESUNIT_NONE) {
+ i_tags_setn(&im->tags, "i_aspect_only", 1);
+ i_tags_set(&im->tags, "tiff_resolutionunit_name", "none", -1);
+ }
+ else if (resunit == RESUNIT_INCH) {
+ i_tags_set(&im->tags, "tiff_resolutionunit_name", "inch", -1);
+ }
+ else {
+ i_tags_set(&im->tags, "tiff_resolutionunit_name", "unknown", -1);
+ }
+ /* tifflib doesn't seem to provide a way to get to the original rational
+ value of these, which would let me provide a more reasonable
+ precision. So make up a number. */
+ i_tags_set_float2(&im->tags, "i_xres", 0, xres, 6);
+ i_tags_set_float2(&im->tags, "i_yres", 0, yres, 6);
+ }
+
+ /* Text tags */
+ for (i = 0; i < text_tag_count; ++i) {
+ char *data;
+ if (TIFFGetField(tif, text_tag_names[i].tag, &data)) {
+ mm_log((1, "i_readtiff_wiol: tag %d has value %s\n",
+ text_tag_names[i].tag, data));
+ i_tags_set(&im->tags, text_tag_names[i].name, data, -1);
+ }
+ }
+
+ i_tags_set(&im->tags, "i_format", "tiff", 4);
+ if (warn_buffer && *warn_buffer) {
+ i_tags_set(&im->tags, "i_warning", warn_buffer, -1);
+ *warn_buffer = '\0';
+ }
+
+ for (i = 0; i < compress_value_count; ++i) {
+ if (compress_values[i].tag == compress) {
+ i_tags_set(&im->tags, "tiff_compression", compress_values[i].name, -1);
+ break;
+ }
+ }
+
+ return im;
+}
+
+/*
+=item i_readtiff_wiol(im, ig)
+
+=cut
+*/
+i_img*
+i_readtiff_wiol(io_glue *ig, int allow_incomplete, int page) {
+ TIFF* tif;
+ TIFFErrorHandler old_handler;
+ TIFFErrorHandler old_warn_handler;
+ i_img *im;
+
+ i_clear_error();
+ old_handler = TIFFSetErrorHandler(error_handler);
+ old_warn_handler = TIFFSetWarningHandler(warn_handler);
+ if (warn_buffer)
+ *warn_buffer = '\0';
+
+ /* Add code to get the filename info from the iolayer */
+ /* Also add code to check for mmapped code */
+
+ mm_log((1, "i_readtiff_wiol(ig %p, allow_incomplete %d, page %d)\n", ig, allow_incomplete, page));
+
+ tif = TIFFClientOpen("(Iolayer)",
+ "rm",
+ (thandle_t) ig,
+ (TIFFReadWriteProc) ig->readcb,
+ (TIFFReadWriteProc) ig->writecb,
+ (TIFFSeekProc) comp_seek,
+ (TIFFCloseProc) ig->closecb,
+ ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
+ (TIFFMapFileProc) comp_mmap,
+ (TIFFUnmapFileProc) comp_munmap);
+
+ if (!tif) {
+ mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
+ i_push_error(0, "Error opening file");
+ TIFFSetErrorHandler(old_handler);
+ TIFFSetWarningHandler(old_warn_handler);
+ return NULL;
+ }
+
+ if (page != 0) {
+ if (!TIFFSetDirectory(tif, page)) {
+ mm_log((1, "i_readtiff_wiol: Unable to switch to directory %d\n", page));
+ i_push_errorf(0, "could not switch to page %d", page);
+ TIFFSetErrorHandler(old_handler);
+ TIFFSetWarningHandler(old_warn_handler);
+ TIFFClose(tif);
+ return NULL;
+ }
+ }
+
+ im = read_one_tiff(tif, allow_incomplete);
+
+ if (TIFFLastDirectory(tif)) mm_log((1, "Last directory of tiff file\n"));
+ TIFFSetErrorHandler(old_handler);
+ TIFFSetWarningHandler(old_warn_handler);
+ TIFFClose(tif);
+ return im;
+}
+
+/*
+=item i_readtiff_multi_wiol(ig, *count)
+
+Reads multiple images from a TIFF.
+
+=cut
+*/
+i_img**
+i_readtiff_multi_wiol(io_glue *ig, int *count) {
+ TIFF* tif;
+ TIFFErrorHandler old_handler;
+ TIFFErrorHandler old_warn_handler;
+ i_img **results = NULL;
+ int result_alloc = 0;
+ int dirnum = 0;
+
+ i_clear_error();
+ old_handler = TIFFSetErrorHandler(error_handler);
+ old_warn_handler = TIFFSetWarningHandler(warn_handler);
+ if (warn_buffer)
+ *warn_buffer = '\0';
+
+ /* Add code to get the filename info from the iolayer */
+ /* Also add code to check for mmapped code */
+
+ mm_log((1, "i_readtiff_wiol(ig %p, length %d)\n", ig));
+
+ tif = TIFFClientOpen("(Iolayer)",
+ "rm",
+ (thandle_t) ig,
+ (TIFFReadWriteProc) ig->readcb,
+ (TIFFReadWriteProc) ig->writecb,
+ (TIFFSeekProc) comp_seek,
+ (TIFFCloseProc) ig->closecb,
+ ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
+ (TIFFMapFileProc) comp_mmap,
+ (TIFFUnmapFileProc) comp_munmap);
+
+ if (!tif) {
+ mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
+ i_push_error(0, "Error opening file");
+ TIFFSetErrorHandler(old_handler);
+ TIFFSetWarningHandler(old_warn_handler);
+ return NULL;
+ }
+
+ *count = 0;
+ do {
+ i_img *im = read_one_tiff(tif, 0);
+ if (!im)
+ break;
+ if (++*count > result_alloc) {
+ if (result_alloc == 0) {
+ result_alloc = 5;
+ results = mymalloc(result_alloc * sizeof(i_img *));
+ }
+ else {
+ i_img **newresults;
+ result_alloc *= 2;
+ newresults = myrealloc(results, result_alloc * sizeof(i_img *));
+ if (!newresults) {
+ i_img_destroy(im); /* don't leak it */
+ break;
+ }
+ results = newresults;
+ }
+ }
+ results[*count-1] = im;
+ } while (TIFFSetDirectory(tif, ++dirnum));
+
+ TIFFSetWarningHandler(old_warn_handler);
+ TIFFSetErrorHandler(old_handler);
+ TIFFClose(tif);
+ return results;
+}
+
+undef_int
+i_writetiff_low_faxable(TIFF *tif, i_img *im, int fine) {
+ uint32 width, height;
+ unsigned char *linebuf = NULL;
+ uint32 y;
+ int rc;
+ uint32 x;
+ uint32 rowsperstrip;
+ float vres = fine ? 196 : 98;
+ int luma_chan;
+
+ width = im->xsize;
+ height = im->ysize;
+
+ switch (im->channels) {
+ case 1:
+ case 2:
+ luma_chan = 0;
+ break;
+ case 3:
+ case 4:
+ luma_chan = 1;
+ break;
+ default:
+ /* This means a colorspace we don't handle yet */
+ mm_log((1, "i_writetiff_wiol_faxable: don't handle %d channel images.\n", im->channels));
+ return 0;
+ }
+
+ /* Add code to get the filename info from the iolayer */
+ /* Also add code to check for mmapped code */
+
+
+ mm_log((1, "i_writetiff_wiol_faxable: width=%d, height=%d, channels=%d\n", width, height, im->channels));
+
+ if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width) )
+ { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField width=%d failed\n", width)); return 0; }
+ if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height) )
+ { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField length=%d failed\n", height)); return 0; }
+ if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1))
+ { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField samplesperpixel=1 failed\n")); return 0; }
+ if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT))
+ { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Orientation=topleft\n")); return 0; }
+ if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 1) )
+ { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField bitpersample=1\n")); return 0; }
+ if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG))
+ { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField planarconfig\n")); return 0; }
+ if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE))
+ { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField photometric=%d\n", PHOTOMETRIC_MINISBLACK)); return 0; }
+ if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, 3))
+ { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField compression=3\n")); return 0; }
+
+ linebuf = (unsigned char *)_TIFFmalloc( TIFFScanlineSize(tif) );
+
+ if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) {
+ mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField rowsperstrip=-1\n")); return 0; }
+
+ TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
+ TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rc);
+
+ mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField rowsperstrip=%d\n", rowsperstrip));
+ mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField scanlinesize=%d\n", TIFFScanlineSize(tif) ));
+ mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField planarconfig=%d == %d\n", rc, PLANARCONFIG_CONTIG));
+
+ if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)204))
+ { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Xresolution=204\n")); return 0; }
+ if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, vres))
+ { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Yresolution=196\n")); return 0; }
+ if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)) {
+ mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField ResolutionUnit=%d\n", RESUNIT_INCH)); return 0;
+ }
+
+ if (!save_tiff_tags(tif, im)) {
+ return 0;
+ }
+
+ for (y=0; y<height; y++) {
+ int linebufpos=0;
+ for(x=0; x<width; x+=8) {
+ int bits;
+ int bitpos;
+ i_sample_t luma[8];
+ uint8 bitval = 128;
+ linebuf[linebufpos]=0;
+ bits = width-x; if(bits>8) bits=8;
+ i_gsamp(im, x, x+8, y, luma, &luma_chan, 1);
+ for(bitpos=0;bitpos<bits;bitpos++) {
+ linebuf[linebufpos] |= ((luma[bitpos] < 128) ? bitval : 0);
+ bitval >>= 1;
+ }
+ linebufpos++;
+ }
+ if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) {
+ mm_log((1, "i_writetiff_wiol_faxable: TIFFWriteScanline failed.\n"));
+ break;
+ }
+ }
+ if (linebuf) _TIFFfree(linebuf);
+
+ return 1;
+}
+
+static uint16
+find_compression(char const *name, uint16 *compress) {
+ int i;
+
+ for (i = 0; i < compress_value_count; ++i) {
+ if (strcmp(compress_values[i].name, name) == 0) {
+ *compress = (uint16)compress_values[i].tag;
+ return 1;
+ }
+ }
+ *compress = COMPRESSION_NONE;
+
+ return 0;
+}
+
+static uint16
+get_compression(i_img *im, uint16 def_compress) {
+ int entry;
+ int value;
+
+ if (i_tags_find(&im->tags, "tiff_compression", 0, &entry)
+ && im->tags.tags[entry].data) {
+ uint16 compress;
+ if (find_compression(im->tags.tags[entry].data, &compress)
+ && myTIFFIsCODECConfigured(compress))
+ return compress;
+ }
+ if (i_tags_get_int(&im->tags, "tiff_compression", 0, &value)) {
+ if ((uint16)value == value
+ && myTIFFIsCODECConfigured((uint16)value))
+ return (uint16)value;
+ }
+
+ return def_compress;
+}
+
+int
+i_tiff_has_compression(const char *name) {
+ uint16 compress;
+
+ if (!find_compression(name, &compress))
+ return 0;
+
+ return myTIFFIsCODECConfigured(compress);
+}
+
+static int
+set_base_tags(TIFF *tif, i_img *im, uint16 compress, uint16 photometric,
+ uint16 bits_per_sample, uint16 samples_per_pixel) {
+ double xres, yres;
+ int resunit;
+ int got_xres, got_yres;
+ int aspect_only;
+
+ if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, im->xsize)) {
+ i_push_error(0, "write TIFF: setting width tag");
+ return 0;
+ }
+ if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, im->ysize)) {
+ i_push_error(0, "write TIFF: setting length tag");
+ return 0;
+ }
+ if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT)) {
+ i_push_error(0, "write TIFF: setting orientation tag");
+ return 0;
+ }
+ if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) {
+ i_push_error(0, "write TIFF: setting planar configuration tag");
+ return 0;
+ }
+ if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric)) {
+ i_push_error(0, "write TIFF: setting photometric tag");
+ return 0;
+ }
+ if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, compress)) {
+ i_push_error(0, "write TIFF: setting compression tag");
+ return 0;
+ }
+ if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bits_per_sample)) {
+ i_push_error(0, "write TIFF: setting bits per sample tag");
+ return 0;
+ }
+ if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, samples_per_pixel)) {
+ i_push_error(0, "write TIFF: setting samples per pixel tag");
+ return 0;
+ }
+
+ got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres);
+ got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres);
+ if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only))
+ aspect_only = 0;
+ if (!i_tags_get_int(&im->tags, "tiff_resolutionunit", 0, &resunit))
+ resunit = RESUNIT_INCH;
+ if (got_xres || got_yres) {
+ if (!got_xres)
+ xres = yres;
+ else if (!got_yres)
+ yres = xres;
+ if (aspect_only) {
+ resunit = RESUNIT_NONE;
+ }
+ else {
+ if (resunit == RESUNIT_CENTIMETER) {
+ xres /= 2.54;
+ yres /= 2.54;
+ }
+ else {
+ resunit = RESUNIT_INCH;
+ }
+ }
+ if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)xres)) {
+ i_push_error(0, "write TIFF: setting xresolution tag");
+ return 0;
+ }
+ if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, (float)yres)) {
+ i_push_error(0, "write TIFF: setting yresolution tag");
+ return 0;
+ }
+ if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, (uint16)resunit)) {
+ i_push_error(0, "write TIFF: setting resolutionunit tag");
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int
+write_one_bilevel(TIFF *tif, i_img *im, int zero_is_white) {
+ uint16 compress = get_compression(im, COMPRESSION_PACKBITS);
+ uint16 photometric;
+ unsigned char *in_row;
+ unsigned char *out_row;
+ unsigned out_size;
+ int x, y;
+ int invert;
+
+ mm_log((1, "tiff - write_one_bilevel(tif %p, im %p, zero_is_white %d)\n",
+ tif, im, zero_is_white));
+
+ /* ignore a silly choice */
+ if (compress == COMPRESSION_JPEG)
+ compress = COMPRESSION_PACKBITS;
+
+ switch (compress) {
+ case COMPRESSION_CCITTRLE:
+ case COMPRESSION_CCITTFAX3:
+ case COMPRESSION_CCITTFAX4:
+ /* natural fax photometric */
+ photometric = PHOTOMETRIC_MINISWHITE;
+ break;
+
+ default:
+ /* natural for most computer images */
+ photometric = PHOTOMETRIC_MINISBLACK;
+ break;
+ }
+
+ if (!set_base_tags(tif, im, compress, photometric, 1, 1))
+ return 0;
+
+ if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) {
+ i_push_error(0, "write TIFF: setting rows per strip tag");
+ return 0;
+ }
+
+ out_size = TIFFScanlineSize(tif);
+ out_row = (unsigned char *)_TIFFmalloc( out_size );
+ in_row = mymalloc(im->xsize);
+
+ invert = (photometric == PHOTOMETRIC_MINISWHITE) != (zero_is_white != 0);
+
+ for (y = 0; y < im->ysize; ++y) {
+ int mask = 0x80;
+ unsigned char *outp = out_row;
+ memset(out_row, 0, out_size);
+ i_gpal(im, 0, im->xsize, y, in_row);
+ for (x = 0; x < im->xsize; ++x) {
+ if (invert ? !in_row[x] : in_row[x]) {
+ *outp |= mask;
+ }
+ mask >>= 1;
+ if (!mask) {
+ ++outp;
+ mask = 0x80;
+ }
+ }
+ if (TIFFWriteScanline(tif, out_row, y, 0) < 0) {
+ _TIFFfree(out_row);
+ myfree(in_row);
+ i_push_error(0, "write TIFF: write scan line failed");
+ return 0;
+ }
+ }
+
+ _TIFFfree(out_row);
+ myfree(in_row);
+
+ return 1;
+}
+
+static int
+set_palette(TIFF *tif, i_img *im, int size) {
+ int count;
+ uint16 *colors;
+ uint16 *out[3];
+ i_color c;
+ int i, ch;
+
+ colors = (uint16 *)_TIFFmalloc(sizeof(uint16) * 3 * size);
+ out[0] = colors;
+ out[1] = colors + size;
+ out[2] = colors + 2 * size;
+
+ count = i_colorcount(im);
+ for (i = 0; i < count; ++i) {
+ i_getcolors(im, i, &c, 1);
+ for (ch = 0; ch < 3; ++ch)
+ out[ch][i] = c.channel[ch] * 257;
+ }
+ for (; i < size; ++i) {
+ for (ch = 0; ch < 3; ++ch)
+ out[ch][i] = 0;
+ }
+ if (!TIFFSetField(tif, TIFFTAG_COLORMAP, out[0], out[1], out[2])) {
+ _TIFFfree(colors);
+ i_push_error(0, "write TIFF: setting color map");
+ return 0;
+ }
+ _TIFFfree(colors);
+
+ return 1;
+}
+
+static int
+write_one_paletted8(TIFF *tif, i_img *im) {
+ uint16 compress = get_compression(im, COMPRESSION_PACKBITS);
+ unsigned char *out_row;
+ unsigned out_size;
+ int y;
+
+ mm_log((1, "tiff - write_one_paletted8(tif %p, im %p)\n", tif, im));
+
+ /* ignore a silly choice */
+ if (compress == COMPRESSION_JPEG ||
+ compress == COMPRESSION_CCITTRLE ||
+ compress == COMPRESSION_CCITTFAX3 ||
+ compress == COMPRESSION_CCITTFAX4)
+ compress = COMPRESSION_PACKBITS;
+
+ if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) {
+ i_push_error(0, "write TIFF: setting rows per strip tag");
+ return 0;
+ }
+
+ if (!set_base_tags(tif, im, compress, PHOTOMETRIC_PALETTE, 8, 1))
+ return 0;
+
+ if (!set_palette(tif, im, 256))
+ return 0;
+
+ out_size = TIFFScanlineSize(tif);
+ out_row = (unsigned char *)_TIFFmalloc( out_size );
+
+ for (y = 0; y < im->ysize; ++y) {
+ i_gpal(im, 0, im->xsize, y, out_row);
+ if (TIFFWriteScanline(tif, out_row, y, 0) < 0) {
+ _TIFFfree(out_row);
+ i_push_error(0, "write TIFF: write scan line failed");
+ return 0;
+ }
+ }
+
+ _TIFFfree(out_row);
+
+ return 1;
+}
+
+static int
+write_one_paletted4(TIFF *tif, i_img *im) {
+ uint16 compress = get_compression(im, COMPRESSION_PACKBITS);
+ unsigned char *in_row;
+ unsigned char *out_row;
+ unsigned out_size;
+ int y;
+
+ mm_log((1, "tiff - write_one_paletted4(tif %p, im %p)\n", tif, im));
+
+ /* ignore a silly choice */
+ if (compress == COMPRESSION_JPEG ||
+ compress == COMPRESSION_CCITTRLE ||
+ compress == COMPRESSION_CCITTFAX3 ||
+ compress == COMPRESSION_CCITTFAX4)
+ compress = COMPRESSION_PACKBITS;
+
+ if (!set_base_tags(tif, im, compress, PHOTOMETRIC_PALETTE, 4, 1))
+ return 0;
+
+ if (!set_palette(tif, im, 16))
+ return 0;
+
+ if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) {
+ i_push_error(0, "write TIFF: setting rows per strip tag");
+ return 0;
+ }
+
+ in_row = mymalloc(im->xsize);
+ out_size = TIFFScanlineSize(tif);
+ out_row = (unsigned char *)_TIFFmalloc( out_size );
+
+ for (y = 0; y < im->ysize; ++y) {
+ i_gpal(im, 0, im->xsize, y, in_row);
+ memset(out_row, 0, out_size);
+ pack_4bit_to(out_row, in_row, im->xsize);
+ if (TIFFWriteScanline(tif, out_row, y, 0) < 0) {
+ _TIFFfree(out_row);
+ i_push_error(0, "write TIFF: write scan line failed");
+ return 0;
+ }
+ }
+
+ myfree(in_row);
+ _TIFFfree(out_row);
+
+ return 1;
+}
+
+static int
+set_direct_tags(TIFF *tif, i_img *im, uint16 compress,
+ uint16 bits_per_sample) {
+ uint16 extras = EXTRASAMPLE_ASSOCALPHA;
+ uint16 extra_count = im->channels == 2 || im->channels == 4;
+ uint16 photometric = im->channels >= 3 ?
+ PHOTOMETRIC_RGB : PHOTOMETRIC_MINISBLACK;
+
+ if (!set_base_tags(tif, im, compress, photometric, bits_per_sample,
+ im->channels)) {
+ return 0;
+ }
+
+ if (extra_count) {
+ if (!TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, extra_count, &extras)) {
+ i_push_error(0, "write TIFF: setting extra samples tag");
+ return 0;
+ }
+ }
+
+ if (compress == COMPRESSION_JPEG) {
+ int jpeg_quality;
+ if (i_tags_get_int(&im->tags, "tiff_jpegquality", 0, &jpeg_quality)
+ && jpeg_quality >= 0 && jpeg_quality <= 100) {
+ if (!TIFFSetField(tif, TIFFTAG_JPEGQUALITY, jpeg_quality)) {
+ i_push_error(0, "write TIFF: setting jpeg quality pseudo-tag");
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+static int
+write_one_32(TIFF *tif, i_img *im) {
+ uint16 compress = get_compression(im, COMPRESSION_PACKBITS);
+ unsigned *in_row;
+ size_t out_size;
+ uint32 *out_row;
+ int y;
+ size_t sample_count = im->xsize * im->channels;
+ size_t sample_index;
+
+ mm_log((1, "tiff - write_one_32(tif %p, im %p)\n", tif, im));
+
+ /* only 8 and 12 bit samples are supported by jpeg compression */
+ if (compress == COMPRESSION_JPEG)
+ compress = COMPRESSION_PACKBITS;
+
+ if (!set_direct_tags(tif, im, compress, 32))
+ return 0;
+
+ in_row = mymalloc(sample_count * sizeof(unsigned));
+ out_size = TIFFScanlineSize(tif);
+ out_row = (uint32 *)_TIFFmalloc( out_size );
+
+ for (y = 0; y < im->ysize; ++y) {
+ if (i_gsamp_bits(im, 0, im->xsize, y, in_row, NULL, im->channels, 32) <= 0) {
+ i_push_error(0, "Cannot read 32-bit samples");
+ return 0;
+ }
+ for (sample_index = 0; sample_index < sample_count; ++sample_index)
+ out_row[sample_index] = in_row[sample_index];
+ if (TIFFWriteScanline(tif, out_row, y, 0) < 0) {
+ myfree(in_row);
+ _TIFFfree(out_row);
+ i_push_error(0, "write TIFF: write scan line failed");
+ return 0;
+ }
+ }
+
+ myfree(in_row);
+ _TIFFfree(out_row);
+
+ return 1;
+}
+
+static int
+write_one_16(TIFF *tif, i_img *im) {
+ uint16 compress = get_compression(im, COMPRESSION_PACKBITS);
+ unsigned *in_row;
+ size_t out_size;
+ uint16 *out_row;
+ int y;
+ size_t sample_count = im->xsize * im->channels;
+ size_t sample_index;
+
+ mm_log((1, "tiff - write_one_16(tif %p, im %p)\n", tif, im));
+
+ /* only 8 and 12 bit samples are supported by jpeg compression */
+ if (compress == COMPRESSION_JPEG)
+ compress = COMPRESSION_PACKBITS;
+
+ if (!set_direct_tags(tif, im, compress, 16))
+ return 0;
+
+ in_row = mymalloc(sample_count * sizeof(unsigned));
+ out_size = TIFFScanlineSize(tif);
+ out_row = (uint16 *)_TIFFmalloc( out_size );
+
+ for (y = 0; y < im->ysize; ++y) {
+ if (i_gsamp_bits(im, 0, im->xsize, y, in_row, NULL, im->channels, 16) <= 0) {
+ i_push_error(0, "Cannot read 16-bit samples");
+ return 0;
+ }
+ for (sample_index = 0; sample_index < sample_count; ++sample_index)
+ out_row[sample_index] = in_row[sample_index];
+ if (TIFFWriteScanline(tif, out_row, y, 0) < 0) {
+ myfree(in_row);
+ _TIFFfree(out_row);
+ i_push_error(0, "write TIFF: write scan line failed");
+ return 0;
+ }
+ }
+
+ myfree(in_row);
+ _TIFFfree(out_row);
+
+ return 1;
+}
+
+static int
+write_one_8(TIFF *tif, i_img *im) {
+ uint16 compress = get_compression(im, COMPRESSION_PACKBITS);
+ size_t out_size;
+ unsigned char *out_row;
+ int y;
+ size_t sample_count = im->xsize * im->channels;
+
+ mm_log((1, "tiff - write_one_8(tif %p, im %p)\n", tif, im));
+
+ if (!set_direct_tags(tif, im, compress, 8))
+ return 0;
+
+ out_size = TIFFScanlineSize(tif);
+ if (out_size < sample_count)
+ out_size = sample_count;
+ out_row = (unsigned char *)_TIFFmalloc( out_size );
+
+ for (y = 0; y < im->ysize; ++y) {
+ if (i_gsamp(im, 0, im->xsize, y, out_row, NULL, im->channels) <= 0) {
+ i_push_error(0, "Cannot read 8-bit samples");
+ return 0;
+ }
+ if (TIFFWriteScanline(tif, out_row, y, 0) < 0) {
+ _TIFFfree(out_row);
+ i_push_error(0, "write TIFF: write scan line failed");
+ return 0;
+ }
+ }
+ _TIFFfree(out_row);
+
+ return 1;
+}
+
+static int
+i_writetiff_low(TIFF *tif, i_img *im) {
+ uint32 width, height;
+ uint16 channels;
+ int zero_is_white;
+
+ width = im->xsize;
+ height = im->ysize;
+ channels = im->channels;
+
+ mm_log((1, "i_writetiff_low: width=%d, height=%d, channels=%d, bits=%d\n", width, height, channels, im->bits));
+ if (im->type == i_palette_type) {
+ mm_log((1, "i_writetiff_low: paletted, colors=%d\n", i_colorcount(im)));
+ }
+
+ if (i_img_is_monochrome(im, &zero_is_white)) {
+ if (!write_one_bilevel(tif, im, zero_is_white))
+ return 0;
+ }
+ else if (im->type == i_palette_type) {
+ if (i_colorcount(im) <= 16) {
+ if (!write_one_paletted4(tif, im))
+ return 0;
+ }
+ else {
+ if (!write_one_paletted8(tif, im))
+ return 0;
+ }
+ }
+ else if (im->bits > 16) {
+ if (!write_one_32(tif, im))
+ return 0;
+ }
+ else if (im->bits > 8) {
+ if (!write_one_16(tif, im))
+ return 0;
+ }
+ else {
+ if (!write_one_8(tif, im))
+ return 0;
+ }
+
+ if (!save_tiff_tags(tif, im))
+ return 0;
+
+ return 1;
+}
+
+/*
+=item i_writetiff_multi_wiol(ig, imgs, count, fine_mode)
+
+Stores an image in the iolayer object.
+
+ ig - io_object that defines source to write to
+ imgs,count - the images to write
+
+=cut
+*/
+
+undef_int
+i_writetiff_multi_wiol(io_glue *ig, i_img **imgs, int count) {
+ TIFF* tif;
+ TIFFErrorHandler old_handler;
+ int i;
+
+ old_handler = TIFFSetErrorHandler(error_handler);
+
+ i_clear_error();
+ mm_log((1, "i_writetiff_multi_wiol(ig 0x%p, imgs 0x%p, count %d)\n",
+ ig, imgs, count));
+
+ /* FIXME: Enable the mmap interface */
+
+ tif = TIFFClientOpen("No name",
+ "wm",
+ (thandle_t) ig,
+ (TIFFReadWriteProc) ig->readcb,
+ (TIFFReadWriteProc) ig->writecb,
+ (TIFFSeekProc) comp_seek,
+ (TIFFCloseProc) ig->closecb,
+ ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
+ (TIFFMapFileProc) comp_mmap,
+ (TIFFUnmapFileProc) comp_munmap);
+
+
+
+ if (!tif) {
+ mm_log((1, "i_writetiff_multi_wiol: Unable to open tif file for writing\n"));
+ i_push_error(0, "Could not create TIFF object");
+ TIFFSetErrorHandler(old_handler);
+ return 0;
+ }
+
+ for (i = 0; i < count; ++i) {
+ if (!i_writetiff_low(tif, imgs[i])) {
+ TIFFClose(tif);
+ TIFFSetErrorHandler(old_handler);
+ return 0;
+ }
+
+ if (!TIFFWriteDirectory(tif)) {
+ i_push_error(0, "Cannot write TIFF directory");
+ TIFFClose(tif);
+ TIFFSetErrorHandler(old_handler);
+ return 0;
+ }
+ }
+
+ TIFFSetErrorHandler(old_handler);
+ (void) TIFFClose(tif);
+
+ return 1;
+}
+
+/*
+=item i_writetiff_multi_wiol_faxable(ig, imgs, count, fine_mode)
+
+Stores an image in the iolayer object.
+
+ ig - io_object that defines source to write to
+ imgs,count - the images to write
+ fine_mode - select fine or normal mode fax images
+
+=cut
+*/
+
+
+undef_int
+i_writetiff_multi_wiol_faxable(io_glue *ig, i_img **imgs, int count, int fine) {
+ TIFF* tif;
+ int i;
+ TIFFErrorHandler old_handler;
+
+ old_handler = TIFFSetErrorHandler(error_handler);
+
+ i_clear_error();
+ mm_log((1, "i_writetiff_multi_wiol(ig 0x%p, imgs 0x%p, count %d)\n",
+ ig, imgs, count));
+
+ /* FIXME: Enable the mmap interface */
+
+ tif = TIFFClientOpen("No name",
+ "wm",
+ (thandle_t) ig,
+ (TIFFReadWriteProc) ig->readcb,
+ (TIFFReadWriteProc) ig->writecb,
+ (TIFFSeekProc) comp_seek,
+ (TIFFCloseProc) ig->closecb,
+ ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
+ (TIFFMapFileProc) comp_mmap,
+ (TIFFUnmapFileProc) comp_munmap);
+
+
+
+ if (!tif) {
+ mm_log((1, "i_writetiff_mulit_wiol: Unable to open tif file for writing\n"));
+ i_push_error(0, "Could not create TIFF object");
+ TIFFSetErrorHandler(old_handler);
+ return 0;
+ }
+
+ for (i = 0; i < count; ++i) {
+ if (!i_writetiff_low_faxable(tif, imgs[i], fine)) {
+ TIFFClose(tif);
+ TIFFSetErrorHandler(old_handler);
+ return 0;
+ }
+
+ if (!TIFFWriteDirectory(tif)) {
+ i_push_error(0, "Cannot write TIFF directory");
+ TIFFClose(tif);
+ TIFFSetErrorHandler(old_handler);
+ return 0;
+ }
+ }
+
+ (void) TIFFClose(tif);
+ TIFFSetErrorHandler(old_handler);
+
+ return 1;
+}
+
+/*
+=item i_writetiff_wiol(im, ig)
+
+Stores an image in the iolayer object.
+
+ im - image object to write out
+ ig - io_object that defines source to write to
+
+=cut
+*/
+undef_int
+i_writetiff_wiol(i_img *img, io_glue *ig) {
+ TIFF* tif;
+ TIFFErrorHandler old_handler;
+
+ old_handler = TIFFSetErrorHandler(error_handler);
+
+ i_clear_error();
+ mm_log((1, "i_writetiff_wiol(img %p, ig 0x%p)\n", img, ig));
+
+ /* FIXME: Enable the mmap interface */
+
+ tif = TIFFClientOpen("No name",
+ "wm",
+ (thandle_t) ig,
+ (TIFFReadWriteProc) ig->readcb,
+ (TIFFReadWriteProc) ig->writecb,
+ (TIFFSeekProc) comp_seek,
+ (TIFFCloseProc) ig->closecb,
+ ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
+ (TIFFMapFileProc) comp_mmap,
+ (TIFFUnmapFileProc) comp_munmap);
+
+
+
+ if (!tif) {
+ mm_log((1, "i_writetiff_wiol: Unable to open tif file for writing\n"));
+ i_push_error(0, "Could not create TIFF object");
+ TIFFSetErrorHandler(old_handler);
+ return 0;
+ }
+
+ if (!i_writetiff_low(tif, img)) {
+ TIFFClose(tif);
+ TIFFSetErrorHandler(old_handler);
+ return 0;
+ }
+
+ (void) TIFFClose(tif);
+ TIFFSetErrorHandler(old_handler);
+
+ return 1;
+}
+
+
+
+/*
+=item i_writetiff_wiol_faxable(i_img *, io_glue *)
+
+Stores an image in the iolayer object in faxable tiff format.
+
+ im - image object to write out
+ ig - io_object that defines source to write to
+
+Note, this may be rewritten to use to simply be a call to a
+lower-level function that gives more options for writing tiff at some
+point.
+
+=cut
+*/
+
+undef_int
+i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine) {
+ TIFF* tif;
+ TIFFErrorHandler old_handler;
+
+ old_handler = TIFFSetErrorHandler(error_handler);
+
+ i_clear_error();
+ mm_log((1, "i_writetiff_wiol(img %p, ig 0x%p)\n", im, ig));
+
+ /* FIXME: Enable the mmap interface */
+
+ tif = TIFFClientOpen("No name",
+ "wm",
+ (thandle_t) ig,
+ (TIFFReadWriteProc) ig->readcb,
+ (TIFFReadWriteProc) ig->writecb,
+ (TIFFSeekProc) comp_seek,
+ (TIFFCloseProc) ig->closecb,
+ ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
+ (TIFFMapFileProc) comp_mmap,
+ (TIFFUnmapFileProc) comp_munmap);
+
+
+
+ if (!tif) {
+ mm_log((1, "i_writetiff_wiol: Unable to open tif file for writing\n"));
+ i_push_error(0, "Could not create TIFF object");
+ TIFFSetErrorHandler(old_handler);
+ return 0;
+ }
+
+ if (!i_writetiff_low_faxable(tif, im, fine)) {
+ TIFFClose(tif);
+ TIFFSetErrorHandler(old_handler);
+ return 0;
+ }
+
+ (void) TIFFClose(tif);
+ TIFFSetErrorHandler(old_handler);
+
+ return 1;
+}
+
+static int save_tiff_tags(TIFF *tif, i_img *im) {
+ int i;
+
+ for (i = 0; i < text_tag_count; ++i) {
+ int entry;
+ if (i_tags_find(&im->tags, text_tag_names[i].name, 0, &entry)) {
+ if (!TIFFSetField(tif, text_tag_names[i].tag,
+ im->tags.tags[entry].data)) {
+ i_push_errorf(0, "cannot save %s to TIFF", text_tag_names[i].name);
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+
+static void
+unpack_4bit_to(unsigned char *dest, const unsigned char *src,
+ int src_byte_count) {
+ while (src_byte_count > 0) {
+ *dest++ = *src >> 4;
+ *dest++ = *src++ & 0xf;
+ --src_byte_count;
+ }
+}
+
+static void pack_4bit_to(unsigned char *dest, const unsigned char *src,
+ int pixel_count) {
+ int i = 0;
+ while (i < pixel_count) {
+ if ((i & 1) == 0) {
+ *dest = *src++ << 4;
+ }
+ else {
+ *dest++ |= *src++;
+ }
+ ++i;
+ }
+}
+
+static i_img *
+make_rgb(TIFF *tif, int width, int height, int *alpha_chan) {
+ uint16 photometric;
+ uint16 channels, in_channels;
+ uint16 extra_count;
+ uint16 *extras;
+
+ TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &in_channels);
+ TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric);
+
+ switch (photometric) {
+ case PHOTOMETRIC_SEPARATED:
+ channels = 3;
+ break;
+
+ case PHOTOMETRIC_MINISWHITE:
+ case PHOTOMETRIC_MINISBLACK:
+ /* the TIFF RGBA functions expand single channel grey into RGB,
+ so reduce it, we move the alpha channel into the right place
+ if needed */
+ channels = 1;
+ break;
+
+ default:
+ channels = 3;
+ break;
+ }
+ /* TIFF images can have more than one alpha channel, but Imager can't
+ this ignores the possibility of 2 channel images with 2 alpha,
+ but there's not much I can do about that */
+ *alpha_chan = 0;
+ if (TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extra_count, &extras)
+ && extra_count) {
+ *alpha_chan = channels++;
+ }
+
+ return i_img_8_new(width, height, channels);
+}
+
+static i_img *
+read_one_rgb_lines(TIFF *tif, int width, int height, int allow_incomplete) {
+ i_img *im;
+ uint32* raster = NULL;
+ uint32 rowsperstrip, row;
+ i_color *line_buf;
+ int alpha_chan;
+ int rc;
+
+ im = make_rgb(tif, width, height, &alpha_chan);
+ if (!im)
+ return NULL;
+
+ rc = TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
+ mm_log((1, "i_readtiff_wiol: rowsperstrip=%d rc = %d\n", rowsperstrip, rc));
+
+ if (rc != 1 || rowsperstrip==-1) {
+ rowsperstrip = height;
+ }
+
+ raster = (uint32*)_TIFFmalloc(width * rowsperstrip * sizeof (uint32));
+ if (!raster) {
+ i_img_destroy(im);
+ i_push_error(0, "No space for raster buffer");
+ return NULL;
+ }
+
+ line_buf = mymalloc(sizeof(i_color) * width);
+
+ for( row = 0; row < height; row += rowsperstrip ) {
+ uint32 newrows, i_row;
+
+ if (!TIFFReadRGBAStrip(tif, row, raster)) {
+ if (allow_incomplete) {
+ i_tags_setn(&im->tags, "i_lines_read", row);
+ i_tags_setn(&im->tags, "i_incomplete", 1);
+ break;
+ }
+ else {
+ i_push_error(0, "could not read TIFF image strip");
+ _TIFFfree(raster);
+ i_img_destroy(im);
+ return NULL;
+ }
+ }
+
+ newrows = (row+rowsperstrip > height) ? height-row : rowsperstrip;
+ mm_log((1, "newrows=%d\n", newrows));
+
+ for( i_row = 0; i_row < newrows; i_row++ ) {
+ uint32 x;
+ i_color *outp = line_buf;
+
+ for(x = 0; x<width; x++) {
+ uint32 temp = raster[x+width*(newrows-i_row-1)];
+ outp->rgba.r = TIFFGetR(temp);
+ outp->rgba.g = TIFFGetG(temp);
+ outp->rgba.b = TIFFGetB(temp);
+
+ if (alpha_chan) {
+ /* the libtiff RGBA code expands greyscale into RGBA, so put the
+ alpha in the right place and scale it */
+ int ch;
+ outp->channel[alpha_chan] = TIFFGetA(temp);
+ if (outp->channel[alpha_chan]) {
+ for (ch = 0; ch < alpha_chan; ++ch) {
+ outp->channel[ch] = outp->channel[ch] * 255 / outp->channel[alpha_chan];
+ }
+ }
+ }
+
+ outp++;
+ }
+ i_plin(im, 0, width, i_row+row, line_buf);
+ }
+ }
+
+ myfree(line_buf);
+ _TIFFfree(raster);
+
+ return im;
+}
+
+/* adapted from libtiff
+
+ libtiff's TIFFReadRGBATile succeeds even when asked to read an
+ invalid tile, which means we have no way of knowing whether the data
+ we received from it is valid or not.
+
+ So the caller here has set stoponerror to 1 so that
+ TIFFRGBAImageGet() will fail.
+
+ read_one_rgb_tiled() then takes that into account for i_incomplete
+ or failure.
+ */
+static int
+myTIFFReadRGBATile(TIFFRGBAImage *img, uint32 col, uint32 row, uint32 * raster)
+
+{
+ int ok;
+ uint32 tile_xsize, tile_ysize;
+ uint32 read_xsize, read_ysize;
+ uint32 i_row;
+
+ /*
+ * Verify that our request is legal - on a tile file, and on a
+ * tile boundary.
+ */
+
+ TIFFGetFieldDefaulted(img->tif, TIFFTAG_TILEWIDTH, &tile_xsize);
+ TIFFGetFieldDefaulted(img->tif, TIFFTAG_TILELENGTH, &tile_ysize);
+ if( (col % tile_xsize) != 0 || (row % tile_ysize) != 0 )
+ {
+ i_push_errorf(0, "Row/col passed to myTIFFReadRGBATile() must be top"
+ "left corner of a tile.");
+ return 0;
+ }
+
+ /*
+ * The TIFFRGBAImageGet() function doesn't allow us to get off the
+ * edge of the image, even to fill an otherwise valid tile. So we
+ * figure out how much we can read, and fix up the tile buffer to
+ * a full tile configuration afterwards.
+ */
+
+ if( row + tile_ysize > img->height )
+ read_ysize = img->height - row;
+ else
+ read_ysize = tile_ysize;
+
+ if( col + tile_xsize > img->width )
+ read_xsize = img->width - col;
+ else
+ read_xsize = tile_xsize;
+
+ /*
+ * Read the chunk of imagery.
+ */
+
+ img->row_offset = row;
+ img->col_offset = col;
+
+ ok = TIFFRGBAImageGet(img, raster, read_xsize, read_ysize );
+
+ /*
+ * If our read was incomplete we will need to fix up the tile by
+ * shifting the data around as if a full tile of data is being returned.
+ *
+ * This is all the more complicated because the image is organized in
+ * bottom to top format.
+ */
+
+ if( read_xsize == tile_xsize && read_ysize == tile_ysize )
+ return( ok );
+
+ for( i_row = 0; i_row < read_ysize; i_row++ ) {
+ memmove( raster + (tile_ysize - i_row - 1) * tile_xsize,
+ raster + (read_ysize - i_row - 1) * read_xsize,
+ read_xsize * sizeof(uint32) );
+ _TIFFmemset( raster + (tile_ysize - i_row - 1) * tile_xsize+read_xsize,
+ 0, sizeof(uint32) * (tile_xsize - read_xsize) );
+ }
+
+ for( i_row = read_ysize; i_row < tile_ysize; i_row++ ) {
+ _TIFFmemset( raster + (tile_ysize - i_row - 1) * tile_xsize,
+ 0, sizeof(uint32) * tile_xsize );
+ }
+
+ return (ok);
+}
+
+static i_img *
+read_one_rgb_tiled(TIFF *tif, int width, int height, int allow_incomplete) {
+ i_img *im;
+ uint32* raster = NULL;
+ int ok = 1;
+ uint32 row, col;
+ uint32 tile_width, tile_height;
+ unsigned long pixels = 0;
+ char emsg[1024] = "";
+ TIFFRGBAImage img;
+ i_color *line;
+ int alpha_chan;
+
+ im = make_rgb(tif, width, height, &alpha_chan);
+ if (!im)
+ return NULL;
+
+ if (!TIFFRGBAImageOK(tif, emsg)
+ || !TIFFRGBAImageBegin(&img, tif, 1, emsg)) {
+ i_push_error(0, emsg);
+ i_img_destroy(im);
+ return( 0 );
+ }
+
+ TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width);
+ TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height);
+ mm_log((1, "i_readtiff_wiol: tile_width=%d, tile_height=%d\n", tile_width, tile_height));
+
+ raster = (uint32*)_TIFFmalloc(tile_width * tile_height * sizeof (uint32));
+ if (!raster) {
+ i_img_destroy(im);
+ i_push_error(0, "No space for raster buffer");
+ TIFFRGBAImageEnd(&img);
+ return NULL;
+ }
+ line = mymalloc(tile_width * sizeof(i_color));
+
+ for( row = 0; row < height; row += tile_height ) {
+ for( col = 0; col < width; col += tile_width ) {
+
+ /* Read the tile into an RGBA array */
+ if (myTIFFReadRGBATile(&img, col, row, raster)) {
+ uint32 i_row, x;
+ uint32 newrows = (row+tile_height > height) ? height-row : tile_height;
+ uint32 newcols = (col+tile_width > width ) ? width-col : tile_width;
+
+ mm_log((1, "i_readtiff_wiol: tile(%d, %d) newcols=%d newrows=%d\n", col, row, newcols, newrows));
+ for( i_row = 0; i_row < newrows; i_row++ ) {
+ i_color *outp = line;
+ for(x = 0; x < newcols; x++) {
+ uint32 temp = raster[x+tile_width*(tile_height-i_row-1)];
+ outp->rgba.r = TIFFGetR(temp);
+ outp->rgba.g = TIFFGetG(temp);
+ outp->rgba.b = TIFFGetB(temp);
+ outp->rgba.a = TIFFGetA(temp);
+
+ if (alpha_chan) {
+ /* the libtiff RGBA code expands greyscale into RGBA, so put the
+ alpha in the right place and scale it */
+ int ch;
+ outp->channel[alpha_chan] = TIFFGetA(temp);
+
+ if (outp->channel[alpha_chan]) {
+ for (ch = 0; ch < alpha_chan; ++ch) {
+ outp->channel[ch] = outp->channel[ch] * 255 / outp->channel[alpha_chan];
+ }
+ }
+ }
+
+ ++outp;
+ }
+ i_plin(im, col, col+newcols, row+i_row, line);
+ }
+ pixels += newrows * newcols;
+ }
+ else {
+ if (allow_incomplete) {
+ ok = 0;
+ }
+ else {
+ goto error;
+ }
+ }
+ }
+ }
+
+ if (!ok) {
+ if (pixels == 0) {
+ i_push_error(0, "TIFF: No image data could be read from the image");
+ goto error;
+ }
+
+ /* incomplete image */
+ i_tags_setn(&im->tags, "i_incomplete", 1);
+ i_tags_setn(&im->tags, "i_lines_read", pixels / width);
+ }
+
+ myfree(line);
+ TIFFRGBAImageEnd(&img);
+ _TIFFfree(raster);
+
+ return im;
+
+ error:
+ myfree(line);
+ _TIFFfree(raster);
+ TIFFRGBAImageEnd(&img);
+ i_img_destroy(im);
+ return NULL;
+}
+
+char const *
+i_tiff_libversion(void) {
+ return TIFFGetVersion();
+}
+
+static int
+setup_paletted(read_state_t *state) {
+ uint16 *maps[3];
+ int i, ch;
+ int color_count = 1 << state->bits_per_sample;
+
+ state->img = i_img_pal_new(state->width, state->height, 3, 256);
+ if (!state->img)
+ return 0;
+
+ /* setup the color map */
+ if (!TIFFGetField(state->tif, TIFFTAG_COLORMAP, maps+0, maps+1, maps+2)) {
+ i_push_error(0, "Cannot get colormap for paletted image");
+ i_img_destroy(state->img);
+ return 0;
+ }
+ for (i = 0; i < color_count; ++i) {
+ i_color c;
+ for (ch = 0; ch < 3; ++ch) {
+ c.channel[ch] = Sample16To8(maps[ch][i]);
+ }
+ i_addcolors(state->img, &c, 1);
+ }
+
+ return 1;
+}
+
+static int
+tile_contig_getter(read_state_t *state, read_putter_t putter) {
+ uint32 tile_width, tile_height;
+ uint32 this_tile_height, this_tile_width;
+ uint32 rows_left, cols_left;
+ uint32 x, y;
+
+ state->raster = _TIFFmalloc(TIFFTileSize(state->tif));
+ if (!state->raster) {
+ i_push_error(0, "tiff: Out of memory allocating tile buffer");
+ return 0;
+ }
+
+ TIFFGetField(state->tif, TIFFTAG_TILEWIDTH, &tile_width);
+ TIFFGetField(state->tif, TIFFTAG_TILELENGTH, &tile_height);
+ rows_left = state->height;
+ for (y = 0; y < state->height; y += this_tile_height) {
+ this_tile_height = rows_left > tile_height ? tile_height : rows_left;
+
+ cols_left = state->width;
+ for (x = 0; x < state->width; x += this_tile_width) {
+ this_tile_width = cols_left > tile_width ? tile_width : cols_left;
+
+ if (TIFFReadTile(state->tif,
+ state->raster,
+ x, y, 0, 0) < 0) {
+ if (!state->allow_incomplete) {
+ return 0;
+ }
+ }
+ else {
+ putter(state, x, y, this_tile_width, this_tile_height, tile_width - this_tile_width);
+ }
+
+ cols_left -= this_tile_width;
+ }
+
+ rows_left -= this_tile_height;
+ }
+
+ return 1;
+}
+
+static int
+strip_contig_getter(read_state_t *state, read_putter_t putter) {
+ uint32 rows_per_strip;
+ tsize_t strip_size = TIFFStripSize(state->tif);
+ uint32 y, strip_rows, rows_left;
+
+ state->raster = _TIFFmalloc(strip_size);
+ if (!state->raster) {
+ i_push_error(0, "tiff: Out of memory allocating strip buffer");
+ return 0;
+ }
+
+ TIFFGetFieldDefaulted(state->tif, TIFFTAG_ROWSPERSTRIP, &rows_per_strip);
+ rows_left = state->height;
+ for (y = 0; y < state->height; y += strip_rows) {
+ strip_rows = rows_left > rows_per_strip ? rows_per_strip : rows_left;
+ if (TIFFReadEncodedStrip(state->tif,
+ TIFFComputeStrip(state->tif, y, 0),
+ state->raster,
+ strip_size) < 0) {
+ if (!state->allow_incomplete)
+ return 0;
+ }
+ else {
+ putter(state, 0, y, state->width, strip_rows, 0);
+ }
+ rows_left -= strip_rows;
+ }
+
+ return 1;
+}
+
+static int
+paletted_putter8(read_state_t *state, int x, int y, int width, int height, int extras) {
+ unsigned char *p = state->raster;
+
+ state->pixels_read += (unsigned long) width * height;
+ while (height > 0) {
+ i_ppal(state->img, x, x + width, y, p);
+ p += width + extras;
+ --height;
+ ++y;
+ }
+
+ return 1;
+}
+
+static int
+paletted_putter4(read_state_t *state, int x, int y, int width, int height, int extras) {
+ uint32 img_line_size = (width + 1) / 2;
+ uint32 skip_line_size = (width + extras + 1) / 2;
+ unsigned char *p = state->raster;
+
+ if (!state->line_buf)
+ state->line_buf = mymalloc(state->width);
+
+ state->pixels_read += (unsigned long) width * height;
+ while (height > 0) {
+ unpack_4bit_to(state->line_buf, p, img_line_size);
+ i_ppal(state->img, x, x + width, y, state->line_buf);
+ p += skip_line_size;
+ --height;
+ ++y;
+ }
+
+ return 1;
+}
+
+static void
+rgb_channels(read_state_t *state, int *out_channels) {
+ uint16 extra_count;
+ uint16 *extras;
+
+ /* safe defaults */
+ *out_channels = 3;
+ state->alpha_chan = 0;
+ state->scale_alpha = 0;
+
+ /* plain RGB */
+ if (state->samples_per_pixel == 3)
+ return;
+
+ if (!TIFFGetField(state->tif, TIFFTAG_EXTRASAMPLES, &extra_count, &extras)) {
+ mm_log((1, "tiff: samples != 3 but no extra samples tag\n"));
+ return;
+ }
+
+ if (!extra_count) {
+ mm_log((1, "tiff: samples != 3 but no extra samples listed"));
+ return;
+ }
+
+ ++*out_channels;
+ state->alpha_chan = 3;
+ switch (*extras) {
+ case EXTRASAMPLE_UNSPECIFIED:
+ case EXTRASAMPLE_ASSOCALPHA:
+ state->scale_alpha = 1;
+ break;
+
+ case EXTRASAMPLE_UNASSALPHA:
+ state->scale_alpha = 0;
+ break;
+
+ default:
+ mm_log((1, "tiff: unknown extra sample type %d, treating as assoc alpha\n",
+ *extras));
+ state->scale_alpha = 1;
+ break;
+ }
+ mm_log((1, "tiff alpha channel %d scale %d\n", state->alpha_chan, state->scale_alpha));
+}
+
+static void
+grey_channels(read_state_t *state, int *out_channels) {
+ uint16 extra_count;
+ uint16 *extras;
+
+ /* safe defaults */
+ *out_channels = 1;
+ state->alpha_chan = 0;
+ state->scale_alpha = 0;
+
+ /* plain grey */
+ if (state->samples_per_pixel == 1)
+ return;
+
+ if (!TIFFGetField(state->tif, TIFFTAG_EXTRASAMPLES, &extra_count, &extras)) {
+ mm_log((1, "tiff: samples != 1 but no extra samples tag\n"));
+ return;
+ }
+
+ if (!extra_count) {
+ mm_log((1, "tiff: samples != 1 but no extra samples listed"));
+ return;
+ }
+
+ ++*out_channels;
+ state->alpha_chan = 1;
+ switch (*extras) {
+ case EXTRASAMPLE_UNSPECIFIED:
+ case EXTRASAMPLE_ASSOCALPHA:
+ state->scale_alpha = 1;
+ break;
+
+ case EXTRASAMPLE_UNASSALPHA:
+ state->scale_alpha = 0;
+ break;
+
+ default:
+ mm_log((1, "tiff: unknown extra sample type %d, treating as assoc alpha\n",
+ *extras));
+ state->scale_alpha = 1;
+ break;
+ }
+}
+
+static int
+setup_16_rgb(read_state_t *state) {
+ int out_channels;
+
+ rgb_channels(state, &out_channels);
+
+ state->img = i_img_16_new(state->width, state->height, out_channels);
+ if (!state->img)
+ return 0;
+ state->line_buf = mymalloc(sizeof(unsigned) * state->width * out_channels);
+
+ return 1;
+}
+
+static int
+setup_16_grey(read_state_t *state) {
+ int out_channels;
+
+ grey_channels(state, &out_channels);
+
+ state->img = i_img_16_new(state->width, state->height, out_channels);
+ if (!state->img)
+ return 0;
+ state->line_buf = mymalloc(sizeof(unsigned) * state->width * out_channels);
+
+ return 1;
+}
+
+static int
+putter_16(read_state_t *state, int x, int y, int width, int height,
+ int row_extras) {
+ uint16 *p = state->raster;
+ int out_chan = state->img->channels;
+
+ state->pixels_read += (unsigned long) width * height;
+ while (height > 0) {
+ int i;
+ int ch;
+ unsigned *outp = state->line_buf;
+
+ for (i = 0; i < width; ++i) {
+ for (ch = 0; ch < out_chan; ++ch) {
+ outp[ch] = p[ch];
+ }
+ if (state->alpha_chan && state->scale_alpha && outp[state->alpha_chan]) {
+ for (ch = 0; ch < state->alpha_chan; ++ch) {
+ int result = 0.5 + (outp[ch] * 65535.0 / outp[state->alpha_chan]);
+ outp[ch] = CLAMP16(result);
+ }
+ }
+ p += state->samples_per_pixel;
+ outp += out_chan;
+ }
+
+ i_psamp_bits(state->img, x, x + width, y, state->line_buf, NULL, out_chan, 16);
+
+ p += row_extras * state->samples_per_pixel;
+ --height;
+ ++y;
+ }
+
+ return 1;
+}
+
+static int
+setup_8_rgb(read_state_t *state) {
+ int out_channels;
+
+ rgb_channels(state, &out_channels);
+
+ state->img = i_img_8_new(state->width, state->height, out_channels);
+ if (!state->img)
+ return 0;
+ state->line_buf = mymalloc(sizeof(unsigned) * state->width * out_channels);
+
+ return 1;
+}
+
+static int
+setup_8_grey(read_state_t *state) {
+ int out_channels;
+
+ grey_channels(state, &out_channels);
+
+ state->img = i_img_8_new(state->width, state->height, out_channels);
+ if (!state->img)
+ return 0;
+ state->line_buf = mymalloc(sizeof(i_color) * state->width * out_channels);
+
+ return 1;
+}
+
+static int
+putter_8(read_state_t *state, int x, int y, int width, int height,
+ int row_extras) {
+ unsigned char *p = state->raster;
+ int out_chan = state->img->channels;
+
+ state->pixels_read += (unsigned long) width * height;
+ while (height > 0) {
+ int i;
+ int ch;
+ i_color *outp = state->line_buf;
+
+ for (i = 0; i < width; ++i) {
+ for (ch = 0; ch < out_chan; ++ch) {
+ outp->channel[ch] = p[ch];
+ }
+ if (state->alpha_chan && state->scale_alpha
+ && outp->channel[state->alpha_chan]) {
+ for (ch = 0; ch < state->alpha_chan; ++ch) {
+ int result = (outp->channel[ch] * 255 + 127) / outp->channel[state->alpha_chan];
+
+ outp->channel[ch] = CLAMP8(result);
+ }
+ }
+ p += state->samples_per_pixel;
+ outp++;
+ }
+
+ i_plin(state->img, x, x + width, y, state->line_buf);
+
+ p += row_extras * state->samples_per_pixel;
+ --height;
+ ++y;
+ }
+
+ return 1;
+}
+
+static int
+setup_32_rgb(read_state_t *state) {
+ int out_channels;
+
+ rgb_channels(state, &out_channels);
+
+ state->img = i_img_double_new(state->width, state->height, out_channels);
+ if (!state->img)
+ return 0;
+ state->line_buf = mymalloc(sizeof(i_fcolor) * state->width);
+
+ return 1;
+}
+
+static int
+setup_32_grey(read_state_t *state) {
+ int out_channels;
+
+ grey_channels(state, &out_channels);
+
+ state->img = i_img_double_new(state->width, state->height, out_channels);
+ if (!state->img)
+ return 0;
+ state->line_buf = mymalloc(sizeof(i_fcolor) * state->width);
+
+ return 1;
+}
+
+static int
+putter_32(read_state_t *state, int x, int y, int width, int height,
+ int row_extras) {
+ uint32 *p = state->raster;
+ int out_chan = state->img->channels;
+
+ state->pixels_read += (unsigned long) width * height;
+ while (height > 0) {
+ int i;
+ int ch;
+ i_fcolor *outp = state->line_buf;
+
+ for (i = 0; i < width; ++i) {
+ for (ch = 0; ch < out_chan; ++ch) {
+ outp->channel[ch] = p[ch] / 4294967295.0;
+ }
+ if (state->alpha_chan && state->scale_alpha && outp->channel[state->alpha_chan]) {
+ for (ch = 0; ch < state->alpha_chan; ++ch)
+ outp->channel[ch] /= outp->channel[state->alpha_chan];
+ }
+ p += state->samples_per_pixel;
+ outp++;
+ }
+
+ i_plinf(state->img, x, x + width, y, state->line_buf);
+
+ p += row_extras * state->samples_per_pixel;
+ --height;
+ ++y;
+ }
+
+ return 1;
+}
+
+static int
+setup_bilevel(read_state_t *state) {
+ i_color black, white;
+ state->img = i_img_pal_new(state->width, state->height, 1, 256);
+ if (!state->img)
+ return 0;
+ black.channel[0] = black.channel[1] = black.channel[2] =
+ black.channel[3] = 0;
+ white.channel[0] = white.channel[1] = white.channel[2] =
+ white.channel[3] = 255;
+ if (state->photometric == PHOTOMETRIC_MINISBLACK) {
+ i_addcolors(state->img, &black, 1);
+ i_addcolors(state->img, &white, 1);
+ }
+ else {
+ i_addcolors(state->img, &white, 1);
+ i_addcolors(state->img, &black, 1);
+ }
+ state->line_buf = mymalloc(state->width);
+
+ return 1;
+}
+
+static int
+putter_bilevel(read_state_t *state, int x, int y, int width, int height,
+ int row_extras) {
+ unsigned char *line_in = state->raster;
+ size_t line_size = (width + row_extras + 7) / 8;
+
+ /* tifflib returns the bits in MSB2LSB order even when the file is
+ in LSB2MSB, so we only need to handle MSB2LSB */
+ state->pixels_read += (unsigned long) width * height;
+ while (height > 0) {
+ int i;
+ unsigned char *outp = state->line_buf;
+ unsigned char *inp = line_in;
+ unsigned mask = 0x80;
+
+ for (i = 0; i < width; ++i) {
+ *outp++ = *inp & mask ? 1 : 0;
+ mask >>= 1;
+ if (!mask) {
+ ++inp;
+ mask = 0x80;
+ }
+ }
+
+ i_ppal(state->img, x, x + width, y, state->line_buf);
+
+ line_in += line_size;
+ --height;
+ ++y;
+ }
+
+ return 1;
+}
+
+static void
+cmyk_channels(read_state_t *state, int *out_channels) {
+ uint16 extra_count;
+ uint16 *extras;
+
+ /* safe defaults */
+ *out_channels = 3;
+ state->alpha_chan = 0;
+ state->scale_alpha = 0;
+
+ /* plain CMYK */
+ if (state->samples_per_pixel == 4)
+ return;
+
+ if (!TIFFGetField(state->tif, TIFFTAG_EXTRASAMPLES, &extra_count, &extras)) {
+ mm_log((1, "tiff: CMYK samples != 4 but no extra samples tag\n"));
+ return;
+ }
+
+ if (!extra_count) {
+ mm_log((1, "tiff: CMYK samples != 4 but no extra samples listed"));
+ return;
+ }
+
+ ++*out_channels;
+ state->alpha_chan = 4;
+ switch (*extras) {
+ case EXTRASAMPLE_UNSPECIFIED:
+ case EXTRASAMPLE_ASSOCALPHA:
+ state->scale_alpha = 1;
+ break;
+
+ case EXTRASAMPLE_UNASSALPHA:
+ state->scale_alpha = 0;
+ break;
+
+ default:
+ mm_log((1, "tiff: unknown extra sample type %d, treating as assoc alpha\n",
+ *extras));
+ state->scale_alpha = 1;
+ break;
+ }
+}
+
+static int
+setup_cmyk8(read_state_t *state) {
+ int channels;
+
+ cmyk_channels(state, &channels);
+ state->img = i_img_8_new(state->width, state->height, channels);
+
+ state->line_buf = mymalloc(sizeof(i_color) * state->width);
+
+ return 1;
+}
+
+static int
+putter_cmyk8(read_state_t *state, int x, int y, int width, int height,
+ int row_extras) {
+ unsigned char *p = state->raster;
+
+ state->pixels_read += (unsigned long) width * height;
+ while (height > 0) {
+ int i;
+ int ch;
+ i_color *outp = state->line_buf;
+
+ for (i = 0; i < width; ++i) {
+ unsigned char c, m, y, k;
+ c = p[0];
+ m = p[1];
+ y = p[2];
+ k = 255 - p[3];
+ outp->rgba.r = (k * (255 - c)) / 255;
+ outp->rgba.g = (k * (255 - m)) / 255;
+ outp->rgba.b = (k * (255 - y)) / 255;
+ if (state->alpha_chan) {
+ outp->rgba.a = p[state->alpha_chan];
+ if (state->scale_alpha
+ && outp->rgba.a) {
+ for (ch = 0; ch < 3; ++ch) {
+ int result = (outp->channel[ch] * 255 + 127) / outp->rgba.a;
+ outp->channel[ch] = CLAMP8(result);
+ }
+ }
+ }
+ p += state->samples_per_pixel;
+ outp++;
+ }
+
+ i_plin(state->img, x, x + width, y, state->line_buf);
+
+ p += row_extras * state->samples_per_pixel;
+ --height;
+ ++y;
+ }
+
+ return 1;
+}
+
+static int
+setup_cmyk16(read_state_t *state) {
+ int channels;
+
+ cmyk_channels(state, &channels);
+ state->img = i_img_16_new(state->width, state->height, channels);
+
+ state->line_buf = mymalloc(sizeof(unsigned) * state->width * channels);
+
+ return 1;
+}
+
+static int
+putter_cmyk16(read_state_t *state, int x, int y, int width, int height,
+ int row_extras) {
+ uint16 *p = state->raster;
+ int out_chan = state->img->channels;
+
+ mm_log((4, "putter_cmyk16(%p, %d, %d, %d, %d, %d)\n", x, y, width, height, row_extras));
+
+ state->pixels_read += (unsigned long) width * height;
+ while (height > 0) {
+ int i;
+ int ch;
+ unsigned *outp = state->line_buf;
+
+ for (i = 0; i < width; ++i) {
+ unsigned c, m, y, k;
+ c = p[0];
+ m = p[1];
+ y = p[2];
+ k = 65535 - p[3];
+ outp[0] = (k * (65535U - c)) / 65535U;
+ outp[1] = (k * (65535U - m)) / 65535U;
+ outp[2] = (k * (65535U - y)) / 65535U;
+ if (state->alpha_chan) {
+ outp[3] = p[state->alpha_chan];
+ if (state->scale_alpha
+ && outp[3]) {
+ for (ch = 0; ch < 3; ++ch) {
+ int result = (outp[ch] * 65535 + 32767) / outp[3];
+ outp[3] = CLAMP16(result);
+ }
+ }
+ }
+ p += state->samples_per_pixel;
+ outp += out_chan;
+ }
+
+ i_psamp_bits(state->img, x, x + width, y, state->line_buf, NULL, out_chan, 16);
+
+ p += row_extras * state->samples_per_pixel;
+ --height;
+ ++y;
+ }
+
+ return 1;
+}
+
+/*
+
+ Older versions of tifflib we support don't define this, so define it
+ ourselves.
+
+ If you want this detection to do anything useful, use a newer
+ release of tifflib.
+
+ */
+#if TIFFLIB_VERSION < 20031121
+
+int
+TIFFIsCODECConfigured(uint16 scheme) {
+ switch (scheme) {
+ /* these schemes are all shipped with tifflib */
+ case COMPRESSION_NONE:
+ case COMPRESSION_PACKBITS:
+ case COMPRESSION_CCITTRLE:
+ case COMPRESSION_CCITTRLEW:
+ case COMPRESSION_CCITTFAX3:
+ case COMPRESSION_CCITTFAX4:
+ return 1;
+
+ /* these require external library support */
+ default:
+ case COMPRESSION_JPEG:
+ case COMPRESSION_LZW:
+ case COMPRESSION_DEFLATE:
+ case COMPRESSION_ADOBE_DEFLATE:
+ return 0;
+ }
+}
+
+#endif
+
+static int
+myTIFFIsCODECConfigured(uint16 scheme) {
+#if TIFFLIB_VERSION < 20040724
+ if (scheme == COMPRESSION_LZW)
+ return 0;
+#endif
+
+ return TIFFIsCODECConfigured(scheme);
+}
+
+/*
+=back
+
+=head1 AUTHOR
+
+Arnar M. Hrafnkelsson <addi@umich.edu>, Tony Cook <tony@imager.perl.org>
+
+=head1 SEE ALSO
+
+Imager(3)
+
+=cut
+*/
--- /dev/null
+#!perl -w
+use strict;
+use Test::More tests => 216;
+use Imager qw(:all);
+use Imager::Test qw(is_image is_image_similar test_image test_image_16 test_image_double test_image_raw);
+
+BEGIN { use_ok("Imager::File::TIFF"); }
+
+-d "testout"
+ or mkdir "testout";
+
+$|=1; # give us some progress in the test harness
+init_log("testout/t106tiff.log",1);
+
+my $green=i_color_new(0,255,0,255);
+my $blue=i_color_new(0,0,255,255);
+my $red=i_color_new(255,0,0,255);
+
+my $img=test_image_raw();
+
+my $ver_string = Imager::File::TIFF::i_tiff_libversion();
+ok(my ($full, $major, $minor, $point) =
+ $ver_string =~ /Version +((\d+)\.(\d+).(\d+))/,
+ "extract library version")
+ or diag("Could not extract from:\n$ver_string");
+diag("libtiff release $full") if $full;
+# make something we can compare
+my $cmp_ver = sprintf("%03d%03d%03d", $major, $minor, $point);
+if ($cmp_ver lt '003007000') {
+ diag("You have an old version of libtiff - $full, some tests will be skipped");
+}
+
+Imager::i_tags_add($img, "i_xres", 0, "300", 0);
+Imager::i_tags_add($img, "i_yres", 0, undef, 250);
+# resolutionunit is centimeters
+Imager::i_tags_add($img, "tiff_resolutionunit", 0, undef, 3);
+Imager::i_tags_add($img, "tiff_software", 0, "t106tiff.t", 0);
+open(FH,">testout/t106.tiff") || die "cannot open testout/t106.tiff for writing\n";
+binmode(FH);
+my $IO = Imager::io_new_fd(fileno(FH));
+ok(Imager::File::TIFF::i_writetiff_wiol($img, $IO), "write low level")
+ or print "# ", Imager->_error_as_msg, "\n";
+close(FH);
+
+open(FH,"testout/t106.tiff") or die "cannot open testout/t106.tiff\n";
+binmode(FH);
+$IO = Imager::io_new_fd(fileno(FH));
+my $cmpimg = Imager::File::TIFF::i_readtiff_wiol($IO);
+ok($cmpimg, "read low-level");
+
+close(FH);
+
+print "# tiff average mean square pixel difference: ",sqrt(i_img_diff($img,$cmpimg))/150*150,"\n";
+
+ok(!i_img_diff($img, $cmpimg), "compare written and read image");
+
+# check the tags are ok
+my %tags = map { Imager::i_tags_get($cmpimg, $_) }
+ 0 .. Imager::i_tags_count($cmpimg) - 1;
+ok(abs($tags{i_xres} - 300) < 0.5, "i_xres in range");
+ok(abs($tags{i_yres} - 250) < 0.5, "i_yres in range");
+is($tags{tiff_resolutionunit}, 3, "tiff_resolutionunit");
+is($tags{tiff_software}, 't106tiff.t', "tiff_software");
+is($tags{tiff_photometric}, 2, "tiff_photometric"); # PHOTOMETRIC_RGB is 2
+is($tags{tiff_bitspersample}, 8, "tiff_bitspersample");
+
+$IO = Imager::io_new_bufchain();
+
+ok(Imager::File::TIFF::i_writetiff_wiol($img, $IO), "write to buffer chain");
+my $tiffdata = Imager::io_slurp($IO);
+
+open(FH,"testout/t106.tiff");
+binmode FH;
+my $odata;
+{ local $/;
+ $odata = <FH>;
+}
+
+is($odata, $tiffdata, "same data in file as in memory");
+
+# test Micksa's tiff writer
+# a shortish fax page
+my $faximg = Imager::ImgRaw::new(1728, 2000, 1);
+my $black = i_color_new(0,0,0,255);
+my $white = i_color_new(255,255,255,255);
+# vaguely test-patterny
+i_box_filled($faximg, 0, 0, 1728, 2000, $white);
+i_box_filled($faximg, 100,100,1628, 200, $black);
+my $width = 1;
+my $pos = 100;
+while ($width+$pos < 1628) {
+ i_box_filled($faximg, $pos, 300, $pos+$width-1, 400, $black);
+ $pos += $width + 20;
+ $width += 2;
+}
+open FH, "> testout/t106tiff_fax.tiff"
+ or die "Cannot create testout/t106tiff_fax.tiff: $!";
+binmode FH;
+$IO = Imager::io_new_fd(fileno(FH));
+ok(Imager::File::TIFF::i_writetiff_wiol_faxable($faximg, $IO, 1), "write faxable, low level");
+close FH;
+
+# test the OO interface
+my $ooim = Imager->new;
+ok($ooim->read(file=>'testout/t106.tiff'), "read OO");
+ok($ooim->write(file=>'testout/t106_oo.tiff'), "write OO");
+
+# OO with the fax image
+my $oofim = Imager->new;
+ok($oofim->read(file=>'testout/t106tiff_fax.tiff'),
+ "read fax OO");
+
+# this should have tags set for the resolution
+%tags = map @$_, $oofim->tags;
+is($tags{i_xres}, 204, "fax i_xres");
+is($tags{i_yres}, 196, "fax i_yres");
+ok(!$tags{i_aspect_only}, "i_aspect_only");
+# resunit_inches
+is($tags{tiff_resolutionunit}, 2, "tiff_resolutionunit");
+is($tags{tiff_bitspersample}, 1, "tiff_bitspersample");
+is($tags{tiff_photometric}, 0, "tiff_photometric");
+
+ok($oofim->write(file=>'testout/t106_oo_fax.tiff', class=>'fax'),
+ "write OO, faxable");
+
+# the following should fail since there's no type and no filename
+my $oodata;
+ok(!$ooim->write(data=>\$oodata), "write with no type and no filename to guess with");
+
+# OO to data
+ok($ooim->write(data=>\$oodata, type=>'tiff'), "write to data")
+ or print "# ",$ooim->errstr, "\n";
+is($oodata, $tiffdata, "check data matches between memory and file");
+
+# make sure we can write non-fine mode
+ok($oofim->write(file=>'testout/t106_oo_faxlo.tiff', class=>'fax', fax_fine=>0), "write OO, fax standard mode");
+
+# paletted reads
+my $img4 = Imager->new;
+ok($img4->read(file=>'testimg/comp4.tif'), "reading 4-bit paletted")
+ or print "# ", $img4->errstr, "\n";
+is($img4->type, 'paletted', "image isn't paletted");
+print "# colors: ", $img4->colorcount,"\n";
+ cmp_ok($img4->colorcount, '<=', 16, "more than 16 colors!");
+#ok($img4->write(file=>'testout/t106_was4.ppm'),
+# "Cannot write img4");
+# I know I'm using BMP before it's test, but comp4.tif started life
+# as comp4.bmp
+my $bmp4 = Imager->new;
+ok($bmp4->read(file=>'testimg/comp4.bmp'), "reading 4-bit bmp!");
+my $diff = i_img_diff($img4->{IMG}, $bmp4->{IMG});
+print "# diff $diff\n";
+ok($diff == 0, "image mismatch");
+my $img4t = Imager->new;
+ok($img4t->read(file => 'testimg/comp4t.tif'), "read 4-bit paletted, tiled")
+ or print "# ", $img4t->errstr, "\n";
+is_image($bmp4, $img4t, "check tiled version matches");
+my $img8 = Imager->new;
+ok($img8->read(file=>'testimg/comp8.tif'), "reading 8-bit paletted");
+is($img8->type, 'paletted', "image isn't paletted");
+print "# colors: ", $img8->colorcount,"\n";
+#ok($img8->write(file=>'testout/t106_was8.ppm'),
+# "Cannot write img8");
+ok($img8->colorcount == 256, "more colors than expected");
+my $bmp8 = Imager->new;
+ok($bmp8->read(file=>'testimg/comp8.bmp'), "reading 8-bit bmp!");
+$diff = i_img_diff($img8->{IMG}, $bmp8->{IMG});
+print "# diff $diff\n";
+ok($diff == 0, "image mismatch");
+my $bad = Imager->new;
+ok($bad->read(file=>'testimg/comp4bad.tif',
+ allow_incomplete=>1), "bad image not returned");
+ok(scalar $bad->tags(name=>'i_incomplete'), "incomplete tag not set");
+ok($img8->write(file=>'testout/t106_pal8.tif'), "writing 8-bit paletted");
+my $cmp8 = Imager->new;
+ok($cmp8->read(file=>'testout/t106_pal8.tif'),
+ "reading 8-bit paletted");
+#print "# ",$cmp8->errstr,"\n";
+is($cmp8->type, 'paletted', "pal8 isn't paletted");
+is($cmp8->colorcount, 256, "pal8 bad colorcount");
+$diff = i_img_diff($img8->{IMG}, $cmp8->{IMG});
+print "# diff $diff\n";
+ok($diff == 0, "written image doesn't match read");
+ok($img4->write(file=>'testout/t106_pal4.tif'), "writing 4-bit paletted");
+ok(my $cmp4 = Imager->new->read(file=>'testout/t106_pal4.tif'),
+ "reading 4-bit paletted");
+is($cmp4->type, 'paletted', "pal4 isn't paletted");
+is($cmp4->colorcount, 16, "pal4 bad colorcount");
+$diff = i_img_diff($img4->{IMG}, $cmp4->{IMG});
+print "# diff $diff\n";
+ok($diff == 0, "written image doesn't match read");
+
+my $work;
+my $seekpos;
+sub io_writer {
+ my ($what) = @_;
+ if ($seekpos > length $work) {
+ $work .= "\0" x ($seekpos - length $work);
+ }
+ substr($work, $seekpos, length $what) = $what;
+ $seekpos += length $what;
+
+ 1;
+}
+sub io_reader {
+ my ($size, $maxread) = @_;
+ #print "io_reader($size, $maxread) pos $seekpos\n";
+ my $out = substr($work, $seekpos, $maxread);
+ $seekpos += length $out;
+ $out;
+}
+sub io_reader2 {
+ my ($size, $maxread) = @_;
+ #print "io_reader2($size, $maxread) pos $seekpos\n";
+ my $out = substr($work, $seekpos, $size);
+ $seekpos += length $out;
+ $out;
+}
+use IO::Seekable;
+sub io_seeker {
+ my ($offset, $whence) = @_;
+ #print "io_seeker($offset, $whence)\n";
+ if ($whence == SEEK_SET) {
+ $seekpos = $offset;
+ }
+ elsif ($whence == SEEK_CUR) {
+ $seekpos += $offset;
+ }
+ else { # SEEK_END
+ $seekpos = length($work) + $offset;
+ }
+ #print "-> $seekpos\n";
+ $seekpos;
+}
+my $did_close;
+sub io_closer {
+ ++$did_close;
+}
+
+# read via cb
+$work = $tiffdata;
+$seekpos = 0;
+my $IO2 = Imager::io_new_cb(undef, \&io_reader, \&io_seeker, undef);
+ok($IO2, "new readcb obj");
+my $img5 = Imager::File::TIFF::i_readtiff_wiol($IO2);
+ok($img5, "read via cb");
+ok(i_img_diff($img5, $img) == 0, "read from cb diff");
+
+# read via cb2
+$work = $tiffdata;
+$seekpos = 0;
+my $IO3 = Imager::io_new_cb(undef, \&io_reader2, \&io_seeker, undef);
+ok($IO3, "new readcb2 obj");
+my $img6 = Imager::File::TIFF::i_readtiff_wiol($IO3);
+ok($img6, "read via cb2");
+ok(i_img_diff($img6, $img) == 0, "read from cb2 diff");
+
+# write via cb
+$work = '';
+$seekpos = 0;
+my $IO4 = Imager::io_new_cb(\&io_writer, \&io_reader, \&io_seeker,
+ \&io_closer);
+ok($IO4, "new writecb obj");
+ok(Imager::File::TIFF::i_writetiff_wiol($img, $IO4), "write to cb");
+is($work, $odata, "write cb match");
+ok($did_close, "write cb did close");
+open D1, ">testout/d1.tiff" or die;
+print D1 $work;
+close D1;
+open D2, ">testout/d2.tiff" or die;
+print D2 $tiffdata;
+close D2;
+
+# write via cb2
+$work = '';
+$seekpos = 0;
+$did_close = 0;
+my $IO5 = Imager::io_new_cb(\&io_writer, \&io_reader, \&io_seeker,
+ \&io_closer, 1);
+ok($IO5, "new writecb obj 2");
+ok(Imager::File::TIFF::i_writetiff_wiol($img, $IO5), "write to cb2");
+is($work, $odata, "write cb2 match");
+ok($did_close, "write cb2 did close");
+
+open D3, ">testout/d3.tiff" or die;
+print D3 $work;
+close D3;
+
+# multi-image write/read
+my @imgs;
+push(@imgs, map $ooim->copy(), 1..3);
+for my $i (0..$#imgs) {
+ $imgs[$i]->addtag(name=>"tiff_pagename", value=>"Page ".($i+1));
+}
+my $rc = Imager->write_multi({file=>'testout/t106_multi.tif'}, @imgs);
+ok($rc, "writing multiple images to tiff");
+my @out = Imager->read_multi(file=>'testout/t106_multi.tif');
+ok(@out == @imgs, "reading multiple images from tiff");
+@out == @imgs or print "# ",scalar @out, " ",Imager->errstr,"\n";
+for my $i (0..$#imgs) {
+ ok(i_img_diff($imgs[$i]{IMG}, $out[$i]{IMG}) == 0,
+ "comparing image $i");
+ my ($tag) = $out[$i]->tags(name=>'tiff_pagename');
+ is($tag, "Page ".($i+1),
+ "tag doesn't match original image");
+}
+
+# writing even more images to tiff - we weren't handling more than five
+# correctly on read
+@imgs = map $ooim->copy(), 1..40;
+$rc = Imager->write_multi({file=>'testout/t106_multi2.tif'}, @imgs);
+ok($rc, "writing 40 images to tiff");
+@out = Imager->read_multi(file=>'testout/t106_multi2.tif');
+ok(@imgs == @out, "reading 40 images from tiff");
+# force some allocation activity - helps crash here if it's the problem
+@out = @imgs = ();
+
+# multi-image fax files
+ok(Imager->write_multi({file=>'testout/t106_faxmulti.tiff', class=>'fax'},
+ $oofim, $oofim), "write multi fax image");
+@imgs = Imager->read_multi(file=>'testout/t106_faxmulti.tiff');
+ok(@imgs == 2, "reading multipage fax");
+ok(Imager::i_img_diff($imgs[0]{IMG}, $oofim->{IMG}) == 0,
+ "compare first fax image");
+ok(Imager::i_img_diff($imgs[1]{IMG}, $oofim->{IMG}) == 0,
+ "compare second fax image");
+
+my ($format) = $imgs[0]->tags(name=>'i_format');
+is($format, 'tiff', "check i_format tag");
+
+my $unit = $imgs[0]->tags(name=>'tiff_resolutionunit');
+ok(defined $unit && $unit == 2, "check tiff_resolutionunit tag");
+my $unitname = $imgs[0]->tags(name=>'tiff_resolutionunit_name');
+is($unitname, 'inch', "check tiff_resolutionunit_name tag");
+
+my $warned = Imager->new;
+ok($warned->read(file=>"testimg/tiffwarn.tif"), "read tiffwarn.tif");
+my ($warning) = $warned->tags(name=>'i_warning');
+ok(defined $warning && $warning =~ /unknown field with tag 28712/,
+ "check that warning tag set and correct");
+
+{ # support for reading a given page
+ # first build a simple test image
+ my $im1 = Imager->new(xsize=>50, ysize=>50);
+ $im1->box(filled=>1, color=>$blue);
+ $im1->addtag(name=>'tiff_pagename', value => "Page One");
+ my $im2 = Imager->new(xsize=>60, ysize=>60);
+ $im2->box(filled=>1, color=>$green);
+ $im2->addtag(name=>'tiff_pagename', value=>"Page Two");
+
+ # read second page
+ my $page_file = 'testout/t106_pages.tif';
+ ok(Imager->write_multi({ file=> $page_file}, $im1, $im2),
+ "build simple multiimage for page tests");
+ my $imwork = Imager->new;
+ ok($imwork->read(file=>$page_file, page=>1),
+ "read second page");
+ is($im2->getwidth, $imwork->getwidth, "check width");
+ is($im2->getwidth, $imwork->getheight, "check height");
+ is(i_img_diff($imwork->{IMG}, $im2->{IMG}), 0,
+ "check image content");
+ my ($page_name) = $imwork->tags(name=>'tiff_pagename');
+ is($page_name, 'Page Two', "check tag we set");
+
+ # try an out of range page
+ ok(!$imwork->read(file=>$page_file, page=>2),
+ "check out of range page");
+ is($imwork->errstr, "could not switch to page 2", "check message");
+}
+
+{ # test writing returns an error message correctly
+ # open a file read only and try to write to it
+ open TIFF, "> testout/t106_empty.tif" or die;
+ close TIFF;
+ open TIFF, "< testout/t106_empty.tif"
+ or skip "Cannot open testout/t106_empty.tif for reading", 8;
+ binmode TIFF;
+ my $im = Imager->new(xsize=>100, ysize=>100);
+ ok(!$im->write(fh => \*TIFF, type=>'tiff'),
+ "fail to write to read only handle");
+ cmp_ok($im->errstr, '=~', 'Could not create TIFF object: Error writing TIFF header: write\(\)',
+ "check error message");
+ ok(!Imager->write_multi({ type => 'tiff', fh => \*TIFF }, $im),
+ "fail to write multi to read only handle");
+ cmp_ok(Imager->errstr, '=~', 'Could not create TIFF object: Error writing TIFF header: write\(\)',
+ "check error message");
+ ok(!$im->write(fh => \*TIFF, type=>'tiff', class=>'fax'),
+ "fail to write to read only handle (fax)");
+ cmp_ok($im->errstr, '=~', 'Could not create TIFF object: Error writing TIFF header: write\(\)',
+ "check error message");
+ ok(!Imager->write_multi({ type => 'tiff', fh => \*TIFF, class=>'fax' }, $im),
+ "fail to write multi to read only handle (fax)");
+ cmp_ok(Imager->errstr, '=~', 'Could not create TIFF object: Error writing TIFF header: write\(\)',
+ "check error message");
+}
+
+{ # test reading returns an error correctly - use test script as an
+ # invalid TIFF file
+ my $im = Imager->new;
+ ok(!$im->read(file=>'t/t10tiff.t', type=>'tiff'),
+ "fail to read script as image");
+ # we get different magic number values depending on the platform
+ # byte ordering
+ cmp_ok($im->errstr, '=~',
+ "Error opening file: Not a TIFF (?:or MDI )?file, bad magic number (8483 \\(0x2123\\)|8993 \\(0x2321\\))",
+ "check error message");
+ my @ims = Imager->read_multi(file =>'t/t106tiff.t', type=>'tiff');
+ ok(!@ims, "fail to read_multi script as image");
+ cmp_ok($im->errstr, '=~',
+ "Error opening file: Not a TIFF (?:or MDI )?file, bad magic number (8483 \\(0x2123\\)|8993 \\(0x2321\\))",
+ "check error message");
+}
+
+{ # write_multi to data
+ my $data;
+ my $im = Imager->new(xsize => 50, ysize => 50);
+ ok(Imager->write_multi({ data => \$data, type=>'tiff' }, $im, $im),
+ "write multi to in memory");
+ ok(length $data, "make sure something written");
+ my @im = Imager->read_multi(data => $data);
+ is(@im, 2, "make sure we can read it back");
+ is(Imager::i_img_diff($im[0]{IMG}, $im->{IMG}), 0,
+ "check first image");
+ is(Imager::i_img_diff($im[1]{IMG}, $im->{IMG}), 0,
+ "check second image");
+}
+
+{ # handling of an alpha channel for various images
+ my $photo_rgb = 2;
+ my $photo_cmyk = 5;
+ my $photo_cielab = 8;
+ my @alpha_images =
+ (
+ [ 'srgb.tif', 3, $photo_rgb, '003005005' ],
+ [ 'srgba.tif', 4, $photo_rgb, '003005005' ],
+ [ 'srgbaa.tif', 4, $photo_rgb, '003005005' ],
+ [ 'scmyk.tif', 3, $photo_cmyk, '003005005' ],
+ [ 'scmyka.tif', 4, $photo_cmyk, '003005005' ],
+ [ 'scmykaa.tif', 4, $photo_cmyk, '003005005' ],
+ [ 'slab.tif', 3, $photo_cielab, '003006001' ],
+ );
+
+ for my $test (@alpha_images) {
+ my ($input, $channels, $photo, $need_ver) = @$test;
+
+ SKIP: {
+ my $skipped = $channels == 4 ? 4 : 3;
+ $need_ver le $cmp_ver
+ or skip("Your ancient tifflib is buggy/limited for this test", $skipped);
+ my $im = Imager->new;
+ ok($im->read(file => "testimg/$input"),
+ "read alpha test $input")
+ or print "# ", $im->errstr, "\n";
+ is($im->getchannels, $channels, "channels for $input match");
+ is($im->tags(name=>'tiff_photometric'), $photo,
+ "photometric for $input match");
+ $channels == 4
+ or next;
+ my $c = $im->getpixel(x => 0, 'y' => 7);
+ is(($c->rgba)[3], 0, "bottom row should have 0 alpha");
+ }
+ }
+}
+
+{
+ ok(grep($_ eq 'tiff', Imager->read_types), "check tiff in read types");
+ ok(grep($_ eq 'tiff', Imager->write_types), "check tiff in write types");
+}
+
+{ # reading tile based images
+ my $im = Imager->new;
+ ok($im->read(file => 'testimg/pengtile.tif'), "read tiled image")
+ or print "# ", $im->errstr, "\n";
+ # compare it
+ my $comp = Imager->new;
+ ok($comp->read(file => 'testimg/penguin-base.ppm'), 'read comparison image');
+ is_image($im, $comp, 'compare them');
+}
+
+SKIP:
+{ # failing to read tile based images
+ # we grab our tiled image and patch a tile offset to nowhere
+ ok(open(TIFF, '< testimg/pengtile.tif'), 'open pengtile.tif')
+ or skip 'cannot open testimg/pengtile.tif', 4;
+
+ $cmp_ver ge '003005007'
+ or skip("Your ancient tifflib has bad error handling", 4);
+ binmode TIFF;
+ my $data = do { local $/; <TIFF>; };
+
+ # patch a tile offset
+ substr($data, 0x1AFA0, 4) = pack("H*", "00000200");
+
+ #open PIPE, "| bytedump -a | less" or die;
+ #print PIPE $data;
+ #close PIPE;
+
+ my $allow = Imager->new;
+ ok($allow->read(data => $data, allow_incomplete => 1),
+ "read incomplete tiled");
+ ok($allow->tags(name => 'i_incomplete'), 'i_incomplete set');
+ is($allow->tags(name => 'i_lines_read'), 173,
+ 'check i_lines_read set appropriately');
+
+ my $fail = Imager->new;
+ ok(!$fail->read(data => $data), "read fail tiled");
+}
+
+{ # read 16-bit/sample
+ my $im16 = Imager->new;
+ ok($im16->read(file => 'testimg/rgb16.tif'), "read 16-bit rgb");
+ is($im16->bits, 16, 'got a 16-bit image');
+ my $im16t = Imager->new;
+ ok($im16t->read(file => 'testimg/rgb16t.tif'), "read 16-bit rgb tiled");
+ is($im16t->bits, 16, 'got a 16-bit image');
+ is_image($im16, $im16t, 'check they match');
+
+ my $grey16 = Imager->new;
+ ok($grey16->read(file => 'testimg/grey16.tif'), "read 16-bit grey")
+ or print "# ", $grey16->errstr, "\n";
+ is($grey16->bits, 16, 'got a 16-bit image');
+ is($grey16->getchannels, 1, 'and its grey');
+ my $comp16 = $im16->convert(matrix => [ [ 0.299, 0.587, 0.114 ] ]);
+ is_image($grey16, $comp16, 'compare grey to converted');
+
+ my $grey32 = Imager->new;
+ ok($grey32->read(file => 'testimg/grey32.tif'), "read 32-bit grey")
+ or print "# ", $grey32->errstr, "\n";
+ is($grey32->bits, 'double', 'got a double image');
+ is($grey32->getchannels, 2, 'and its grey + alpha');
+ is($grey32->tags(name => 'tiff_bitspersample'), 32,
+ "check bits per sample");
+ my $base = test_image_double->convert(preset =>'grey')
+ ->convert(preset => 'addalpha');
+ is_image($grey32, $base, 'compare to original');
+}
+
+{ # read 16, 32-bit/sample and compare to the original
+ my $rgba = Imager->new;
+ ok($rgba->read(file => 'testimg/srgba.tif'),
+ "read base rgba image");
+ my $rgba16 = Imager->new;
+ ok($rgba16->read(file => 'testimg/srgba16.tif'),
+ "read 16-bit/sample rgba image");
+ is_image($rgba, $rgba16, "check they match");
+ is($rgba16->bits, 16, 'check we got the right type');
+
+ my $rgba32 = Imager->new;
+ ok($rgba32->read(file => 'testimg/srgba32.tif'),
+ "read 32-bit/sample rgba image");
+ is_image($rgba, $rgba32, "check they match");
+ is($rgba32->bits, 'double', 'check we got the right type');
+
+ my $cmyka16 = Imager->new;
+ ok($cmyka16->read(file => 'testimg/scmyka16.tif'),
+ "read cmyk 16-bit")
+ or print "# ", $cmyka16->errstr, "\n";
+ is($cmyka16->bits, 16, "check we got the right type");
+ is_image_similar($rgba, $cmyka16, 10, "check image data");
+
+ # tiled, non-contig, should fallback to RGBA code
+ my $rgbatsep = Imager->new;
+ ok($rgbatsep->read(file => 'testimg/rgbatsep.tif'),
+ "read tiled, separated rgba image")
+ or diag($rgbatsep->errstr);
+ is_image($rgba, $rgbatsep, "check they match");
+}
+{ # read bi-level
+ my $pbm = Imager->new;
+ ok($pbm->read(file => 'testimg/imager.pbm'), "read original pbm");
+ my $tif = Imager->new;
+ ok($tif->read(file => 'testimg/imager.tif'), "read mono tif");
+ is_image($pbm, $tif, "compare them");
+ is($tif->type, 'paletted', 'check image type');
+ is($tif->colorcount, 2, 'check we got a "mono" image');
+}
+
+{ # check alpha channels scaled correctly for fallback handler
+ my $im = Imager->new;
+ ok($im->read(file=>'testimg/alpha.tif'), 'read alpha check image');
+ my @colors =
+ (
+ [ 0, 0, 0 ],
+ [ 255, 255, 255 ],
+ [ 127, 0, 127 ],
+ [ 127, 127, 0 ],
+ );
+ my @alphas = ( 255, 191, 127, 63 );
+ my $ok = 1;
+ my $msg = 'alpha check ok';
+ CHECKER:
+ for my $y (0 .. 3) {
+ for my $x (0 .. 3) {
+ my $c = $im->getpixel(x => $x, 'y' => $y);
+ my @c = $c->rgba;
+ my $alpha = pop @c;
+ if ($alpha != $alphas[$y]) {
+ $ok = 0;
+ $msg = "($x,$y) alpha mismatch $alpha vs $alphas[$y]";
+ last CHECKER;
+ }
+ my $expect = $colors[$x];
+ for my $ch (0 .. 2) {
+ if (abs($expect->[$ch]-$c[$ch]) > 3) {
+ $ok = 0;
+ $msg = "($x,$y)[$ch] color mismatch got $c[$ch] vs expected $expect->[$ch]";
+ last CHECKER;
+ }
+ }
+ }
+ }
+ ok($ok, $msg);
+}
+
+{ # check alpha channels scaled correctly for greyscale
+ my $im = Imager->new;
+ ok($im->read(file=>'testimg/gralpha.tif'), 'read alpha check grey image');
+ my @greys = ( 0, 255, 52, 112 );
+ my @alphas = ( 255, 191, 127, 63 );
+ my $ok = 1;
+ my $msg = 'alpha check ok';
+ CHECKER:
+ for my $y (0 .. 3) {
+ for my $x (0 .. 3) {
+ my $c = $im->getpixel(x => $x, 'y' => $y);
+ my ($grey, $alpha) = $c->rgba;
+ if ($alpha != $alphas[$y]) {
+ $ok = 0;
+ $msg = "($x,$y) alpha mismatch $alpha vs $alphas[$y]";
+ last CHECKER;
+ }
+ if (abs($greys[$x] - $grey) > 3) {
+ $ok = 0;
+ $msg = "($x,$y) grey mismatch $grey vs $greys[$x]";
+ last CHECKER;
+ }
+ }
+ }
+ ok($ok, $msg);
+}
+
+{ # 16-bit writes
+ my $orig = test_image_16();
+ my $data;
+ ok($orig->write(data => \$data, type => 'tiff',
+ tiff_compression => 'none'), "write 16-bit/sample");
+ my $im = Imager->new;
+ ok($im->read(data => $data), "read it back");
+ is_image($im, $orig, "check read data matches");
+ is($im->tags(name => 'tiff_bitspersample'), 16, "correct bits");
+ is($im->bits, 16, 'check image bits');
+ is($im->tags(name => 'tiff_photometric'), 2, "correct photometric");
+ is($im->tags(name => 'tiff_compression'), 'none', "no compression");
+ is($im->getchannels, 3, 'correct channels');
+}
+
+{ # 8-bit writes
+ # and check compression
+ my $compress = Imager::File::TIFF::i_tiff_has_compression('lzw') ? 'lzw' : 'packbits';
+ my $orig = test_image()->convert(preset=>'grey')
+ ->convert(preset => 'addalpha');
+ my $data;
+ ok($orig->write(data => \$data, type => 'tiff',
+ tiff_compression=> $compress),
+ "write 8 bit")
+ or print "# ", $orig->errstr, "\n";
+ my $im = Imager->new;
+ ok($im->read(data => $data), "read it back");
+ is_image($im, $orig, "check read data matches");
+ is($im->tags(name => 'tiff_bitspersample'), 8, 'correct bits');
+ is($im->bits, 8, 'check image bits');
+ is($im->tags(name => 'tiff_photometric'), 1, 'correct photometric');
+ is($im->tags(name => 'tiff_compression'), $compress,
+ "$compress compression");
+ is($im->getchannels, 2, 'correct channels');
+}
+
+{ # double writes
+ my $orig = test_image_double()->convert(preset=>'addalpha');
+ my $data;
+ ok($orig->write(data => \$data, type => 'tiff',
+ tiff_compression => 'none'),
+ "write 32-bit/sample from double")
+ or print "# ", $orig->errstr, "\n";
+ my $im = Imager->new;
+ ok($im->read(data => $data), "read it back");
+ is_image($im, $orig, "check read data matches");
+ is($im->tags(name => 'tiff_bitspersample'), 32, "correct bits");
+ is($im->bits, 'double', 'check image bits');
+ is($im->tags(name => 'tiff_photometric'), 2, "correct photometric");
+ is($im->tags(name => 'tiff_compression'), 'none', "no compression");
+ is($im->getchannels, 4, 'correct channels');
+}
+
+{ # bilevel
+ my $im = test_image()->convert(preset => 'grey')
+ ->to_paletted(make_colors => 'mono',
+ translate => 'errdiff');
+ my $faxdata;
+
+ # fax compression is written as miniswhite
+ ok($im->write(data => \$faxdata, type => 'tiff',
+ tiff_compression => 'fax3'),
+ "write bilevel fax compressed");
+ my $fax = Imager->new;
+ ok($fax->read(data => $faxdata), "read it back");
+ ok($fax->is_bilevel, "got a bi-level image back");
+ is($fax->tags(name => 'tiff_compression'), 'fax3',
+ "check fax compression used");
+ is_image($fax, $im, "compare to original");
+
+ # other compresion written as minisblack
+ my $packdata;
+ ok($im->write(data => \$packdata, type => 'tiff',
+ tiff_compression => 'jpeg'),
+ "write bilevel packbits compressed");
+ my $packim = Imager->new;
+ ok($packim->read(data => $packdata), "read it back");
+ ok($packim->is_bilevel, "got a bi-level image back");
+ is($packim->tags(name => 'tiff_compression'), 'packbits',
+ "check fallback compression used");
+ is_image($packim, $im, "compare to original");
+}
+
+{ # fallback handling of tiff
+ is(Imager::File::TIFF::i_tiff_has_compression('none'), 1, "can always do uncompresed");
+ is(Imager::File::TIFF::i_tiff_has_compression('xxx'), '', "can't do xxx compression");
+}
+
+