/*
Copyright (c) 2012-2017 Ronie Martinez (ronmarti18@gmail.com)
Copyright (c) 2013 Yuezhao Huang (huangezhao@gmail.com)
All rights reserved.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library 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 Lesser General Public License for more
details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301  USA
*/

#include "qpsdhandler.h"

#ifdef QT_DEBUG
#include <QDebug>
#endif

/* tristimulus reference: Adobe RGB (1998) Color Image Encoding
 * http://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
 * D65 0.9505, 1.0000, 1.0891
 * D50 0.9642, 1.000, 0.8249
 */

static const qreal refX = 0.9642;
static const qreal refY = 1.0000;
static const qreal refZ = 0.8249;
static const qreal e = 216 / 24389;
static const qreal k = 24389 / 27;
static const qreal gammaPsd = 563 / 256; //2.19921875

static QRgb xyzToRgb(qreal x, qreal y, qreal z, qreal alpha = 1.0)
{
    /* http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
     * Adobe RGB
     * D65
         2.0413690 -0.5649464 -0.3446944
        -0.9692660  1.8760108  0.0415560
         0.0134474 -0.1183897  1.0154096
     * D50
         1.9624274 -0.6105343 -0.3413404
        -0.9787684  1.9161415  0.0334540
         0.0286869 -0.1406752  1.3487655
     */
    /* D65 */
    /* qreal varR = (x * 2.0413690) + (y * -0.5649464) + (z * -0.3446944);
     * qreal varG = (x * -0.9692660) + (y * 1.8760108) + (z * 0.0415560);
     * qreal varB = (x * 0.0134474) + (y * -0.1183897) + (z * 1.0154096);
     */

    /* D50 */
    qreal varR = (x * 1.9624274) + (y * -0.6105343) + (z * -0.3413404);
    qreal varG = (x * -0.9787684) + (y * 1.9161415) + (z * 0.0334540);
    qreal varB = (x * 0.0286869) + (y * -0.1406752) + (z * 1.3487655);

    /* gamma companding */
    qreal red = qPow(varR, 1 / gammaPsd);
    qreal green = qPow(varG, 1 / gammaPsd);
    qreal blue = qPow(varB, 1 / gammaPsd);

    /* values sometimes fall outside the range 0-1 */
    red = (red < 0) ? 0 : ((red > 1) ? 1 : red);
    green = (green < 0) ? 0 : ((green > 1) ? 1 : green);
    blue = (blue < 0) ? 0 : ((blue > 1) ? 1 : blue);

    /* QColor::fromRgbF accepts value from 0-1, therefore, there's
     * no need to multiply it by 255 and use qRgb()
     */
    return QColor::fromRgbF(red, green, blue, alpha).rgba();
}

static QRgb labToRgb(qreal lightness, qreal a, qreal b, quint8 alpha = 255)
{
    /* ranges:
     * lightness = 0 to 100
     * a = -128 to 127
     * b = -128 to 127
     */

    lightness /= 2.55;
    a -= 128;
    b -= 128;
    qreal fy = (lightness + 16.0) / 116.0;
    qreal fx = (a / 500.0) + fy;
    qreal fz = fy - (b / 200.0);

    qreal varY = (lightness > k * e) ? pow(fy, 3) : lightness / k;
    qreal varX = (pow(fx, 3) > e) ? pow(fx, 3) : ((116.0 * fx) - 16.0) / k;
    qreal varZ = (pow(fz, 3) > e) ? pow(fz, 3) : ((116.0 * fz) - 16.0) / k;

    return xyzToRgb(refX * varX, refY * varY, refZ * varZ, alpha / 255);
}

/**
 * @brief QPsdHandler::isValidSignature Validates if signature is 8BPS.
 * @param signature Signature.
 * @return bool.
 */
bool QPsdHandler::isValidSignature(quint32 signature)
{
    return signature == 0x38425053; //'8BPS'
}

/**
 * @brief QPsdHandler::isValidVersion Validates if version is 1(PSD) or 2(PSB).
 * @param version Version.
 * @return bool.
 */
bool QPsdHandler::isValidVersion(quint16 version)
{
    switch (version) {
    case 1:
        //check if format is empty or it is not psd
        if (format().isEmpty() || format() != "psd")
            setFormat("psd");
        return true;
        break;
    case 2:
        //check if format is empty or it is not psb
        if (format().isEmpty() || format() != "psb")
            setFormat("psb");
        return true;
        break;
    default:
        return false;
        break;
    }
}

