/* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-06-17 * Description : A TIFF IO file for DImg framework * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2006-2008 by Gilles Caulier * * Specifications & references: * - TIFF 6.0 : http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf * - TIFF/EP : http://www.map.tu.chiba-u.ac.jp/IEC/100/TA2/recdoc/N4378.pdf * - TIFF/Tags : http://www.awaresystems.be/imaging/tiff/tifftags.html * - DNG : http://www.adobe.com/products/dng/pdfs/dng_spec.pdf * * Others Linux Tiff Loader implementation using libtiff: * - http://websvn.kde.org/trunk/koffice/filters/krita/tiff/kis_tiff_converter.cc * - http://artis.inrialpes.fr/Software/TiffIO/ * - http://cvs.graphicsmagick.org/cgi-bin/cvsweb.cgi/GraphicsMagick/coders/tiff.c * - http://freeimage.cvs.sourceforge.net/freeimage/FreeImage/Source/FreeImage/PluginTIFF.cpp * - http://freeimage.cvs.sourceforge.net/freeimage/FreeImage/Source/Metadata/XTIFF.cpp * - https://subversion.imagemagick.org/subversion/ImageMagick/trunk/coders/tiff.c * * Test images repository: * - http://www.remotesensing.org/libtiff/images.html * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ // This line must be commented to prevent any latency time // when we use threaded image loader interface for each image // files io. Uncomment this line only for debugging. //#define ENABLE_DEBUG_MESSAGES // C ANSI includes. extern "C" { #include } // C++ includes. #include // TQt includes. #include // Local includes. #include "ddebug.h" #include "dimg.h" #include "dimgloaderobserver.h" #include "dmetadata.h" #include "tiffloader.h" namespace Digikam { // To manage Errors/Warnings handling provide by libtiff void TIFFLoader::dimg_tiff_warning(const char* module, const char* format, va_list warnings) { #ifdef ENABLE_DEBUG_MESSAGES char message[4096]; vsnprintf(message, 4096, format, warnings); DDebug() << module << "::" << message << endl; #else Q_UNUSED(module); Q_UNUSED(format); Q_UNUSED(warnings); #endif } void TIFFLoader::dimg_tiff_error(const char* module, const char* format, va_list errors) { #ifdef ENABLE_DEBUG_MESSAGES char message[4096]; vsnprintf(message, 4096, format, errors); DDebug() << module << "::" << message << endl; #else Q_UNUSED(module); Q_UNUSED(format); Q_UNUSED(errors); #endif } TIFFLoader::TIFFLoader(DImg* image) : DImgLoader(image) { m_hasAlpha = false; m_sixteenBit = false; } bool TIFFLoader::load(const TQString& filePath, DImgLoaderObserver *observer) { readMetadata(filePath, DImg::TIFF); // ------------------------------------------------------------------- // TIFF error handling. If an errors/warnings occurs during reading, // libtiff will call these methods TIFFSetWarningHandler(dimg_tiff_warning); TIFFSetErrorHandler(dimg_tiff_error); // ------------------------------------------------------------------- // Open the file TIFF* tif = TIFFOpen(TQFile::encodeName(filePath), "r"); if (!tif) { DDebug() << k_funcinfo << "Cannot open image file." << endl; return false; } #ifdef ENABLE_DEBUG_MESSAGES TIFFPrintDirectory(tif, stdout, 0); #endif // ------------------------------------------------------------------- // Get image information. uint32 w, h; uint16 bits_per_sample; uint16 samples_per_pixel; uint16 photometric; uint32 rows_per_strip; tsize_t strip_size; tstrip_t num_of_strips; TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGEWIDTH, &w); TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGELENGTH, &h); TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel); if (TIFFGetFieldDefaulted(tif, TIFFTAG_ROWSPERSTRIP, &rows_per_strip) == 0 || rows_per_strip == 0 || rows_per_strip == (unsigned int)-1) { DWarning() << "TIFF loader: Cannot handle non-stripped images. Loading file " << filePath << endl; TIFFClose(tif); return false; } if (bits_per_sample == 0 || samples_per_pixel == 0 || rows_per_strip == 0 || rows_per_strip > h) { DWarning() << "TIFF loader: Encountered invalid value 0 in image." << " bits_per_sample " << bits_per_sample << " samples_per_pixel " << samples_per_pixel << " rows_per_strip " << rows_per_strip << " Loading file " << filePath << endl; TIFFClose(tif); return false; } // TODO: check others TIFF color-spaces here. Actually, only RGB and MINISBLACK // have been tested. // Complete description of TIFFTAG_PHOTOMETRIC tag can be found at this url: // http://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric); if (photometric != PHOTOMETRIC_RGB && photometric != PHOTOMETRIC_MINISBLACK) { DWarning() << "Can't handle image without RGB color-space: " << photometric << endl; TIFFClose(tif); return false; } if (samples_per_pixel == 4) m_hasAlpha = true; else m_hasAlpha = false; if (bits_per_sample == 16) m_sixteenBit = true; else m_sixteenBit = false; // ------------------------------------------------------------------- // Read image ICC profile TQMap& metaData = imageMetaData(); uchar *profile_data=0; uint32 profile_size; if (TIFFGetField (tif, TIFFTAG_ICCPROFILE, &profile_size, &profile_data)) { TQByteArray profile_rawdata(profile_size); memcpy(profile_rawdata.data(), profile_data, profile_size); metaData.insert(DImg::ICC, profile_rawdata); } else { // If ICC profile is null, check Exif metadata. checkExifWorkingColorSpace(); } // ------------------------------------------------------------------- // Get image data. if (observer) observer->progressInfo(m_image, 0.1); uchar* data = 0; strip_size = TIFFStripSize(tif); num_of_strips = TIFFNumberOfStrips(tif); if (bits_per_sample == 16) // 16 bits image. { data = new uchar[w*h*8]; uchar* strip = new uchar[strip_size]; long offset = 0; long bytesRead = 0; uint checkpoint = 0; for (tstrip_t st=0; st < num_of_strips; st++) { if (observer && st == checkpoint) { checkpoint += granularity(observer, num_of_strips, 0.8); if (!observer->continueQuery(m_image)) { delete [] data; delete [] strip; TIFFClose(tif); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)st)/((float)num_of_strips) ))); } bytesRead = TIFFReadEncodedStrip(tif, st, strip, strip_size); if (bytesRead == -1) { DDebug() << k_funcinfo << "Failed to read strip" << endl; delete [] data; TIFFClose(tif); return false; } ushort *stripPtr = (ushort*)(strip); ushort *dataPtr = (ushort*)(data + offset); ushort *p; // tiff data is read as BGR or ABGR or Greyscale if (samples_per_pixel == 3) { for (int i=0; i < bytesRead/6; i++) { p = dataPtr; // See B.K.O #148037 : take a care about byte order with Motorola computers. if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC { p[3] = *stripPtr++; p[0] = *stripPtr++; p[1] = *stripPtr++; p[2] = 0xFFFF; } else { p[2] = *stripPtr++; p[1] = *stripPtr++; p[0] = *stripPtr++; p[3] = 0xFFFF; } dataPtr += 4; } offset += bytesRead/6 * 8; } else if (samples_per_pixel == 1) // See B.K.O #148400: Greyscale pictures only have _one_ sample per pixel { for (int i=0; i < bytesRead/2; i++) { // We have to read two bytes for one pixel p = dataPtr; // See B.K.O #148037 : take a care about byte order with Motorola computers. if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC { p[3] = 0xFFFF; p[0] = *stripPtr; p[1] = *stripPtr; p[2] = *stripPtr++; } else { p[0] = *stripPtr; // RGB have to be set to the _same_ value p[1] = *stripPtr; p[2] = *stripPtr++; p[3] = 0xFFFF; // set alpha to 100% } dataPtr += 4; } offset += bytesRead*4; // The _byte_offset in the data array is, of course, four times bytesRead } else // ABGR { for (int i=0; i < bytesRead/8; i++) { p = dataPtr; // See B.K.O #148037 : take a care about byte order with Motorola computers. if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC { p[3] = *stripPtr++; p[0] = *stripPtr++; p[1] = *stripPtr++; p[2] = *stripPtr++; } else { p[2] = *stripPtr++; p[1] = *stripPtr++; p[0] = *stripPtr++; p[3] = *stripPtr++; } dataPtr += 4; } offset += bytesRead; } } delete [] strip; } else // Non 16 bits images ==> get it on BGRA 8 bits. { data = new uchar[w*h*4]; uchar* strip = new uchar[w*rows_per_strip*4]; long offset = 0; long pixelsRead = 0; // this is inspired by TIFFReadRGBAStrip, tif_getimage.c char emsg[1024] = ""; TIFFRGBAImage img; uint32 rows_to_read; uint checkpoint = 0; // test whether libtiff can read format and initiate reading if (!TIFFRGBAImageOK(tif, emsg) || !TIFFRGBAImageBegin(&img, tif, 0, emsg)) { DDebug() << k_funcinfo << "Failed to set up RGBA reading of image, filename " << TIFFFileName(tif) << " error message from Libtiff: " << emsg << endl; delete [] data; delete [] strip; TIFFClose(tif); return false; } img.req_orientation = ORIENTATION_TOPLEFT; // read strips from image: read rows_per_strip, so always start at beginning of a strip for (uint row = 0; row < h; row += rows_per_strip) { if (observer && row >= checkpoint) { checkpoint += granularity(observer, h, 0.8); if (!observer->continueQuery(m_image)) { delete [] data; delete [] strip; TIFFClose(tif); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)row)/((float)h) ))); } img.row_offset = row; img.col_offset = 0; if( row + rows_per_strip > img.height ) rows_to_read = img.height - row; else rows_to_read = rows_per_strip; // Read data if (TIFFRGBAImageGet(&img, (uint32*)strip, img.width, rows_to_read ) == -1) { DDebug() << k_funcinfo << "Failed to read image data" << endl; delete [] data; delete [] strip; TIFFClose(tif); return false; } pixelsRead = rows_to_read * img.width; uchar *stripPtr = (uchar*)(strip); uchar *dataPtr = (uchar*)(data + offset); uchar *p; // Reverse red and blue for (int i=0; i < pixelsRead; i++) { p = dataPtr; // See B.K.O #148037 : take a care about byte order with Motorola computers. if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC { p[3] = *stripPtr++; p[0] = *stripPtr++; p[1] = *stripPtr++; p[2] = *stripPtr++; } else { p[2] = *stripPtr++; p[1] = *stripPtr++; p[0] = *stripPtr++; p[3] = *stripPtr++; } dataPtr += 4; } offset += pixelsRead * 4; } TIFFRGBAImageEnd(&img); delete [] strip; } // ------------------------------------------------------------------- TIFFClose(tif); if (observer) observer->progressInfo(m_image, 1.0); imageWidth() = w; imageHeight() = h; imageData() = data; imageSetAttribute("format", "TIFF"); return true; } bool TIFFLoader::save(const TQString& filePath, DImgLoaderObserver *observer) { TIFF *tif; uchar *data; uint32 w, h; w = imageWidth(); h = imageHeight(); data = imageData(); // ------------------------------------------------------------------- // TIFF error handling. If an errors/warnings occurs during reading, // libtiff will call these methods TIFFSetWarningHandler(dimg_tiff_warning); TIFFSetErrorHandler(dimg_tiff_error); // ------------------------------------------------------------------- // Open the file tif = TIFFOpen(TQFile::encodeName(filePath), "w"); if (!tif) { DDebug() << k_funcinfo << "Cannot open target image file." << endl; return false; } // ------------------------------------------------------------------- // Set image properties TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, w); TIFFSetField(tif, TIFFTAG_IMAGELENGTH, h); TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE); // Image must be compressed using deflate algorithm ? TQVariant compressAttr = imageGetAttribute("compress"); bool compress = compressAttr.isValid() ? compressAttr.toBool() : false; if (compress) { TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_ADOBE_DEFLATE); TIFFSetField(tif, TIFFTAG_ZIPQUALITY, 9); // NOTE : this tag values aren't defined in libtiff 3.6.1. '2' is PREDICTOR_HORIZONTAL. // Use horizontal differencing for images which are // likely to be continuous tone. The TIFF spec says that this // usually leads to better compression. // See this url for more details: // http://www.awaresystems.be/imaging/tiff/tifftags/predictor.html TIFFSetField(tif, TIFFTAG_PREDICTOR, 2); } else TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); // Image has an alpha channel ? if (imageHasAlpha()) { uint16 sampleinfo[1] = { EXTRASAMPLE_UNASSALPHA }; TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 4); TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, EXTRASAMPLE_ASSOCALPHA, sampleinfo); } else { TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); } TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, (uint16)imageBitsDepth()); TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, 0)); // ------------------------------------------------------------------- // Write meta-data Tags contents. DMetadata metaData; metaData.setExif(m_image->getExif()); metaData.setIptc(m_image->getIptc()); // Standard IPTC tag (available with libtiff 3.6.1) TQByteArray ba = metaData.getIptc(true); if (!ba.isEmpty()) { #if defined(TIFFTAG_PHOTOSHOP) TIFFSetField (tif, TIFFTAG_PHOTOSHOP, (uint32)ba.size(), (uchar *)ba.data()); #endif } // Standard XMP tag (available with libtiff 3.6.1) #if defined(TIFFTAG_XMLPACKET) tiffSetExifDataTag(tif, TIFFTAG_XMLPACKET, &metaData, "Exif.Image.XMLPacket"); #endif // Standard Exif Ascii tags (available with libtiff 3.6.1) tiffSetExifAsciiTag(tif, TIFFTAG_DOCUMENTNAME, &metaData, "Exif.Image.DocumentName"); tiffSetExifAsciiTag(tif, TIFFTAG_IMAGEDESCRIPTION, &metaData, "Exif.Image.ImageDescription"); tiffSetExifAsciiTag(tif, TIFFTAG_MAKE, &metaData, "Exif.Image.Make"); tiffSetExifAsciiTag(tif, TIFFTAG_MODEL, &metaData, "Exif.Image.Model"); tiffSetExifAsciiTag(tif, TIFFTAG_DATETIME, &metaData, "Exif.Image.DateTime"); tiffSetExifAsciiTag(tif, TIFFTAG_ARTIST, &metaData, "Exif.Image.Artist"); tiffSetExifAsciiTag(tif, TIFFTAG_COPYRIGHT, &metaData, "Exif.Image.Copyright"); TQString soft = metaData.getExifTagString("Exif.Image.Software"); TQString libtiffver(TIFFLIB_VERSION_STR); libtiffver.replace('\n', ' '); soft.append(TQString(" ( %1 )").arg(libtiffver)); TIFFSetField(tif, TIFFTAG_SOFTWARE, (const char*)soft.ascii()); // NOTE: All others Exif tags will be written by Exiv2 (<= 0.18) // ------------------------------------------------------------------- // Write ICC profil. TQByteArray profile_rawdata = m_image->getICCProfil(); if (!profile_rawdata.isEmpty()) { #if defined(TIFFTAG_ICCPROFILE) TIFFSetField(tif, TIFFTAG_ICCPROFILE, (uint32)profile_rawdata.size(), (uchar *)profile_rawdata.data()); #endif } // ------------------------------------------------------------------- // Write full image data in tiff directory IFD0 if (observer) observer->progressInfo(m_image, 0.1); uint8 *buf=0; uchar *pixel; double alpha_factor; uint32 x, y; uint8 r8, g8, b8, a8=0; uint16 r16, g16, b16, a16=0; int i=0; buf = (uint8 *) _TIFFmalloc(TIFFScanlineSize(tif)); if (!buf) { DDebug() << k_funcinfo << "Cannot allocate memory buffer for main image." << endl; TIFFClose(tif); return false; } uint checkpoint = 0; for (y = 0; y < h; y++) { if (observer && y == checkpoint) { checkpoint += granularity(observer, h, 0.8); if (!observer->continueQuery(m_image)) { _TIFFfree(buf); TIFFClose(tif); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)y)/((float)h) ))); } i = 0; for (x = 0; x < w; x++) { pixel = &data[((y * w) + x) * imageBytesDepth()]; if ( imageSixteenBit() ) // 16 bits image. { b16 = (uint16)(pixel[0]+256*pixel[1]); g16 = (uint16)(pixel[2]+256*pixel[3]); r16 = (uint16)(pixel[4]+256*pixel[5]); if (imageHasAlpha()) { // TIFF makes you pre-mutiply the rgb components by alpha a16 = (uint16)(pixel[6]+256*pixel[7]); alpha_factor = ((double)a16 / 65535.0); r16 = (uint16)(r16*alpha_factor); g16 = (uint16)(g16*alpha_factor); b16 = (uint16)(b16*alpha_factor); } // This might be endian dependent buf[i++] = (uint8)(r16); buf[i++] = (uint8)(r16 >> 8); buf[i++] = (uint8)(g16); buf[i++] = (uint8)(g16 >> 8); buf[i++] = (uint8)(b16); buf[i++] = (uint8)(b16 >> 8); if (imageHasAlpha()) { buf[i++] = (uint8)(a16) ; buf[i++] = (uint8)(a16 >> 8) ; } } else // 8 bits image. { b8 = (uint8)pixel[0]; g8 = (uint8)pixel[1]; r8 = (uint8)pixel[2]; if (imageHasAlpha()) { // TIFF makes you pre-mutiply the rgb components by alpha a8 = (uint8)(pixel[3]); alpha_factor = ((double)a8 / 255.0); r8 = (uint8)(r8*alpha_factor); g8 = (uint8)(g8*alpha_factor); b8 = (uint8)(b8*alpha_factor); } // This might be endian dependent buf[i++] = r8; buf[i++] = g8; buf[i++] = b8; if (imageHasAlpha()) buf[i++] = a8; } } if (!TIFFWriteScanline(tif, buf, y, 0)) { DDebug() << k_funcinfo << "Cannot write main image to target file." << endl; _TIFFfree(buf); TIFFClose(tif); return false; } } _TIFFfree(buf); TIFFWriteDirectory(tif); // ------------------------------------------------------------------- // Write thumbnail in tiff directory IFD1 TQImage thumb = m_image->smoothScale(160, 120, TQSize::ScaleMin).copyTQImage(); TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, (uint32)thumb.width()); TIFFSetField(tif, TIFFTAG_IMAGELENGTH, (uint32)thumb.height()); TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE); TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, 0)); uchar *pixelThumb; uchar *dataThumb = thumb.bits(); uint8 *bufThumb = (uint8 *) _TIFFmalloc(TIFFScanlineSize(tif)); if (!bufThumb) { DDebug() << k_funcinfo << "Cannot allocate memory buffer for thumbnail." << endl; TIFFClose(tif); return false; } for (y = 0 ; y < uint32(thumb.height()) ; y++) { i = 0; for (x = 0 ; x < uint32(thumb.width()) ; x++) { pixelThumb = &dataThumb[((y * thumb.width()) + x) * 4]; // This might be endian dependent bufThumb[i++] = (uint8)pixelThumb[2]; bufThumb[i++] = (uint8)pixelThumb[1]; bufThumb[i++] = (uint8)pixelThumb[0]; } if (!TIFFWriteScanline(tif, bufThumb, y, 0)) { DDebug() << k_funcinfo << "Cannot write thumbnail to target file." << endl; _TIFFfree(bufThumb); TIFFClose(tif); return false; } } _TIFFfree(bufThumb); TIFFClose(tif); // ------------------------------------------------------------------- if (observer) observer->progressInfo(m_image, 1.0); imageSetAttribute("savedformat", "TIFF"); saveMetadata(filePath); return true; } bool TIFFLoader::hasAlpha() const { return m_hasAlpha; } bool TIFFLoader::sixteenBit() const { return m_sixteenBit; } void TIFFLoader::tiffSetExifAsciiTag(TIFF* tif, ttag_t tiffTag, const DMetadata *metaData, const char* exifTagName) { TQByteArray tag = metaData->getExifTagData(exifTagName); if (!tag.isEmpty()) { TQCString str(tag.data(), tag.size()); TIFFSetField(tif, tiffTag, (const char*)str); } } void TIFFLoader::tiffSetExifDataTag(TIFF* tif, ttag_t tiffTag, const DMetadata *metaData, const char* exifTagName) { TQByteArray tag = metaData->getExifTagData(exifTagName); if (!tag.isEmpty()) { TIFFSetField (tif, tiffTag, (uint32)tag.size(), (char *)tag.data()); } } } // NameSpace Digikam