| // ========================================================== |
| // 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) |
| // |
| // 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 "../LibTIFF/tiffiop.h" |
| #include "FreeImage.h" |
| #include "Utilities.h" |
| #include "../Metadata/FreeImageTag.h" |
| |
| // ---------------------------------------------------------- |
| // geotiff interface (see XTIFF.cpp) |
| // ---------------------------------------------------------- |
| |
| // Extended TIFF Directory GEO Tag Support |
| void XTIFFInitialize(); |
| |
| // GeoTIFF profile |
| void tiff_read_geotiff_profile(TIFF *tif, FIBITMAP *dib); |
| void tiff_write_geotiff_profile(TIFF *tif, FIBITMAP *dib); |
| |
| // ---------------------------------------------------------- |
| // exif interface (see XTIFF.cpp) |
| // ---------------------------------------------------------- |
| |
| // TIFF Exif profile |
| BOOL tiff_read_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, |
| LoadAsRGBF = 5 |
| } TIFFLoadMethod; |
| |
| // ---------------------------------------------------------- |
| // local prototypes |
| // ---------------------------------------------------------- |
| |
| static tsize_t _tiffReadProc(thandle_t handle, tdata_t buf, tsize_t size); |
| static tsize_t _tiffWriteProc(thandle_t handle, tdata_t buf, tsize_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, tdata_t* pbase, toff_t* psize); |
| static void _tiffUnmapProc(thandle_t fd, tdata_t 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(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); |
| |
| |
| // ========================================================== |
| // Plugin Interface |
| // ========================================================== |
| |
| static int s_format_id; |
| |
| typedef struct { |
| FreeImageIO *io; |
| fi_handle handle; |
| TIFF *tif; |
| } fi_TIFFIO; |
| |
| // ---------------------------------------------------------- |
| // libtiff interface |
| // ---------------------------------------------------------- |
| |
| static tsize_t |
| _tiffReadProc(thandle_t handle, tdata_t buf, tsize_t size) { |
| fi_TIFFIO *fio = (fi_TIFFIO*)handle; |
| return fio->io->read_proc(buf, size, 1, fio->handle) * size; |
| } |
| |
| static tsize_t |
| _tiffWriteProc(thandle_t handle, tdata_t buf, tsize_t size) { |
| fi_TIFFIO *fio = (fi_TIFFIO*)handle; |
| return fio->io->write_proc(buf, 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, 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 fd, tdata_t* pbase, toff_t* psize) { |
| return 0; |
| } |
| |
| static void |
| _tiffUnmapProc(thandle_t fd, tdata_t base, toff_t size) { |
| } |
| |
| // ---------------------------------------------------------- |
| // Open a TIFF file descriptor for read/writing. |
| // ---------------------------------------------------------- |
| |
| TIFF * |
| TIFFFdOpen(thandle_t handle, const char *name, const char *mode) { |
| TIFF *tif; |
| |
| // Set up the callback for extended TIFF directory tag support |
| // (see XTIFF.cpp) |
| XTIFFInitialize(); |
| |
| // Open the file; the callback will set everything up |
| tif = TIFFClientOpen(name, mode, handle, |
| _tiffReadProc, _tiffWriteProc, _tiffSeekProc, _tiffCloseProc, |
| _tiffSizeProc, _tiffMapProc, _tiffUnmapProc); |
| |
| // Warning: tif_fd is declared as 'int' currently (see libTIFF), |
| // may result in incorrect file pointers inside libTIFF on |
| // 64bit machines (sizeof(int) != sizeof(long)). |
| // Needs to be fixed within libTIFF. |
| if (tif) |
| tif->tif_fd = (long)handle; |
| |
| return tif; |
| } |
| |
| // ---------------------------------------------------------- |
| // Open a TIFF file for read/writing. |
| // ---------------------------------------------------------- |
| |
| TIFF* |
| TIFFOpen(const char* name, const char* mode) { |
| return 0; |
| } |
| |
| // ---------------------------------------------------------- |
| // TIFF library FreeImage-specific routines. |
| // ---------------------------------------------------------- |
| |
| tdata_t |
| _TIFFmalloc(tsize_t s) { |
| return malloc(s); |
| } |
| |
| void |
| _TIFFfree(tdata_t p) { |
| free(p); |
| } |
| |
| tdata_t |
| _TIFFrealloc(tdata_t p, tsize_t s) { |
| return realloc(p, s); |
| } |
| |
| void |
| _TIFFmemset(tdata_t p, int v, tsize_t c) { |
| memset(p, v, (size_t) c); |
| } |
| |
| void |
| _TIFFmemcpy(tdata_t d, const tdata_t s, tsize_t c) { |
| memcpy(d, s, (size_t) c); |
| } |
| |
| int |
| _TIFFmemcmp(const tdata_t p1, const tdata_t p2, tsize_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<<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 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(FREE_IMAGE_TYPE fit, int width, int height, uint16 bitspersample, uint16 samplesperpixel) { |
| FIBITMAP *dib = NULL; |
| |
| uint16 bpp = bitspersample * samplesperpixel; |
| |
| if(fit == FIT_BITMAP) { |
| // standard bitmap type |
| |
| if(bitspersample == 16) { |
| if(samplesperpixel == 1) { |
| // 16-bit greyscale -> convert to 8-bit |
| dib = FreeImage_Allocate(width, height, 8); |
| } |
| else if(samplesperpixel == 3) { |
| // 48-bit RGB -> convert to 24-bit RGB |
| dib = FreeImage_Allocate(width, height, 24, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); |
| } |
| } |
| else if(bpp == 16) { |
| if((samplesperpixel == 2) && (bitspersample == 8)) { |
| // 8-bit indexed + 8-bit alpha channel -> convert to 8-bit transparent |
| dib = FreeImage_Allocate(width, height, 8); |
| } else { |
| // 16-bit RGB -> expect it to be 565 |
| dib = FreeImage_Allocate(width, height, bpp, FI16_565_RED_MASK, FI16_565_GREEN_MASK, FI16_565_BLUE_MASK); |
| } |
| } |
| else if(bpp == 24) { |
| // BGR |
| dib = FreeImage_Allocate(width, height, bpp, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); |
| } |
| else if(bpp == 32) { |
| // BGRA |
| dib = FreeImage_Allocate(width, height, bpp, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); |
| } |
| else { |
| // anything else <= 8-bit |
| dib = FreeImage_Allocate(width, height, bpp); |
| } |
| } else { |
| // other bitmap types |
| |
| dib = FreeImage_AllocateT(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 64: |
| fit = FIT_DOUBLE; |
| break; |
| case 96: |
| fit = FIT_RGBF; |
| 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(bpp == 64) 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 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) { |
| // 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) { |
| uint32 exif_offset = 0; |
| |
| // read EXIF-TIFF tags |
| 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; |
| |
| return tiff_read_exif_tags(tiff, TagLib::EXIF_EXIF, dib); |
| } |
| |
| return FALSE; |
| } |
| |
| /** |
| 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 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); |
| |
| // 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 }; |
| BYTE tiff_id2[] = { 0x4D, 0x4D, 0x00, 0x2A }; |
| 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; |
| |
| 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) |
| ); |
| } |
| |
| static BOOL DLL_CALLCONV |
| SupportsICCProfiles() { |
| 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 { |
| 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: |
| return TRUE; |
| 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 samplesperpixel; |
| uint16 photometric; |
| uint16 planar_config; |
| |
| 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(bitspersample >= 8) { |
| loadMethod = LoadAsRBGA; |
| } |
| break; |
| case PHOTOMETRIC_YCBCR: |
| case PHOTOMETRIC_CIELAB: |
| case PHOTOMETRIC_ICCLAB: |
| case PHOTOMETRIC_ITULAB: |
| loadMethod = LoadAsRBGA; |
| break; |
| case PHOTOMETRIC_LOGLUV: |
| loadMethod = LoadAsRGBF; |
| break; |
| case PHOTOMETRIC_SEPARATED: |
| if(planar_config == PLANARCONFIG_CONTIG) { |
| // 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((((flags & TIFF_CMYK) == TIFF_CMYK) || samplesperpixel > 4) && !bIsTiled) { |
| loadMethod = LoadAsCMYK; |
| } else { |
| loadMethod = LoadAsRBGA; |
| } |
| } else if(planar_config == PLANARCONFIG_SEPARATE) { |
| // TIFFReadRGBAImage doesn't support this case ... |
| 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 == 2) && (bitspersample == 8)) { |
| loadMethod = LoadAs8BitTrns; |
| } else { |
| loadMethod = LoadAsGenericStrip; |
| } |
| break; |
| default: |
| loadMethod = LoadAsGenericStrip; |
| break; |
| } |
| |
| if((loadMethod == LoadAsGenericStrip) && bIsTiled) { |
| loadMethod = LoadAsTiled; |
| } |
| |
| return loadMethod; |
| } |
| |
| |
| static FIBITMAP * DLL_CALLCONV |
| Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { |
| if ((handle != NULL) && (data != NULL)) { |
| TIFF *tif = NULL; |
| uint32 height = 0; |
| uint32 width = 0; |
| uint16 bitspersample = 1; |
| uint16 samplesperpixel = 1; |
| uint32 rowsperstrip; |
| uint16 photometric = PHOTOMETRIC_MINISWHITE; |
| uint16 compression = COMPRESSION_NONE; |
| uint16 planar_config; |
| |
| FIBITMAP *dib = NULL; |
| uint32 iccSize = 0; // ICC profile length |
| void *iccBuf = NULL; // ICC profile data |
| |
| try { |
| fi_TIFFIO *fio = (fi_TIFFIO*)data; |
| tif = fio->tif; |
| |
| if (page != -1) { |
| if (!tif || !TIFFSetDirectory(tif, (tdir_t)page)) { |
| throw "Error encountered while opening TIFF file"; |
| } |
| } |
| |
| // 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 (compression == COMPRESSION_OJPEG) |
| throw "6.0 JPEG encoding is not supported"; |
| |
| if((photometric == PHOTOMETRIC_SEPARATED) && (bitspersample == 16)) |
| throw "Unable to handle 16-bit CMYK TIFF"; |
| |
| 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 = (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, 0)) { |
| _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) { |
| 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(image_type, width, height, bitspersample, samplesperpixel); |
| if (dib == NULL) { |
| // free the raster pointer and output an error if allocation failed |
| |
| _TIFFfree(raster); |
| |
| throw FI_MSG_ERROR_DIB_MEMORY; |
| } |
| |
| // fill in the resolution (english or universal) |
| |
| ReadResolution(tif, dib); |
| |
| // 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); |
| |
| FreeImage_SetTransparent(dib, has_alpha); |
| |
| } else if(loadMethod == LoadAs8BitTrns) { |
| // --------------------------------------------------------------------------------- |
| // 8-bit + 8-bit alpha layer loading |
| // --------------------------------------------------------------------------------- |
| |
| // create a new 8-bit DIB |
| dib = CreateImageType(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); |
| |
| // calculate the line + pitch (separate for scr & dest) |
| |
| tsize_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 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) { |
| 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) { |
| tsize_t stripsize = TIFFStripSize(tif) * sizeof(BYTE); |
| BYTE *buf = (BYTE*)malloc(2 * stripsize); |
| 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 |
| // --------------------------------------------------------------------------------- |
| |
| BOOL has_alpha = FALSE; |
| |
| // 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); |
| uint16 spp = MIN(samplesperpixel, (uint16)4); |
| if((planar_config == PLANARCONFIG_SEPARATE) && !isCMYKA && ((flags & TIFF_CMYK) != TIFF_CMYK)) { |
| // CMYK picture => convert to RGB 24-bit |
| spp = 3; |
| } |
| |
| // create a new DIB |
| dib = CreateImageType(image_type, width, height, bitspersample, spp); |
| if (dib == NULL) { |
| throw FI_MSG_ERROR_MEMORY; |
| } |
| |
| // fill in the resolution (english or universal) |
| |
| ReadResolution(tif, dib); |
| |
| // calculate the line + pitch (separate for scr & dest) |
| |
| tsize_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 |
| |
| 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) { |
| 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; |
| } |
| if(isCMYKA) { |
| // CMYKA picture |
| for (int l = 0; l < nrow; l++) { |
| // Here we know: samples-per-pixel was >= 5 on CMYKA picture |
| // This should be converted to RGBA or CMYK, depending on |
| // TIFF_CMYK is given. The resulting image always has 32bpp. |
| |
| BYTE *p = bits; |
| BYTE *b = buf + l * src_line; |
| |
| for (uint32 x = 0; x < (uint32)(src_line / samplesperpixel); x++) { |
| if ((flags & TIFF_CMYK) == TIFF_CMYK) { |
| memcpy(p, b, spp); |
| } else { |
| BYTE k = 255 - b[3]; |
| p[FI_RGBA_RED] = (k*(255-b[0]))/255; |
| p[FI_RGBA_GREEN] = (k*(255-b[1]))/255; |
| p[FI_RGBA_BLUE] = (k*(255-b[2]))/255; |
| if ((p[FI_RGBA_ALPHA] = b[4]) != 0) |
| has_alpha = TRUE; |
| } |
| b += samplesperpixel; |
| p += spp; |
| } |
| bits -= dst_pitch; |
| } |
| } |
| else { |
| // CMYK picture: just copy |
| for (int l = 0; l < nrow; l++) { |
| BYTE *b = buf + l * src_line; |
| memcpy(bits, b, src_line); |
| bits -= dst_pitch; |
| } |
| } |
| } |
| |
| free(buf); |
| } |
| else if(planar_config == PLANARCONFIG_SEPARATE) { |
| uint16 sample; |
| BYTE *channel; |
| tsize_t stripsize = TIFFStripSize(tif) * sizeof(BYTE); |
| BYTE *buf = (BYTE*)malloc(samplesperpixel * stripsize); |
| if(buf == NULL) throw FI_MSG_ERROR_MEMORY; |
| |
| for (uint32 y = 0; y < height; y += rowsperstrip) { |
| int32 nrow = (y + rowsperstrip > height ? height - y : rowsperstrip); |
| |
| // read all separated strips |
| channel = buf; |
| for(sample = 0; sample < samplesperpixel; sample++) { |
| if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, y, sample), channel, nrow * src_line) == -1) { |
| free(buf); |
| throw FI_MSG_ERROR_PARSING; |
| } |
| channel += stripsize; |
| } |
| if ((flags & TIFF_CMYK) == TIFF_CMYK) { |
| // CMYK or CMYKA picture: load as 32-bit CMYK, skipping possibly present alpha channel(s) |
| for (int l = 0; l < nrow; l++) { |
| channel = buf; |
| for(sample = 0; sample < spp; sample++) { |
| BYTE *src_bits = channel + l * src_line; |
| BYTE *dst_bits = bits; |
| for (uint32 x = 0; x < (uint32)(src_line); x++) { |
| dst_bits[sample] = src_bits[x]; |
| dst_bits += spp; |
| } |
| channel += stripsize; |
| } |
| bits -= dst_pitch; |
| } |
| } |
| else if(isCMYKA) { |
| // CMYKA picture: convert to RGBA, skipping possibly some alpha channel(s) |
| for (int l = 0; l < nrow; l++) { |
| BYTE *c_channel = buf + l * src_line; |
| BYTE *m_channel = buf + stripsize + l * src_line; |
| BYTE *y_channel = buf + 2*stripsize + l * src_line; |
| BYTE *k_channel = buf + 3*stripsize + l * src_line; |
| BYTE *a_channel = buf + 4*stripsize + l * src_line; |
| BYTE *dst_bits = bits; |
| for (uint32 x = 0; x < (uint32)(src_line); x++) { |
| BYTE k = 255 - k_channel[x]; |
| dst_bits[FI_RGBA_RED] = (k*(255-c_channel[x]))/255; |
| dst_bits[FI_RGBA_GREEN] = (k*(255-m_channel[x]))/255; |
| dst_bits[FI_RGBA_BLUE] = (k*(255-y_channel[x]))/255; |
| if ((dst_bits[FI_RGBA_ALPHA] = a_channel[x]) != 0) |
| has_alpha = TRUE; |
| dst_bits += spp; |
| } |
| bits -= dst_pitch; |
| } |
| } |
| else { |
| // CMYK picture: convert to RGB |
| for (int l = 0; l < nrow; l++) { |
| BYTE *c_channel = buf + l * src_line; |
| BYTE *m_channel = buf + stripsize + l * src_line; |
| BYTE *y_channel = buf + 2*stripsize + l * src_line; |
| BYTE *k_channel = buf + 3*stripsize + l * src_line; |
| BYTE *dst_bits = bits; |
| for (uint32 x = 0; x < (uint32)(src_line); x++) { |
| BYTE k = 255 - k_channel[x]; |
| dst_bits[FI_RGBA_RED] = (k*(255-c_channel[x]))/255; |
| dst_bits[FI_RGBA_GREEN] = (k*(255-m_channel[x]))/255; |
| dst_bits[FI_RGBA_BLUE] = (k*(255-y_channel[x]))/255; |
| dst_bits += spp; |
| } |
| bits -= dst_pitch; |
| } |
| } |
| } |
| |
| free(buf); |
| } |
| |
| FreeImage_SetTransparent(dib, has_alpha); |
| |
| } else if(loadMethod == LoadAsGenericStrip) { |
| // --------------------------------------------------------------------------------- |
| // Generic loading |
| // --------------------------------------------------------------------------------- |
| |
| // create a new DIB |
| dib = CreateImageType(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); |
| |
| // calculate the line + pitch (separate for scr & dest) |
| |
| tsize_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 |
| |
| if(planar_config == PLANARCONFIG_CONTIG) { |
| BOOL bThrowMessage = FALSE; |
| 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) { |
| // 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; |
| */ |
| } |
| // color/greyscale picture (1-, 4-, 8-bit) or special type (int, long, double, ...) |
| // ... just copy |
| for (int l = 0; l < nrow; l++) { |
| memcpy(bits, buf + l * src_line, src_line); |
| bits -= dst_pitch; |
| } |
| } |
| |
| free(buf); |
| |
| if(bThrowMessage) { |
| FreeImage_OutputMessageProc(s_format_id, "Warning: parsing error. Image may be incomplete or contain invalid data !"); |
| } |
| } |
| else if(planar_config == PLANARCONFIG_SEPARATE) { |
| BOOL bThrowMessage = FALSE; |
| uint16 sample; |
| BYTE *channel; |
| tsize_t stripsize = TIFFStripSize(tif) * sizeof(BYTE); |
| BYTE *buf = (BYTE*)malloc(samplesperpixel * stripsize); |
| if(buf == NULL) throw FI_MSG_ERROR_MEMORY; |
| |
| int bytespersample = bitspersample / 8; |
| int bytesperpixel = bytespersample * samplesperpixel; |
| |
| for (uint32 y = 0; y < height; y += rowsperstrip) { |
| int32 nrow = (y + rowsperstrip > height ? height - y : rowsperstrip); |
| |
| // read all separated strips |
| channel = buf; |
| for(sample = 0; sample < samplesperpixel; sample++) { |
| if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, y, sample), channel, nrow * 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; |
| */ |
| } |
| channel += stripsize; |
| } |
| |
| // reconstruct the picture |
| for (int l = 0; l < nrow; l++) { |
| channel = buf; |
| for(sample = 0; sample < samplesperpixel; sample++) { |
| BYTE *src_bits = channel + l * src_line; |
| BYTE *dst_bits = bits + sample * bytespersample; |
| for (uint32 x = 0; x < (uint32)(src_line / bytespersample); x++) { |
| memcpy(dst_bits, src_bits, bytespersample); |
| src_bits += bytespersample; |
| dst_bits += bytesperpixel; |
| } |
| channel += stripsize; |
| } |
| bits -= dst_pitch; |
| } |
| } |
| |
| free(buf); |
| |
| if(bThrowMessage) { |
| FreeImage_OutputMessageProc(s_format_id, "Warning: parsing error. Image may be incomplete or contain invalid data !"); |
| } |
| } |
| |
| } else if(loadMethod == LoadAsTiled) { |
| // --------------------------------------------------------------------------------- |
| // Tiled image loading |
| // --------------------------------------------------------------------------------- |
| |
| uint32 tileWidth, tileHeight; |
| uint32 src_line = 0; |
| |
| // create a new DIB |
| dib = CreateImageType(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"; |
| } |
| |
| // get the maximum number of bytes required to contain a tile |
| tsize_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); |
| int tileRowSize = TIFFTileRowSize(tif); |
| int imageRowSize = TIFFScanlineSize(tif); |
| |
| |
| // 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) { |
| uint32 x, y, rowSize; |
| for (y = 0; y < height; y += tileHeight) { |
| int32 nrows = (y + tileHeight > height ? height - y : tileHeight); |
| |
| for (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; |
| } |
| |
| } |
| else if(planar_config == PLANARCONFIG_SEPARATE) { |
| free(tileBuffer); |
| throw "Separated tiled TIFF images are not supported"; |
| } |
| |
| free(tileBuffer); |
| |
| } else if(loadMethod == LoadAsRGBF) { |
| // --------------------------------------------------------------------------------- |
| // RGBF loading |
| // --------------------------------------------------------------------------------- |
| |
| double stonits; // input conversion to nits |
| if (!TIFFGetField(tif, TIFFTAG_STONITS, &stonits)) { |
| stonits = 1; |
| } |
| |
| // create a new DIB |
| dib = CreateImageType(image_type, width, height, bitspersample, samplesperpixel); |
| if (dib == NULL) { |
| throw FI_MSG_ERROR_MEMORY; |
| } |
| |
| // fill in the resolution (english or universal) |
| |
| ReadResolution(tif, dib); |
| |
| // calculate the line + pitch (separate for scr & dest) |
| |
| tsize_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 |
| |
| 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) { |
| 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 happend according to the LogLuv specification |
| throw "Unable to handle PLANARCONFIG_SEPARATE LogLuv images"; |
| } |
| |
| } 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 && ((flags & TIFF_CMYK) == TIFF_CMYK)) { |
| FreeImage_GetICCProfile(dib)->flags |= FIICC_COLOR_IS_CMYK; |
| } |
| |
| // copy TIFF metadata (must be done after FreeImage_Allocate) |
| |
| ReadMetadata(tif, dib); |
| |
| return (FIBITMAP *)dib; |
| |
| } catch (const char *message) { |
| if(dib) FreeImage_Unload(dib); |
| if(message) FreeImage_OutputMessageProc(s_format_id, message); |
| return NULL; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static BOOL DLL_CALLCONV |
| Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) { |
| if ((dib != NULL) && (handle != NULL) && (data != NULL)) { |
| fi_TIFFIO *fio = (fi_TIFFIO*)data; |
| TIFF *out = fio->tif; |
| |
| int32 height; |
| int32 width; |
| uint32 rowsperstrip = (uint32) -1; |
| uint16 bitsperpixel; |
| uint16 bitspersample; |
| uint16 samplesperpixel; |
| uint16 photometric; |
| uint16 pitch; |
| int32 x, y; |
| |
| FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(dib); |
| |
| width = FreeImage_GetWidth(dib); |
| height = FreeImage_GetHeight(dib); |
| bitsperpixel = (uint16)FreeImage_GetBPP(dib); |
| |
| FIICCPROFILE *iccProfile = FreeImage_GetICCProfile(dib); |
| |
| 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; |
| 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; |
| photometric = PHOTOMETRIC_LOGLUV; |
| // the library converts to and from floating-point XYZ CIE values |
| TIFFSetField(out, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT); |
| // TIFFSetField(out, TIFFTAG_STONITS, 1.0); // assume unknown |
| } 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_ROWSPERSTRIP, TIFFDefaultStripSize(out, rowsperstrip)); |
| |
| // handle metrics |
| |
| WriteResolution(out, dib); |
| |
| // multi-paging |
| |
| if (page >= 0) { |
| char page_number[20]; |
| sprintf(page_number, "Page %d", page); |
| |
| TIFFSetField(out, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE); |
| TIFFSetField(out, TIFFTAG_PAGENUMBER, page); |
| TIFFSetField(out, TIFFTAG_PAGENAME, page_number); |
| } else { |
| TIFFSetField(out, TIFFTAG_SUBFILETYPE, 0); |
| } |
| |
| // 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 |
| |
| WriteCompression(out, bitspersample, samplesperpixel, photometric, flags); |
| |
| // metadata |
| |
| WriteMetadata(out, dib); |
| |
| |
| // read the DIB lines from bottom to top |
| // and save them in the TIF |
| // ------------------------------------- |
| |
| pitch = (uint16)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 (y = height - 1; y >= 0; y--) { |
| BYTE *bits = FreeImage_GetScanLine(dib, y); |
| |
| BYTE *p = bits, *b = buffer; |
| |
| for(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 (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 (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 (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; |
| } |
| } |
| |
| } else if(image_type == FIT_RGBF) { |
| // 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 (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 { |
| // special bitmap type (int, long, double, etc.) |
| |
| switch(bitsperpixel) { |
| case 16: |
| case 32: |
| case 48: |
| case 64: |
| case 128: |
| { |
| BYTE *buffer = (BYTE *)malloc(pitch * sizeof(BYTE)); |
| if(buffer == NULL) throw FI_MSG_ERROR_MEMORY; |
| |
| for (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; |
| } |
| } |
| |
| // write out the directory tag if we wrote a page other than -1 |
| |
| if (page >= 0) |
| TIFFWriteDirectory(out); |
| |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| // ========================================================== |
| // Init |
| // ========================================================== |
| |
| void DLL_CALLCONV |
| InitTIFF(Plugin *plugin, int format_id) { |
| s_format_id = format_id; |
| |
| 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; |
| } |