| // ========================================================== |
| // 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 "third_party/tiff/libtiff/tiffiop.h" |
| #include "third_party/ilmbase/Half/half.h" |
| #include "../Metadata/FreeImageTag.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 <sys/stat.h> |
| |
| 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; |
| } |
| |
| // Ifdef around this code so that we can build for SketchUp on Linux. |
| // FreeImage for SketchUp on Linux uses the third_party/tiff library |
| // instead of the FreeImage tiff library to avoid conflicts with |
| // escher code. In this case these methods are already defined and |
| // we don't want to define them again. |
| #ifndef USE_THIRD_PARTY_TIFF |
| |
| /** |
| 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; |
| |
| #endif |
| |
| // ---------------------------------------------------------- |
| |
| #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<<bitspersample, red, green, blue) == 16) { |
| for (int i = (1 << bitspersample) - 1; i >= 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<uint16>(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<uint16>(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<uint16>(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; |
| } |