/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qtiffhandler.h"
#include <qvariant.h>
#include <qdebug.h>
#include <qimage.h>
#include <qglobal.h>
extern "C" {
#include "tiffio.h"
}

QT_BEGIN_NAMESPACE

tsize_t qtiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
{
    QIODevice* device = static_cast<QTiffHandler*>(fd)->device();
    return device->isReadable() ? device->read(static_cast<char *>(buf), size) : -1;
}

tsize_t qtiffWriteProc(thandle_t fd, tdata_t buf, tsize_t size)
{
    return static_cast<QTiffHandler*>(fd)->device()->write(static_cast<char *>(buf), size);
}

toff_t qtiffSeekProc(thandle_t fd, toff_t off, int whence)
{
    QIODevice *device = static_cast<QTiffHandler*>(fd)->device();
    switch (whence) {
    case SEEK_SET:
        device->seek(off);
        break;
    case SEEK_CUR:
        device->seek(device->pos() + off);
        break;
    case SEEK_END:
        device->seek(device->size() + off);
        break;
    }

    return device->pos();
}

int qtiffCloseProc(thandle_t /*fd*/)
{
    return 0;
}

toff_t qtiffSizeProc(thandle_t fd)
{
    return static_cast<QTiffHandler*>(fd)->device()->size();
}

int qtiffMapProc(thandle_t /*fd*/, tdata_t* /*pbase*/, toff_t* /*psize*/)
{
    return 0;
}

void qtiffUnmapProc(thandle_t /*fd*/, tdata_t /*base*/, toff_t /*size*/)
{
}

// for 32 bits images
inline void rotate_right_mirror_horizontal(QImage *const image)// rotate right->mirrored horizontal
{
    const int height = image->height();
    const int width = image->width();
    QImage generated(/* width = */ height, /* height = */ width, image->format());
    const uint32 *originalPixel = reinterpret_cast<const uint32*>(image->bits());
    uint32 *const generatedPixels = reinterpret_cast<uint32*>(generated.bits());
    for (int row=0; row < height; ++row) {
        for (int col=0; col < width; ++col) {
            int idx = col * height + row;
            generatedPixels[idx] = *originalPixel;
            ++originalPixel;
        }
    }
    *image = generated;
}

inline void rotate_right_mirror_vertical(QImage *const image) // rotate right->mirrored vertical
{
    const int height = image->height();
    const int width = image->width();
    QImage generated(/* width = */ height, /* height = */ width, image->format());
    const int lastCol = width - 1;
    const int lastRow = height - 1;
    const uint32 *pixel = reinterpret_cast<const uint32*>(image->bits());
    uint32 *const generatedBits = reinterpret_cast<uint32*>(generated.bits());
    for (int row=0; row < height; ++row) {
        for (int col=0; col < width; ++col) {
            int idx = (lastCol - col) * height + (lastRow - row);
            generatedBits[idx] = *pixel;
            ++pixel;
        }
    }
    *image = generated;
}

QTiffHandler::QTiffHandler() : QImageIOHandler()
{
    compression = NoCompression;
}

bool QTiffHandler::canRead() const
{
    if (canRead(device())) {
        setFormat("tiff");
        return true;
    }
    return false;
}

bool QTiffHandler::canRead(QIODevice *device)
{
    if (!device) {
        qWarning("QTiffHandler::canRead() called with no device");
        return false;
    }

    // current implementation uses TIFFClientOpen which needs to be
    // able to seek, so sequential devices are not supported
    QByteArray header = device->peek(4);
    return header == QByteArray::fromRawData("\x49\x49\x2A\x00", 4)
           || header == QByteArray::fromRawData("\x4D\x4D\x00\x2A", 4);
}

