summaryrefslogtreecommitdiff
path: root/src/im_format_tiff.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/im_format_tiff.cpp')
-rw-r--r--src/im_format_tiff.cpp1421
1 files changed, 1421 insertions, 0 deletions
diff --git a/src/im_format_tiff.cpp b/src/im_format_tiff.cpp
new file mode 100644
index 0000000..98467f9
--- /dev/null
+++ b/src/im_format_tiff.cpp
@@ -0,0 +1,1421 @@
+/** \file
+ * \brief TIFF - Tagged Image File Format
+ *
+ * See Copyright Notice in im_lib.h
+ * See libTIFF Copyright Notice in tiff.h
+ * $Id: im_format_tiff.cpp,v 1.1 2008/10/17 06:10:16 scuri Exp $
+ */
+
+#include "im_format.h"
+#include "im_util.h"
+#include "im_format_all.h"
+#include "im_counter.h"
+
+#include "tiffiop.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <memory.h>
+
+
+#define TIFFTAG_GEOPIXELSCALE 33550
+#define TIFFTAG_INTERGRAPH_MATRIX 33920
+#define TIFFTAG_GEOTIEPOINTS 33922
+#define TIFFTAG_GEOTRANSMATRIX 34264
+#define TIFFTAG_GEOKEYDIRECTORY 34735
+#define TIFFTAG_GEODOUBLEPARAMS 34736
+#define TIFFTAG_GEOASCIIPARAMS 34737
+
+#define TIFFTAG_CFAREPEATPATTERNDIM 33421 /* dimensions of CFA pattern */
+#define TIFFTAG_CFAPATTERN 33422 /* color filter array pattern */
+#define PHOTOMETRIC_CFA 32803 /* color filter array */
+#define PHOTOMETRIC_LINEARRAW 34892
+
+static const TIFFFieldInfo iTiffFieldInfo[] =
+{
+ /* missing in libTIFF (fixed in libtiff 4.0) */
+ { EXIFTAG_COLORSPACE, 1, 1, TIFF_SHORT, FIELD_CUSTOM, TRUE, FALSE, "ColorSpace" },
+
+ /* Patch from Dave Coffin (Used for DNG) */
+ { TIFFTAG_WHITELEVEL, -2, -1, TIFF_LONG, FIELD_CUSTOM, 0, 1, "WhiteLevel" },
+ { TIFFTAG_WHITELEVEL, -2, -1, TIFF_SHORT, FIELD_CUSTOM, 0, 1, "WhiteLevel" },
+ { TIFFTAG_CFAREPEATPATTERNDIM, 2, 2, TIFF_SHORT, FIELD_CUSTOM, 0, 0, "CFARepeatPatternDim" },
+ { TIFFTAG_CFAPATTERN, -1, -1, TIFF_BYTE, FIELD_CUSTOM, 0, 1, "CFAPattern" },
+
+ /* GeoTIFF Tags */
+ { TIFFTAG_GEOPIXELSCALE, -1,-1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE, TRUE, "GeoPixelScale" },
+ { TIFFTAG_INTERGRAPH_MATRIX,-1,-1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE, TRUE, "Intergraph TransformationMatrix" },
+ { TIFFTAG_GEOTIEPOINTS, -1,-1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE, TRUE, "GeoTiePoints" },
+ { TIFFTAG_GEOTRANSMATRIX, -1,-1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE, TRUE, "GeoTransformationMatrix" },
+ { TIFFTAG_GEOKEYDIRECTORY,-1,-1, TIFF_SHORT, FIELD_CUSTOM, TRUE, TRUE, "GeoKeyDirectory" },
+ { TIFFTAG_GEODOUBLEPARAMS, -1,-1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE, TRUE, "GeoDoubleParams" },
+ { TIFFTAG_GEOASCIIPARAMS, -1,-1, TIFF_ASCII, FIELD_CUSTOM, TRUE, FALSE, "GeoASCIIParams" }
+};
+
+#define IMTIFF_NUMCOMP 15
+
+/* this list must be sorted because of bsearch */
+static uint16 iTIFFCompIdTable [IMTIFF_NUMCOMP] =
+{
+ COMPRESSION_NONE,
+ COMPRESSION_CCITTRLE,
+ COMPRESSION_CCITTFAX3,
+ COMPRESSION_CCITTFAX4,
+ COMPRESSION_LZW,
+ COMPRESSION_JPEG,
+ COMPRESSION_ADOBE_DEFLATE,
+ COMPRESSION_NEXT,
+ COMPRESSION_CCITTRLEW,
+ COMPRESSION_PACKBITS,
+ COMPRESSION_THUNDERSCAN,
+ COMPRESSION_PIXARLOG,
+ COMPRESSION_DEFLATE,
+ COMPRESSION_SGILOG,
+ COMPRESSION_SGILOG24
+};
+
+static int iTIFFCompareCompID(const void *elem1, const void *elem2)
+{
+ const uint16 *tiff_comp_elem1 = (const uint16 *)elem1;
+ const uint16 *tiff_comp_elem2 = (const uint16 *)elem2;
+
+ if (*tiff_comp_elem1 > *tiff_comp_elem2)
+ return 1;
+
+ if (*tiff_comp_elem1 < *tiff_comp_elem2)
+ return -1;
+
+ return 0;
+}
+
+static int iTIFFGetCompIndex(uint16 Compression)
+{
+ if (Compression == COMPRESSION_OJPEG)
+ Compression = COMPRESSION_JPEG;
+
+ uint16* comp_result = (uint16 *)bsearch(&Compression, iTIFFCompIdTable, sizeof(iTIFFCompIdTable)/sizeof(uint16), sizeof(uint16), iTIFFCompareCompID);
+
+ if (comp_result == NULL)
+ {
+ return -1;
+ }
+
+ return (comp_result - iTIFFCompIdTable);
+}
+
+/* this list must follow iTIFFCompIdTable order */
+static const char* iTIFFCompTable[IMTIFF_NUMCOMP] =
+{
+ "NONE",
+ "CCITTRLE",
+ "CCITTFAX3",
+ "CCITTFAX4",
+ "LZW",
+ "JPEG",
+ "ADOBEDEFLATE",
+ "NEXT",
+ "CCITTRLEW",
+ "RLE",
+ "THUNDERSCAN",
+ "PIXARLOG",
+ "DEFLATE",
+ "SGILOG",
+ "SGILOG24"
+};
+
+static uint16 iTIFFCompFind(const char* compression)
+{
+ for(int i = 0; i < IMTIFF_NUMCOMP; i++)
+ {
+ if (imStrEqual(compression, iTIFFCompTable[i]))
+ return iTIFFCompIdTable[i];
+ }
+
+ return (uint16)-1;
+}
+
+static uint16 iTIFFCompDefault(int color_space, int data_type)
+{
+ if (color_space == IM_BINARY)
+ return COMPRESSION_CCITTRLE;
+
+ if (color_space == IM_MAP)
+ return COMPRESSION_PACKBITS;
+
+ if (color_space == IM_YCBCR && data_type == IM_BYTE)
+ return COMPRESSION_JPEG;
+
+ if (color_space == IM_XYZ)
+ return COMPRESSION_SGILOG;
+
+ if (data_type >= IM_FLOAT)
+ return COMPRESSION_NONE;
+
+ return COMPRESSION_LZW;
+}
+
+static uint16 iTIFFCompCalc(const char* compression, int color_mode, int data_type)
+{
+ uint16 Compression;
+ if (compression[0] == 0)
+ Compression = iTIFFCompDefault(imColorModeSpace(color_mode), data_type);
+ else
+ Compression = iTIFFCompFind(compression);
+
+ return Compression;
+}
+
+static int iTIFFWriteTag(TIFF* tiff, int index, const char* name, int data_type, int count, const void* data)
+{
+ const TIFFFieldInfo *fld = TIFFFieldWithName(tiff, name);
+ (void)data_type;
+ (void)index;
+ if (fld)
+ {
+ if (fld->field_tag == TIFFTAG_EXIFIFD || /* offset */
+ fld->field_tag == TIFFTAG_GPSIFD ||
+ fld->field_tag == TIFFTAG_INTEROPERABILITYIFD ||
+ fld->field_tag == TIFFTAG_SUBIFD ||
+ fld->field_tag == TIFFTAG_COLORMAP || /* handled elsewhere */
+ fld->field_tag == TIFFTAG_EXTRASAMPLES ||
+ fld->field_tag == TIFFTAG_TRANSFERFUNCTION ||
+ fld->field_tag == TIFFTAG_RESOLUTIONUNIT ||
+ fld->field_tag == TIFFTAG_XRESOLUTION ||
+ fld->field_tag == TIFFTAG_YRESOLUTION ||
+ fld->field_tag == TIFFTAG_INKNAMES)
+ return 1;
+
+ if (fld->field_passcount)
+ {
+ double* double_data = NULL;
+
+ if (fld->field_type==TIFF_DOUBLE)
+ {
+ float* float_data = (float*)data;
+ double_data = new double [count];
+ for (int p = 0; p < count; p++)
+ double_data[p] = float_data[p];
+ data = double_data;
+ }
+
+ if (fld->field_writecount == TIFF_VARIABLE2)
+ {
+ uint32 value_count = (uint32)count;
+ if (TIFFSetField(tiff, fld->field_tag, value_count, data) != 1)
+ return 1;
+ }
+ else
+ {
+ uint16 value_count = (uint16)count;
+ if (TIFFSetField(tiff, fld->field_tag, value_count, data) != 1)
+ return 1;
+ }
+
+ if (fld->field_type==TIFF_DOUBLE)
+ delete [] double_data;
+ }
+ else
+ {
+ if (fld->field_tag == TIFFTAG_PAGENUMBER ||
+ fld->field_tag == TIFFTAG_HALFTONEHINTS ||
+ fld->field_tag == TIFFTAG_YCBCRSUBSAMPLING ||
+ fld->field_tag == TIFFTAG_DOTRANGE)
+ {
+ // there are 2 separated ushort values
+ uint16* ushort_value = (uint16*)data;
+ TIFFSetField(tiff, fld->field_tag, ushort_value[0], ushort_value[1]);
+ return 1;
+ }
+
+ if (count > 1 || fld->field_type == TIFF_ASCII)
+ TIFFSetField(tiff, fld->field_tag, data);
+ else
+ {
+ switch(fld->field_type)
+ {
+ case TIFF_UNDEFINED:
+ case TIFF_ASCII:
+ case TIFF_BYTE:
+ case TIFF_SBYTE:
+ {
+ imbyte* byte_data = (imbyte*)data;
+ TIFFSetField(tiff, fld->field_tag, *byte_data);
+ }
+ break;
+ case TIFF_SHORT:
+ case TIFF_SSHORT:
+ {
+ imushort* short_data = (imushort*)data;
+ TIFFSetField(tiff, fld->field_tag, *short_data);
+ }
+ break;
+ case TIFF_LONG:
+ case TIFF_SLONG:
+ {
+ int* long_data = (int*)data;
+ TIFFSetField(tiff, fld->field_tag, *long_data);
+ }
+ break;
+ case TIFF_RATIONAL:
+ case TIFF_SRATIONAL:
+ case TIFF_FLOAT:
+ {
+ float* float_data = (float*)data;
+ TIFFSetField(tiff, fld->field_tag, *float_data);
+ }
+ break;
+ case TIFF_DOUBLE:
+ {
+ float* float_data = (float*)data;
+ TIFFSetField(tiff, fld->field_tag, (double)*float_data);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ return 1;
+}
+
+static void iTIFFWriteCustomTags(TIFF* tiff, imAttribTable* attrib_table)
+{
+ attrib_table->ForEach(tiff, (imAttribTableCallback)iTIFFWriteTag);
+}
+
+static void iTIFFReadCustomTags(TIFF* tiff, imAttribTable* attrib_table)
+{
+ int i;
+ short tag_count;
+
+ tag_count = (short) TIFFGetTagListCount(tiff);
+ for( i = 0; i < tag_count; i++ )
+ {
+ ttag_t tag = TIFFGetTagListEntry(tiff, i);
+ const TIFFFieldInfo *fld;
+
+ fld = TIFFFieldWithTag(tiff, tag);
+ if (fld == NULL)
+ continue;
+
+ if (fld->field_tag == TIFFTAG_EXIFIFD || /* offset */
+ fld->field_tag == TIFFTAG_GPSIFD ||
+ fld->field_tag == TIFFTAG_INTEROPERABILITYIFD ||
+ fld->field_tag == TIFFTAG_SUBIFD ||
+ fld->field_tag == TIFFTAG_COLORMAP || /* handled elsewhere */
+ fld->field_tag == TIFFTAG_EXTRASAMPLES ||
+ fld->field_tag == TIFFTAG_TRANSFERFUNCTION ||
+ fld->field_tag == TIFFTAG_RESOLUTIONUNIT ||
+ fld->field_tag == TIFFTAG_XRESOLUTION ||
+ fld->field_tag == TIFFTAG_YRESOLUTION ||
+ fld->field_tag == TIFFTAG_INKNAMES)
+ continue;
+
+ if (fld->field_tag == TIFFTAG_BLACKLEVEL ||
+ fld->field_tag == TIFFTAG_DEFAULTCROPSIZE ||
+ fld->field_tag == TIFFTAG_DEFAULTCROPORIGIN)
+ {
+ /* libTIFF bug. When reading custom tags there is an incorrect interpretation of the tag
+ that leads to return always type=RATIONAL for these tags. */
+ continue;
+ }
+
+ int data_type = -1, data_count = -1;
+ void* data = NULL;
+
+ if (fld->field_passcount)
+ {
+ if (fld->field_readcount == TIFF_VARIABLE2)
+ {
+ uint32 value_count;
+ if (TIFFGetField(tiff, tag, &value_count, &data) != 1)
+ continue;
+ data_count = value_count;
+ }
+ else
+ {
+ uint16 value_count;
+ if (TIFFGetField(tiff, tag, &value_count, &data) != 1)
+ continue;
+ data_count = value_count;
+ }
+
+ switch(fld->field_type)
+ {
+ case TIFF_UNDEFINED:
+ case TIFF_ASCII:
+ case TIFF_BYTE:
+ case TIFF_SBYTE:
+ data_type = IM_BYTE;
+ break;
+ case TIFF_SHORT:
+ case TIFF_SSHORT:
+ data_type = IM_USHORT;
+ break;
+ case TIFF_LONG:
+ case TIFF_SLONG:
+ data_type = IM_INT;
+ break;
+ case TIFF_RATIONAL:
+ case TIFF_SRATIONAL:
+ case TIFF_FLOAT:
+ data_type = IM_FLOAT;
+ break;
+ case TIFF_DOUBLE:
+ {
+ double* double_data = (double*)data;
+ float* float_data = new float [data_count];
+ for (int p = 0; p < data_count; p++)
+ float_data[p] = (float)double_data[p];
+ attrib_table->Set(fld->field_name, IM_FLOAT, data_count, float_data);
+ delete [] float_data;
+ }
+ continue;
+ default:
+ continue;
+ }
+
+ if (data && data_count > 0)
+ attrib_table->Set(fld->field_name, data_type, data_count, data);
+ }
+ else
+ {
+ data_count = fld->field_readcount;
+
+ if (fld->field_tag == TIFFTAG_PAGENUMBER ||
+ fld->field_tag == TIFFTAG_HALFTONEHINTS ||
+ fld->field_tag == TIFFTAG_YCBCRSUBSAMPLING ||
+ fld->field_tag == TIFFTAG_DOTRANGE)
+ {
+ // there are 2 separated ushort values
+ uint16 ushort_value[2];
+ if (TIFFGetField(tiff, fld->field_tag, &ushort_value[0], &ushort_value[1]))
+ attrib_table->Set(fld->field_name, IM_USHORT, 2, ushort_value);
+ continue;
+ }
+
+ switch(fld->field_type)
+ {
+ case TIFF_UNDEFINED:
+ case TIFF_BYTE:
+ case TIFF_SBYTE:
+ case TIFF_ASCII:
+ data_type = IM_BYTE;
+ break;
+ case TIFF_SHORT:
+ case TIFF_SSHORT:
+ data_type = IM_USHORT;
+ break;
+ case TIFF_LONG:
+ case TIFF_SLONG:
+ data_type = IM_INT;
+ break;
+ case TIFF_RATIONAL:
+ case TIFF_SRATIONAL:
+ case TIFF_FLOAT:
+ case TIFF_DOUBLE:
+ data_type = IM_FLOAT;
+ break;
+ default:
+ continue;
+ }
+
+ if (fld->field_type == TIFF_ASCII ||
+ fld->field_readcount == TIFF_VARIABLE ||
+ fld->field_readcount == TIFF_VARIABLE2 ||
+ fld->field_readcount == TIFF_SPP ||
+ data_count > 1)
+ {
+ if (TIFFGetField(tiff, tag, &data) != 1)
+ continue;
+
+ if (data)
+ {
+ if (fld->field_type == TIFF_ASCII && data_count == -1)
+ data_count = strlen((char*)data)+1;
+
+ if (data_count > 0)
+ {
+ if (fld->field_type == TIFF_DOUBLE)
+ {
+ double* double_data = (double*)data;
+ float* float_data = new float [data_count];
+ for (int p = 0; p < data_count; p++)
+ float_data[p] = (float)double_data[p];
+ attrib_table->Set(fld->field_name, IM_FLOAT, data_count, float_data);
+ delete [] float_data;
+ }
+ else
+ attrib_table->Set(fld->field_name, data_type, data_count, data);
+ }
+ }
+ }
+ else if (data_count == 1)
+ {
+ data = malloc(imDataTypeSize(data_type));
+ if (TIFFGetField(tiff, tag, data) == 1)
+ attrib_table->Set(fld->field_name, data_type, data_count, data);
+ free(data);
+ data = NULL;
+ }
+ }
+ }
+}
+
+static void iTIFFReadAttributes(TIFF* tiff, imAttribTable* attrib_table)
+{
+ uint16 ResolutionUnit = RESUNIT_NONE;
+ TIFFGetField(tiff, TIFFTAG_RESOLUTIONUNIT, &ResolutionUnit);
+ if (ResolutionUnit != RESUNIT_NONE)
+ {
+ float xres = 0, yres = 0;
+
+ TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &xres);
+ TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &yres);
+
+ if (xres != 0 && yres != 0)
+ {
+ if (ResolutionUnit == RESUNIT_INCH)
+ attrib_table->Set("ResolutionUnit", IM_BYTE, 4, "DPI");
+ else
+ attrib_table->Set("ResolutionUnit", IM_BYTE, 4, "DPC");
+
+ attrib_table->Set("XResolution", IM_FLOAT, 1, (void*)&xres);
+ attrib_table->Set("YResolution", IM_FLOAT, 1, (void*)&yres);
+ }
+ }
+
+ uint16 *transferfunction[3];
+ if (TIFFGetField(tiff, TIFFTAG_TRANSFERFUNCTION, &transferfunction[0], &transferfunction[1], &transferfunction[2]))
+ {
+ uint16 SamplesPerPixel = 1, BitsPerSample = 1, ExtraSamples = 0, *SampleInfo;
+ TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &BitsPerSample);
+ TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &ExtraSamples, &SampleInfo);
+ TIFFGetFieldDefaulted(tiff, TIFFTAG_SAMPLESPERPIXEL, &SamplesPerPixel);
+
+ int num = (SamplesPerPixel - ExtraSamples) > 1 ? 3 : 1;
+ int count = 1L<<BitsPerSample;
+ if (num == 1)
+ attrib_table->Set("TransferFunction0", IM_USHORT, count, transferfunction[0]);
+ else
+ {
+ attrib_table->Set("TransferFunction0", IM_USHORT, count, transferfunction[0]);
+ attrib_table->Set("TransferFunction1", IM_USHORT, count, transferfunction[1]);
+ attrib_table->Set("TransferFunction2", IM_USHORT, count, transferfunction[2]);
+ }
+ }
+
+ char *inknames;
+ if (TIFFGetField(tiff, TIFFTAG_INKNAMES, &inknames))
+ {
+ // Ink names are separated by '0', so strlen will measure only the first string
+ uint16 numinks;
+ TIFFGetField(tiff, TIFFTAG_NUMBEROFINKS, &numinks);
+ int inknameslen = 0;
+ for (int k = 0; k < (int)numinks; k++)
+ inknameslen += strlen(inknames+inknameslen)+1;
+ attrib_table->Set("InkNames", IM_BYTE, inknameslen, inknames);
+ }
+
+ iTIFFReadCustomTags(tiff, attrib_table);
+
+ uint32 offset;
+ if (TIFFGetField(tiff, TIFFTAG_EXIFIFD, &offset))
+ {
+ tdir_t cur_dir = TIFFCurrentDirectory(tiff);
+
+ if (!TIFFReadEXIFDirectory(tiff, offset))
+ {
+ TIFFSetDirectory(tiff, cur_dir);
+ return;
+ }
+
+ iTIFFReadCustomTags(tiff, attrib_table);
+ TIFFSetDirectory(tiff, cur_dir);
+ }
+}
+
+static void iTIFFWriteAttributes(TIFF* tiff, imAttribTable* attrib_table)
+{
+ char* res_unit = (char*)attrib_table->Get("ResolutionUnit");
+ if (res_unit)
+ {
+ float* xres = (float*)attrib_table->Get("XResolution");
+ float* yres = (float*)attrib_table->Get("YResolution");
+
+ if (xres && yres)
+ {
+ uint16 tiff_res_unit = RESUNIT_CENTIMETER;
+ if (imStrEqual(res_unit, "DPI"))
+ tiff_res_unit = RESUNIT_INCH;
+
+ TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, tiff_res_unit);
+ TIFFSetField(tiff, TIFFTAG_XRESOLUTION, *xres);
+ TIFFSetField(tiff, TIFFTAG_YRESOLUTION, *yres);
+ }
+ }
+
+ uint16 *transferfunction0 = (uint16*)attrib_table->Get("TransferFunction0");
+ if (transferfunction0)
+ {
+ uint16 SamplesPerPixel = 1, ExtraSamples = 0, *SampleInfo;
+ TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &ExtraSamples, &SampleInfo);
+ TIFFGetFieldDefaulted(tiff, TIFFTAG_SAMPLESPERPIXEL, &SamplesPerPixel);
+
+ int num = (SamplesPerPixel - ExtraSamples) > 1 ? 3 : 1;
+ if (num == 1)
+ TIFFSetField(tiff, TIFFTAG_TRANSFERFUNCTION, transferfunction0);
+ else
+ {
+ uint16 *transferfunction1 = (uint16*)attrib_table->Get("TransferFunction1");
+ uint16 *transferfunction2 = (uint16*)attrib_table->Get("TransferFunction2");
+
+ if (transferfunction1 && transferfunction2)
+ TIFFSetField(tiff, TIFFTAG_TRANSFERFUNCTION, transferfunction0, transferfunction1, transferfunction2);
+ }
+ }
+
+ char* inknames = (char*)attrib_table->Get("InkNames");
+ if (inknames)
+ TIFFSetField(tiff, TIFFTAG_INKNAMES, inknames);
+
+ int proflength;
+ const void* profdata = attrib_table->Get("ICCProfile", (int*)NULL, &proflength);
+ if (profdata)
+ TIFFSetField(tiff, TIFFTAG_ICCPROFILE, proflength, profdata);
+
+ iTIFFWriteCustomTags(tiff, attrib_table);
+}
+
+class imFormatTIFF: public imFormat
+{
+ TIFF* tiff;
+ int invert, // must invert black and white reference
+ cpx_int, // original data is a complex integer
+ lab_fix, // convert CIE Lab to unsigned
+ extra_sample_size, // eliminate extra samples if more than one
+ sample_size,
+ start_plane; // first band to read in a multiband image
+
+ void** tile_buf;
+ int tile_buf_count, tile_width, tile_height, start_row, tile_line_size, tile_line_raw_size;
+
+ int ReadTileline(void* line_buffer, int row, int plane);
+
+public:
+ imFormatTIFF()
+ :imFormat("TIFF",
+ "Tagged Image File Format",
+ "*.tif;*.tiff;",
+ iTIFFCompTable,
+ IMTIFF_NUMCOMP,
+ 1)
+ {}
+ ~imFormatTIFF() {}
+
+ int Open(const char* file_name);
+ int New(const char* file_name);
+ void Close();
+ void* Handle(int index);
+ int ReadImageInfo(int index);
+ int ReadImageData(void* data);
+ int WriteImageInfo();
+ int WriteImageData(void* data);
+ int CanWrite(const char* compression, int color_mode, int data_type) const;
+};
+
+static void iTIFFDefaultDirectory(TIFF *tiff)
+{
+ /* Install the IM Tag field info */
+ TIFFMergeFieldInfo(tiff, iTiffFieldInfo, TIFFArrayCount(iTiffFieldInfo));
+}
+
+void imFormatRegisterTIFF(void)
+{
+ TIFFSetTagExtender(iTIFFDefaultDirectory);
+ imFormatRegister(new imFormatTIFF());
+}
+
+int imFormatTIFF::Open(const char* file_name)
+{
+ this->tiff = TIFFOpen(file_name, "r");
+ if (this->tiff == NULL)
+ return IM_ERR_FORMAT;
+
+ // Return the compression of the first image in the file.
+ uint16 Compression = COMPRESSION_NONE;
+ TIFFGetField(this->tiff, TIFFTAG_COMPRESSION, &Compression);
+ int comp_index = iTIFFGetCompIndex(Compression);
+ if (comp_index == -1) return IM_ERR_COMPRESS;
+ strcpy(this->compression, iTIFFCompTable[comp_index]);
+
+ this->image_count = TIFFNumberOfDirectories(this->tiff);
+ this->tile_buf = 0;
+ this->start_plane = 0;
+
+ return IM_ERR_NONE;
+}
+
+int imFormatTIFF::New(const char* file_name)
+{
+ this->tiff = TIFFOpen(file_name, "w");
+ if (this->tiff == NULL)
+ return IM_ERR_OPEN;
+
+ this->tile_buf = 0;
+
+ return IM_ERR_NONE;
+}
+
+void imFormatTIFF::Close()
+{
+ if (this->tile_buf)
+ {
+ for (int i = 0; i < this->tile_buf_count; i++)
+ free(this->tile_buf[i]);
+ free(this->tile_buf);
+ }
+
+ TIFFClose(this->tiff);
+}
+
+void* imFormatTIFF::Handle(int index)
+{
+ if (index == 0)
+ return (void*)this->tiff->tif_fd;
+ else if (index == 1)
+ return (void*)this->tiff;
+ else
+ return NULL;
+}
+
+int imFormatTIFF::ReadImageInfo(int index)
+{
+ this->cpx_int = 0;
+ this->invert = 0;
+ this->lab_fix = 0;
+ this->extra_sample_size = 0;
+
+ if (!TIFFSetDirectory(this->tiff, (tdir_t)index))
+ return IM_ERR_ACCESS;
+
+ imAttribTable* attrib_table = AttribTable();
+
+ uint16* attrib_start_plane = (uint16*)attrib_table->Get("MultiBandSelect");
+ if (attrib_start_plane)
+ this->start_plane = *attrib_start_plane;
+ else
+ this->start_plane = 0;
+
+ uint16* sub_ifd = (uint16*)attrib_table->Get("SubIFDSelect");
+
+ /* must clear the attribute list, because TIFF can have many different images */
+ attrib_table->RemoveAll();
+
+ void* data = NULL;
+ if (TIFFGetField(this->tiff, TIFFTAG_DNGVERSION, &data) == 1 && data)
+ {
+ uint32 SubFileType = 0;
+ TIFFGetField(this->tiff, TIFFTAG_SUBFILETYPE, &SubFileType);
+
+ uint16 SubIFDsCount = 0;
+ uint32* SubIFDs = NULL;
+ TIFFGetField(this->tiff, TIFFTAG_SUBIFD, &SubIFDsCount, &SubIFDs);
+ attrib_table->Set("SubIFDCount", IM_USHORT, 1, (void*)&SubIFDsCount);
+
+ /* If is a DNG file, and has SubIFDs,
+ then ignore the thumbnail and position at the desired SubIFD. */
+
+ if (SubFileType == FILETYPE_REDUCEDIMAGE && SubIFDsCount != 0)
+ {
+ int index = sub_ifd? *sub_ifd: 0;
+ if (index >= SubIFDsCount) index = SubIFDsCount-1;
+ uint32 SubIFDOffset = SubIFDs[index];
+
+ /* Load the main image attributes, the SubIFD contains only a few attributes. */
+ iTIFFReadAttributes(this->tiff, attrib_table);
+
+ TIFFSetSubDirectory(this->tiff, SubIFDOffset);
+ }
+ }
+
+ uint16 Compression = COMPRESSION_NONE;
+ TIFFGetField(this->tiff, TIFFTAG_COMPRESSION, &Compression);
+ int comp_index = iTIFFGetCompIndex(Compression);
+ if (comp_index == -1) return IM_ERR_COMPRESS;
+ strcpy(this->compression, iTIFFCompTable[comp_index]);
+
+ if (Compression == COMPRESSION_JPEG || Compression == COMPRESSION_OJPEG)
+ TIFFSetField(this->tiff, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
+
+ uint32 Width;
+ if (!TIFFGetField(this->tiff, TIFFTAG_IMAGEWIDTH, &Width))
+ return IM_ERR_FORMAT;
+ this->width = Width;
+
+ uint32 Height;
+ if (!TIFFGetField(this->tiff, TIFFTAG_IMAGELENGTH, &Height))
+ return IM_ERR_FORMAT;
+ this->height = Height;
+
+ uint16 Photometric;
+ if (!TIFFGetField(this->tiff, TIFFTAG_PHOTOMETRIC, &Photometric))
+ return IM_ERR_FORMAT;
+ attrib_table->Set("Photometric", IM_USHORT, 1, (void*)&Photometric);
+
+ switch(Photometric)
+ {
+ case PHOTOMETRIC_MINISWHITE:
+ this->invert = 1;
+ case PHOTOMETRIC_LINEARRAW:
+ case PHOTOMETRIC_CFA:
+ case PHOTOMETRIC_LOGL:
+ case PHOTOMETRIC_MASK:
+ case PHOTOMETRIC_MINISBLACK:
+ this->file_color_mode = IM_GRAY;
+ break;
+ case PHOTOMETRIC_PALETTE:
+ this->file_color_mode = IM_MAP;
+ break;
+ case PHOTOMETRIC_RGB:
+ this->file_color_mode = IM_RGB;
+ break;
+ case PHOTOMETRIC_SEPARATED:
+ this->file_color_mode = IM_CMYK;
+ break;
+ case PHOTOMETRIC_YCBCR:
+ if (Compression == COMPRESSION_JPEG || Compression == COMPRESSION_OJPEG)
+ this->file_color_mode = IM_RGB;
+ else
+ this->file_color_mode = IM_YCBCR;
+ break;
+ case PHOTOMETRIC_CIELAB:
+ this->lab_fix = 1;
+ case PHOTOMETRIC_ITULAB:
+ case PHOTOMETRIC_ICCLAB:
+ this->file_color_mode = IM_LAB;
+ break;
+ case PHOTOMETRIC_LOGLUV:
+ this->file_color_mode = IM_XYZ;
+ break;
+ default:
+ return IM_ERR_DATA;
+ }
+
+ if (Photometric == PHOTOMETRIC_LOGLUV || Photometric == PHOTOMETRIC_LOGL)
+ TIFFSetField(this->tiff, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT);
+
+ uint16 SamplesPerPixel = 1, BitsPerSample = 1;
+ TIFFGetFieldDefaulted(this->tiff, TIFFTAG_BITSPERSAMPLE, &BitsPerSample);
+ TIFFGetFieldDefaulted(this->tiff, TIFFTAG_SAMPLESPERPIXEL, &SamplesPerPixel);
+
+ if (BitsPerSample == 1 && this->file_color_mode == IM_GRAY)
+ this->file_color_mode = IM_BINARY;
+
+ /* consistency checks */
+ if (Photometric == PHOTOMETRIC_PALETTE && (SamplesPerPixel != 1 || BitsPerSample > 8))
+ return IM_ERR_DATA;
+
+ if (Photometric == PHOTOMETRIC_MASK && (SamplesPerPixel != 1 || BitsPerSample != 1))
+ return IM_ERR_DATA;
+
+ if ((Photometric == PHOTOMETRIC_CFA || Photometric == PHOTOMETRIC_LINEARRAW) && SamplesPerPixel == 3) /* when there are 3 sensors */
+ this->file_color_mode = IM_RGB;
+
+ if ((Photometric == PHOTOMETRIC_CFA || Photometric == PHOTOMETRIC_LINEARRAW) && BitsPerSample == 12)
+ this->convert_bpp = 12;
+
+ uint16 PlanarConfig = PLANARCONFIG_CONTIG;
+ TIFFGetFieldDefaulted(this->tiff, TIFFTAG_PLANARCONFIG, &PlanarConfig);
+
+ if (PlanarConfig == PLANARCONFIG_CONTIG && SamplesPerPixel > 1)
+ this->file_color_mode |= IM_PACKED;
+
+ uint16 ExtraSamples = 0, *SampleInfo;
+ TIFFGetFieldDefaulted(this->tiff, TIFFTAG_EXTRASAMPLES, &ExtraSamples, &SampleInfo);
+ if (ExtraSamples == 1)
+ {
+ switch (SampleInfo[0])
+ {
+ case EXTRASAMPLE_UNSPECIFIED: /* !unspecified data */
+ case EXTRASAMPLE_ASSOCALPHA: /* data is pre-multiplied */
+ case EXTRASAMPLE_UNASSALPHA: /* data is not pre-multiplied */
+ this->file_color_mode |= IM_ALPHA;
+ break;
+ }
+ attrib_table->Set("ExtraSampleInfo", IM_USHORT, 1, (void*)&SampleInfo[0]);
+ }
+ else if ((ExtraSamples > 1) && (PlanarConfig == PLANARCONFIG_CONTIG))
+ {
+ /* usually a multi band image, we read only one band */
+ this->sample_size = (BitsPerSample*(SamplesPerPixel-ExtraSamples) + 7)/8;
+ this->extra_sample_size = (BitsPerSample*SamplesPerPixel + 7)/8;
+
+ /* add space for the line buffer (this is more than necessary) */
+ this->line_buffer_extra = TIFFScanlineSize(this->tiff);
+ }
+
+ uint16 SampleFormat = SAMPLEFORMAT_UINT;
+ TIFFGetField(this->tiff, TIFFTAG_SAMPLEFORMAT, &SampleFormat);
+ switch(SampleFormat)
+ {
+ case SAMPLEFORMAT_VOID:
+ case SAMPLEFORMAT_UINT:
+ if (BitsPerSample < 8)
+ {
+ if (BitsPerSample != 1 && BitsPerSample != 2 && BitsPerSample != 4)
+ return IM_ERR_DATA;
+
+ this->file_data_type = IM_BYTE;
+ this->convert_bpp = BitsPerSample;
+ }
+ else if (BitsPerSample == 8)
+ this->file_data_type = IM_BYTE;
+ else if (BitsPerSample <= 16)
+ this->file_data_type = IM_USHORT;
+ else if (BitsPerSample <= 32)
+ {
+ this->switch_type = 1;
+ this->file_data_type = IM_INT;
+ }
+ else
+ return IM_ERR_DATA;
+ break;
+ case SAMPLEFORMAT_INT:
+ if (BitsPerSample <= 8)
+ {
+ this->switch_type = 1;
+ this->file_data_type = IM_BYTE;
+ }
+ else if (BitsPerSample <= 16)
+ {
+ this->switch_type = 1;
+ this->file_data_type = IM_USHORT;
+ }
+ else if (BitsPerSample <= 32)
+ this->file_data_type = IM_INT;
+ else
+ return IM_ERR_DATA;
+ break;
+ case SAMPLEFORMAT_IEEEFP:
+ if (BitsPerSample == 32)
+ this->file_data_type = IM_FLOAT;
+ else if (BitsPerSample == 64)
+ {
+ this->switch_type = 1;
+ this->file_data_type = IM_FLOAT;
+ }
+ else
+ return IM_ERR_DATA;
+ break;
+ case SAMPLEFORMAT_COMPLEXINT:
+ if (BitsPerSample == 32)
+ {
+ this->cpx_int = 1;
+ this->file_data_type = IM_CFLOAT; // convert short to float
+ }
+ else if (BitsPerSample == 64)
+ {
+ this->cpx_int = 2;
+ this->file_data_type = IM_CFLOAT; // convert int to float
+ }
+ else
+ return IM_ERR_DATA;
+ break;
+ case SAMPLEFORMAT_COMPLEXIEEEFP:
+ if (BitsPerSample == 64)
+ this->file_data_type = IM_CFLOAT;
+ else if (BitsPerSample == 128)
+ {
+ this->switch_type = 1;
+ this->file_data_type = IM_CFLOAT;
+ }
+ else
+ return IM_ERR_DATA;
+ break;
+ default:
+ return IM_ERR_DATA;
+ }
+
+ uint16 *rmap, *gmap, *bmap;
+ if (TIFFGetField(this->tiff, TIFFTAG_COLORMAP, &rmap, &gmap, &bmap))
+ {
+ long palette[256];
+ int palette_count = 1 << BitsPerSample;
+
+ for (int c = 0; c < palette_count; c++)
+ {
+ palette[c] = imColorEncode((unsigned char)(rmap[c] >> 8),
+ (unsigned char)(gmap[c] >> 8),
+ (unsigned char)(bmap[c] >> 8));
+ }
+
+ imFileSetPalette(this, palette, palette_count);
+ }
+
+ if (TIFFIsTiled(this->tiff))
+ {
+ if (this->tile_buf)
+ {
+ for (int i = 0; i < this->tile_buf_count; i++)
+ free(this->tile_buf[i]);
+ free(this->tile_buf);
+ }
+
+ uint32 tileWidth, tileLength;
+ TIFFGetField(this->tiff, TIFFTAG_TILEWIDTH, &tileWidth);
+ TIFFGetField(this->tiff, TIFFTAG_TILELENGTH, &tileLength);
+ this->tile_width = (int)tileWidth;
+ this->tile_height = (int)tileLength;
+
+ this->tile_buf_count = (Width + tileWidth-1) / tileWidth;
+ if (PlanarConfig == PLANARCONFIG_SEPARATE)
+ this->tile_buf_count *= SamplesPerPixel;
+ this->tile_line_size = TIFFTileRowSize(this->tiff);
+ this->tile_line_raw_size = TIFFScanlineSize(this->tiff);
+ this->start_row = 0;
+
+ this->tile_buf = (void**)malloc(sizeof(void*)*this->tile_buf_count);
+ int tile_size = TIFFTileSize(this->tiff);
+ for (int t = 0; t < this->tile_buf_count; t++)
+ this->tile_buf[t] = malloc(tile_size);
+ }
+
+ if (SamplesPerPixel < imColorModeDepth(this->file_color_mode))
+ return IM_ERR_DATA;
+
+ if (SamplesPerPixel > 1 && imColorModeSpace(this->file_color_mode) == IM_GRAY)
+ {
+ /* multiband data, we read only one band */
+ attrib_table->Set("MultiBandCount", IM_USHORT, 1, (void*)&SamplesPerPixel);
+ }
+
+ uint16 Orientation;
+ TIFFGetFieldDefaulted(this->tiff, TIFFTAG_ORIENTATION, &Orientation);
+ switch (Orientation)
+ {
+ case ORIENTATION_TOPRIGHT:
+ case ORIENTATION_RIGHTTOP:
+ case ORIENTATION_LEFTTOP:
+ case ORIENTATION_TOPLEFT:
+ this->file_color_mode |= IM_TOPDOWN;
+ break;
+ }
+ attrib_table->Set("Orientation", IM_USHORT, 1, (void*)&Orientation);
+
+ iTIFFReadAttributes(this->tiff, attrib_table);
+
+ return IM_ERR_NONE;
+}
+
+int imFormatTIFF::WriteImageInfo()
+{
+ this->file_color_mode = this->user_color_mode;
+ this->file_data_type = this->user_data_type;
+ this->lab_fix = 0;
+
+ uint16 Compression = iTIFFCompCalc(this->compression, this->file_color_mode, this->file_data_type);
+ if (Compression == (uint16)-1)
+ return IM_ERR_COMPRESS;
+
+ int comp_index = iTIFFGetCompIndex(Compression);
+ strcpy(this->compression, iTIFFCompTable[comp_index]);
+
+ TIFFSetField(this->tiff, TIFFTAG_COMPRESSION, Compression);
+
+ uint32 Width = this->width;
+ TIFFSetField(this->tiff, TIFFTAG_IMAGEWIDTH, Width);
+
+ uint32 Height = this->height;
+ TIFFSetField(this->tiff, TIFFTAG_IMAGELENGTH, Height);
+
+ static uint16 colorspace2photometric [] =
+ {
+ PHOTOMETRIC_RGB,
+ PHOTOMETRIC_PALETTE,
+ PHOTOMETRIC_MINISBLACK,
+ PHOTOMETRIC_MINISBLACK,
+ PHOTOMETRIC_SEPARATED,
+ PHOTOMETRIC_YCBCR,
+ PHOTOMETRIC_CIELAB,
+ (uint16)-1, // Pure Luv not supported
+ PHOTOMETRIC_LOGLUV // LogLuv Saved as XYZ
+ };
+
+ uint16 Photometric = colorspace2photometric[imColorModeSpace(this->file_color_mode)];
+
+ // Correction for sgi LogL
+ if (Compression == COMPRESSION_SGILOG && Photometric == PHOTOMETRIC_MINISBLACK)
+ Photometric = PHOTOMETRIC_LOGL;
+
+ // Corrections for JPEG, automatic convert from RGB to YCbCr when writing
+ if (Compression == COMPRESSION_JPEG && Photometric == PHOTOMETRIC_RGB)
+ Photometric = PHOTOMETRIC_YCBCR;
+
+ imAttribTable* attrib_table = AttribTable();
+
+ uint16* photometric = (uint16*)attrib_table->Get("Photometric");
+ if (photometric)
+ {
+ if (*photometric == PHOTOMETRIC_MASK && Photometric == PHOTOMETRIC_MINISBLACK)
+ Photometric = PHOTOMETRIC_MASK;
+ else if (*photometric == PHOTOMETRIC_MINISWHITE && Photometric == PHOTOMETRIC_MINISBLACK)
+ Photometric = PHOTOMETRIC_MINISWHITE;
+ else if (*photometric == PHOTOMETRIC_ICCLAB && Photometric == PHOTOMETRIC_CIELAB)
+ Photometric = PHOTOMETRIC_ICCLAB;
+ else if (*photometric == PHOTOMETRIC_ITULAB && Photometric == PHOTOMETRIC_CIELAB)
+ Photometric = PHOTOMETRIC_ITULAB;
+ }
+
+ if (Photometric == PHOTOMETRIC_CIELAB)
+ this->lab_fix = 1;
+
+ TIFFSetField(this->tiff, TIFFTAG_PHOTOMETRIC, Photometric);
+
+ // This is the default, and many software assume/handle only this, so we force it.
+ uint16 PlanarConfig = PLANARCONFIG_CONTIG;
+ TIFFSetField(this->tiff, TIFFTAG_PLANARCONFIG, PlanarConfig);
+ if (imColorModeDepth(this->file_color_mode) > 1)
+ this->file_color_mode |= IM_PACKED;
+
+ // Corrections for JPEG, must be set after Photometric and PlanarConfig
+ if (Compression == COMPRESSION_JPEG && imColorModeSpace(this->file_color_mode) == IM_RGB)
+ TIFFSetField(this->tiff, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
+
+ // Compression options
+ int* zip_quality = (int*)attrib_table->Get("ZIPQuality");
+ if (zip_quality && (Compression == COMPRESSION_DEFLATE || Compression == COMPRESSION_ADOBE_DEFLATE))
+ TIFFSetField(this->tiff, TIFFTAG_ZIPQUALITY, *zip_quality);
+
+ if (Compression == COMPRESSION_JPEG)
+ {
+ int* jpeg_quality = (int*)attrib_table->Get("JPEGQuality");
+ if (jpeg_quality)
+ TIFFSetField(this->tiff, TIFFTAG_JPEGQUALITY, *jpeg_quality);
+ }
+
+ // This is the default, and many software assume/handle only this, so we force it.
+ uint16 Orientation = ORIENTATION_TOPLEFT;
+ TIFFSetField(this->tiff, TIFFTAG_ORIENTATION, Orientation);
+ this->file_color_mode |= IM_TOPDOWN;
+
+ static uint16 datatype2format[] =
+ {
+ SAMPLEFORMAT_UINT,
+ SAMPLEFORMAT_UINT,
+ SAMPLEFORMAT_INT,
+ SAMPLEFORMAT_IEEEFP,
+ SAMPLEFORMAT_COMPLEXIEEEFP
+ };
+ uint16 SampleFormat = datatype2format[this->file_data_type];
+ TIFFSetField(this->tiff, TIFFTAG_SAMPLEFORMAT, SampleFormat);
+
+ uint16 BitsPerSample = (uint16)(imDataTypeSize(this->file_data_type)*8);
+ if (imColorModeSpace(this->file_color_mode) == IM_BINARY)
+ {
+ BitsPerSample = 1;
+ this->convert_bpp = 1;
+ }
+ TIFFSetField(this->tiff, TIFFTAG_BITSPERSAMPLE, BitsPerSample);
+
+ // Correction for Luv, this will change BitsperSample and SampleFormat
+ if (Photometric == PHOTOMETRIC_LOGLUV || Photometric == PHOTOMETRIC_LOGL)
+ TIFFSetField(this->tiff, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT);
+
+ uint16 SamplesPerPixel = (uint16)imColorModeDepth(this->file_color_mode);
+ TIFFSetField(this->tiff, TIFFTAG_SAMPLESPERPIXEL, SamplesPerPixel);
+
+ if (imColorModeHasAlpha(this->file_color_mode))
+ {
+ uint16 ExtraSamples = 1, SampleInfo[1] = {EXTRASAMPLE_UNASSALPHA};
+ uint16* sample_info = (uint16*)attrib_table->Get("ExtraSampleInfo");
+ if (sample_info) SampleInfo[0] = *sample_info;
+ TIFFSetField(this->tiff, TIFFTAG_EXTRASAMPLES, ExtraSamples, SampleInfo);
+ }
+
+ if (imColorModeSpace(this->file_color_mode) == IM_MAP)
+ {
+ uint16 rmap[256], gmap[256], bmap[256];
+ memset(rmap, 0, 256 * 2);
+ memset(gmap, 0, 256 * 2);
+ memset(bmap, 0, 256 * 2);
+
+ unsigned char r, g, b;
+ for (int c = 0; c < this->palette_count; c++)
+ {
+ imColorDecode(&r, &g, &b, this->palette[c]);
+ rmap[c] = (uint16)(((uint16)r) << 8);
+ gmap[c] = (uint16)(((uint16)g) << 8);
+ bmap[c] = (uint16)(((uint16)b) << 8);
+ }
+
+ TIFFSetField(this->tiff, TIFFTAG_COLORMAP, rmap, gmap, bmap);
+ }
+
+ // Force libTIFF to calculate best RowsPerStrip
+ uint32 RowsPerStrip = (uint32)-1;
+ RowsPerStrip = TIFFDefaultStripSize(this->tiff, RowsPerStrip);
+ TIFFSetField(this->tiff, TIFFTAG_ROWSPERSTRIP, RowsPerStrip);
+
+ iTIFFWriteAttributes(this->tiff, attrib_table);
+
+ return IM_ERR_NONE;
+}
+
+static void iTIFFExpandComplexInt(void* line_buffer, int count, int cpx_int)
+{
+ count *= 2;
+
+ // conversion will be done in place
+
+ if (cpx_int == 1)
+ {
+ // convert short to float, expanding from 16 to 32 bits
+ short* short_buffer = (short*)line_buffer;
+ float* float_buffer = (float*)line_buffer;
+
+ float_buffer += count-1; // from end to start
+ short_buffer += count-1;
+
+ for (int i = 0; i < count; i++)
+ *float_buffer-- = (float)(*short_buffer--);
+ }
+ else
+ {
+ // convert int to float, same size not expanding
+ int* int_buffer = (int*)line_buffer;
+ float* float_buffer = (float*)line_buffer;
+
+ for (int i = 0; i < count; i++)
+ *float_buffer++ = (float)(*int_buffer++);
+ }
+}
+
+static void iTIFFExtraSamplesFix(unsigned char* line_buffer, int width, int sample_size, int extra_sample_size, int plane)
+{
+ /* ignore all the other extra samples, here the samples are packed */
+ for (int i = 1; i < width; i++)
+ {
+ memcpy(line_buffer + i*sample_size, line_buffer + i*extra_sample_size + plane, sample_size);
+ }
+}
+
+/*
+For CIELab (PhotometricInterpretation = 8), the L* component is encoded in 8 bits as an unsigned integer
+range [0,255], and encoded in 16 bits as an unsigned integer range [0,65535]. The a* and b* components
+are encoded in 8 bits as signed integers range [-128,127], and encoded in 16 bits as signed integers range [-
+32768,32767]. The 8 bit chrominance values are exactly equal to the 1976 CIE a* and b* values, while the
+16 bit values are equal to 256 times the 1976 CIE a* and b* values.
+
+For ICCLab (PhotometricInterpretation = 9), the L* component is encoded in 8 bits as an unsigned integer
+range [0,255], and encoded in 16 bits as an unsigned integer range [0,65280]. The a* and b* components
+are encoded in 8 bits as unsigned integers range [0,255], and encoded in 16 bits as unsigned integers range
+[0,65535]. The 8 bit chrominance values are exactly equal to the 1976 CIE a* and b* values plus 128,
+while the 16 bit values are equal to 256 times the 1976 CIE a* and b* values plus 32768 (this is also 256
+times the 8 bit encoding). PhotometricInterpretation 9 is designed to match the encoding used by the ICC
+profile specification.
+*/
+
+static void iTIFFLabFix(void* line_buffer, int width, int data_type, int is_new)
+{
+ if (data_type == IM_BYTE)
+ {
+ imbyte* byte_buffer = (imbyte*)line_buffer;
+
+ int offset = 128;
+ if (is_new) offset = -128;
+
+ for (int i = 0; i < width; i++)
+ {
+ *(byte_buffer+1) = (imbyte)(*((char*)byte_buffer+1) + offset);
+ *(byte_buffer+2) = (imbyte)(*((char*)byte_buffer+2) + offset);
+
+ byte_buffer += 3;
+ }
+ }
+ else if (data_type == IM_USHORT)
+ {
+ imushort* ushort_buffer = (imushort*)line_buffer;
+
+ int offset = 32768;
+ if (is_new) offset = -32768;
+
+ for (int i = 0; i < width; i++)
+ {
+ *(ushort_buffer+1) = (imushort)(*((short*)ushort_buffer+1) + offset);
+ *(ushort_buffer+2) = (imushort)(*((short*)ushort_buffer+2) + offset);
+
+ ushort_buffer += 3;
+ }
+ }
+ // Do NOT know how it is encoded for other data types.
+}
+
+int imFormatTIFF::ReadTileline(void* line_buffer, int row, int plane)
+{
+ int t;
+
+ if (row == 0)
+ this->start_row = 0;
+
+ if (row == this->start_row + this->tile_width)
+ this->start_row = row;
+
+ // load a line of tiles
+ if (row == this->start_row)
+ {
+ int x = 0;
+ for (t = 0; t < this->tile_buf_count; t++)
+ {
+ if (TIFFReadTile(this->tiff, this->tile_buf[t], x, start_row, 0, (tsample_t)plane) <= 0)
+ return -1;
+
+ x += this->tile_width;
+ }
+ }
+
+ int line_size = this->tile_line_size;
+ int tile_line = row - this->start_row;
+
+ for (t = 0; t < this->tile_buf_count; t++)
+ {
+ if (t == this->tile_buf_count-1)
+ {
+ int extra = this->tile_line_size*this->tile_buf_count - this->tile_line_raw_size;
+ line_size -= extra;
+ }
+
+ memcpy(line_buffer, (imbyte*)(this->tile_buf[t]) + tile_line*tile_line_size, line_size);
+ line_buffer = (imbyte*)(line_buffer) + line_size;
+ }
+
+ return 1;
+}
+
+int imFormatTIFF::ReadImageData(void* data)
+{
+ int count = imFileLineBufferCount(this);
+
+ imCounterTotal(this->counter, count, "Reading TIFF...");
+
+ int row = 0, plane = this->start_plane;
+ for (int i = 0; i < count; i++)
+ {
+ if (TIFFIsTiled(this->tiff))
+ {
+ if (ReadTileline(this->line_buffer, row, (tsample_t)plane) <= 0)
+ return IM_ERR_ACCESS;
+ }
+ else
+ {
+ if (TIFFReadScanline(this->tiff, this->line_buffer, row, (tsample_t)plane) <= 0)
+ return IM_ERR_ACCESS;
+ }
+
+ if (this->invert && this->file_data_type == IM_BYTE)
+ {
+ unsigned char* buf = (unsigned char*)this->line_buffer;
+ for (int b = 0; b < this->line_buffer_size; b++)
+ {
+ *buf = ~(*buf);
+ buf++;
+ }
+ }
+
+ if (this->cpx_int)
+ {
+ int line_count = imImageLineCount(this->width, this->user_color_mode);
+ iTIFFExpandComplexInt(this->line_buffer, line_count, this->cpx_int);
+ }
+
+ if (this->lab_fix)
+ iTIFFLabFix(this->line_buffer, this->width, this->file_data_type, 0);
+
+ if (this->extra_sample_size)
+ iTIFFExtraSamplesFix((imbyte*)this->line_buffer, this->width, this->sample_size, this->extra_sample_size, plane);
+
+ imFileLineBufferRead(this, data, row, plane);
+
+ if (!imCounterInc(this->counter))
+ return IM_ERR_COUNTER;
+
+ imFileLineBufferInc(this, &row, &plane);
+ }
+
+ return IM_ERR_NONE;
+}
+
+int imFormatTIFF::WriteImageData(void* data)
+{
+ int count = imFileLineBufferCount(this);
+
+ imCounterTotal(this->counter, count, "Writing TIFF...");
+
+ int row = 0, plane = 0;
+ for (int i = 0; i < count; i++)
+ {
+ imFileLineBufferWrite(this, data, row, plane);
+
+ if (this->lab_fix)
+ iTIFFLabFix(this->line_buffer, this->width, this->file_data_type, 1);
+
+ if (TIFFWriteScanline(this->tiff, this->line_buffer, row, (tsample_t)plane) <= 0)
+ return IM_ERR_ACCESS;
+
+ if (!imCounterInc(this->counter))
+ return IM_ERR_COUNTER;
+
+ imFileLineBufferInc(this, &row, &plane);
+ }
+
+ this->image_count++;
+
+ if (!TIFFWriteDirectory(this->tiff))
+ return IM_ERR_ACCESS;
+
+ return IM_ERR_NONE;
+}
+
+int imFormatTIFF::CanWrite(const char* compression, int color_mode, int data_type) const
+{
+ if (!compression)
+ return IM_ERR_NONE;
+
+ if (imColorModeSpace(color_mode) == IM_LUV)
+ return IM_ERR_DATA;
+
+ uint16 Compression = iTIFFCompCalc(compression, color_mode, data_type);
+ if (Compression == (uint16)-1)
+ return IM_ERR_COMPRESS;
+
+ /* no support for 2 bpp or 4 bpp */
+ if (Compression == COMPRESSION_THUNDERSCAN || Compression == COMPRESSION_NEXT)
+ return IM_ERR_COMPRESS;
+
+ /* Binary compression restrictions */
+ if ((Compression == COMPRESSION_CCITTRLE || Compression == COMPRESSION_CCITTRLEW ||
+ Compression == COMPRESSION_CCITTFAX3 || Compression == COMPRESSION_CCITTFAX4) &&
+ imColorModeSpace(color_mode) != IM_BINARY)
+ return IM_ERR_COMPRESS;
+
+ /* JPEG compression restrictions */
+ if (Compression == COMPRESSION_JPEG &&
+ (data_type != IM_BYTE ||
+ imColorModeSpace(color_mode) == IM_MAP || imColorModeSpace(color_mode) == IM_BINARY))
+ return IM_ERR_COMPRESS;
+
+ /* Pixar log accepts only 3 types */
+ if (Compression == COMPRESSION_PIXARLOG &&
+ data_type != IM_BYTE && data_type != IM_USHORT && data_type != IM_FLOAT)
+ return IM_ERR_COMPRESS;
+
+ /* SGI Luv compression restrictions */
+ if ((Compression == COMPRESSION_SGILOG || Compression == COMPRESSION_SGILOG24) &&
+ (imColorModeSpace(color_mode) != IM_XYZ || data_type != IM_FLOAT))
+ return IM_ERR_COMPRESS;
+
+ return IM_ERR_NONE;
+}