/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtTest/QtTest>
#include <QtCore/qt_windows.h>
#include <QtCore/QFileInfo>
#include <QtGui/QPainter>
#include <QtGui/QPixmap>
#include <QtGui/QImage>
#include <QtWinExtras/QtWin>

class tst_QPixmap : public QObject
{
    Q_OBJECT

public:
    tst_QPixmap() : m_dataDirectory(QFINDTESTDATA("data")) {}

private slots:
    void initTestCase();

    void toHBITMAP_data();
    void toHBITMAP();
    void fromHBITMAP_data();
    void fromHBITMAP();

    void toHICON_data();
    void toHICON();
    void fromHICON_data();
    void fromHICON();

    void imageConversion();

private:
    const QString m_dataDirectory;
};

void tst_QPixmap::initTestCase()
{
    QVERIFY(!m_dataDirectory.isEmpty());
}

void tst_QPixmap::toHBITMAP_data()
{
    QTest::addColumn<int>("red");
    QTest::addColumn<int>("green");
    QTest::addColumn<int>("blue");

    QTest::newRow("red")   << 255 << 0 << 0;
    QTest::newRow("green") << 0 << 255 << 0;
    QTest::newRow("blue")  << 0 << 0 << 255;
}

void tst_QPixmap::toHBITMAP()
{
    QFETCH(int, red);
    QFETCH(int, green);
    QFETCH(int, blue);

    QPixmap pm(100, 100);
    pm.fill(QColor(red, green, blue));

    const HBITMAP bitmap = QtWin::toHBITMAP(pm);

    QVERIFY(bitmap != nullptr);

    // Verify size
    BITMAP bitmapInfo;
    memset(&bitmapInfo, 0, sizeof(BITMAP));

    QVERIFY(GetObject(bitmap, sizeof(BITMAP), &bitmapInfo));

    QCOMPARE(LONG(100), bitmapInfo.bmWidth);
    QCOMPARE(LONG(100), bitmapInfo.bmHeight);

    const HDC displayDc = GetDC(nullptr);
    const HDC bitmapDc = CreateCompatibleDC(displayDc);

    const auto nullBitmap = static_cast<HBITMAP>(SelectObject(bitmapDc, bitmap));

    const COLORREF pixel = GetPixel(bitmapDc, 0, 0);
    QCOMPARE(int(GetRValue(pixel)), red);
    QCOMPARE(int(GetGValue(pixel)), green);
    QCOMPARE(int(GetBValue(pixel)), blue);

    // Clean up
    SelectObject(bitmapDc, nullBitmap);
    DeleteObject(bitmap);
    DeleteDC(bitmapDc);
    ReleaseDC(nullptr, displayDc);
}

void tst_QPixmap::fromHBITMAP_data()
{
    toHBITMAP_data();
}

void tst_QPixmap::fromHBITMAP()
{
    QFETCH(int, red);
    QFETCH(int, green);
    QFETCH(int, blue);

    const HDC displayDc = GetDC(nullptr);
    const HDC bitmapDc = CreateCompatibleDC(displayDc);
    const HBITMAP bitmap = CreateCompatibleBitmap(displayDc, 100, 100);
    SelectObject(bitmapDc, bitmap);

    SelectObject(bitmapDc, GetStockObject(NULL_PEN));
    const HGDIOBJ oldBrush = SelectObject(bitmapDc, CreateSolidBrush(RGB(red, green, blue)));
    Rectangle(bitmapDc, 0, 0, 100, 100);

    const QPixmap pixmap = QtWin::fromHBITMAP(bitmap);
    QCOMPARE(pixmap.width(), 100);
    QCOMPARE(pixmap.height(), 100);

    const QImage image = pixmap.toImage();
    const QRgb pixel = image.pixel(0, 0);
    QCOMPARE(qRed(pixel), red);
    QCOMPARE(qGreen(pixel), green);
    QCOMPARE(qBlue(pixel), blue);

    DeleteObject(SelectObject(bitmapDc, oldBrush));
    DeleteObject(SelectObject(bitmapDc, bitmap));
    DeleteDC(bitmapDc);
    ReleaseDC(nullptr, displayDc);
}

static bool compareImages(const QImage &actual, const QImage &expected,
                          QByteArray *errorMessage)
{
    if (actual.size() != expected.size()) {
        QString s;
        QDebug(&s) << "Size mismatch, actual: " << actual.size() << " expected: " << expected.size();
        *errorMessage = s.toLocal8Bit();
        return false;
    }
    if (actual.format() != expected.format()) {
        *errorMessage = QByteArrayLiteral("Format mismatch, actual: ")
            + QByteArray::number(actual.format())
            + QByteArrayLiteral(", expected: ") + QByteArray::number(expected.format());
        return false;
    }

    static const int fuzz = 1;

    for (int y = 0; y < actual.height(); ++y) {
        for (int x = 0; x < expected.width(); ++x) {
            const QRgb actualRgb = actual.pixel(x, y);
            const QRgb expectedRgb = expected.pixel(x, y);
            const bool pixelMatches =
                qAbs(qRed(actualRgb) - qRed(expectedRgb)) <= fuzz
                && qAbs(qGreen(actualRgb) - qGreen(expectedRgb)) <= fuzz
                && qAbs(qBlue(actualRgb) - qBlue(expectedRgb)) <= fuzz
                && qAbs(qAlpha(actualRgb) - qAlpha(expectedRgb)) <= fuzz;
            if (!pixelMatches) {
                QString s;
                QDebug(&s) << "Pixmal mismatch at " << x << ',' << y << " actual: "
                           << QColor(actualRgb) << " expected: " << QColor(expectedRgb);
                *errorMessage = s.toLocal8Bit();
                return false;
            }
        }
    }
    return true;
}