bool QTiffHandler::read(QImage *image)
{
    if (!canRead())
        return false;

    TIFF *const tiff = TIFFClientOpen("foo",
                                      "r",
                                      this,
                                      qtiffReadProc,
                                      qtiffWriteProc,
                                      qtiffSeekProc,
                                      qtiffCloseProc,
                                      qtiffSizeProc,
                                      qtiffMapProc,
                                      qtiffUnmapProc);

    if (!tiff) {
        return false;
    }
    uint32 width;
    uint32 height;
    uint16 photometric;
    if (!TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width)
        || !TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height)
        || !TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric)) {
        TIFFClose(tiff);
        return false;
    }

    if (photometric == PHOTOMETRIC_MINISBLACK || photometric == PHOTOMETRIC_MINISWHITE) {
        if (image->size() != QSize(width, height) || image->format() != QImage::Format_Mono)
            *image = QImage(width, height, QImage::Format_Mono);
        QVector<QRgb> colortable(2);
        if (photometric == PHOTOMETRIC_MINISBLACK) {
            colortable[0] = 0xff000000;
            colortable[1] = 0xffffffff;
        } else {
            colortable[0] = 0xffffffff;
            colortable[1] = 0xff000000;
        }
        image->setColorTable(colortable);

        if (!image->isNull()) {
            for (uint32 y=0; y<height; ++y) {
                if (TIFFReadScanline(tiff, image->scanLine(y), y, 0) < 0) {
                        TIFFClose(tiff);
                        return false;
                }
            }
        }
    } else {
        uint16 bitPerSample;
        if (!TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bitPerSample)) {
            TIFFClose(tiff);
            return false;
        }
        if (photometric == PHOTOMETRIC_PALETTE && bitPerSample == 8) {
            if (image->size() != QSize(width, height) || image->format() != QImage::Format_Indexed8)
                *image = QImage(width, height, QImage::Format_Indexed8);
            if (!image->isNull()) {
                // create the color table
                const uint16 tableSize = 256;
                uint16 *redTable = static_cast<uint16 *>(qMalloc(tableSize * sizeof(uint16)));
                uint16 *greenTable = static_cast<uint16 *>(qMalloc(tableSize * sizeof(uint16)));
                uint16 *blueTable = static_cast<uint16 *>(qMalloc(tableSize * sizeof(uint16)));
                if (!redTable || !greenTable || !blueTable) {
                    TIFFClose(tiff);
                    return false;
                }
                if (!TIFFGetField(tiff, TIFFTAG_COLORMAP, &redTable, &greenTable, &blueTable)) {
                    TIFFClose(tiff);
                    return false;
                }

                QVector<QRgb> qtColorTable(tableSize);
                for (int i = 0; i<tableSize ;++i) {
                    const int red = redTable[i] / 257;
                    const int green = greenTable[i] / 257;
                    const int blue = blueTable[i] / 257;
                    qtColorTable[i] = qRgb(red, green, blue);

                }

                image->setColorTable(qtColorTable);
                for (uint32 y=0; y<height; ++y) {
                    if (TIFFReadScanline(tiff, image->scanLine(y), y, 0) < 0) {
                        TIFFClose(tiff);
                        return false;
                    }
                }

                // free redTable, greenTable and greenTable done by libtiff
            }
        } else {
            if (image->size() != QSize(width, height) || image->format() != QImage::Format_ARGB32)
                *image = QImage(width, height, QImage::Format_ARGB32);
            if (!image->isNull()) {
                const int stopOnError = 1;
                if (TIFFReadRGBAImageOriented(tiff, width, height, reinterpret_cast<uint32 *>(image->bits()), ORIENTATION_TOPLEFT, stopOnError)) {
                    for (uint32 y=0; y<height; ++y)
                        convert32BitOrder(image->scanLine(y), width);
                } else {
                    TIFFClose(tiff);
                    return false;
                }
            }
        }
    }

    if (image->isNull()) {
        TIFFClose(tiff);
        return false;
    }

    float resX = 0;
    float resY = 0;
    uint16 resUnit = RESUNIT_NONE;
    if (TIFFGetField(tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit)
        && TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &resX)
        && TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &resY)) {

        switch(resUnit) {
        case RESUNIT_CENTIMETER:
            image->setDotsPerMeterX(qRound(resX * 100));
            image->setDotsPerMeterY(qRound(resY * 100));
            break;
        case RESUNIT_INCH:
            image->setDotsPerMeterX(qRound(resX * (100 / 2.54)));
            image->setDotsPerMeterY(qRound(resY * (100 / 2.54)));
            break;
        default:
            // do nothing as defaults have already
            // been set within the QImage class
            break;
        }
    }

    // rotate the image if the orientation is defined in the file
    uint16 orientationTag;
    if (TIFFGetField(tiff, TIFFTAG_ORIENTATION, &orientationTag)) {
        if (image->format() == QImage::Format_ARGB32) {
            // TIFFReadRGBAImageOriented() flip the image but does not rotate them
            switch (orientationTag) {
            case 5:
                rotate_right_mirror_horizontal(image);
                break;
            case 6:
                rotate_right_mirror_vertical(image);
                break;
            case 7:
                rotate_right_mirror_horizontal(image);
                break;
            case 8:
                rotate_right_mirror_vertical(image);
                break;
            }
        } else {
            switch (orientationTag) {
            case 1: // default orientation
                break;
            case 2: // mirror horizontal
                *image = image->mirrored(true, false);
                break;
            case 3: // mirror both
                *image = image->mirrored(true, true);
                break;
            case 4: // mirror vertical
                *image = image->mirrored(false, true);
                break;
            case 5: // rotate right mirror horizontal
                {
                    QMatrix transformation;
                    transformation.rotate(90);
                    *image = image->transformed(transformation);
                    *image = image->mirrored(true, false);
                    break;
                }
            case 6: // rotate right
                {
                    QMatrix transformation;
                    transformation.rotate(90);
                    *image = image->transformed(transformation);
                    break;
                }
            case 7: // rotate right, mirror vertical
                {
                    QMatrix transformation;
                    transformation.rotate(90);
                    *image = image->transformed(transformation);
                    *image = image->mirrored(false, true);
                    break;
                }
            case 8: // rotate left
                {
                    QMatrix transformation;
                    transformation.rotate(270);
                    *image = image->transformed(transformation);
                    break;
                }
            }
        }
    }


    TIFFClose(tiff);
    return true;
}