/**
 * @brief QPsdHandler::isChannelCountSupported Validates if the number of channels is supported.
 *      Supported range is 1 to 56.
 * @param channels Number of channels.
 * @return bool.
 */
bool QPsdHandler::isChannelCountSupported(quint16 channels)
{
//    return channels >= 1 && channels <= 56;
//    NOTE: found a sample file with channels > 56 and Photoshop can still read it
//    though the documentation says it should be within 1 to 56 channels
    return channels >= 1; //breaking "56-max rule"
}

/**
 * @brief QPsdHandler::isValidWidthOrHeight Validates if width or height is within valid range.
 *      Supported range is 1 to 30,000 for PSD and 1 to 300,000 for PSB.
 * @param version Version.
 * @param value Value of width or height.
 * @return bool.
 */
bool QPsdHandler::isValidWidthOrHeight(quint16 version, quint32 value)
{
    if (version == 1 && (value > 30000 || value == 0))
        return false;
    if (version == 2 && (value > 300000 || value == 0))
        return false;
    return true;
}

/**
 * @brief QPsdHandler::isSupportedDepth Validates if depth is supported.
 * Supported values are 1, 8, 16 and 32.
 * @param depth Depth.
 * @return bool.
 */
bool QPsdHandler::isSupportedDepth(quint16 depth)
{
    switch (depth) {
    case 1:
    case 8:
    case 16:
    case 32:
        return true;
        break;
    default:
        return false;
        break;
    }
}

/**
 * @brief QPsdHandler::isSupportedColorMode Validates if color mode is supported.
 *      Supported values are Bitmap = 0; Grayscale = 1; Indexed = 2; RGB = 3; CMYK = 4;
 *      Multichannel = 7; Duotone = 8; Lab = 9
 * @param colorMode
 * @return
 */
bool QPsdHandler::isSupportedColorMode(quint16 colorMode)
{
    switch (colorMode) {
    case 0:
    case 1:
    case 2:
    case 3:
    case 4:
    case 7:
    case 8:
    case 9:
        return true;
        break;
    default:
        return false;
        break;
    }
}

/**
 * @brief QPsdHandler::getColorData Extracts the color data from the input stream.
 * @param input QDataStream.
 * @return bool.
 */
QByteArray QPsdHandler::readColorData(QDataStream &input)
{
    quint32 length;
    QByteArray colorData;
    input >> length;
    if (length != 0) {
        colorData.resize(length);
        input.readRawData(colorData.data(), length);
    }
    return colorData;
}

/**
 * @brief QPsdHandler::skipImageResources Skips the image resources section.
 * @param input QDataStream.
 */
void QPsdHandler::skipImageResources(QDataStream &input)
{
    quint32 length;
    input >> length;
    input.skipRawData(length);
}

/**
 * @brief QPsdHandler::skipLayerAndMaskSection Skips the layer and mask section.
 *      The size of Layer and Mask Section is stored in 4 bytes for PSD files
 *      and 8 bytes for PSB files.
 * @param input QDataStream.
 */
void QPsdHandler::skipLayerAndMaskSection(QDataStream &input)
{
    if (format() == "psd") {
        quint32 layerAndMaskInfoLength;
        input >> layerAndMaskInfoLength;
        input.skipRawData(layerAndMaskInfoLength);
    } else if (format() == "psb") {
        quint64 layerAndMaskInfoLength;
        input >> layerAndMaskInfoLength;
        input.skipRawData(layerAndMaskInfoLength);
    }
}

