// ========================================================== // TIFF Loader and Writer // // Design and implementation by // - Floris van den Berg (flvdberg@wxs.nl) // - Hervé Drolon (drolon@infonie.fr) // - Markus Loibl (markus.loibl@epost.de) // - Luca Piergentili (l.pierge@terra.es) // - Detlev Vendt (detlev.vendt@brillit.de) // - Mihail Naydenov (mnaydenov@users.sourceforge.net) // // This file is part of FreeImage 3 // // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER // THIS DISCLAIMER. // // Use at your own risk! // ========================================================== #ifdef _MSC_VER #pragma warning (disable : 4786) // identifier was truncated to 'number' characters #endif #ifdef unix #undef unix #endif #ifdef __unix #undef __unix #endif #include "FreeImage.h" #include "Utilities.h" #include "../LibTIFF4/tiffiop.h" #include "../Metadata/FreeImageTag.h" #include "../OpenEXR/Half/half.h" #include "FreeImageIO.h" #include "PSDParser.h" // -------------------------------------------------------------------------- // GeoTIFF profile (see XTIFF.cpp) // -------------------------------------------------------------------------- void XTIFFInitialize(); BOOL tiff_read_geotiff_profile(TIFF *tif, FIBITMAP *dib); BOOL tiff_write_geotiff_profile(TIFF *tif, FIBITMAP *dib); // -------------------------------------------------------------------------- // TIFF Exif profile (see XTIFF.cpp) // ---------------------------------------------------------- BOOL tiff_read_exif_tags(TIFF *tif, TagLib::MDMODEL md_model, FIBITMAP *dib); BOOL tiff_write_exif_tags(TIFF *tif, TagLib::MDMODEL md_model, FIBITMAP *dib); // -------------------------------------------------------------------------- // LogLuv conversion functions interface (see TIFFLogLuv.cpp) // -------------------------------------------------------------------------- void tiff_ConvertLineXYZToRGB(BYTE *target, BYTE *source, double stonits, int width_in_pixels); void tiff_ConvertLineRGBToXYZ(BYTE *target, BYTE *source, int width_in_pixels); // ---------------------------------------------------------- /** Supported loading methods */ typedef enum { LoadAsRBGA = 0, LoadAsCMYK = 1, LoadAs8BitTrns = 2, LoadAsGenericStrip = 3, LoadAsTiled = 4, LoadAsLogLuv = 5, LoadAsHalfFloat = 6 } TIFFLoadMethod; // ---------------------------------------------------------- // local prototypes // ---------------------------------------------------------- static tmsize_t _tiffReadProc(thandle_t handle, void* buf, tmsize_t size); static tmsize_t _tiffWriteProc(thandle_t handle, void* buf, tmsize_t size); static toff_t _tiffSeekProc(thandle_t handle, toff_t off, int whence); static int _tiffCloseProc(thandle_t fd); static int _tiffMapProc(thandle_t fd, void** pbase, toff_t* psize); static void _tiffUnmapProc(thandle_t fd, void* base, toff_t size); static uint16 CheckColormap(int n, uint16* r, uint16* g, uint16* b); static uint16 GetPhotometric(FIBITMAP *dib); static void ReadResolution(TIFF *tiff, FIBITMAP *dib); static void WriteResolution(TIFF *tiff, FIBITMAP *dib); static void ReadPalette(TIFF *tiff, uint16 photometric, uint16 bitspersample, FIBITMAP *dib); static FIBITMAP* CreateImageType(BOOL header_only, FREE_IMAGE_TYPE fit, int width, int height, uint16 bitspersample, uint16 samplesperpixel); static FREE_IMAGE_TYPE ReadImageType(TIFF *tiff, uint16 bitspersample, uint16 samplesperpixel); static void WriteImageType(TIFF *tiff, FREE_IMAGE_TYPE fit); static void WriteCompression(TIFF *tiff, uint16 bitspersample, uint16 samplesperpixel, uint16 photometric, int flags); static BOOL tiff_read_iptc_profile(TIFF *tiff, FIBITMAP *dib); static BOOL tiff_read_xmp_profile(TIFF *tiff, FIBITMAP *dib); static BOOL tiff_read_exif_profile(TIFF *tiff, FIBITMAP *dib); static void ReadMetadata(TIFF *tiff, FIBITMAP *dib); static BOOL tiff_write_iptc_profile(TIFF *tiff, FIBITMAP *dib); static BOOL tiff_write_xmp_profile(TIFF *tiff, FIBITMAP *dib); static void WriteMetadata(TIFF *tiff, FIBITMAP *dib); static TIFFLoadMethod FindLoadMethod(TIFF *tif, uint16 photometric, uint16 bitspersample, uint16 samplesperpixel, FREE_IMAGE_TYPE image_type, int flags); static void ReadThumbnail(FreeImageIO *io, fi_handle handle, void *data, TIFF *tiff, FIBITMAP *dib); // ========================================================== // Plugin Interface // ========================================================== static int s_format_id; typedef struct { FreeImageIO *io; fi_handle handle; TIFF *tif; } fi_TIFFIO; // ---------------------------------------------------------- // libtiff interface // ---------------------------------------------------------- static tmsize_t _tiffReadProc(thandle_t handle, void *buf, tmsize_t size) { fi_TIFFIO *fio = (fi_TIFFIO*)handle; return fio->io->read_proc(buf, (unsigned)size, 1, fio->handle) * size; } static tmsize_t _tiffWriteProc(thandle_t handle, void *buf, tmsize_t size) { fi_TIFFIO *fio = (fi_TIFFIO*)handle; return fio->io->write_proc(buf, (unsigned)size, 1, fio->handle) * size; } static toff_t _tiffSeekProc(thandle_t handle, toff_t off, int whence) { fi_TIFFIO *fio = (fi_TIFFIO*)handle; fio->io->seek_proc(fio->handle, (long)off, whence); return fio->io->tell_proc(fio->handle); } static int _tiffCloseProc(thandle_t fd) { return 0; } #include static toff_t _tiffSizeProc(thandle_t handle) { fi_TIFFIO *fio = (fi_TIFFIO*)handle; long currPos = fio->io->tell_proc(fio->handle); fio->io->seek_proc(fio->handle, 0, SEEK_END); long fileSize = fio->io->tell_proc(fio->handle); fio->io->seek_proc(fio->handle, currPos, SEEK_SET); return fileSize; } static int _tiffMapProc(thandle_t, void** base, toff_t* size) { return 0; } static void _tiffUnmapProc(thandle_t, void* base, toff_t size) { } /** Open a TIFF file descriptor for reading or writing @param handle File handle @param name Name of the file handle @param mode Specifies if the file is to be opened for reading ("r") or writing ("w") */ TIFF * TIFFFdOpen(thandle_t handle, const char *name, const char *mode) { TIFF *tif; // Open the file; the callback will set everything up tif = TIFFClientOpen(name, mode, handle, _tiffReadProc, _tiffWriteProc, _tiffSeekProc, _tiffCloseProc, _tiffSizeProc, _tiffMapProc, _tiffUnmapProc); return tif; } /** Open a TIFF file for reading or writing @param name @param mode */ TIFF* TIFFOpen(const char* name, const char* mode) { return 0; } // ---------------------------------------------------------- // TIFF library FreeImage-specific routines. // ---------------------------------------------------------- void* _TIFFmalloc(tmsize_t s) { return malloc(s); } void _TIFFfree(void *p) { free(p); } void* _TIFFrealloc(void* p, tmsize_t s) { return realloc(p, s); } void _TIFFmemset(void* p, int v, tmsize_t c) { memset(p, v, (size_t) c); } void _TIFFmemcpy(void* d, const void* s, tmsize_t c) { memcpy(d, s, (size_t) c); } int _TIFFmemcmp(const void* p1, const void* p2, tmsize_t c) { return (memcmp(p1, p2, (size_t) c)); } // ---------------------------------------------------------- // in FreeImage warnings and errors are disabled // ---------------------------------------------------------- static void msdosWarningHandler(const char* module, const char* fmt, va_list ap) { } TIFFErrorHandler _TIFFwarningHandler = msdosWarningHandler; static void msdosErrorHandler(const char* module, const char* fmt, va_list ap) { // use this for diagnostic only (do not use otherwise, even in DEBUG mode) /* if (module != NULL) { char msg[1024]; vsprintf(msg, fmt, ap); FreeImage_OutputMessageProc(s_format_id, "%s: %s", module, msg); } */ } TIFFErrorHandler _TIFFerrorHandler = msdosErrorHandler; // ---------------------------------------------------------- #define CVT(x) (((x) * 255L) / ((1L<<16)-1)) #define SCALE(x) (((x)*((1L<<16)-1))/255) // ========================================================== // Internal functions // ========================================================== static uint16 CheckColormap(int n, uint16* r, uint16* g, uint16* b) { while (n-- > 0) { if (*r++ >= 256 || *g++ >= 256 || *b++ >= 256) { return 16; } } return 8; } /** Get the TIFFTAG_PHOTOMETRIC value from the dib */ static uint16 GetPhotometric(FIBITMAP *dib) { FREE_IMAGE_COLOR_TYPE color_type = FreeImage_GetColorType(dib); switch(color_type) { case FIC_MINISWHITE: // min value is white return PHOTOMETRIC_MINISWHITE; case FIC_MINISBLACK: // min value is black return PHOTOMETRIC_MINISBLACK; case FIC_PALETTE: // color map indexed return PHOTOMETRIC_PALETTE; case FIC_RGB: // RGB color model case FIC_RGBALPHA: // RGB color model with alpha channel return PHOTOMETRIC_RGB; case FIC_CMYK: // CMYK color model return PHOTOMETRIC_RGB; // default to RGB unless the save flag is set to TIFF_CMYK default: return PHOTOMETRIC_MINISBLACK; } } /** Get the resolution from the TIFF and fill the dib with universal units */ static void ReadResolution(TIFF *tiff, FIBITMAP *dib) { float fResX = 300.0; float fResY = 300.0; uint16 resUnit = RESUNIT_INCH; TIFFGetField(tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit); TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &fResX); TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &fResY); // If we don't have a valid resolution unit and valid resolution is specified then assume inch if (resUnit == RESUNIT_NONE && fResX > 0.0 && fResY > 0.0) { resUnit = RESUNIT_INCH; } if (resUnit == RESUNIT_INCH) { FreeImage_SetDotsPerMeterX(dib, (unsigned) (fResX/0.0254000 + 0.5)); FreeImage_SetDotsPerMeterY(dib, (unsigned) (fResY/0.0254000 + 0.5)); } else if(resUnit == RESUNIT_CENTIMETER) { FreeImage_SetDotsPerMeterX(dib, (unsigned) (fResX*100.0 + 0.5)); FreeImage_SetDotsPerMeterY(dib, (unsigned) (fResY*100.0 + 0.5)); } } /** Set the resolution to the TIFF using english units */ static void WriteResolution(TIFF *tiff, FIBITMAP *dib) { double res; TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH); res = (unsigned long) (0.5 + 0.0254 * FreeImage_GetDotsPerMeterX(dib)); TIFFSetField(tiff, TIFFTAG_XRESOLUTION, res); res = (unsigned long) (0.5 + 0.0254 * FreeImage_GetDotsPerMeterY(dib)); TIFFSetField(tiff, TIFFTAG_YRESOLUTION, res); } /** Fill the dib palette according to the TIFF photometric */ static void ReadPalette(TIFF *tiff, uint16 photometric, uint16 bitspersample, FIBITMAP *dib) { RGBQUAD *pal = FreeImage_GetPalette(dib); switch(photometric) { case PHOTOMETRIC_MINISBLACK: // bitmap and greyscale image types case PHOTOMETRIC_MINISWHITE: // Monochrome image if (bitspersample == 1) { if (photometric == PHOTOMETRIC_MINISWHITE) { pal[0].rgbRed = pal[0].rgbGreen = pal[0].rgbBlue = 255; pal[1].rgbRed = pal[1].rgbGreen = pal[1].rgbBlue = 0; } else { pal[0].rgbRed = pal[0].rgbGreen = pal[0].rgbBlue = 0; pal[1].rgbRed = pal[1].rgbGreen = pal[1].rgbBlue = 255; } } else if ((bitspersample == 4) ||(bitspersample == 8)) { // need to build the scale for greyscale images int ncolors = FreeImage_GetColorsUsed(dib); if (photometric == PHOTOMETRIC_MINISBLACK) { for (int i = 0; i < ncolors; i++) { pal[i].rgbRed = pal[i].rgbGreen = pal[i].rgbBlue = (BYTE)(i*(255/(ncolors-1))); } } else { for (int i = 0; i < ncolors; i++) { pal[i].rgbRed = pal[i].rgbGreen = pal[i].rgbBlue = (BYTE)(255-i*(255/(ncolors-1))); } } } break; case PHOTOMETRIC_PALETTE: // color map indexed uint16 *red; uint16 *green; uint16 *blue; TIFFGetField(tiff, TIFFTAG_COLORMAP, &red, &green, &blue); // load the palette in the DIB if (CheckColormap(1<= 0; i--) { pal[i].rgbRed =(BYTE) CVT(red[i]); pal[i].rgbGreen = (BYTE) CVT(green[i]); pal[i].rgbBlue = (BYTE) CVT(blue[i]); } } else { for (int i = (1 << bitspersample) - 1; i >= 0; i--) { pal[i].rgbRed = (BYTE) red[i]; pal[i].rgbGreen = (BYTE) green[i]; pal[i].rgbBlue = (BYTE) blue[i]; } } break; } } /** Allocate a FIBITMAP @param header_only If TRUE, allocate a 'header only' FIBITMAP, otherwise allocate a full FIBITMAP @param fit Image type @param width Image width in pixels @param height Image height in pixels @param bitspersample # bits per sample @param samplesperpixel # samples per pixel @return Returns the allocated image if successful, returns NULL otherwise */ static FIBITMAP* CreateImageType(BOOL header_only, FREE_IMAGE_TYPE fit, int width, int height, uint16 bitspersample, uint16 samplesperpixel) { FIBITMAP *dib = NULL; if((width < 0) || (height < 0)) { // check for malicious images return NULL; } int bpp = bitspersample * samplesperpixel; if(fit == FIT_BITMAP) { // standard bitmap type if(bpp == 16) { if((samplesperpixel == 2) && (bitspersample == 8)) { // 8-bit indexed + 8-bit alpha channel -> convert to 8-bit transparent dib = FreeImage_AllocateHeader(header_only, width, height, 8); } else { // 16-bit RGB -> expect it to be 565 dib = FreeImage_AllocateHeader(header_only, width, height, bpp, FI16_565_RED_MASK, FI16_565_GREEN_MASK, FI16_565_BLUE_MASK); } } else { dib = FreeImage_AllocateHeader(header_only, width, height, MIN(bpp, 32), FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); } } else { // other bitmap types dib = FreeImage_AllocateHeaderT(header_only, fit, width, height, bpp); } return dib; } /** Read the TIFFTAG_SAMPLEFORMAT tag and convert to FREE_IMAGE_TYPE @param tiff LibTIFF TIFF Handle @param bitspersample # bit per sample @param samplesperpixel # samples per pixel @return Returns the image type as a FREE_IMAGE_TYPE value */ static FREE_IMAGE_TYPE ReadImageType(TIFF *tiff, uint16 bitspersample, uint16 samplesperpixel) { uint16 sampleformat = 0; FREE_IMAGE_TYPE fit = FIT_BITMAP ; uint16 bpp = bitspersample * samplesperpixel; // try the sampleformat tag if(TIFFGetField(tiff, TIFFTAG_SAMPLEFORMAT, &sampleformat)) { switch (sampleformat) { case SAMPLEFORMAT_UINT: switch (bpp) { case 1: case 4: case 8: case 24: fit = FIT_BITMAP; break; case 16: // 8-bit + alpha or 16-bit greyscale if(samplesperpixel == 2) { fit = FIT_BITMAP; } else { fit = FIT_UINT16; } break; case 32: if(samplesperpixel == 4) { fit = FIT_BITMAP; } else { fit = FIT_UINT32; } break; case 48: if(samplesperpixel == 3) { fit = FIT_RGB16; } break; case 64: if(samplesperpixel == 4) { fit = FIT_RGBA16; } break; } break; case SAMPLEFORMAT_INT: switch (bpp) { case 16: if(samplesperpixel == 3) { fit = FIT_BITMAP; } else { fit = FIT_INT16; } break; case 32: fit = FIT_INT32; break; } break; case SAMPLEFORMAT_IEEEFP: switch (bpp) { case 32: fit = FIT_FLOAT; break; case 48: // 3 x half float => convert to RGBF if((samplesperpixel == 3) && (bitspersample == 16)) { fit = FIT_RGBF; } break; case 64: if(samplesperpixel == 2) { fit = FIT_FLOAT; } else { fit = FIT_DOUBLE; } break; case 96: fit = FIT_RGBF; break; default: if(bpp >= 128) { fit = FIT_RGBAF; } break; } break; case SAMPLEFORMAT_COMPLEXIEEEFP: switch (bpp) { case 64: break; case 128: fit = FIT_COMPLEX; break; } break; } } // no sampleformat tag : assume SAMPLEFORMAT_UINT else { if(samplesperpixel == 1) { switch (bpp) { case 16: fit = FIT_UINT16; break; case 32: fit = FIT_UINT32; break; } } else if(samplesperpixel == 3) { if(bpp == 48) fit = FIT_RGB16; } else if(samplesperpixel >= 4) { if(bitspersample == 16) { fit = FIT_RGBA16; } } } return fit; } /** Convert FREE_IMAGE_TYPE and write TIFFTAG_SAMPLEFORMAT @param tiff LibTIFF TIFF Handle @param fit Image type as a FREE_IMAGE_TYPE value */ static void WriteImageType(TIFF *tiff, FREE_IMAGE_TYPE fit) { switch(fit) { case FIT_BITMAP: // standard image: 1-, 4-, 8-, 16-, 24-, 32-bit case FIT_UINT16: // array of unsigned short : unsigned 16-bit case FIT_UINT32: // array of unsigned long : unsigned 32-bit case FIT_RGB16: // 48-bit RGB image : 3 x 16-bit case FIT_RGBA16: // 64-bit RGBA image : 4 x 16-bit TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); break; case FIT_INT16: // array of short : signed 16-bit case FIT_INT32: // array of long : signed 32-bit TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_INT); break; case FIT_FLOAT: // array of float : 32-bit case FIT_DOUBLE: // array of double : 64-bit case FIT_RGBF: // 96-bit RGB float image : 3 x 32-bit IEEE floating point case FIT_RGBAF: // 128-bit RGBA float image : 4 x 32-bit IEEE floating point TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP); break; case FIT_COMPLEX: // array of COMPLEX : 2 x 64-bit TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_COMPLEXIEEEFP); break; } } /** Select the compression algorithm @param tiff LibTIFF TIFF Handle @param */ static void WriteCompression(TIFF *tiff, uint16 bitspersample, uint16 samplesperpixel, uint16 photometric, int flags) { uint16 compression; uint16 bitsperpixel = bitspersample * samplesperpixel; if(photometric == PHOTOMETRIC_LOGLUV) { compression = COMPRESSION_SGILOG; } else if ((flags & TIFF_PACKBITS) == TIFF_PACKBITS) { compression = COMPRESSION_PACKBITS; } else if ((flags & TIFF_DEFLATE) == TIFF_DEFLATE) { compression = COMPRESSION_DEFLATE; } else if ((flags & TIFF_ADOBE_DEFLATE) == TIFF_ADOBE_DEFLATE) { compression = COMPRESSION_ADOBE_DEFLATE; } else if ((flags & TIFF_NONE) == TIFF_NONE) { compression = COMPRESSION_NONE; } else if ((bitsperpixel == 1) && ((flags & TIFF_CCITTFAX3) == TIFF_CCITTFAX3)) { compression = COMPRESSION_CCITTFAX3; } else if ((bitsperpixel == 1) && ((flags & TIFF_CCITTFAX4) == TIFF_CCITTFAX4)) { compression = COMPRESSION_CCITTFAX4; } else if ((flags & TIFF_LZW) == TIFF_LZW) { compression = COMPRESSION_LZW; } else if ((flags & TIFF_JPEG) == TIFF_JPEG) { if(((bitsperpixel == 8) && (photometric != PHOTOMETRIC_PALETTE)) || (bitsperpixel == 24)) { compression = COMPRESSION_JPEG; // RowsPerStrip must be multiple of 8 for JPEG uint32 rowsperstrip = (uint32) -1; rowsperstrip = TIFFDefaultStripSize(tiff, rowsperstrip); rowsperstrip = rowsperstrip + (8 - (rowsperstrip % 8)); // overwrite previous RowsPerStrip TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, rowsperstrip); } else { // default to LZW compression = COMPRESSION_LZW; } } else { // default compression scheme switch(bitsperpixel) { case 1: compression = COMPRESSION_CCITTFAX4; break; case 4: case 8: case 16: case 24: case 32: compression = COMPRESSION_LZW; break; case 48: case 64: case 96: case 128: compression = COMPRESSION_LZW; break; default : compression = COMPRESSION_NONE; break; } } TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression); if(compression == COMPRESSION_LZW) { // This option is only meaningful with LZW compression: a predictor value of 2 // causes each scanline of the output image to undergo horizontal differencing // before it is encoded; a value of 1 forces each scanline to be encoded without differencing. // Found on LibTIFF mailing list : // LZW without differencing works well for 1-bit images, 4-bit grayscale images, // and many palette-color images. But natural 24-bit color images and some 8-bit // grayscale images do much better with differencing. if((bitspersample == 8) || (bitspersample == 16)) { if ((bitsperpixel >= 8) && (photometric != PHOTOMETRIC_PALETTE)) { TIFFSetField(tiff, TIFFTAG_PREDICTOR, 2); } else { TIFFSetField(tiff, TIFFTAG_PREDICTOR, 1); } } else { TIFFSetField(tiff, TIFFTAG_PREDICTOR, 1); } } else if((compression == COMPRESSION_CCITTFAX3) || (compression == COMPRESSION_CCITTFAX4)) { uint32 imageLength = 0; TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &imageLength); // overwrite previous RowsPerStrip TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, imageLength); if(compression == COMPRESSION_CCITTFAX3) { // try to be compliant with the TIFF Class F specification // that documents the TIFF tags specific to FAX applications // see http://palimpsest.stanford.edu/bytopic/imaging/std/tiff-f.html uint32 group3options = GROUP3OPT_2DENCODING | GROUP3OPT_FILLBITS; TIFFSetField(tiff, TIFFTAG_GROUP3OPTIONS, group3options); // 2d-encoded, has aligned EOL TIFFSetField(tiff, TIFFTAG_FILLORDER, FILLORDER_LSB2MSB); // lsb-to-msb fillorder } } } // ========================================================== // TIFF metadata routines // ========================================================== /** Read the TIFFTAG_RICHTIFFIPTC tag (IPTC/NAA or Adobe Photoshop profile) */ static BOOL tiff_read_iptc_profile(TIFF *tiff, FIBITMAP *dib) { BYTE *profile = NULL; uint32 profile_size = 0; if(TIFFGetField(tiff,TIFFTAG_RICHTIFFIPTC, &profile_size, &profile) == 1) { if (TIFFIsByteSwapped(tiff) != 0) { TIFFSwabArrayOfLong((uint32 *) profile, (unsigned long)profile_size); } return read_iptc_profile(dib, profile, 4 * profile_size); } return FALSE; } /** Read the TIFFTAG_XMLPACKET tag (XMP profile) @param dib Input FIBITMAP @param tiff LibTIFF TIFF handle @return Returns TRUE if successful, FALSE otherwise */ static BOOL tiff_read_xmp_profile(TIFF *tiff, FIBITMAP *dib) { BYTE *profile = NULL; uint32 profile_size = 0; if (TIFFGetField(tiff, TIFFTAG_XMLPACKET, &profile_size, &profile) == 1) { // create a tag FITAG *tag = FreeImage_CreateTag(); if(!tag) return FALSE; FreeImage_SetTagID(tag, TIFFTAG_XMLPACKET); // 700 FreeImage_SetTagKey(tag, g_TagLib_XMPFieldName); FreeImage_SetTagLength(tag, profile_size); FreeImage_SetTagCount(tag, profile_size); FreeImage_SetTagType(tag, FIDT_ASCII); FreeImage_SetTagValue(tag, profile); // store the tag FreeImage_SetMetadata(FIMD_XMP, dib, FreeImage_GetTagKey(tag), tag); // destroy the tag FreeImage_DeleteTag(tag); return TRUE; } return FALSE; } /** Read the Exif profile embedded in a TIFF @param dib Input FIBITMAP @param tiff LibTIFF TIFF handle @return Returns TRUE if successful, FALSE otherwise */ static BOOL tiff_read_exif_profile(TIFF *tiff, FIBITMAP *dib) { BOOL bResult = FALSE; toff_t exif_offset = 0; // read EXIF-TIFF tags bResult = tiff_read_exif_tags(tiff, TagLib::EXIF_MAIN, dib); // get the IFD offset if(TIFFGetField(tiff, TIFFTAG_EXIFIFD, &exif_offset)) { // read EXIF tags if(!TIFFReadEXIFDirectory(tiff, exif_offset)) { return FALSE; } // read all known exif tags bResult = tiff_read_exif_tags(tiff, TagLib::EXIF_EXIF, dib); } return bResult; } /** Read TIFF special profiles */ static void ReadMetadata(TIFF *tiff, FIBITMAP *dib) { // IPTC/NAA tiff_read_iptc_profile(tiff, dib); // Adobe XMP tiff_read_xmp_profile(tiff, dib); // GeoTIFF tiff_read_geotiff_profile(tiff, dib); // Exif-TIFF tiff_read_exif_profile(tiff, dib); } // ---------------------------------------------------------- /** Write the TIFFTAG_RICHTIFFIPTC tag (IPTC/NAA or Adobe Photoshop profile) */ static BOOL tiff_write_iptc_profile(TIFF *tiff, FIBITMAP *dib) { if(FreeImage_GetMetadataCount(FIMD_IPTC, dib)) { BYTE *profile = NULL; uint32 profile_size = 0; // create a binary profile if(write_iptc_profile(dib, &profile, &profile_size)) { uint32 iptc_size = profile_size; iptc_size += (4-(iptc_size & 0x03)); // Round up for long word alignment BYTE *iptc_profile = (BYTE*)malloc(iptc_size); if(!iptc_profile) { free(profile); return FALSE; } memset(iptc_profile, 0, iptc_size); memcpy(iptc_profile, profile, profile_size); if (TIFFIsByteSwapped(tiff)) { TIFFSwabArrayOfLong((uint32 *) iptc_profile, (unsigned long)iptc_size/4); } // Tag is type TIFF_LONG so byte length is divided by four TIFFSetField(tiff, TIFFTAG_RICHTIFFIPTC, iptc_size/4, iptc_profile); // release the profile data free(iptc_profile); free(profile); return TRUE; } } return FALSE; } /** Write the TIFFTAG_XMLPACKET tag (XMP profile) @param dib Input FIBITMAP @param tiff LibTIFF TIFF handle @return Returns TRUE if successful, FALSE otherwise */ static BOOL tiff_write_xmp_profile(TIFF *tiff, FIBITMAP *dib) { FITAG *tag_xmp = NULL; FreeImage_GetMetadata(FIMD_XMP, dib, g_TagLib_XMPFieldName, &tag_xmp); if(tag_xmp && (NULL != FreeImage_GetTagValue(tag_xmp))) { TIFFSetField(tiff, TIFFTAG_XMLPACKET, (uint32)FreeImage_GetTagLength(tag_xmp), (BYTE*)FreeImage_GetTagValue(tag_xmp)); return TRUE; } return FALSE; } /** Write the Exif profile to TIFF @param dib Input FIBITMAP @param tiff LibTIFF TIFF handle @return Returns TRUE if successful, FALSE otherwise */ static BOOL tiff_write_exif_profile(TIFF *tiff, FIBITMAP *dib) { BOOL bResult = FALSE; // write EXIF_MAIN tags, EXIF_EXIF not supported yet bResult = tiff_write_exif_tags(tiff, TagLib::EXIF_MAIN, dib); return bResult; } /** Write TIFF special profiles */ static void WriteMetadata(TIFF *tiff, FIBITMAP *dib) { // IPTC tiff_write_iptc_profile(tiff, dib); // Adobe XMP tiff_write_xmp_profile(tiff, dib); // EXIF_MAIN tags tiff_write_exif_profile(tiff, dib); // GeoTIFF tags tiff_write_geotiff_profile(tiff, dib); } // ========================================================== // Plugin Implementation // ========================================================== static const char * DLL_CALLCONV Format() { return "TIFF"; } static const char * DLL_CALLCONV Description() { return "Tagged Image File Format"; } static const char * DLL_CALLCONV Extension() { return "tif,tiff"; } static const char * DLL_CALLCONV RegExpr() { return "^[MI][MI][\\x01*][\\x01*]"; } static const char * DLL_CALLCONV MimeType() { return "image/tiff"; } static BOOL DLL_CALLCONV Validate(FreeImageIO *io, fi_handle handle) { BYTE tiff_id1[] = { 0x49, 0x49, 0x2A, 0x00 }; // Classic TIFF, little-endian BYTE tiff_id2[] = { 0x4D, 0x4D, 0x00, 0x2A }; // Classic TIFF, big-endian BYTE tiff_id3[] = { 0x49, 0x49, 0x2B, 0x00 }; // Big TIFF, little-endian BYTE tiff_id4[] = { 0x4D, 0x4D, 0x00, 0x2B }; // Big TIFF, big-endian BYTE signature[4] = { 0, 0, 0, 0 }; io->read_proc(signature, 1, 4, handle); if(memcmp(tiff_id1, signature, 4) == 0) return TRUE; if(memcmp(tiff_id2, signature, 4) == 0) return TRUE; if(memcmp(tiff_id3, signature, 4) == 0) return TRUE; if(memcmp(tiff_id4, signature, 4) == 0) return TRUE; return FALSE; } static BOOL DLL_CALLCONV SupportsExportDepth(int depth) { return ( (depth == 1) || (depth == 4) || (depth == 8) || (depth == 24) || (depth == 32) ); } static BOOL DLL_CALLCONV SupportsExportType(FREE_IMAGE_TYPE type) { return ( (type == FIT_BITMAP) || (type == FIT_UINT16) || (type == FIT_INT16) || (type == FIT_UINT32) || (type == FIT_INT32) || (type == FIT_FLOAT) || (type == FIT_DOUBLE) || (type == FIT_COMPLEX) || (type == FIT_RGB16) || (type == FIT_RGBA16) || (type == FIT_RGBF) || (type == FIT_RGBAF) ); } static BOOL DLL_CALLCONV SupportsICCProfiles() { return TRUE; } static BOOL DLL_CALLCONV SupportsNoPixels() { return TRUE; } // ---------------------------------------------------------- static void * DLL_CALLCONV Open(FreeImageIO *io, fi_handle handle, BOOL read) { // wrapper for TIFF I/O fi_TIFFIO *fio = (fi_TIFFIO*)malloc(sizeof(fi_TIFFIO)); if(!fio) return NULL; fio->io = io; fio->handle = handle; if (read) { fio->tif = TIFFFdOpen((thandle_t)fio, "", "r"); } else { // mode = "w" : write Classic TIFF // mode = "w8" : write Big TIFF fio->tif = TIFFFdOpen((thandle_t)fio, "", "w"); } if(fio->tif == NULL) { free(fio); FreeImage_OutputMessageProc(s_format_id, "Error while opening TIFF: data is invalid"); return NULL; } return fio; } static void DLL_CALLCONV Close(FreeImageIO *io, fi_handle handle, void *data) { if(data) { fi_TIFFIO *fio = (fi_TIFFIO*)data; TIFFClose(fio->tif); free(fio); } } // ---------------------------------------------------------- static int DLL_CALLCONV PageCount(FreeImageIO *io, fi_handle handle, void *data) { if(data) { fi_TIFFIO *fio = (fi_TIFFIO*)data; TIFF *tif = (TIFF *)fio->tif; int nr_ifd = 0; do { nr_ifd++; } while (TIFFReadDirectory(tif)); return nr_ifd; } return 0; } // ---------------------------------------------------------- /** check for uncommon bitspersample values (e.g. 10, 12, ...) @param photometric TIFFTAG_PHOTOMETRIC tiff tag @param bitspersample TIFFTAG_BITSPERSAMPLE tiff tag @return Returns FALSE if a uncommon bit-depth is encountered, returns TRUE otherwise */ static BOOL IsValidBitsPerSample(uint16 photometric, uint16 bitspersample) { switch(bitspersample) { case 1: case 4: if((photometric == PHOTOMETRIC_MINISWHITE) || (photometric == PHOTOMETRIC_MINISBLACK) || (photometric == PHOTOMETRIC_PALETTE)) { return TRUE; } else { return FALSE; } break; case 8: return TRUE; case 16: if(photometric != PHOTOMETRIC_PALETTE) { return TRUE; } else { return FALSE; } break; case 32: if((photometric == PHOTOMETRIC_MINISWHITE) || (photometric == PHOTOMETRIC_MINISBLACK) || (photometric == PHOTOMETRIC_LOGLUV)) { return TRUE; } else { return FALSE; } break; case 64: case 128: if(photometric == PHOTOMETRIC_MINISBLACK) { return TRUE; } else { return FALSE; } break; default: return FALSE; } } static TIFFLoadMethod FindLoadMethod(TIFF *tif, FREE_IMAGE_TYPE image_type, int flags) { uint16 bitspersample = (uint16)-1; uint16 samplesperpixel = (uint16)-1; uint16 photometric = (uint16)-1; uint16 planar_config = (uint16)-1; TIFFLoadMethod loadMethod = LoadAsGenericStrip; TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric); TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel); TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitspersample); TIFFGetFieldDefaulted(tif, TIFFTAG_PLANARCONFIG, &planar_config); BOOL bIsTiled = (TIFFIsTiled(tif) == 0) ? FALSE:TRUE; switch(photometric) { // convert to 24 or 32 bits RGB if the image is full color case PHOTOMETRIC_RGB: if((image_type == FIT_RGB16) || (image_type == FIT_RGBA16)) { // load 48-bit RGB and 64-bit RGBA without conversion loadMethod = LoadAsGenericStrip; } else if(image_type == FIT_RGBF) { if((samplesperpixel == 3) && (bitspersample == 16)) { // load 3 x 16-bit half as RGBF loadMethod = LoadAsHalfFloat; } } break; case PHOTOMETRIC_YCBCR: case PHOTOMETRIC_CIELAB: case PHOTOMETRIC_ICCLAB: case PHOTOMETRIC_ITULAB: loadMethod = LoadAsRBGA; break; case PHOTOMETRIC_LOGLUV: loadMethod = LoadAsLogLuv; break; case PHOTOMETRIC_SEPARATED: // if image is PHOTOMETRIC_SEPARATED _and_ comes with an ICC profile, // then the image should preserve its original (CMYK) colour model and // should be read as CMYK (to keep the match of pixel and profile and // to avoid multiple conversions. Conversion can be done by changing // the profile from it's original CMYK to an RGB profile with an // apropriate color management system. Works with non-tiled TIFFs. if(!bIsTiled) { loadMethod = LoadAsCMYK; } break; case PHOTOMETRIC_MINISWHITE: case PHOTOMETRIC_MINISBLACK: case PHOTOMETRIC_PALETTE: // When samplesperpixel = 2 and bitspersample = 8, set the image as a // 8-bit indexed image + 8-bit alpha layer image // and convert to a 8-bit image with a transparency table if((samplesperpixel > 1) && (bitspersample == 8)) { loadMethod = LoadAs8BitTrns; } else { loadMethod = LoadAsGenericStrip; } break; default: loadMethod = LoadAsGenericStrip; break; } if((loadMethod == LoadAsGenericStrip) && bIsTiled) { loadMethod = LoadAsTiled; } return loadMethod; } // ========================================================== // TIFF thumbnail routines // ========================================================== static FIBITMAP * DLL_CALLCONV Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data); /** Read embedded thumbnail */ static void ReadThumbnail(FreeImageIO *io, fi_handle handle, void *data, TIFF *tiff, FIBITMAP *dib) { FIBITMAP* thumbnail = NULL; // read exif thumbnail (IFD 1) ... uint32 exif_offset = 0; if(TIFFGetField(tiff, TIFFTAG_EXIFIFD, &exif_offset)) { if(TIFFLastDirectory(tiff) != 0) { // save current position long tell_pos = io->tell_proc(handle); uint16 cur_dir = TIFFCurrentDirectory(tiff); // load the thumbnail int page = 1; int flags = TIFF_DEFAULT; thumbnail = Load(io, handle, page, flags, data); // store the thumbnail (remember to release it later ...) FreeImage_SetThumbnail(dib, thumbnail); // restore current position io->seek_proc(handle, tell_pos, SEEK_SET); TIFFSetDirectory(tiff, cur_dir); } } // ... or read the first subIFD if(!thumbnail) { uint16 subIFD_count = 0; uint64* subIFD_offsets = NULL; // ### Theoretically this should also read the first subIFD from a Photoshop-created file with "pyramid". // It does not however - the tag is there (using Tag Viewer app) but libtiff refuses to read it if(TIFFGetField(tiff, TIFFTAG_SUBIFD, &subIFD_count, &subIFD_offsets)) { if(subIFD_count > 0) { // save current position long tell_pos = io->tell_proc(handle); uint16 cur_dir = TIFFCurrentDirectory(tiff); if(TIFFSetSubDirectory(tiff, subIFD_offsets[0])) { // load the thumbnail int page = -1; int flags = TIFF_DEFAULT; thumbnail = Load(io, handle, page, flags, data); // store the thumbnail (remember to release it later ...) FreeImage_SetThumbnail(dib, thumbnail); } // restore current position io->seek_proc(handle, tell_pos, SEEK_SET); TIFFSetDirectory(tiff, cur_dir); } } } // ... or read Photoshop thumbnail if(!thumbnail) { uint32 ps_size = 0; void *ps_data = NULL; if(TIFFGetField(tiff, TIFFTAG_PHOTOSHOP, &ps_size, &ps_data)) { FIMEMORY *handle = FreeImage_OpenMemory((BYTE*)ps_data, ps_size); FreeImageIO io; SetMemoryIO(&io); psdParser parser; parser.ReadImageResources(&io, handle, ps_size); FreeImage_SetThumbnail(dib, parser.GetThumbnail()); FreeImage_CloseMemory(handle); } } // release thumbnail FreeImage_Unload(thumbnail); } // -------------------------------------------------------------------------- static FIBITMAP * DLL_CALLCONV Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { if (!handle || !data ) { return NULL; } TIFF *tif = NULL; uint32 height = 0; uint32 width = 0; uint16 bitspersample = 1; uint16 samplesperpixel = 1; uint32 rowsperstrip = (uint32)-1; uint16 photometric = PHOTOMETRIC_MINISWHITE; uint16 compression = (uint16)-1; uint16 planar_config; FIBITMAP *dib = NULL; uint32 iccSize = 0; // ICC profile length void *iccBuf = NULL; // ICC profile data const BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS; try { fi_TIFFIO *fio = (fi_TIFFIO*)data; tif = fio->tif; if (page != -1) { if (!tif || !TIFFSetDirectory(tif, (uint16)page)) { throw "Error encountered while opening TIFF file"; } } const BOOL asCMYK = (flags & TIFF_CMYK) == TIFF_CMYK; // first, get the photometric, the compression and basic metadata // --------------------------------------------------------------------------------- TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric); TIFFGetField(tif, TIFFTAG_COMPRESSION, &compression); // check for HDR formats // --------------------------------------------------------------------------------- if(photometric == PHOTOMETRIC_LOGLUV) { // check the compression if(compression != COMPRESSION_SGILOG && compression != COMPRESSION_SGILOG24) { throw "Only support SGILOG compressed LogLuv data"; } // set decoder to output in IEEE 32-bit float XYZ values TIFFSetField(tif, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT); } // --------------------------------------------------------------------------------- TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width); TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height); TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel); TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitspersample); TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip); TIFFGetField(tif, TIFFTAG_ICCPROFILE, &iccSize, &iccBuf); TIFFGetFieldDefaulted(tif, TIFFTAG_PLANARCONFIG, &planar_config); // check for unsupported formats // --------------------------------------------------------------------------------- if(IsValidBitsPerSample(photometric, bitspersample) == FALSE) { FreeImage_OutputMessageProc(s_format_id, "Unable to handle this format: bitspersample = %d, samplesperpixel = %d, photometric = %d", (int)bitspersample, (int)samplesperpixel, (int)photometric); throw (char*)NULL; } // --------------------------------------------------------------------------------- // get image data type FREE_IMAGE_TYPE image_type = ReadImageType(tif, bitspersample, samplesperpixel); // get the most appropriate loading method TIFFLoadMethod loadMethod = FindLoadMethod(tif, image_type, flags); // --------------------------------------------------------------------------------- if(loadMethod == LoadAsRBGA) { // --------------------------------------------------------------------------------- // RGB[A] loading using the TIFFReadRGBAImage() API // --------------------------------------------------------------------------------- BOOL has_alpha = FALSE; // Read the whole image into one big RGBA buffer and then // convert it to a DIB. This is using the traditional // TIFFReadRGBAImage() API that we trust. uint32 *raster = NULL; if(!header_only) { raster = (uint32*)_TIFFmalloc(width * height * sizeof(uint32)); if (raster == NULL) { throw FI_MSG_ERROR_MEMORY; } // read the image in one chunk into an RGBA array if (!TIFFReadRGBAImage(tif, width, height, raster, 1)) { _TIFFfree(raster); throw FI_MSG_ERROR_UNSUPPORTED_FORMAT; } } // TIFFReadRGBAImage always deliveres 3 or 4 samples per pixel images // (RGB or RGBA, see below). Cut-off possibly present channels (additional // alpha channels) from e.g. Photoshop. Any CMYK(A..) is now treated as RGB, // any additional alpha channel on RGB(AA..) is lost on conversion to RGB(A) if(samplesperpixel > 4) { // TODO Write to Extra Channels FreeImage_OutputMessageProc(s_format_id, "Warning: %d additional alpha channel(s) ignored", samplesperpixel-4); samplesperpixel = 4; } // create a new DIB (take care of different samples-per-pixel in case // of converted CMYK image (RGB conversion is on sample per pixel less) if (photometric == PHOTOMETRIC_SEPARATED && samplesperpixel == 4) { samplesperpixel = 3; } dib = CreateImageType(header_only, image_type, width, height, bitspersample, samplesperpixel); if (dib == NULL) { // free the raster pointer and output an error if allocation failed if(raster) { _TIFFfree(raster); } throw FI_MSG_ERROR_DIB_MEMORY; } // fill in the resolution (english or universal) ReadResolution(tif, dib); if(!header_only) { // read the raster lines and save them in the DIB // with RGB mode, we have to change the order of the 3 samples RGB // We use macros for extracting components from the packed ABGR // form returned by TIFFReadRGBAImage. uint32 *row = &raster[0]; if (samplesperpixel == 4) { // 32-bit RGBA for (uint32 y = 0; y < height; y++) { BYTE *bits = FreeImage_GetScanLine(dib, y); for (uint32 x = 0; x < width; x++) { bits[FI_RGBA_BLUE] = (BYTE)TIFFGetB(row[x]); bits[FI_RGBA_GREEN] = (BYTE)TIFFGetG(row[x]); bits[FI_RGBA_RED] = (BYTE)TIFFGetR(row[x]); bits[FI_RGBA_ALPHA] = (BYTE)TIFFGetA(row[x]); if (bits[FI_RGBA_ALPHA] != 0) { has_alpha = TRUE; } bits += 4; } row += width; } } else { // 24-bit RGB for (uint32 y = 0; y < height; y++) { BYTE *bits = FreeImage_GetScanLine(dib, y); for (uint32 x = 0; x < width; x++) { bits[FI_RGBA_BLUE] = (BYTE)TIFFGetB(row[x]); bits[FI_RGBA_GREEN] = (BYTE)TIFFGetG(row[x]); bits[FI_RGBA_RED] = (BYTE)TIFFGetR(row[x]); bits += 3; } row += width; } } _TIFFfree(raster); } // ### Not correct when header only FreeImage_SetTransparent(dib, has_alpha); } else if(loadMethod == LoadAs8BitTrns) { // --------------------------------------------------------------------------------- // 8-bit + 8-bit alpha layer loading // --------------------------------------------------------------------------------- // create a new 8-bit DIB dib = CreateImageType(header_only, image_type, width, height, bitspersample, MIN(2, samplesperpixel)); if (dib == NULL) { throw FI_MSG_ERROR_MEMORY; } // fill in the resolution (english or universal) ReadResolution(tif, dib); // set up the colormap based on photometric ReadPalette(tif, photometric, bitspersample, dib); // calculate the line + pitch (separate for scr & dest) const tmsize_t src_line = TIFFScanlineSize(tif); // here, the pitch is 2x less than the original as we only keep the first layer int dst_pitch = FreeImage_GetPitch(dib); // transparency table for 8-bit + 8-bit alpha images BYTE trns[256]; // clear the transparency table memset(trns, 0xFF, 256 * sizeof(BYTE)); // In the tiff file the lines are saved from up to down // In a DIB the lines must be saved from down to up BYTE *bits = FreeImage_GetScanLine(dib, height - 1); // read the tiff lines and save them in the DIB if(planar_config == PLANARCONFIG_CONTIG && !header_only) { BYTE *buf = (BYTE*)malloc(TIFFStripSize(tif) * sizeof(BYTE)); if(buf == NULL) { throw FI_MSG_ERROR_MEMORY; } for (uint32 y = 0; y < height; y += rowsperstrip) { int32 nrow = (y + rowsperstrip > height ? height - y : rowsperstrip); if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, y, 0), buf, nrow * src_line) == -1) { free(buf); throw FI_MSG_ERROR_PARSING; } for (int l = 0; l < nrow; l++) { BYTE *p = bits; BYTE *b = buf + l * src_line; for(uint32 x = 0; x < (uint32)(src_line / samplesperpixel); x++) { // copy the 8-bit layer *p = b[0]; // convert the 8-bit alpha layer to a trns table trns[ b[0] ] = b[1]; p++; b += samplesperpixel; } bits -= dst_pitch; } } free(buf); } else if(planar_config == PLANARCONFIG_SEPARATE && !header_only) { tmsize_t stripsize = TIFFStripSize(tif) * sizeof(BYTE); BYTE *buf = (BYTE*)malloc(2 * stripsize); if(buf == NULL) { throw FI_MSG_ERROR_MEMORY; } BYTE *grey = buf; BYTE *alpha = buf + stripsize; for (uint32 y = 0; y < height; y += rowsperstrip) { int32 nrow = (y + rowsperstrip > height ? height - y : rowsperstrip); if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, y, 0), grey, nrow * src_line) == -1) { free(buf); throw FI_MSG_ERROR_PARSING; } if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, y, 1), alpha, nrow * src_line) == -1) { free(buf); throw FI_MSG_ERROR_PARSING; } for (int l = 0; l < nrow; l++) { BYTE *p = bits; BYTE *g = grey + l * src_line; BYTE *a = alpha + l * src_line; for(uint32 x = 0; x < (uint32)(src_line); x++) { // copy the 8-bit layer *p = g[0]; // convert the 8-bit alpha layer to a trns table trns[ g[0] ] = a[0]; p++; g++; a++; } bits -= dst_pitch; } } free(buf); } FreeImage_SetTransparencyTable(dib, &trns[0], 256); FreeImage_SetTransparent(dib, TRUE); } else if(loadMethod == LoadAsCMYK) { // --------------------------------------------------------------------------------- // CMYK loading // --------------------------------------------------------------------------------- // At this place, samplesperpixel could be > 4, esp. when a CMYK(A) format // is recognized. Where all other formats are handled straight-forward, this // format has to be handled special BOOL isCMYKA = (photometric == PHOTOMETRIC_SEPARATED) && (samplesperpixel > 4); // We use a temp dib to store the alpha for the CMYKA to RGBA conversion // NOTE this is until we have Extra channels implementation. // Also then it will be possible to merge LoadAsCMYK with LoadAsGenericStrip FIBITMAP *alpha = NULL; unsigned alpha_pitch = 0; BYTE *alpha_bits = NULL; unsigned alpha_Bpp = 0; if(isCMYKA && !asCMYK && !header_only) { if(bitspersample == 16) { alpha = FreeImage_AllocateT(FIT_UINT16, width, height); } else if (bitspersample == 8) { alpha = FreeImage_Allocate(width, height, 8); } if(!alpha) { FreeImage_OutputMessageProc(s_format_id, "Failed to allocate temporary alpha channel"); } else { alpha_bits = FreeImage_GetScanLine(alpha, height - 1); alpha_pitch = FreeImage_GetPitch(alpha); alpha_Bpp = FreeImage_GetBPP(alpha) / 8; } } // create a new DIB const uint16 chCount = MIN(samplesperpixel, 4); dib = CreateImageType(header_only, image_type, width, height, bitspersample, chCount); if (dib == NULL) { FreeImage_Unload(alpha); throw FI_MSG_ERROR_MEMORY; } // fill in the resolution (english or universal) ReadResolution(tif, dib); if(!header_only) { // calculate the line + pitch (separate for scr & dest) const tmsize_t src_line = TIFFScanlineSize(tif); const tmsize_t dst_line = FreeImage_GetLine(dib); const unsigned dib_pitch = FreeImage_GetPitch(dib); const unsigned dibBpp = FreeImage_GetBPP(dib) / 8; const unsigned Bpc = dibBpp / chCount; const unsigned srcBpp = bitspersample * samplesperpixel / 8; assert(Bpc <= 2); //< CMYK is only BYTE or SHORT // In the tiff file the lines are save from up to down // In a DIB the lines must be saved from down to up BYTE *bits = FreeImage_GetScanLine(dib, height - 1); // read the tiff lines and save them in the DIB BYTE *buf = (BYTE*)malloc(TIFFStripSize(tif) * sizeof(BYTE)); if(buf == NULL) { FreeImage_Unload(alpha); throw FI_MSG_ERROR_MEMORY; } if(planar_config == PLANARCONFIG_CONTIG) { // - loop for strip blocks - for (uint32 y = 0; y < height; y += rowsperstrip) { const int32 strips = (y + rowsperstrip > height ? height - y : rowsperstrip); if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, y, 0), buf, strips * src_line) == -1) { free(buf); FreeImage_Unload(alpha); throw FI_MSG_ERROR_PARSING; } // - loop for strips - if(src_line != dst_line) { // CMYKA+ if(alpha) { for (int l = 0; l < strips; l++) { for(BYTE *pixel = bits, *al_pixel = alpha_bits, *src_pixel = buf + l * src_line; pixel < bits + dib_pitch; pixel += dibBpp, al_pixel += alpha_Bpp, src_pixel += srcBpp) { // copy pixel byte by byte BYTE b = 0; for( ; b < dibBpp; ++b) { pixel[b] = src_pixel[b]; } // TODO write the remaining bytes to extra channel(s) // HACK write the first alpha to a separate dib (assume BYTE or WORD) al_pixel[0] = src_pixel[b]; if(Bpc > 1) { al_pixel[1] = src_pixel[b + 1]; } } bits -= dib_pitch; alpha_bits -= alpha_pitch; } } else { // alpha/extra channels alloc failed for (int l = 0; l < strips; l++) { for(BYTE* pixel = bits, * src_pixel = buf + l * src_line; pixel < bits + dst_line; pixel += dibBpp, src_pixel += srcBpp) { AssignPixel(pixel, src_pixel, dibBpp); } bits -= dib_pitch; } } } else { // CMYK to CMYK for (int l = 0; l < strips; l++) { BYTE *b = buf + l * src_line; memcpy(bits, b, src_line); bits -= dib_pitch; } } } // height } else if(planar_config == PLANARCONFIG_SEPARATE) { BYTE *dib_strip = bits; BYTE *al_strip = alpha_bits; // - loop for strip blocks - for (uint32 y = 0; y < height; y += rowsperstrip) { const int32 strips = (y + rowsperstrip > height ? height - y : rowsperstrip); // - loop for channels (planes) - for(uint16 sample = 0; sample < samplesperpixel; sample++) { if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, y, sample), buf, strips * src_line) == -1) { free(buf); FreeImage_Unload(alpha); throw FI_MSG_ERROR_PARSING; } BYTE *dst_strip = dib_strip; unsigned dst_pitch = dib_pitch; uint16 ch = sample; unsigned Bpp = dibBpp; if(sample >= chCount) { // TODO Write to Extra Channel // HACK redirect write to temp alpha if(alpha && sample == chCount) { dst_strip = al_strip; dst_pitch = alpha_pitch; ch = 0; Bpp = alpha_Bpp; } else { break; } } const unsigned channelOffset = ch * Bpc; // - loop for strips in block - BYTE *src_line_begin = buf; BYTE *dst_line_begin = dst_strip; for (int l = 0; l < strips; l++, src_line_begin += src_line, dst_line_begin -= dst_pitch ) { // - loop for pixels in strip - const BYTE* const src_line_end = src_line_begin + src_line; for (BYTE *src_bits = src_line_begin, * dst_bits = dst_line_begin; src_bits < src_line_end; src_bits += Bpc, dst_bits += Bpp) { AssignPixel(dst_bits + channelOffset, src_bits, Bpc); } // line } // strips } // channels // done with a strip block, incr to the next dib_strip -= strips * dib_pitch; al_strip -= strips * alpha_pitch; } //< height } free(buf); if(!asCMYK) { ConvertCMYKtoRGBA(dib); // The ICC Profile is invalid, clear it iccSize = 0; iccBuf = NULL; if(isCMYKA) { // HACK until we have Extra channels. (ConvertCMYKtoRGBA will then do the work) FreeImage_SetChannel(dib, alpha, FICC_ALPHA); FreeImage_Unload(alpha); alpha = NULL; } else { FIBITMAP *t = RemoveAlphaChannel(dib); if(t) { FreeImage_Unload(dib); dib = t; } else { FreeImage_OutputMessageProc(s_format_id, "Cannot allocate memory for buffer. CMYK image converted to RGB + pending Alpha"); } } } } // !header_only } else if(loadMethod == LoadAsGenericStrip) { // --------------------------------------------------------------------------------- // Generic loading // --------------------------------------------------------------------------------- // create a new DIB const uint16 chCount = MIN(samplesperpixel, 4); dib = CreateImageType(header_only, image_type, width, height, bitspersample, chCount); if (dib == NULL) { throw FI_MSG_ERROR_MEMORY; } // fill in the resolution (english or universal) ReadResolution(tif, dib); // set up the colormap based on photometric ReadPalette(tif, photometric, bitspersample, dib); if(!header_only) { // calculate the line + pitch (separate for scr & dest) const tmsize_t src_line = TIFFScanlineSize(tif); const tmsize_t dst_line = FreeImage_GetLine(dib); const unsigned dst_pitch = FreeImage_GetPitch(dib); const unsigned Bpp = FreeImage_GetBPP(dib) / 8; const unsigned srcBpp = bitspersample * samplesperpixel / 8; // In the tiff file the lines are save from up to down // In a DIB the lines must be saved from down to up BYTE *bits = FreeImage_GetScanLine(dib, height - 1); // read the tiff lines and save them in the DIB BYTE *buf = (BYTE*)malloc(TIFFStripSize(tif) * sizeof(BYTE)); if(buf == NULL) { throw FI_MSG_ERROR_MEMORY; } memset(buf, 0, TIFFStripSize(tif) * sizeof(BYTE)); BOOL bThrowMessage = FALSE; if(planar_config == PLANARCONFIG_CONTIG) { for (uint32 y = 0; y < height; y += rowsperstrip) { int32 strips = (y + rowsperstrip > height ? height - y : rowsperstrip); if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, y, 0), buf, strips * src_line) == -1) { // ignore errors as they can be frequent and not really valid errors, especially with fax images bThrowMessage = TRUE; /* free(buf); throw FI_MSG_ERROR_PARSING; */ } if(src_line == dst_line) { // channel count match for (int l = 0; l < strips; l++) { memcpy(bits, buf + l * src_line, src_line); bits -= dst_pitch; } } else { for (int l = 0; l < strips; l++) { for(BYTE *pixel = bits, *src_pixel = buf + l * src_line; pixel < bits + dst_pitch; pixel += Bpp, src_pixel += srcBpp) { AssignPixel(pixel, src_pixel, Bpp); } bits -= dst_pitch; } } } } else if(planar_config == PLANARCONFIG_SEPARATE) { const unsigned Bpc = bitspersample / 8; BYTE* dib_strip = bits; // - loop for strip blocks - for (uint32 y = 0; y < height; y += rowsperstrip) { const int32 strips = (y + rowsperstrip > height ? height - y : rowsperstrip); // - loop for channels (planes) - for(uint16 sample = 0; sample < samplesperpixel; sample++) { if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, y, sample), buf, strips * src_line) == -1) { // ignore errors as they can be frequent and not really valid errors, especially with fax images bThrowMessage = TRUE; } if(sample >= chCount) { // TODO Write to Extra Channel break; } const unsigned channelOffset = sample * Bpc; // - loop for strips in block - BYTE* src_line_begin = buf; BYTE* dst_line_begin = dib_strip; for (int l = 0; l < strips; l++, src_line_begin += src_line, dst_line_begin -= dst_pitch ) { // - loop for pixels in strip - const BYTE* const src_line_end = src_line_begin + src_line; for (BYTE* src_bits = src_line_begin, * dst_bits = dst_line_begin; src_bits < src_line_end; src_bits += Bpc, dst_bits += Bpp) { // actually assigns channel AssignPixel(dst_bits + channelOffset, src_bits, Bpc); } // line } // strips } // channels // done with a strip block, incr to the next dib_strip -= strips * dst_pitch; } // height } free(buf); if(bThrowMessage) { FreeImage_OutputMessageProc(s_format_id, "Warning: parsing error. Image may be incomplete or contain invalid data !"); } #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR SwapRedBlue32(dib); #endif } // !header only } else if(loadMethod == LoadAsTiled) { // --------------------------------------------------------------------------------- // Tiled image loading // --------------------------------------------------------------------------------- uint32 tileWidth, tileHeight; uint32 src_line = 0; // create a new DIB dib = CreateImageType( header_only, image_type, width, height, bitspersample, samplesperpixel); if (dib == NULL) { throw FI_MSG_ERROR_MEMORY; } // fill in the resolution (english or universal) ReadResolution(tif, dib); // set up the colormap based on photometric ReadPalette(tif, photometric, bitspersample, dib); // get the tile geometry if(!TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tileWidth) || !TIFFGetField(tif, TIFFTAG_TILELENGTH, &tileHeight)) { throw "Invalid tiled TIFF image"; } // read the tiff lines and save them in the DIB if(planar_config == PLANARCONFIG_CONTIG && !header_only) { // get the maximum number of bytes required to contain a tile tmsize_t tileSize = TIFFTileSize(tif); // allocate tile buffer BYTE *tileBuffer = (BYTE*)malloc(tileSize * sizeof(BYTE)); if(tileBuffer == NULL) { throw FI_MSG_ERROR_MEMORY; } // calculate src line and dst pitch int dst_pitch = FreeImage_GetPitch(dib); uint32 tileRowSize = (uint32)TIFFTileRowSize(tif); uint32 imageRowSize = (uint32)TIFFScanlineSize(tif); // In the tiff file the lines are saved from up to down // In a DIB the lines must be saved from down to up BYTE *bits = FreeImage_GetScanLine(dib, height - 1); for (uint32 y = 0; y < height; y += tileHeight) { int32 nrows = (y + tileHeight > height ? height - y : tileHeight); for (uint32 x = 0, rowSize = 0; x < width; x += tileWidth, rowSize += tileRowSize) { memset(tileBuffer, 0, tileSize); // read one tile if (TIFFReadTile(tif, tileBuffer, x, y, 0, 0) < 0) { free(tileBuffer); throw "Corrupted tiled TIFF file"; } // convert to strip if(x + tileWidth > width) { src_line = imageRowSize - rowSize; } else { src_line = tileRowSize; } BYTE *src_bits = tileBuffer; BYTE *dst_bits = bits + rowSize; for(int k = 0; k < nrows; k++) { memcpy(dst_bits, src_bits, src_line); src_bits += tileRowSize; dst_bits -= dst_pitch; } } bits -= nrows * dst_pitch; } #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR SwapRedBlue32(dib); #endif free(tileBuffer); } else if(planar_config == PLANARCONFIG_SEPARATE) { throw "Separated tiled TIFF images are not supported"; } } else if(loadMethod == LoadAsLogLuv) { // --------------------------------------------------------------------------------- // RGBF LogLuv compressed loading // --------------------------------------------------------------------------------- double stonits; // input conversion to nits if (!TIFFGetField(tif, TIFFTAG_STONITS, &stonits)) { stonits = 1; } // create a new DIB dib = CreateImageType(header_only, image_type, width, height, bitspersample, samplesperpixel); if (dib == NULL) { throw FI_MSG_ERROR_MEMORY; } // fill in the resolution (english or universal) ReadResolution(tif, dib); if(planar_config == PLANARCONFIG_CONTIG && !header_only) { // calculate the line + pitch (separate for scr & dest) tmsize_t src_line = TIFFScanlineSize(tif); int dst_pitch = FreeImage_GetPitch(dib); // In the tiff file the lines are save from up to down // In a DIB the lines must be saved from down to up BYTE *bits = FreeImage_GetScanLine(dib, height - 1); // read the tiff lines and save them in the DIB BYTE *buf = (BYTE*)malloc(TIFFStripSize(tif) * sizeof(BYTE)); if(buf == NULL) { throw FI_MSG_ERROR_MEMORY; } for (uint32 y = 0; y < height; y += rowsperstrip) { int32 nrow = (y + rowsperstrip > height ? height - y : rowsperstrip); if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, y, 0), buf, nrow * src_line) == -1) { free(buf); throw FI_MSG_ERROR_PARSING; } // convert from XYZ to RGB for (int l = 0; l < nrow; l++) { tiff_ConvertLineXYZToRGB(bits, buf + l * src_line, stonits, width); bits -= dst_pitch; } } free(buf); } else if(planar_config == PLANARCONFIG_SEPARATE) { // this cannot happen according to the LogLuv specification throw "Unable to handle PLANARCONFIG_SEPARATE LogLuv images"; } } else if(loadMethod == LoadAsHalfFloat) { // --------------------------------------------------------------------------------- // RGBF loading from a half format // --------------------------------------------------------------------------------- // create a new DIB dib = CreateImageType(header_only, image_type, width, height, bitspersample, samplesperpixel); if (dib == NULL) { throw FI_MSG_ERROR_MEMORY; } // fill in the resolution (english or universal) ReadResolution(tif, dib); if(!header_only) { // calculate the line + pitch (separate for scr & dest) tmsize_t src_line = TIFFScanlineSize(tif); unsigned dst_pitch = FreeImage_GetPitch(dib); // In the tiff file the lines are save from up to down // In a DIB the lines must be saved from down to up BYTE *bits = FreeImage_GetScanLine(dib, height - 1); // read the tiff lines and save them in the DIB if(planar_config == PLANARCONFIG_CONTIG) { BYTE *buf = (BYTE*)malloc(TIFFStripSize(tif) * sizeof(BYTE)); if(buf == NULL) { throw FI_MSG_ERROR_MEMORY; } for (uint32 y = 0; y < height; y += rowsperstrip) { uint32 nrow = (y + rowsperstrip > height ? height - y : rowsperstrip); if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, y, 0), buf, nrow * src_line) == -1) { free(buf); throw FI_MSG_ERROR_PARSING; } // convert from half (16-bit) to float (32-bit) // !!! use OpenEXR half helper class half half_value; for (uint32 l = 0; l < nrow; l++) { WORD *src_pixel = (WORD*)(buf + l * src_line); float *dst_pixel = (float*)bits; for(tmsize_t x = 0; x < (tmsize_t)(src_line / sizeof(WORD)); x++) { half_value.setBits(src_pixel[x]); dst_pixel[x] = half_value; } bits -= dst_pitch; } } free(buf); } else if(planar_config == PLANARCONFIG_SEPARATE) { // this use case was never encountered yet throw "Unable to handle PLANARCONFIG_SEPARATE RGB half float images"; } } // !header only } else { // --------------------------------------------------------------------------------- // Unknown or unsupported format // --------------------------------------------------------------------------------- throw FI_MSG_ERROR_UNSUPPORTED_FORMAT; } // copy ICC profile data (must be done after FreeImage_Allocate) FreeImage_CreateICCProfile(dib, iccBuf, iccSize); if (photometric == PHOTOMETRIC_SEPARATED && asCMYK) { FreeImage_GetICCProfile(dib)->flags |= FIICC_COLOR_IS_CMYK; } // copy TIFF metadata (must be done after FreeImage_Allocate) ReadMetadata(tif, dib); // copy TIFF thumbnail (must be done after FreeImage_Allocate) ReadThumbnail(io, handle, data, tif, dib); return (FIBITMAP *)dib; } catch (const char *message) { if(dib) { FreeImage_Unload(dib); } if(message) { FreeImage_OutputMessageProc(s_format_id, message); } return NULL; } } // -------------------------------------------------------------------------- static BOOL SaveOneTIFF(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data, unsigned ifd, unsigned ifdCount) { if (!dib || !handle || !data) { return FALSE; } try { fi_TIFFIO *fio = (fi_TIFFIO*)data; TIFF *out = fio->tif; const FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(dib); const uint32 width = FreeImage_GetWidth(dib); const uint32 height = FreeImage_GetHeight(dib); const uint16 bitsperpixel = (uint16)FreeImage_GetBPP(dib); const FIICCPROFILE* iccProfile = FreeImage_GetICCProfile(dib); // setup out-variables based on dib and flag options uint16 bitspersample; uint16 samplesperpixel; uint16 photometric; if(image_type == FIT_BITMAP) { // standard image: 1-, 4-, 8-, 16-, 24-, 32-bit samplesperpixel = ((bitsperpixel == 24) ? 3 : ((bitsperpixel == 32) ? 4 : 1)); bitspersample = bitsperpixel / samplesperpixel; photometric = GetPhotometric(dib); if((bitsperpixel == 8) && FreeImage_IsTransparent(dib)) { // 8-bit transparent picture : convert later to 8-bit + 8-bit alpha samplesperpixel = 2; bitspersample = 8; } else if(bitsperpixel == 32) { // 32-bit images : check for CMYK or alpha transparency if((((iccProfile->flags & FIICC_COLOR_IS_CMYK) == FIICC_COLOR_IS_CMYK) || ((flags & TIFF_CMYK) == TIFF_CMYK))) { // CMYK support photometric = PHOTOMETRIC_SEPARATED; TIFFSetField(out, TIFFTAG_INKSET, INKSET_CMYK); TIFFSetField(out, TIFFTAG_NUMBEROFINKS, 4); } else if(photometric == PHOTOMETRIC_RGB) { // transparency mask support uint16 sampleinfo[1]; // unassociated alpha data is transparency information sampleinfo[0] = EXTRASAMPLE_UNASSALPHA; TIFFSetField(out, TIFFTAG_EXTRASAMPLES, 1, sampleinfo); } } } else if(image_type == FIT_RGB16) { // 48-bit RGB samplesperpixel = 3; bitspersample = bitsperpixel / samplesperpixel; photometric = PHOTOMETRIC_RGB; } else if(image_type == FIT_RGBA16) { // 64-bit RGBA samplesperpixel = 4; bitspersample = bitsperpixel / samplesperpixel; if((((iccProfile->flags & FIICC_COLOR_IS_CMYK) == FIICC_COLOR_IS_CMYK) || ((flags & TIFF_CMYK) == TIFF_CMYK))) { // CMYK support photometric = PHOTOMETRIC_SEPARATED; TIFFSetField(out, TIFFTAG_INKSET, INKSET_CMYK); TIFFSetField(out, TIFFTAG_NUMBEROFINKS, 4); } else { photometric = PHOTOMETRIC_RGB; // transparency mask support uint16 sampleinfo[1]; // unassociated alpha data is transparency information sampleinfo[0] = EXTRASAMPLE_UNASSALPHA; TIFFSetField(out, TIFFTAG_EXTRASAMPLES, 1, sampleinfo); } } else if(image_type == FIT_RGBF) { // 96-bit RGBF => store with a LogLuv encoding ? samplesperpixel = 3; bitspersample = bitsperpixel / samplesperpixel; // the library converts to and from floating-point XYZ CIE values if((flags & TIFF_LOGLUV) == TIFF_LOGLUV) { photometric = PHOTOMETRIC_LOGLUV; TIFFSetField(out, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT); // TIFFSetField(out, TIFFTAG_STONITS, 1.0); // assume unknown } else { // store with default compression (LZW) or with input compression flag photometric = PHOTOMETRIC_RGB; } } else if (image_type == FIT_RGBAF) { // 128-bit RGBAF => store with default compression (LZW) or with input compression flag samplesperpixel = 4; bitspersample = bitsperpixel / samplesperpixel; photometric = PHOTOMETRIC_RGB; } else { // special image type (int, long, double, ...) samplesperpixel = 1; bitspersample = bitsperpixel; photometric = PHOTOMETRIC_MINISBLACK; } // set image data type WriteImageType(out, image_type); // write possible ICC profile if (iccProfile->size && iccProfile->data) { TIFFSetField(out, TIFFTAG_ICCPROFILE, iccProfile->size, iccProfile->data); } // handle standard width/height/bpp stuff TIFFSetField(out, TIFFTAG_IMAGEWIDTH, width); TIFFSetField(out, TIFFTAG_IMAGELENGTH, height); TIFFSetField(out, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel); TIFFSetField(out, TIFFTAG_BITSPERSAMPLE, bitspersample); TIFFSetField(out, TIFFTAG_PHOTOMETRIC, photometric); TIFFSetField(out, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); // single image plane TIFFSetField(out, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); TIFFSetField(out, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB); TIFFSetField(out, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(out, (uint32) -1)); // handle metrics WriteResolution(out, dib); // multi-paging if (page >= 0) { char page_number[20]; sprintf(page_number, "Page %d", page); TIFFSetField(out, TIFFTAG_SUBFILETYPE, (uint32)FILETYPE_PAGE); TIFFSetField(out, TIFFTAG_PAGENUMBER, (uint16)page, (uint16)0); TIFFSetField(out, TIFFTAG_PAGENAME, page_number); } else { // is it a thumbnail ? TIFFSetField(out, TIFFTAG_SUBFILETYPE, (ifd == 0) ? (uint32)0 : (uint32)FILETYPE_REDUCEDIMAGE); } // palettes (image colormaps are automatically scaled to 16-bits) if (photometric == PHOTOMETRIC_PALETTE) { uint16 *r, *g, *b; uint16 nColors = (uint16)FreeImage_GetColorsUsed(dib); RGBQUAD *pal = FreeImage_GetPalette(dib); r = (uint16 *) _TIFFmalloc(sizeof(uint16) * 3 * nColors); if(r == NULL) { throw FI_MSG_ERROR_MEMORY; } g = r + nColors; b = g + nColors; for (int i = nColors - 1; i >= 0; i--) { r[i] = SCALE((uint16)pal[i].rgbRed); g[i] = SCALE((uint16)pal[i].rgbGreen); b[i] = SCALE((uint16)pal[i].rgbBlue); } TIFFSetField(out, TIFFTAG_COLORMAP, r, g, b); _TIFFfree(r); } // compression tag WriteCompression(out, bitspersample, samplesperpixel, photometric, flags); // metadata WriteMetadata(out, dib); // thumbnail tag if((ifd == 0) && (ifdCount > 1)) { uint16 nsubifd = 1; uint64 subifd[1]; subifd[0] = 0; TIFFSetField(out, TIFFTAG_SUBIFD, nsubifd, subifd); } // read the DIB lines from bottom to top // and save them in the TIF // ------------------------------------- const uint32 pitch = FreeImage_GetPitch(dib); if(image_type == FIT_BITMAP) { // standard bitmap type switch(bitsperpixel) { case 1 : case 4 : case 8 : { if((bitsperpixel == 8) && FreeImage_IsTransparent(dib)) { // 8-bit transparent picture : convert to 8-bit + 8-bit alpha // get the transparency table BYTE *trns = FreeImage_GetTransparencyTable(dib); BYTE *buffer = (BYTE *)malloc(2 * width * sizeof(BYTE)); if(buffer == NULL) { throw FI_MSG_ERROR_MEMORY; } for (int y = height - 1; y >= 0; y--) { BYTE *bits = FreeImage_GetScanLine(dib, y); BYTE *p = bits, *b = buffer; for(uint32 x = 0; x < width; x++) { // copy the 8-bit layer b[0] = *p; // convert the trns table to a 8-bit alpha layer b[1] = trns[ b[0] ]; p++; b += samplesperpixel; } // write the scanline to disc TIFFWriteScanline(out, buffer, height - y - 1, 0); } free(buffer); } else { // other cases BYTE *buffer = (BYTE *)malloc(pitch * sizeof(BYTE)); if(buffer == NULL) { throw FI_MSG_ERROR_MEMORY; } for (uint32 y = 0; y < height; y++) { // get a copy of the scanline memcpy(buffer, FreeImage_GetScanLine(dib, height - y - 1), pitch); // write the scanline to disc TIFFWriteScanline(out, buffer, y, 0); } free(buffer); } break; } case 24: case 32: { BYTE *buffer = (BYTE *)malloc(pitch * sizeof(BYTE)); if(buffer == NULL) { throw FI_MSG_ERROR_MEMORY; } for (uint32 y = 0; y < height; y++) { // get a copy of the scanline memcpy(buffer, FreeImage_GetScanLine(dib, height - y - 1), pitch); #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR if (photometric != PHOTOMETRIC_SEPARATED) { // TIFFs store color data RGB(A) instead of BGR(A) BYTE *pBuf = buffer; for (uint32 x = 0; x < width; x++) { INPLACESWAP(pBuf[0], pBuf[2]); pBuf += samplesperpixel; } } #endif // write the scanline to disc TIFFWriteScanline(out, buffer, y, 0); } free(buffer); break; } }//< switch (bitsperpixel) } else if(image_type == FIT_RGBF && (flags & TIFF_LOGLUV) == TIFF_LOGLUV) { // RGBF image => store as XYZ using a LogLuv encoding BYTE *buffer = (BYTE *)malloc(pitch * sizeof(BYTE)); if(buffer == NULL) { throw FI_MSG_ERROR_MEMORY; } for (uint32 y = 0; y < height; y++) { // get a copy of the scanline and convert from RGB to XYZ tiff_ConvertLineRGBToXYZ(buffer, FreeImage_GetScanLine(dib, height - y - 1), width); // write the scanline to disc TIFFWriteScanline(out, buffer, y, 0); } free(buffer); } else { // just dump the dib (tiff supports all dib types) BYTE *buffer = (BYTE *)malloc(pitch * sizeof(BYTE)); if(buffer == NULL) { throw FI_MSG_ERROR_MEMORY; } for (uint32 y = 0; y < height; y++) { // get a copy of the scanline memcpy(buffer, FreeImage_GetScanLine(dib, height - y - 1), pitch); // write the scanline to disc TIFFWriteScanline(out, buffer, y, 0); } free(buffer); } // write out the directory tag if we wrote a page other than -1 or if we have a thumbnail to write later if( (page >= 0) || ((ifd == 0) && (ifdCount > 1)) ) { TIFFWriteDirectory(out); // else: TIFFClose will WriteDirectory } return TRUE; } catch(const char *text) { FreeImage_OutputMessageProc(s_format_id, text); return FALSE; } } static BOOL DLL_CALLCONV Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) { BOOL bResult = FALSE; // handle thumbnail as SubIFD const BOOL bHasThumbnail = (FreeImage_GetThumbnail(dib) != NULL); const unsigned ifdCount = bHasThumbnail ? 2 : 1; FIBITMAP *bitmap = dib; for(unsigned ifd = 0; ifd < ifdCount; ifd++) { // redirect dib to thumbnail for the second pass if(ifd == 1) { bitmap = FreeImage_GetThumbnail(dib); } bResult = SaveOneTIFF(io, bitmap, handle, page, flags, data, ifd, ifdCount); if(!bResult) { return FALSE; } } return bResult; } // ========================================================== // Init // ========================================================== void DLL_CALLCONV InitTIFF(Plugin *plugin, int format_id) { s_format_id = format_id; // Set up the callback for extended TIFF directory tag support (see XTIFF.cpp) // Must be called before using libtiff XTIFFInitialize(); plugin->format_proc = Format; plugin->description_proc = Description; plugin->extension_proc = Extension; plugin->regexpr_proc = RegExpr; plugin->open_proc = Open; plugin->close_proc = Close; plugin->pagecount_proc = PageCount; plugin->pagecapability_proc = NULL; plugin->load_proc = Load; plugin->save_proc = Save; plugin->validate_proc = Validate; plugin->mime_proc = MimeType; plugin->supports_export_bpp_proc = SupportsExportDepth; plugin->supports_export_type_proc = SupportsExportType; plugin->supports_icc_profiles_proc = SupportsICCProfiles; plugin->supports_no_pixels_proc = SupportsNoPixels; }