bool QTiffHandler::write(const QImage &image)
{
    if (!device()->isWritable())
        return false;

    TIFF *const tiff = TIFFClientOpen("foo",
                                      "w",
                                      this,
                                      qtiffReadProc,
                                      qtiffWriteProc,
                                      qtiffSeekProc,
                                      qtiffCloseProc,
                                      qtiffSizeProc,
                                      qtiffMapProc,
                                      qtiffUnmapProc);
    if (!tiff)
        return false;

    const int width = image.width();
    const int height = image.height();

    if (!TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, width)
        || !TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, height)
        || !TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) {
        TIFFClose(tiff);
        return false;
    }

    // set the resolution
    bool  resolutionSet = false;
    const int dotPerMeterX = image.dotsPerMeterX();
    const int dotPerMeterY = image.dotsPerMeterY();
    if ((dotPerMeterX % 100) == 0
        && (dotPerMeterY % 100) == 0) {
        resolutionSet = TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER)
                        && TIFFSetField(tiff, TIFFTAG_XRESOLUTION, dotPerMeterX/100.0)
                        && TIFFSetField(tiff, TIFFTAG_YRESOLUTION, dotPerMeterY/100.0);
    } else {
        resolutionSet = TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)
                        && TIFFSetField(tiff, TIFFTAG_XRESOLUTION, static_cast<float>(image.logicalDpiX()))
                        && TIFFSetField(tiff, TIFFTAG_YRESOLUTION, static_cast<float>(image.logicalDpiY()));
    }
    if (!resolutionSet) {
        TIFFClose(tiff);
        return false;
    }

    // configure image depth
    const QImage::Format format = image.format();
    if (format == QImage::Format_Mono || format == QImage::Format_MonoLSB) {
        uint16 photometric = PHOTOMETRIC_MINISBLACK;
        if (image.colorTable().at(0) == 0xffffffff)
            photometric = PHOTOMETRIC_MINISWHITE;
        if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, photometric)
            || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_CCITTRLE)) {
            TIFFClose(tiff);
            return false;
        }

        // try to do the conversion in chunks no greater than 16 MB
        int chunks = (width * height / (1024 * 1024 * 16)) + 1;
        int chunkHeight = qMax(height / chunks, 1);

        int y = 0;
        while (y < height) {
            QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_Mono);

            int chunkStart = y;
            int chunkEnd = y + chunk.height();
            while (y < chunkEnd) {
                if (TIFFWriteScanline(tiff, reinterpret_cast<uint32 *>(chunk.scanLine(y - chunkStart)), y) != 1) {
                    TIFFClose(tiff);
                    return false;
                }
                ++y;
            }
        }
        TIFFClose(tiff);
    } else if (format == QImage::Format_Indexed8) {
        if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE)
            || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_PACKBITS)
            || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)) {
            TIFFClose(tiff);
            return false;
        }
        //// write the color table
        // allocate the color tables
        uint16 *redTable = static_cast<uint16 *>(qMalloc(256 * sizeof(uint16)));
        uint16 *greenTable = static_cast<uint16 *>(qMalloc(256 * sizeof(uint16)));
        uint16 *blueTable = static_cast<uint16 *>(qMalloc(256 * sizeof(uint16)));
        if (!redTable || !greenTable || !blueTable) {
            TIFFClose(tiff);
            return false;
        }

        // set the color table
        const QVector<QRgb> colorTable = image.colorTable();

        const int tableSize = colorTable.size();
        Q_ASSERT(tableSize <= 256);
        for (int i = 0; i<tableSize; ++i) {
            const QRgb color = colorTable.at(i);
            redTable[i] = qRed(color) * 257;
            greenTable[i] = qGreen(color) * 257;
            blueTable[i] = qBlue(color) * 257;
        }

        const bool setColorTableSuccess = TIFFSetField(tiff, TIFFTAG_COLORMAP, redTable, greenTable, blueTable);

        qFree(redTable);
        qFree(greenTable);
        qFree(blueTable);

        if (!setColorTableSuccess) {
            TIFFClose(tiff);
            return false;
        }

        //// write the data
        // try to do the conversion in chunks no greater than 16 MB
        int chunks = (width * height/ (1024 * 1024 * 16)) + 1;
        int chunkHeight = qMax(height / chunks, 1);

        int y = 0;
        while (y < height) {
            QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y));

            int chunkStart = y;
            int chunkEnd = y + chunk.height();
            while (y < chunkEnd) {
                if (TIFFWriteScanline(tiff, reinterpret_cast<uint32 *>(chunk.scanLine(y - chunkStart)), y) != 1) {
                    TIFFClose(tiff);
                    return false;
                }
                ++y;
            }
        }
        TIFFClose(tiff);

    } else {
        if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
            || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
            || !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4)
            || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)) {
            TIFFClose(tiff);
            return false;
        }
        // try to do the ARGB32 conversion in chunks no greater than 16 MB
        int chunks = (width * height * 4 / (1024 * 1024 * 16)) + 1;
        int chunkHeight = qMax(height / chunks, 1);

        int y = 0;
        while (y < height) {
            QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_ARGB32);

            int chunkStart = y;
            int chunkEnd = y + chunk.height();
            while (y < chunkEnd) {
                if (QSysInfo::ByteOrder == QSysInfo::LittleEndian)
                    convert32BitOrder(chunk.scanLine(y - chunkStart), width);
                else
                    convert32BitOrderBigEndian(chunk.scanLine(y - chunkStart), width);

                if (TIFFWriteScanline(tiff, reinterpret_cast<uint32 *>(chunk.scanLine(y - chunkStart)), y) != 1) {
                    TIFFClose(tiff);
                    return false;
                }
                ++y;
            }
        }
        TIFFClose(tiff);
    }

    return true;
}