QByteArray QPsdHandler::readImageData(QDataStream &input,
                                      QPsdHandler::Compression compression,
                                      quint64 size)
{
    QByteArray imageData;
    switch (compression) {
    case RAW:
    {
        imageData.resize(size);
        input.readRawData(imageData.data(), size);
    }
        break;
    case RLE: /*RLE COMPRESSED DATA*/
    {
        quint8 byte, count;
        /* Code based on PackBits implementation which is primarily used by
         * Photoshop for RLE encoding/decoding */
        while (!input.atEnd()) {
            input >> byte;
            if (byte > 128) {
                count = 256 - byte ;
                input >>  byte;
                for (quint8 i = 0; i <= count; ++i) {
                    imageData.append(byte);
                }
            } else if (byte < 128) {
                count = byte + 1;
                quint64 size = imageData.size();
                imageData.resize(size + count);
                input.readRawData(imageData.data() + size, count);
            }
        }
    }
        break;
    case ZIP_WITHOUT_PREDICTION:
        /*NOT IMPLEMENTED*/
        break;
    case ZIP_WITH_PREDICTION:
        /*NOT IMPLEMENTED*/
        break;
    }
    return imageData;
}

/**
 * @brief QPsdHandler::processBitmap Generates bitmap image from imageData.
 * @param imageData QByteArray.
 * @param width quint32.
 * @param height quint32.
 * @return QImage.
 */
QImage QPsdHandler::processBitmap(QByteArray& imageData, quint32 width, quint32 height)
{
#ifdef QT_DEBUG
    qDebug() << "Bitmap";
#endif
    QString head = QString("P4\n%1 %2\n").arg(width).arg(height);
    QByteArray buffer(head.toUtf8());
    buffer.append(imageData);
    return QImage::fromData(buffer);
}

QImage QPsdHandler::processGrayscale8(QByteArray& imageData, quint32 width, quint32 height)
{
#ifdef QT_DEBUG
    qDebug() << "8-bit Grayscale";
#endif
    QImage result(width, height, QImage::Format_RGB32);
    quint8 *data = (quint8*)imageData.constData();
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            *p = qRgb(*data, *data, *data);
            ++p; ++data;
        }
    }
    return result;
}

QImage QPsdHandler::processGrayscale8WithAlpha(QByteArray& imageData, quint32 width, quint32 height,
                                               quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "8-bit Grayscale with Alpha";
#endif
    QImage result(width, height, QImage::Format_ARGB32);
    quint8 *data = (quint8*)imageData.constData();
    quint8 *alpha = data + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            *p = qRgba(*data, *data, *data, *alpha);
            ++p; ++data; ++alpha;
        }
    }
    return result;
}

QImage QPsdHandler::processGrayscale16(QByteArray& imageData, quint32 width, quint32 height)
{
#ifdef QT_DEBUG
    qDebug() << "16-bit Grayscale";
#endif
    const qreal scale = (qPow(2, 8) -1 ) / (qPow(2, 16) - 1);
    quint16 data16;
    QImage result(width, height, QImage::Format_RGB32);
    quint8 *data8 = (quint8*)imageData.constData();
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            data16 = ((*data8 << 8) + *(data8 + 1)) * scale;
            *p = qRgb(data16, data16, data16);
            ++p; data8 += 2;
        }
    }
    return result;
}

QImage QPsdHandler::processGrayscale16WithAlpha(QByteArray& imageData, quint32 width, quint32 height,
                                                quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "8-bit Grayscale with Alpha";
#endif
    const qreal scale = (qPow(2, 8) -1 ) / (qPow(2, 16) - 1);
    quint16 data16, alpha16;
    QImage result(width, height, QImage::Format_ARGB32);
    quint8 *data8 = (quint8*)imageData.constData();
    quint8 *alpha8 = data8 + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            data16 = ((*data8 << 8) + *(data8 + 1)) * scale;
            alpha16 = ((*alpha8 << 8) + *(alpha8 + 1)) * scale;
            *p = qRgba(data16, data16, data16, alpha16);
            ++p; data8 += 2; alpha8 += 2;
        }
    }
    return result;
}

QImage QPsdHandler::processIndexed(QByteArray& colorData, QByteArray& imageData, quint32 width,
                                   quint32 height)
{
#ifdef QT_DEBUG
    qDebug() << "Indexed";
#endif
    QImage result(width, height, QImage::Format_Indexed8);
    int indexCount = colorData.size() / 3;
    quint8 *red = (quint8*)colorData.constData();
    quint8 *green = red + indexCount;
    quint8 *blue = green + indexCount;
    for (int i = 0; i < indexCount; ++i) {
        result.setColor(i, qRgb(*red, *green, *blue));
        ++red; ++green; ++blue;
    }
    quint8 *data = (quint8*)imageData.constData();
    for (quint32 i = 0; i < height; ++i) {
        for (quint32 j = 0; j < width; ++j) {
            result.setPixel(j, i, *data);
            ++data;
        }
    }
    return result;
}

