+
+ (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);
+
+ io_glue_commit_types(ig);
+ 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);