QByteArray QTiffHandler::name() const
{
    return "tiff";
}

QVariant QTiffHandler::option(ImageOption option) const
{
    if (option == Size && canRead()) {
        QSize imageSize;
        qint64 pos = device()->pos();
        TIFF *tiff = TIFFClientOpen("foo",
                                    "r",
                                    const_cast<QTiffHandler*>(this),
                                    qtiffReadProc,
                                    qtiffWriteProc,
                                    qtiffSeekProc,
                                    qtiffCloseProc,
                                    qtiffSizeProc,
                                    qtiffMapProc,
                                    qtiffUnmapProc);

        if (tiff) {
            uint32 width = 0;
            uint32 height = 0;
            TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width);
            TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height);
            imageSize = QSize(width, height);
        }
        device()->seek(pos);
        if (imageSize.isValid())
            return imageSize;
    } else if (option == CompressionRatio) {
        return compression;
    } else if (option == ImageFormat) {
        return QImage::Format_ARGB32;
    }
    return QVariant();
}

void QTiffHandler::setOption(ImageOption option, const QVariant &value)
{
    if (option == CompressionRatio && value.type() == QVariant::Int)
        compression = value.toInt();
}

bool QTiffHandler::supportsOption(ImageOption option) const
{
    return option == CompressionRatio
            || option == Size
            || option == ImageFormat;
}

void QTiffHandler::convert32BitOrder(void *buffer, int width)
{
    uint32 *target = reinterpret_cast<uint32 *>(buffer);
    for (int32 x=0; x<width; ++x) {
        uint32 p = target[x];
        // convert between ARGB and ABGR
        target[x] = (p & 0xff000000)
                    | ((p & 0x00ff0000) >> 16)
                    | (p & 0x0000ff00)
                    | ((p & 0x000000ff) << 16);
    }
}

void QTiffHandler::convert32BitOrderBigEndian(void *buffer, int width)
{
    uint32 *target = reinterpret_cast<uint32 *>(buffer);
    for (int32 x=0; x<width; ++x) {
        uint32 p = target[x];
        target[x] = (p & 0xff000000) >> 24
                    | (p & 0x00ff0000) << 8
                    | (p & 0x0000ff00) << 8
                    | (p & 0x000000ff) << 8;
    }
}

QT_END_NAMESPACE