QImage QPsdHandler::processRGB8(QByteArray& imageData, quint32 width, quint32 height,
                                quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "8-bit RGB";
#endif
    QImage result(width, height, QImage::Format_RGB32);
    quint8 *red = (quint8*)imageData.constData();
    quint8 *green = red + totalBytesPerChannel;
    quint8 *blue = green + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            *p = qRgb(*red, *green, *blue);
            ++p; ++red; ++green; ++blue;
        }
    }
    return result;
}

QImage QPsdHandler::processRGB16(QByteArray& imageData, quint32 width, quint32 height,
                                 quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "16-bit RGB";
#endif
    const qreal scale = (qPow(2, 8) - 1) / (qPow(2, 16) - 1);
    QImage result(width, height, QImage::Format_RGB32);
    quint16 red16, blue16, green16;
    quint8 *red8 = (quint8*)imageData.constData();
    quint8 *green8 = red8 + totalBytesPerChannel;
    quint8 *blue8 = green8 + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            red16 = ((*red8 << 8) + *(red8 + 1)) * scale;
            green16 = ((*green8 << 8) + *(green8 + 1)) * scale;
            blue16 = ((*blue8 << 8) + *(blue8 + 1)) * scale;
            *p = qRgb(red16, green16, blue16);
            ++p;  red8 += 2; green8 += 2; blue8 += 2;
        }
    }
    return result;
}

QImage QPsdHandler::processRGB8WithAlpha(QByteArray& imageData, quint32 width, quint32 height,
                                         quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "8-bit RGB with Alpha";
#endif
	QImage result(width, height, QImage::Format_ARGB32);
    quint8 *red = (quint8*)imageData.constData();
    quint8 *green = red + totalBytesPerChannel;
    quint8 *blue = green + totalBytesPerChannel;
    quint8 *alpha = blue + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            // Fix for blending image with white
            if(*alpha != 0) {
                quint8 r = *red;
                quint8 g = *green;
                quint8 b = *blue;
                quint8 a = *alpha;

                quint8 rFixed = (((r + a) - 255) * 255) / a;
                quint8 gFixed = (((g + a) - 255) * 255) / a;
                quint8 bFixed = (((b + a) - 255) * 255) / a;

                *p = qRgba(rFixed, gFixed, bFixed, a);
            } else {
                *p = qRgba(*red, *green, *blue, *alpha);
            }
            ++p; ++red; ++green; ++blue; ++alpha;
        }
    }
    return result;
}

QImage QPsdHandler::processRGB16WithAlpha(QByteArray& imageData, quint32 width, quint32 height,
                                          quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "16-bit RGB with Alpha";
#endif
//    FIXME: blending image with white (see QPsdHandler::processRGB8WithAlpha)
    const qreal scale = (qPow(2, 8) - 1) / (qPow(2, 16) - 1);
    QImage result(width, height, QImage::Format_ARGB32);
    quint16 red16, blue16, green16, alpha16;
    quint8 *red8 = (quint8*)imageData.constData();
    quint8 *green8 = red8 + totalBytesPerChannel;
    quint8 *blue8 = green8 + totalBytesPerChannel;
    quint8 *alpha8 = blue8 + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            red16 = ((*red8 << 8) + *(red8 + 1)) * scale;
            green16 = ((*green8 << 8) + *(green8 + 1)) * scale;
            blue16 = ((*blue8 << 8) + *(blue8 + 1)) * scale;
            alpha16 = ((*alpha8 << 8) + *(alpha8 + 1)) * scale;
            *p = qRgba((quint8)red16, (quint8)green16, (quint8)blue16, (quint8)alpha16);
            ++p;  red8 += 2; green8 += 2; blue8 += 2; alpha8 += 2;
        }
    }
    return result;
}

QImage QPsdHandler::processCMY8(QByteArray& imageData, quint32 width, quint32 height,
                                 quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "8-bit CMY";
#endif
    QImage result(width, height, QImage::Format_RGB32);
    quint8 *cyan = (quint8*)imageData.constData();
    quint8 *magenta = cyan + totalBytesPerChannel;
    quint8 *yellow = magenta + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            *p = QColor::fromCmyk(255 - *cyan, 255 - *magenta, 255 - *yellow, 0).rgba();
            ++p; ++cyan; ++magenta; ++yellow;;
        }
    }
    return result;
}