static inline QString pngFileName(const QString &image, int width, int height)
{
    return image + u'_' + QString::number(width)
        + u'x' + QString::number(height) + QStringLiteral(".png");
}

void tst_QPixmap::toHICON_data()
{
    QTest::addColumn<QString>("image");
    QTest::addColumn<int>("width");
    QTest::addColumn<int>("height");

    QTest::newRow("32bpp_16x16") << m_dataDirectory + QStringLiteral("/icon_32bpp") << 16 << 16;
    QTest::newRow("32bpp_32x32") << m_dataDirectory + QStringLiteral("/icon_32bpp") << 32 << 32;
    QTest::newRow("32bpp_48x48") << m_dataDirectory + QStringLiteral("/icon_32bpp") << 48 << 48;
    QTest::newRow("32bpp_256x256") << m_dataDirectory + QStringLiteral("/icon_32bpp") << 256 << 256;

    QTest::newRow("8bpp_16x16") << m_dataDirectory + QStringLiteral("/icon_8bpp") << 16 << 16;
    QTest::newRow("8bpp_32x32") << m_dataDirectory + QStringLiteral("/icon_8bpp") << 32 << 32;
    QTest::newRow("8bpp_48x48") << m_dataDirectory + QStringLiteral("/icon_8bpp") << 48 << 48;
}

void tst_QPixmap::toHICON()
{
    QFETCH(int, width);
    QFETCH(int, height);
    QFETCH(QString, image);

    QPixmap empty(width, height);
    empty.fill(Qt::transparent);

    const HDC displayDc = GetDC(nullptr);
    const HDC bitmapDc = CreateCompatibleDC(displayDc);
    const HBITMAP bitmap = QtWin::toHBITMAP(empty, QtWin::HBitmapAlpha);
    SelectObject(bitmapDc, bitmap);

    const QString imageFileName = pngFileName(image, width, height);
    QVERIFY2(QFileInfo::exists(imageFileName), qPrintable(imageFileName));

    const QImage imageFromFile = QImage(imageFileName).convertToFormat(QImage::Format_ARGB32_Premultiplied);
    QVERIFY(!imageFromFile.isNull());

    const HICON icon = QtWin::toHICON(QPixmap::fromImage(imageFromFile));

    DrawIconEx(bitmapDc, 0, 0, icon, width, height, 0, nullptr, DI_NORMAL);

    DestroyIcon(icon);
    DeleteDC(bitmapDc);

    const QImage imageFromHICON = QtWin::fromHBITMAP(bitmap, QtWin::HBitmapAlpha).toImage();
    QVERIFY(!imageFromHICON.isNull());

    ReleaseDC(nullptr, displayDc);

    // fuzzy comparison must be used, as the pixel values change slightly during conversion
    // between QImage::Format_ARGB32 and QImage::Format_ARGB32_Premultiplied, or elsewhere
    QByteArray errorMessage;
    QVERIFY2(compareImages(imageFromHICON, imageFromFile, &errorMessage), errorMessage.constData());
}

void tst_QPixmap::fromHICON_data()
{
    toHICON_data();
}

void tst_QPixmap::fromHICON()
{
    QFETCH(int, width);
    QFETCH(int, height);
    QFETCH(QString, image);

    const QString iconFileName = image + QStringLiteral(".ico");
    QVERIFY2(QFileInfo::exists(iconFileName), qPrintable(iconFileName));

    const auto icon =
        static_cast<HICON>(LoadImage(nullptr, reinterpret_cast<const wchar_t *>(iconFileName.utf16()),
                                     IMAGE_ICON, width, height, LR_LOADFROMFILE));
    const QImage imageFromHICON = QtWin::fromHICON(icon).toImage();
    DestroyIcon(icon);

    const QString imageFileName = pngFileName(image, width, height);
    QVERIFY2(QFileInfo::exists(imageFileName), qPrintable(imageFileName));

    const QImage imageFromFile = QImage(imageFileName).convertToFormat(QImage::Format_ARGB32_Premultiplied);
    QVERIFY(!imageFromFile.isNull());

    // fuzzy comparison must be used, as the pixel values change slightly during conversion
    // between QImage::Format_ARGB32 and QImage::Format_ARGB32_Premultiplied, or elsewhere
    QByteArray errorMessage;
    QVERIFY2(compareImages(imageFromHICON, imageFromFile, &errorMessage), errorMessage.constData());
}

void tst_QPixmap::imageConversion()
{
    // Extensive testing of all formats is done in QtGui; this merely tests
    // the exports/linkage.
    QImage image(73, 57, QImage::Format_ARGB32_Premultiplied);
    image.fill(Qt::red);
    QPainter painter(&image);
    painter.drawLine(0, 0, image.width(), image.height());
    const HBITMAP hBitMap = QtWin::imageToHBITMAP(image);
    QVERIFY(hBitMap);
    const QImage fromHBitMap = QtWin::imageFromHBITMAP(hBitMap, QtWin::HBitmapPremultipliedAlpha);
    QCOMPARE(fromHBitMap, image);
    DeleteObject(hBitMap);
}

QTEST_MAIN(tst_QPixmap)

#include "tst_qpixmap.moc"
