/* ============================================================
 *
 * 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 <renchi@pooh.tam.uiuc.edu>
 * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * 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 <tiffvers.h>
}

// C++ includes.

#include <cstdio>

// TQt includes.

#include <tqfile.h>

// 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<int, TQByteArray>& 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 )").tqarg(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