QImage QPsdHandler::processCMYK8(QByteArray& imageData, quint32 width, quint32 height,
                                 quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "8-bit CMYK";
#endif
    QImage result(width, height, QImage::Format_RGB32);
    quint8 *cyan = (quint8*)imageData.constData();
    quint8 *magenta = cyan + totalBytesPerChannel;
    quint8 *yellow = magenta + totalBytesPerChannel;
    quint8 *key = yellow + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            *p = QColor::fromCmyk(255 - *cyan, 255 - *magenta, 255 - *yellow, 255 - *key).rgba();
            ++p; ++cyan; ++magenta; ++yellow; ++key;
        }
    }
    return result;
}

QImage QPsdHandler::processCMYK8WithAlpha(QByteArray& imageData, quint32 width, quint32 height,
                                          quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "8-bit CMYK with Alpha";
#endif
    QImage result(width, height, QImage::Format_ARGB32);
    quint8 *cyan = (quint8*)imageData.constData();
    quint8 *magenta = cyan + totalBytesPerChannel;
    quint8 *yellow = magenta + totalBytesPerChannel;
    quint8 *key = yellow + totalBytesPerChannel;
    quint8 *alpha = key + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            *p = QColor::fromCmyk(255 - *cyan, 255 - *magenta, 255 - *yellow, 255 - *key,
                                  *alpha).rgba();
            ++p; ++alpha; ++cyan; ++magenta; ++yellow; ++key;
        }
    }
    return result;
}

QImage QPsdHandler::processCMYK16(QByteArray& imageData, quint32 width, quint32 height,
                                  quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "16-bit CMYK";
#endif
    const qreal scale = (qPow(2, 8) -1 ) / (qPow(2, 16) - 1);
    QImage result(width, height, QImage::Format_RGB32);
    quint16 cyan16, magenta16, yellow16, key16;
    quint8 *cyan8 = (quint8*)imageData.constData();
    quint8 *magenta8 = cyan8 + totalBytesPerChannel;
    quint8 *yellow8 = magenta8 + totalBytesPerChannel;
    quint8 *key8 = yellow8 + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            cyan16 = ((*cyan8 << 8) + *(cyan8 + 1)) * scale;
            magenta16 = ((*magenta8 << 8) + *(magenta8 + 1)) * scale;
            yellow16 = ((*yellow8 << 8) + *(yellow8 + 1)) * scale;
            key16 = ((*key8 << 8) + *(key8 + 1)) * scale;
            *p = QColor::fromCmyk(255 - (quint8)cyan16,
                                  255 - (quint8)magenta16,
                                  255 - (quint8)yellow16,
                                  255 - (quint8)key16).rgba();
            ++p;  cyan8 += 2; magenta8 += 2; yellow8 += 2; key8 += 2;
        }
    }
    return result;
}

QImage QPsdHandler::processCMYK16WithAlpha(QByteArray& imageData, quint32 width, quint32 height,
                                           quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "16-bit CMYK with Alpha";
#endif
    const qreal scale = (qPow(2, 8) -1 ) / (qPow(2, 16) - 1);
    QImage result(width, height, QImage::Format_ARGB32);
    quint16 cyan16, magenta16, yellow16, key16, alpha16;
    quint8 *cyan8 = (quint8*)imageData.constData();
    quint8 *magenta8 = cyan8 + totalBytesPerChannel;
    quint8 *yellow8 = magenta8 + totalBytesPerChannel;
    quint8 *key8 = yellow8 + totalBytesPerChannel;
    quint8 *alpha8 = key8 + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            cyan16 = ((*cyan8 << 8) + *(cyan8 + 1)) * scale;
            magenta16 = ((*magenta8 << 8) + *(magenta8 + 1)) * scale;
            yellow16 = ((*yellow8 << 8) + *(yellow8 + 1)) * scale;
            key16 = ((*key8 << 8) + *(key8 + 1)) * scale;
            alpha16 = ((*alpha8 << 8) + *(alpha8 + 1)) * scale;
            *p = QColor::fromCmyk(255 - (quint8)cyan16,
                                  255 - (quint8)magenta16,
                                  255 - (quint8)yellow16,
                                  255 - (quint8)key16,
                                  255 - (quint8)alpha16).rgba();
            ++p;  cyan8 += 2; magenta8 += 2; yellow8 += 2; key8 += 2, alpha8 += 2;
        }
    }
    return result;
}

QImage QPsdHandler::processDuotone(QByteArray& imageData, quint32 width, quint32 height)
{
#ifdef QT_DEBUG
    qDebug() << "Duotone";
#endif
    QImage result(width, height, QImage::Format_RGB32);
    quint8 *data = (quint8*)imageData.constData();
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            *p = qRgb(*data, *data, *data);
            ++p; ++data;
        }
    }
    return result;
}

QImage QPsdHandler::processLAB8(QByteArray& imageData, quint32 width, quint32 height,
                                quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "8-bit LAB";
#endif
    QImage result(width, height, QImage::Format_RGB32);
    quint8 *lightness = (quint8*)imageData.constData();
    quint8 *a = lightness + totalBytesPerChannel;
    quint8 *b = a + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            *p = labToRgb(*lightness, *a, *b);
            ++p; ++lightness; ++a; ++b;
        }
    }
    return result;
}

QImage QPsdHandler::processLAB8WithAlpha(QByteArray& imageData, quint32 width, quint32 height,
                                         quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "8-bit LAB with Alpha";
#endif
    QImage result(width, height, QImage::Format_ARGB32);
    quint8 *lightness = (quint8*)imageData.constData();
    quint8 *a = lightness + totalBytesPerChannel;
    quint8 *b = a + totalBytesPerChannel;
    quint8 *alpha = b + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            *p = labToRgb(*lightness, *a, *b, *alpha);
            ++p; ++alpha; ++lightness; ++a; ++b;
        }
    }
    return result;
}

QImage QPsdHandler::processLAB16(QByteArray& imageData, quint32 width, quint32 height,
                                 quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "16-bit LAB";
#endif
    const qreal scale = (qPow(2, 8) - 1 ) / (qPow(2, 16) - 1);
    quint16 lightness16, a16, b16;
    QImage result(width, height, QImage::Format_RGB32);
    quint8 *lightness8 = (quint8*)imageData.constData();
    quint8 *a8 = lightness8 + totalBytesPerChannel;
    quint8 *b8 = a8 + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            lightness16 = ((*lightness8 << 8) + *(lightness8 + 1)) * scale;
            a16 = ((*a8 << 8) + *(a8 + 1)) * scale;
            b16 = ((*b8 << 8) + *(b8 + 1)) * scale;
            *p = labToRgb(lightness16, a16, b16);
            ++p; lightness8 += 2; a8 += 2; b8 += 2;
        }
    }
    return result;
}

QImage QPsdHandler::processLAB16WithAlpha(QByteArray& imageData, quint32 width, quint32 height,
                                          quint64 totalBytesPerChannel)
{
#ifdef QT_DEBUG
    qDebug() << "8-bit LAB with Alpha";
#endif
    const qreal scale = (qPow(2, 8) - 1 ) / (qPow(2, 16) - 1);
    quint16 lightness16, a16, b16, alpha16;
    QImage result(width, height, QImage::Format_ARGB32);
    quint8 *lightness8 = (quint8*)imageData.constData();
    quint8 *a8 = lightness8 + totalBytesPerChannel;
    quint8 *b8 = a8 + totalBytesPerChannel;
    quint8 *alpha8 = b8 + totalBytesPerChannel;
    QRgb  *p, *end;
    for (quint32 y = 0; y < height; ++y) {
        p = (QRgb *)result.scanLine(y);
        end = p + width;
        while (p < end) {
            lightness16 = ((*lightness8 << 8) + *(lightness8 + 1)) * scale;
            a16 = ((*a8 << 8) + *(a8 + 1)) * scale;
            b16 = ((*b8 << 8) + *(b8 + 1)) * scale;
            alpha16 = ((*alpha8 << 8) + *(alpha8 + 1)) * scale;
            *p = labToRgb(lightness16, a16, b16, alpha16);
            ++p; lightness8 += 2; a8 += 2; b8 += 2; alpha8 += 2;
        }
    }
    return result;
}
