diff -r 000000000000 -r 1918ee327afb tests/auto/qpainter/tst_qpainter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/auto/qpainter/tst_qpainter.cpp Mon Jan 11 14:00:40 2010 +0000 @@ -0,0 +1,4356 @@ +/**************************************************************************** +** +** 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 test suite 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 + + +#include +#include +#include +#include +#include +#include +#include +#if !defined(Q_OS_WINCE) && !defined(Q_OS_SYMBIAN) +#include +#include +#ifdef QT3_SUPPORT +#include +#endif +#endif +#include +#include +#include + +#include + +#include + +#include + +#if defined(Q_OS_SYMBIAN) +# define SRCDIR "." +#endif + +Q_DECLARE_METATYPE(QLine) +Q_DECLARE_METATYPE(QRect) +Q_DECLARE_METATYPE(QSize) +Q_DECLARE_METATYPE(QPoint) +Q_DECLARE_METATYPE(QPainterPath) + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QPainter : public QObject +{ +Q_OBJECT + +public: + tst_QPainter(); + virtual ~tst_QPainter(); + + +public slots: + void init(); + void cleanup(); +private slots: + void getSetCheck(); + void qt_format_text_clip(); + void qt_format_text_boundingRect(); + void drawPixmap_comp_data(); + void drawPixmap_comp(); + void saveAndRestore_data(); + void saveAndRestore(); + + void drawLine_data(); + void drawLine(); + void drawLine_clipped(); + void drawLine_task121143(); + void drawLine_task216948(); + + void drawLine_task190634(); + void drawLine_task229459(); + void drawLine_task234891(); + + void drawRect_data() { fillData(); } + void drawRect(); + void drawRect2(); + + void fillRect(); + void fillRect2(); + void fillRect3(); + void fillRect4(); + + void drawEllipse_data(); + void drawEllipse(); + void drawClippedEllipse_data(); + void drawClippedEllipse(); + + void drawPath_data(); + void drawPath(); + void drawPath2(); + void drawPath3(); + + void drawRoundRect_data() { fillData(); } + void drawRoundRect(); + + void qimageFormats_data(); + void qimageFormats(); + void textOnTransparentImage(); + + void initFrom(); + + void setWindow(); + + void combinedMatrix(); + void renderHints(); + + void disableEnableClipping(); + void setClipRect(); + void setEqualClipRegionAndPath_data(); + void setEqualClipRegionAndPath(); + + void clipRectSaveRestore(); + + void clippedFillPath_data(); + void clippedFillPath(); + void clippedLines_data(); + void clippedLines(); + void clippedPolygon_data(); + void clippedPolygon(); + + void clippedText(); + + void setOpacity_data(); + void setOpacity(); + + void drawhelper_blend_untransformed_data(); + void drawhelper_blend_untransformed(); + void drawhelper_blend_tiled_untransformed_data(); + void drawhelper_blend_tiled_untransformed(); + + void porterDuff_warning(); + + void drawhelper_blend_color(); + + void childWidgetViewport(); + + void fillRect_objectBoundingModeGradient(); + void fillRect_stretchToDeviceMode(); + void monoImages(); + + void linearGradientSymmetry(); + void gradientInterpolation(); + + void fpe_pixmapTransform(); + void fpe_zeroLengthLines(); + void fpe_divByZero(); + + void fpe_steepSlopes_data(); + void fpe_steepSlopes(); + void fpe_rasterizeLine_task232012(); + + void fpe_radialGradients(); + + void rasterizer_asserts(); + void rasterizer_negativeCoords(); + + void blendOverFlow_data(); + void blendOverFlow(); + + void largeImagePainting_data(); + void largeImagePainting(); + + void imageScaling_task206785(); + + void outlineFillConsistency(); + + void drawImage_task217400_data(); + void drawImage_task217400(); + void drawImage_1x1(); + void drawImage_task258776(); + void drawRect_task215378(); + void drawRect_task247505(); + + void drawImage_data(); + void drawImage(); + + void clippedImage(); + + void stateResetBetweenQPainters(); + + void imageCoordinateLimit(); + void imageBlending_data(); + void imageBlending(); + void imageBlending_clipped(); + + void paintOnNullPixmap(); + void checkCompositionMode(); + + void drawPolygon(); + + void inactivePainter(); + + void extendedBlendModes(); + + void zeroOpacity(); + void clippingBug(); + void emptyClip(); + + void taskQT4444_dontOverflowDashOffset(); + + void painterBegin(); + +private: + void fillData(); + QColor baseColor( int k, int intensity=255 ); + QImage getResImage( const QString &dir, const QString &addition, const QString &extension ); + QBitmap getBitmap( const QString &dir, const QString &filename, bool mask ); +}; + +// Testing get/set functions +void tst_QPainter::getSetCheck() +{ + QImage img(QSize(10, 10), QImage::Format_ARGB32_Premultiplied); + QPainter obj1; + obj1.begin(&img); + // CompositionMode QPainter::compositionMode() + // void QPainter::setCompositionMode(CompositionMode) + obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_SourceOver)); + QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_SourceOver), obj1.compositionMode()); + obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_DestinationOver)); + QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_DestinationOver), obj1.compositionMode()); + obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_Clear)); + QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_Clear), obj1.compositionMode()); + obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_Source)); + QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_Source), obj1.compositionMode()); + obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_Destination)); + QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_Destination), obj1.compositionMode()); + obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_SourceIn)); + QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_SourceIn), obj1.compositionMode()); + obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_DestinationIn)); + QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_DestinationIn), obj1.compositionMode()); + obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_SourceOut)); + QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_SourceOut), obj1.compositionMode()); + obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_DestinationOut)); + QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_DestinationOut), obj1.compositionMode()); + obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_SourceAtop)); + QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_SourceAtop), obj1.compositionMode()); + obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_DestinationAtop)); + QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_DestinationAtop), obj1.compositionMode()); + obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_Xor)); + QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_Xor), obj1.compositionMode()); + + // const QPen & QPainter::pen() + // void QPainter::setPen(const QPen &) + QPen var3(Qt::red); + obj1.setPen(var3); + QCOMPARE(var3, obj1.pen()); + obj1.setPen(QPen()); + QCOMPARE(QPen(), obj1.pen()); + + // const QBrush & QPainter::brush() + // void QPainter::setBrush(const QBrush &) + QBrush var4(Qt::red); + obj1.setBrush(var4); + QCOMPARE(var4, obj1.brush()); + obj1.setBrush(QBrush()); + QCOMPARE(QBrush(), obj1.brush()); + + // const QBrush & QPainter::background() + // void QPainter::setBackground(const QBrush &) + QBrush var5(Qt::yellow); + obj1.setBackground(var5); + QCOMPARE(var5, obj1.background()); + obj1.setBackground(QBrush()); + QCOMPARE(QBrush(), obj1.background()); + + // bool QPainter::matrixEnabled() + // void QPainter::setMatrixEnabled(bool) + obj1.setMatrixEnabled(false); + QCOMPARE(false, obj1.matrixEnabled()); + obj1.setMatrixEnabled(true); + QCOMPARE(true, obj1.matrixEnabled()); + + // bool QPainter::viewTransformEnabled() + // void QPainter::setViewTransformEnabled(bool) + obj1.setViewTransformEnabled(false); + QCOMPARE(false, obj1.viewTransformEnabled()); + obj1.setViewTransformEnabled(true); + QCOMPARE(true, obj1.viewTransformEnabled()); +} + +Q_DECLARE_METATYPE(QPixmap) +Q_DECLARE_METATYPE(QPolygon) +Q_DECLARE_METATYPE(QBrush) +Q_DECLARE_METATYPE(QPen) +Q_DECLARE_METATYPE(QFont) +Q_DECLARE_METATYPE(QColor) +Q_DECLARE_METATYPE(QRegion) + +tst_QPainter::tst_QPainter() +{ + // QtTestCase sets this to false, but this turns off alpha pixmaps on Unix. + QApplication::setDesktopSettingsAware(TRUE); +} + +tst_QPainter::~tst_QPainter() +{ +} + +void tst_QPainter::init() +{ +} + +void tst_QPainter::cleanup() +{ +} + +/* tests the clipping operations in qt_format_text, making sure + the clip rectangle after the call is the same as before +*/ +void tst_QPainter::qt_format_text_clip() +{ + QVERIFY(1); + QSKIP( "Needs fixing...", SkipAll); + + QWidget *w = new QWidget( 0 ); + + int modes[] = { Qt::AlignVCenter|Qt::TextSingleLine, + Qt::AlignVCenter|Qt::TextSingleLine|Qt::TextDontClip, + Qt::AlignVCenter|Qt::TextWordWrap, + Qt::AlignVCenter|Qt::TextWordWrap|Qt::TextDontClip, + 0 + }; + + int *m = modes; + while( *m ) { + { + QPainter p( w ); + QRegion clipreg = p.clipRegion(); + bool hasClipping = p.hasClipping(); + qreal tx = p.matrix().dx(); + qreal ty = p.matrix().dy(); + + p.drawText( 10, 10, 100, 100, *m, + "fooo" ); + + QVERIFY( clipreg == p.clipRegion() ); + QVERIFY( hasClipping == p.hasClipping() ); + QCOMPARE( tx, p.matrix().dx() ); + QCOMPARE( ty, p.matrix().dy() ); + + p.setClipRect( QRect( 5, 5, 50, 50 ) ); + clipreg = p.clipRegion(); + hasClipping = p.hasClipping(); + + p.drawText( 10, 10, 100, 100, *m, + "fooo" ); + + QVERIFY( clipreg == p.clipRegion() ); + QVERIFY( hasClipping == p.hasClipping() ); + QCOMPARE( tx, p.matrix().dx() ); + QCOMPARE( ty, p.matrix().dy() ); + } + { + QPainter p( w ); + p.setMatrix( QMatrix( 2, 1, 3, 4, 5, 6 ) ); + QRegion clipreg = p.clipRegion(); + bool hasClipping = p.hasClipping(); + qreal tx = p.matrix().dx(); + qreal ty = p.matrix().dy(); + + p.drawText( 10, 10, 100, 100, *m, + "fooo" ); + + QVERIFY( clipreg == p.clipRegion() ); + QVERIFY( hasClipping == p.hasClipping() ); + QCOMPARE( tx, p.matrix().dx() ); + QCOMPARE( ty, p.matrix().dy() ); + + p.setClipRect( QRect( 5, 5, 50, 50 ) ); + clipreg = p.clipRegion(); + hasClipping = p.hasClipping(); + + p.drawText( 10, 10, 100, 100, *m, + "fooo" ); + + QVERIFY( clipreg == p.clipRegion() ); + QVERIFY( hasClipping == p.hasClipping() ); + QCOMPARE( tx, p.matrix().dx() ); + QCOMPARE( ty, p.matrix().dy() ); + } + { + QPainter p( w ); + QRegion clipreg = p.clipRegion(); + bool hasClipping = p.hasClipping(); + qreal tx = p.matrix().dx(); + qreal ty = p.matrix().dy(); + + p.drawText( 10, 10, 100, 100, *m, + "fooo" ); + + QVERIFY( clipreg == p.clipRegion() ); + QVERIFY( hasClipping == p.hasClipping() ); + QCOMPARE( tx, p.matrix().dx() ); + QCOMPARE( ty, p.matrix().dy() ); + + p.setClipRect( QRect( 5, 5, 50, 50 )); + clipreg = p.clipRegion(); + hasClipping = p.hasClipping(); + + p.drawText( 10, 10, 100, 100, *m, + "fooo" ); + + QVERIFY( clipreg == p.clipRegion() ); + QVERIFY( hasClipping == p.hasClipping() ); + QCOMPARE( tx, p.matrix().dx() ); + QCOMPARE( ty, p.matrix().dy() ); + } + { + QPainter p( w ); + p.setMatrix( QMatrix( 2, 1, 3, 4, 5, 6 ) ); + QRegion clipreg = p.clipRegion(); + bool hasClipping = p.hasClipping(); + qreal tx = p.matrix().dx(); + qreal ty = p.matrix().dy(); + + p.drawText( 10, 10, 100, 100, *m, + "fooo" ); + + QVERIFY( clipreg == p.clipRegion() ); + QVERIFY( hasClipping == p.hasClipping() ); + QCOMPARE( tx, p.matrix().dx() ); + QCOMPARE( ty, p.matrix().dy() ); + + p.setClipRect(QRect( 5, 5, 50, 50 )); + clipreg = p.clipRegion(); + hasClipping = p.hasClipping(); + + p.drawText( 10, 10, 100, 100, *m, + "fooo" ); + + QVERIFY( clipreg == p.clipRegion() ); + QVERIFY( hasClipping == p.hasClipping() ); + QCOMPARE( tx, p.matrix().dx() ); + QCOMPARE( ty, p.matrix().dy() ); + } + ++m; + } + delete w; +} + +/* tests the bounding rect calculations in qt_format_text, making sure + the bounding rect has a reasonable value. +*/ +void tst_QPainter::qt_format_text_boundingRect() +{ + QVERIFY(1); + QSKIP( "Needs fixing...", SkipAll); + + { + const char * strings[] = { + "a\n\nb", + "abc", + "a\n \nb", + "this is a longer string", + "\327\222\327\223\327\233\327\223\327\222\327\233\327\222\327\223\327\233", + "aa\327\222\327\233aa", + "\327\222\327\223\327\233\327\223\327\222\327\233\327\222\327\223\327\233", + "\327\222\327\233aa", + "linebreakatend\n", + "some text longer than 30 chars with a line break at the end\n", + "some text\nwith line breaks\nin the middle\nand at the end\n", + "foo\n\n\nfoo", + 0 + }; + + int modes[] = { Qt::AlignVCenter|Qt::TextSingleLine, + Qt::AlignVCenter|Qt::TextSingleLine|Qt::TextDontClip, + Qt::AlignVCenter|Qt::TextWordWrap, + Qt::AlignVCenter|Qt::TextWordWrap|Qt::TextDontClip, + Qt::AlignLeft, + Qt::AlignCenter, + Qt::AlignRight, + 0 + }; + + QFont f; + for(int i = 5; i < 15; ++i) { + f.setPointSize(i); + QFontMetrics fm(f); + const char **str = strings; + while( *str ) { + int *m = modes; + while( *m ) { + QRect br = fm.boundingRect( 0, 0, 2000, 100, *m, QString::fromUtf8( *str ) ); + QVERIFY( br.width() < 800 ); + + QRect br2 = fm.boundingRect( br.x(), br.y(), br.width(), br.height(), *m, QString::fromUtf8( *str ) ); + QCOMPARE( br, br2 ); +#if 0 + { + QPrinter printer; + printer.setOutputToFile(TRUE); + printer.setOutputFileName("tmp.prn"); + QPainter p(&printer); + QRect pbr = p.fontMetrics().boundingRect( 0, 0, 2000, 100, *m, QString::fromUtf8( *str ) ); + QCOMPARE(pbr, br); + } +#endif +#if !defined(Q_OS_WINCE) && !defined(Q_OS_SYMBIAN) + { + QPrinter printer(QPrinter::HighResolution); + if (printer.printerName().isEmpty()) { + QSKIP( "No printers installed, skipping bounding rect test", + SkipSingle ); + break; + } + + printer.setOutputFileName("tmp.prn"); + QPainter p(&printer); + QRect pbr = p.fontMetrics().boundingRect( 0, 0, 12000, 600, *m, QString::fromUtf8( *str ) ); + QVERIFY(pbr.width() > 2*br.width()); + QVERIFY(pbr.height() > 2*br.height()); + } +#endif + ++m; + } + ++str; + } + } + } + + { + const char * strings[] = { + "a", + "a\nb", + "a\n\nb", + "abc", +// "a\n \nb", + "this is a longer string", +// "\327\222\327\223\327\233\327\223\327\222\327\233\327\222\327\223\327\233", +// "aa\327\222\327\233aa", +// "\327\222\327\223\327\233\327\223\327\222\327\233\327\222\327\223\327\233", +// "\327\222\327\233aa", +// "linebreakatend\n", +// "some text longer than 30 chars with a line break at the end\n", +// "some text\nwith line breaks\nin the middle\nand at the end\n", + "foo\n\n\nfoo", + "a\n\n\n\n\nb", + "a\n\n\n\n\n\nb", +// "\347\231\273\351\214\262\346\203\205\345\240\261\343\201\214\350\246\213\343\201\244\343\201\213\343\202\211\343\201\252\343\201\204\343\201\213\347\204\241\345\212\271\343\201\252\343\201\237\343\202\201\343\200\201\nPhotoshop Album \343\202\222\350\265\267\345\213\225\343\201\247\343\201\215\343\201\276\343\201\233\343\202\223\343\200\202\345\206\215\343\202\244\343\203\263\343\202\271\343\203\210\343\203\274\343\203\253\343\201\227\343\201\246\343\201\217\343\201\240\343\201\225\343\201\204\343\200\202" +// "\347\231\273\351\214\262\346\203\205\345\240\261\343\201\214\350\246\213\343\201\244\343\201\213\343\202\211\343\201\252\343\201\204\343\201\213\347\204\241\345\212\271\343\201\252\343\201\237\343\202\201\343\200\201\n\343\202\222\350\265\267\345\213\225\343\201\247\343\201\215\343\201\276\343\201\233\343\202\223\343\200\202\345\206\215\343\202\244\343\203\263\343\202\271\343\203\210\343\203\274\343\203\253\343\201\227\343\201\246\343\201\217\343\201\240\343\201\225\343\201\204\343\200\202", + 0 + }; + + int modes[] = { Qt::AlignVCenter, + Qt::AlignLeft, + Qt::AlignCenter, + Qt::AlignRight, + 0 + }; + + + QFont f; + for(int i = 5; i < 15; ++i) { + f.setPointSize(i); + QFontMetrics fm(f); + const char **str = strings; + while( *str ) { + int *m = modes; + while( *m ) { + QString s = QString::fromUtf8(*str); + QRect br = fm.boundingRect(0, 0, 1000, 1000, *m, s ); + int lines = + s.count("\n"); + int expectedHeight = fm.height()+lines*fm.lineSpacing(); + QCOMPARE(br.height(), expectedHeight); + ++m; + } + ++str; + } + QRect br = fm.boundingRect(0, 0, 100, 0, Qt::TextWordWrap, + "A paragraph with gggggggggggggggggggggggggggggggggggg in the middle."); + QVERIFY(br.height() >= fm.height()+2*fm.lineSpacing()); + } + } +} + + +static const char* const maskSource_data[] = { +"16 13 6 1", +". c None", +"d c #000000", +"# c #999999", +"c c #cccccc", +"b c #ffff00", +"a c #ffffff", +"...#####........", +"..#aaaaa#.......", +".#abcbcba######.", +".#acbcbcaaaaaa#d", +".#abcbcbcbcbcb#d", +"#############b#d", +"#aaaaaaaaaa##c#d", +"#abcbcbcbcbbd##d", +".#abcbcbcbcbcd#d", +".#acbcbcbcbcbd#d", +"..#acbcbcbcbb#dd", +"..#############d", +"...ddddddddddddd"}; + +static const char* const maskResult_data[] = { +"16 13 6 1", +". c #ff0000", +"d c #000000", +"# c #999999", +"c c #cccccc", +"b c #ffff00", +"a c #ffffff", +"...#####........", +"..#aaaaa#.......", +".#abcbcba######.", +".#acbcbcaaaaaa#d", +".#abcbcbcbcbcb#d", +"#############b#d", +"#aaaaaaaaaa##c#d", +"#abcbcbcbcbbd##d", +".#abcbcbcbcbcd#d", +".#acbcbcbcbcbd#d", +"..#acbcbcbcbb#dd", +"..#############d", +"...ddddddddddddd"}; + + +void tst_QPainter::drawPixmap_comp_data() +{ + if (qApp->desktop()->depth() < 24) { + QSKIP("Test only works on 32 bit displays", SkipAll); + return; + } + + QTest::addColumn("dest"); + QTest::addColumn("source"); + + QTest::newRow("0% on 0%, 1") << 0x00000000u<< 0x00000000u; + QTest::newRow("0% on 0%, 2") << 0x00007fffu << 0x00ff007fu; + + QTest::newRow("50% on a=0%") << 0x00000000u << 0x7fff0000u; + QTest::newRow("50% on a=50%") << 0x7f000000u << 0x7fff0000u; + QTest::newRow("50% on deadbeef") << 0xdeafbeefu << 0x7fff0000u; + QTest::newRow("deadbeef on a=0%") << 0x00000000u << 0xdeadbeefu; + QTest::newRow("deadbeef on a=50%") << 0x7f000000u << 0xdeadbeefu; + QTest::newRow("50% blue on 50% red") << 0x7fff0000u << 0x7f0000ffu; + QTest::newRow("50% blue on 50% green") << 0x7f00ff00u << 0x7f0000ffu; + QTest::newRow("50% red on 50% green") << 0x7f00ff00u << 0x7fff0000u; + QTest::newRow("0% on 50%") << 0x7fff00ffu << 0x00ffffffu; + QTest::newRow("100% on deadbeef") << 0xdeafbeefu << 0xffabcdefu; + QTest::newRow("100% on a=0%") << 0x00000000u << 0xffabcdefu; +} + +QRgb qt_compose_alpha(QRgb source, QRgb dest) +{ + int r1 = qRed(dest), g1 = qGreen(dest), b1 = qBlue(dest), a1 = qAlpha(dest); + int r2 = qRed(source), g2 = qGreen(source), b2 = qBlue(source), a2 = qAlpha(source); + + int alpha = qMin(a2 + ((255 - a2) * a1 + 127) / 255, 255); + if (alpha == 0) + return qRgba(0, 0, 0, 0); + + return qRgba( + qMin((r2 * a2 + (255 - a2) * r1 * a1 / 255) / alpha, 255), + qMin((g2 * a2 + (255 - a2) * g1 * a1 / 255) / alpha, 255), + qMin((b2 * a2 + (255 - a2) * b1 * a1 / 255) / alpha, 255), + alpha); +} + +/* Tests that drawing masked pixmaps works +*/ +void tst_QPainter::drawPixmap_comp() +{ +#ifdef Q_WS_MAC + QSKIP("Mac has other ideas about alpha composition", SkipAll); +#endif + + QFETCH(uint, dest); + QFETCH(uint, source); + + QRgb expected = qt_compose_alpha(source, dest); + + QColor c1(qRed(dest), qGreen(dest), qBlue(dest), qAlpha(dest)); + QColor c2(qRed(source), qGreen(source), qBlue(source), qAlpha(source)); + + QPixmap destPm(10, 10), srcPm(10, 10); + destPm.fill(c1); + srcPm.fill(c2); + +#if defined(Q_WS_X11) + if (!destPm.x11PictureHandle()) + QSKIP("Requires XRender support", SkipAll); +#endif + + QPainter p(&destPm); + p.drawPixmap(0, 0, srcPm); + p.end(); + + QImage result = destPm.toImage().convertToFormat(QImage::Format_ARGB32); + bool different = false; + for (int y=0; y off) + || (qAbs(qGreen(pix) - qGreen(expected)) > off) + || (qAbs(qBlue(pix) - qBlue(expected)) > off) + || (qAbs(qAlpha(pix) - qAlpha(expected)) > off); + } + if (diff && !different) + qDebug( "Different at %d,%d pixel [%d,%d,%d,%d] expected [%d,%d,%d,%d]", x, y, + qRed(result.pixel(x, y)), qGreen(result.pixel(x, y)), + qBlue(result.pixel(x, y)), qAlpha(result.pixel(x, y)), + qRed(expected), qGreen(expected), qBlue(expected), qAlpha(expected)); + different |= diff; + } + + QVERIFY(!different); +} + +void tst_QPainter::saveAndRestore_data() +{ + QVERIFY(1); + + QTest::addColumn("font"); + QTest::addColumn("pen"); + QTest::addColumn("brush"); + QTest::addColumn("backgroundColor"); + QTest::addColumn("backgroundMode"); + QTest::addColumn("brushOrigin"); + QTest::addColumn("clipRegion"); + QTest::addColumn("window"); + QTest::addColumn("viewport"); + + QPixmap pixmap(1, 1); + QPainter p(&pixmap); + QFont font = p.font(); + QPen pen = p.pen(); + QBrush brush = p.brush(); + QColor backgroundColor = p.background().color(); + Qt::BGMode backgroundMode = p.backgroundMode(); + QPoint brushOrigin = p.brushOrigin(); + QRegion clipRegion = p.clipRegion(); + QRect window = p.window(); + QRect viewport = p.viewport(); + + QTest::newRow("Original") << font << pen << brush << backgroundColor << int(backgroundMode) + << brushOrigin << clipRegion << window << viewport; + + QFont font2 = font; + font2.setPointSize( 24 ); + QTest::newRow("Modified font.pointSize, brush, backgroundColor, backgroundMode") + << font2 << pen << QBrush(Qt::red) << QColor(Qt::blue) << int(Qt::TransparentMode) + << brushOrigin << clipRegion << window << viewport; + + font2 = font; + font2.setPixelSize( 20 ); + QTest::newRow("Modified font.pixelSize, brushOrigin, pos") + << font2 << pen << brush << backgroundColor << int(backgroundMode) + << QPoint( 50, 32 ) << clipRegion << window << viewport; + + QTest::newRow("Modified clipRegion, window, viewport") + << font << pen << brush << backgroundColor << int(backgroundMode) + << brushOrigin << clipRegion.subtracted(QRect(10,10,50,30)) + << QRect(-500, -500, 500, 500 ) << QRect( 0, 0, 50, 50 ); +} + +void tst_QPainter::saveAndRestore() +{ + QFETCH( QFont, font ); + QFETCH( QPen, pen ); + QFETCH( QBrush, brush ); + QFETCH( QColor, backgroundColor ); + QFETCH( int, backgroundMode ); + QFETCH( QPoint, brushOrigin ); + QFETCH( QRegion, clipRegion ); + QFETCH( QRect, window ); + QFETCH( QRect, viewport ); + + QPixmap pixmap(1, 1); + QPainter painter(&pixmap); + + QFont font_org = painter.font(); + QPen pen_org = painter.pen(); + QBrush brush_org = painter.brush(); + QColor backgroundColor_org = painter.background().color(); + Qt::BGMode backgroundMode_org = painter.backgroundMode(); + QPoint brushOrigin_org = painter.brushOrigin(); + QRegion clipRegion_org = painter.clipRegion(); + QRect window_org = painter.window(); + QRect viewport_org = painter.viewport(); + + painter.save(); + painter.setFont( font ); + painter.setPen( QPen(pen) ); + painter.setBrush( brush ); + painter.setBackground( backgroundColor ); + painter.setBackgroundMode( (Qt::BGMode)backgroundMode ); + painter.setBrushOrigin( brushOrigin ); + painter.setClipRegion( clipRegion ); + painter.setWindow( window ); + painter.setViewport( viewport ); + painter.restore(); + + QCOMPARE( painter.font(), font_org ); + QCOMPARE( painter.font().pointSize(), font_org.pointSize() ); + QCOMPARE( painter.font().pixelSize(), font_org.pixelSize() ); + QCOMPARE( painter.pen(), pen_org ); + QCOMPARE( painter.brush(), brush_org ); + QCOMPARE( painter.background().color(), backgroundColor_org ); + QCOMPARE( painter.backgroundMode(), backgroundMode_org ); + QCOMPARE( painter.brushOrigin(), brushOrigin_org ); + QCOMPARE( painter.clipRegion(), clipRegion_org ); + QCOMPARE( painter.window(), window_org ); + QCOMPARE( painter.viewport(), viewport_org ); +} + +/* + Helper functions +*/ + +QColor tst_QPainter::baseColor( int k, int intensity ) +{ + int r = ( k & 1 ) * intensity; + int g = ( (k>>1) & 1 ) * intensity; + int b = ( (k>>2) & 1 ) * intensity; + return QColor( r, g, b ); +} + +QImage tst_QPainter::getResImage( const QString &dir, const QString &addition, const QString &extension ) +{ + QImage res; + QString resFilename = dir + QString( "/res_%1." ).arg( addition ) + extension; + if ( !res.load( resFilename ) ) { + QWARN(QString("Could not load result data %s %1").arg(resFilename).toLatin1()); + return QImage(); + } + return res; +} + +QBitmap tst_QPainter::getBitmap( const QString &dir, const QString &filename, bool mask ) +{ + QBitmap bm; + QString bmFilename = dir + QString( "/%1.xbm" ).arg( filename ); + if ( !bm.load( bmFilename ) ) { + QWARN(QString("Could not load bitmap '%1'").arg(bmFilename).toLatin1()); + return QBitmap(); + } + if ( mask ) { + QBitmap mask; + QString maskFilename = dir + QString( "/%1-mask.xbm" ).arg( filename ); + if ( !mask.load( maskFilename ) ) { + QWARN(QString("Could not load mask '%1'").arg(maskFilename).toLatin1()); + return QBitmap(); + } + bm.setMask( mask ); + } + return bm; +} + +static int getPaintedPixels(const QImage &image, const QColor &background) +{ + uint color = background.rgba(); + + int pixels = 0; + + for (int y = 0; y < image.height(); ++y) + for (int x = 0; x < image.width(); ++x) + if (image.pixel(x, y) != color) + ++pixels; + + return pixels; +} + +static QRect getPaintedSize(const QImage &image, const QColor &background) +{ + // not the fastest but at least it works.. + int xmin = image.width() + 1; + int xmax = -1; + int ymin = image.height() +1; + int ymax = -1; + + uint color = background.rgba(); + + for ( int y = 0; y < image.height(); ++y ) { + for ( int x = 0; x < image.width(); ++x ) { + QRgb pixel = image.pixel( x, y ); + if ( pixel != color && x < xmin ) + xmin = x; + if ( pixel != color && x > xmax ) + xmax = x; + if ( pixel != color && y < ymin ) + ymin = y; + if ( pixel != color && y > ymax ) + ymax = y; + } + } + + return QRect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1); +} + +static QRect getPaintedSize(const QPixmap &pm, const QColor &background) +{ + return getPaintedSize(pm.toImage(), background); +} + +void tst_QPainter::initFrom() +{ + QWidget *widget = new QWidget(); + QPalette pal = widget->palette(); + pal.setColor(QPalette::Foreground, QColor(255, 0, 0)); + pal.setBrush(QPalette::Background, QColor(0, 255, 0)); + widget->setPalette(pal); + + QFont font = widget->font(); + font.setPointSize(26); + font.setItalic(true); + widget->setFont(font); + + QPixmap pm(100, 100); + QPainter p(&pm); + p.initFrom(widget); + + QCOMPARE(p.font(), font); + QCOMPARE(p.pen().color(), pal.color(QPalette::Foreground)); + QCOMPARE(p.background(), pal.background()); + + delete widget; +} + +void tst_QPainter::drawLine_data() +{ + QTest::addColumn("line"); + + QTest::newRow("0-45") << QLine(0, 20, 100, 0); + QTest::newRow("45-90") << QLine(0, 100, 20, 0); + QTest::newRow("90-135") << QLine(20, 100, 0, 0); + QTest::newRow("135-180") << QLine(100, 20, 0, 0); + QTest::newRow("180-225") << QLine(100, 0, 0, 20); + QTest::newRow("225-270") << QLine(20, 0, 0, 100); + QTest::newRow("270-315") << QLine(0, 0, 20, 100); + QTest::newRow("315-360") << QLine(0, 0, 100, 20); +} + +void tst_QPainter::drawLine() +{ + const int offset = 5; + const int epsilon = 1; // allow for one pixel difference + + QFETCH(QLine, line); + + QPixmap pixmapUnclipped(qMin(line.x1(), line.x2()) + + 2*offset + qAbs(line.dx()), + qMin(line.y1(), line.y2()) + + 2*offset + qAbs(line.dy())); + + { // unclipped + pixmapUnclipped.fill(Qt::white); + QPainter p(&pixmapUnclipped); + p.translate(offset, offset); + p.setPen(QPen(Qt::black)); + p.drawLine(line); + p.end(); + + const QRect painted = getPaintedSize(pixmapUnclipped, Qt::white); + + QLine l = line; + l.translate(offset, offset); + QVERIFY(qAbs(painted.width() - qAbs(l.dx())) <= epsilon); + QVERIFY(qAbs(painted.height() - qAbs(l.dy())) <= epsilon); + QVERIFY(qAbs(painted.top() - qMin(l.y1(), l.y2())) <= epsilon); + QVERIFY(qAbs(painted.left() - qMin(l.x1(), l.x2())) <= epsilon); + QVERIFY(qAbs(painted.bottom() - qMax(l.y1(), l.y2())) <= epsilon); + QVERIFY(qAbs(painted.right() - qMax(l.x1(), l.x2())) <= epsilon); + } + + QPixmap pixmapClipped(qMin(line.x1(), line.x2()) + + 2*offset + qAbs(line.dx()), + qMin(line.y1(), line.y2()) + + 2*offset + qAbs(line.dy())); + { // clipped + const QRect clip = QRect(line.p1(), line.p2()).normalized(); + + pixmapClipped.fill(Qt::white); + QPainter p(&pixmapClipped); + p.translate(offset, offset); + p.setClipRect(clip); + p.setPen(QPen(Qt::black)); + p.drawLine(line); + p.end(); + } + + const QImage unclipped = pixmapUnclipped.toImage(); + const QImage clipped = pixmapClipped.toImage(); + QCOMPARE(unclipped, clipped); +} + +void tst_QPainter::drawLine_clipped() +{ + QImage image(16, 1, QImage::Format_ARGB32_Premultiplied); + image.fill(0x0); + + QPainter p(&image); + p.setPen(QPen(Qt::black, 10)); + + // this should fill the whole image + p.drawLine(-1, -1, 17, 1); + p.end(); + + for (int x = 0; x < 16; ++x) + QCOMPARE(image.pixel(x, 0), 0xff000000); +} + +void tst_QPainter::drawLine_task121143() +{ + QPen pen(Qt::black); + + QImage image(5, 5, QImage::Format_ARGB32_Premultiplied); + image.fill(0xffffffff); + QPainter p(&image); + p.setPen(pen); + p.drawLine(QLine(0, 0+4, 0+4, 0)); + p.end(); + + QImage expected(5, 5, QImage::Format_ARGB32_Premultiplied); + expected.fill(0xffffffff); + for (int x = 0; x < 5; ++x) + expected.setPixel(x, 5-x-1, pen.color().rgb()); + + QCOMPARE(image, expected); +} + +void tst_QPainter::drawLine_task190634() +{ + QPen pen(Qt::black, 3); + + QImage image(32, 32, QImage::Format_ARGB32_Premultiplied); + QPainter p(&image); + p.fillRect(0, 0, image.width(), image.height(), Qt::white); + + p.setPen(pen); + p.drawLine(QLineF(2, -1.6, 10, -1.6)); + p.end(); + + const uint *data = reinterpret_cast(image.bits()); + + for (int i = 0; i < image.width() * image.height(); ++i) + QCOMPARE(data[i], 0xffffffff); + + p.begin(&image); + p.fillRect(0, 0, image.width(), image.height(), Qt::white); + + p.setPen(pen); + p.drawLine(QLineF(-1.6, 2, -1.6, 10)); + p.end(); + + data = reinterpret_cast(image.bits()); + + for (int i = 0; i < image.width() * image.height(); ++i) + QCOMPARE(data[i], 0xffffffff); + + p.begin(&image); + p.fillRect(0, 0, image.width(), image.height(), Qt::white); + + p.setPen(pen); + p.drawLine( QPoint(2,-2), QPoint(3,-5) ); + p.end(); + + data = reinterpret_cast(image.bits()); + + for (int i = 0; i < image.width() * image.height(); ++i) + QCOMPARE(data[i], 0xffffffff); +} + +void tst_QPainter::drawLine_task229459() +{ + QImage image(32, 32, QImage::Format_ARGB32_Premultiplied); + image.fill(0x0); + QPen pen(Qt::black, 64); + + QPainter p(&image); + p.setPen(pen); + p.drawLine(-8, -8, 10000000, 10000000); + p.end(); + + QImage expected = image; + expected.fill(0xff000000); + + QCOMPARE(image, expected); +} + +void tst_QPainter::drawLine_task234891() +{ + QImage img(100, 1000, QImage::Format_ARGB32_Premultiplied); + img.fill(0x0); + QImage expected = img; + + QPainter p(&img); + p.setPen(QPen(QBrush(QColor(255,0,0)), 6)); + p.drawLine(QPointF(25000,100),QPointF(30000,105)); + + p.setPen(QPen(QBrush(QColor(0,255,0)), 6)); + p.drawLine(QPointF(30000,150),QPointF(35000,155)); + + p.setPen(QPen(QBrush(QColor(0,0,255)), 6)); + p.drawLine(QPointF(65000,200),QPointF(66000,205)); + + QCOMPARE(expected, img); +} + +void tst_QPainter::drawLine_task216948() +{ + QImage img(1, 10, QImage::Format_ARGB32_Premultiplied); + img.fill(0x0); + + QPainter p(&img); + QLine line(10, 0, 10, 10); + p.translate(-10, 0); + p.drawLine(line); + p.end(); + + for (int i = 0; i < img.height(); ++i) + QCOMPARE(img.pixel(0, i), QColor(Qt::black).rgba()); +} + +void tst_QPainter::drawRect() +{ + QFETCH(QRect, rect); + QFETCH(bool, usePen); + + QPixmap pixmap(rect.x() + rect.width() + 10, + rect.y() + rect.height() + 10); + { + pixmap.fill(Qt::white); + QPainter p(&pixmap); + p.setPen(usePen ? QPen(Qt::black) : QPen(Qt::NoPen)); + p.setBrush(Qt::black); + p.drawRect(rect); + p.end(); + + int increment = usePen ? 1 : 0; + + const QRect painted = getPaintedSize(pixmap, Qt::white); + QCOMPARE(painted.width(), rect.width() + increment); + QCOMPARE(painted.height(), rect.height() + increment); + } + +#ifdef QT3_SUPPORT + { + if (usePen && (rect.width() < 2 || rect.height() < 2)) + return; + pixmap.fill(Qt::white); + Q3Painter p(&pixmap); + p.setPen(usePen ? QPen(Qt::black) : QPen(Qt::NoPen)); + p.setBrush(Qt::black); + p.drawRect(rect); + p.end(); + + const QRect painted = getPaintedSize(pixmap, Qt::white); + + QCOMPARE(painted.width(), rect.width()); + QCOMPARE(painted.height(), rect.height()); + } +#endif +} + +void tst_QPainter::drawRect2() +{ + QImage image(64, 64, QImage::Format_ARGB32_Premultiplied); + { + image.fill(0xffffffff); + + QTransform transform(0.368567, 0, 0, 0, 0.368567, 0, 0.0289, 0.0289, 1); + + QPainter p(&image); + p.setTransform(transform); + p.setBrush(Qt::red); + p.setPen(Qt::NoPen); + p.drawRect(QRect(14, 14, 39, 39)); + p.end(); + + QRect fill = getPaintedSize(image, Qt::white); + image.fill(0xffffffff); + + p.begin(&image); + p.setTransform(transform); + p.drawRect(QRect(14, 14, 39, 39)); + p.end(); + + QRect stroke = getPaintedSize(image, Qt::white); + QCOMPARE(stroke.adjusted(1, 1, 0, 0), fill.adjusted(0, 0, 1, 1)); + } +} + +void tst_QPainter::fillRect() +{ + QImage image(100, 100, QImage::Format_ARGB32_Premultiplied); + image.fill(QColor(0, 0, 0, 0).rgba()); + + QPainter p(&image); + + p.fillRect(0, 0, 100, 100, QColor(255, 0, 0, 127)); + +// pixmap.save("bla1.png", "PNG"); + QCOMPARE(getPaintedSize(image, QColor(0, 0, 0, 0)), + QRect(0, 0, 100, 100)); + QCOMPARE(getPaintedSize(image, QColor(127, 0, 0, 127)).isValid(), + QRect().isValid()); + + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.fillRect(50, 0, 50, 100, QColor(0, 0, 255, 255)); + + QCOMPARE(getPaintedSize(image, QColor(127, 0, 0, 127)), + QRect(50, 0, 50, 100)); + QCOMPARE(getPaintedSize(image, QColor(0, 0, 127, 127)), + QRect(0, 0, 50, 100)); +} + +void tst_QPainter::fillRect2() +{ + QRgb background = 0x0; + + QImage img(1, 20, QImage::Format_ARGB32_Premultiplied); + img.fill(background); + + QPainter p(&img); + + QRectF rect(0, 1, 1.2, 18); + p.fillRect(rect, Qt::black); + + p.end(); + + QCOMPARE(img.pixel(0, 0), background); + QCOMPARE(img.pixel(0, img.height() - 1), background); + + QCOMPARE(img.pixel(0, 1), img.pixel(0, 2)); + QCOMPARE(img.pixel(0, img.height() - 2), img.pixel(0, img.height() - 3)); +} + +void tst_QPainter::fillRect3() +{ + QImage img(1, 1, QImage::Format_ARGB32_Premultiplied); + img.fill(QColor(Qt::black).rgba()); + + QPainter p(&img); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(img.rect(), Qt::transparent); + p.end(); + + QCOMPARE(img.pixel(0, 0), 0U); +} + +void tst_QPainter::fillRect4() +{ + QImage image(100, 1, QImage::Format_ARGB32_Premultiplied); + image.fill(0x0); + + QImage expected = image; + expected.fill(0xffffffff); + + QPainter p(&image); + p.scale(1.1, 1); + p.setPen(Qt::NoPen); + + for (int i = 0; i < 33; ++i) + p.fillRect(QRectF(3 * i, 0, 3, 1), Qt::white); + + p.end(); + + QCOMPARE(image, expected); +} + +void tst_QPainter::drawPath_data() +{ + QTest::addColumn("path"); + QTest::addColumn("expectedBounds"); + QTest::addColumn("expectedPixels"); + + { + QPainterPath p; + p.addRect(2, 2, 10, 10); + QTest::newRow("int-aligned rect") << p << QRect(2, 2, 10, 10) << 10 * 10; + } + + { + QPainterPath p; + p.addRect(2.25, 2.25, 10, 10); + QTest::newRow("non-aligned rect") << p << QRect(3, 3, 10, 10) << 10 * 10; + } + + { + QPainterPath p; + p.addRect(2.25, 2.25, 10.5, 10.5); + QTest::newRow("non-aligned rect 2") << p << QRect(3, 3, 10, 10) << 10 * 10; + } + + { + QPainterPath p; + p.addRect(2.5, 2.5, 10, 10); + QTest::newRow("non-aligned rect 3") << p << QRect(3, 3, 10, 10) << 10 * 10; + } + + { + QPainterPath p; + p.addRect(2, 2, 10, 10); + p.addRect(4, 4, 6, 6); + QTest::newRow("rect-in-rect") << p << QRect(2, 2, 10, 10) << 10 * 10 - 6 * 6; + } + + { + QPainterPath p; + p.addRect(2, 2, 10, 10); + p.addRect(4, 4, 6, 6); + p.addRect(6, 6, 2, 2); + QTest::newRow("rect-in-rect-in-rect") << p << QRect(2, 2, 10, 10) << 10 * 10 - 6 * 6 + 2 * 2; + } +} + +void tst_QPainter::drawPath() +{ + QFETCH(QPainterPath, path); + QFETCH(QRect, expectedBounds); + QFETCH(int, expectedPixels); + + const int offset = 2; + + QImage image(expectedBounds.width() + 2 * offset, expectedBounds.height() + 2 * offset, + QImage::Format_ARGB32_Premultiplied); + image.fill(QColor(Qt::white).rgb()); + + QPainter p(&image); + p.setPen(Qt::NoPen); + p.setBrush(Qt::black); + p.translate(offset - expectedBounds.left(), offset - expectedBounds.top()); + p.drawPath(path); + p.end(); + + const QRect paintedBounds = getPaintedSize(image, Qt::white); + + QCOMPARE(paintedBounds.x(), offset); + QCOMPARE(paintedBounds.y(), offset); + QCOMPARE(paintedBounds.width(), expectedBounds.width()); + QCOMPARE(paintedBounds.height(), expectedBounds.height()); + + if (expectedPixels != -1) { + int paintedPixels = getPaintedPixels(image, Qt::white); + QCOMPARE(paintedPixels, expectedPixels); + } +} + +void tst_QPainter::drawPath2() +{ + const int w = 50; + + for (int h = 5; h < 200; ++h) { + QPainterPath p1, p2; + p1.lineTo(w, 0); + p1.lineTo(w, h); + + p2.lineTo(w, h); + p2.lineTo(0, h); + + const int offset = 2; + + QImage image(w + 2 * offset, h + 2 * offset, + QImage::Format_ARGB32_Premultiplied); + image.fill(QColor(Qt::white).rgb()); + + QPainter p(&image); + p.setPen(Qt::NoPen); + p.setBrush(Qt::black); + p.translate(offset, offset); + p.drawPath(p1); + p.end(); + + const int p1Pixels = getPaintedPixels(image, Qt::white); + + image.fill(QColor(Qt::white).rgb()); + p.begin(&image); + p.setPen(Qt::NoPen); + p.setBrush(Qt::black); + p.translate(offset, offset); + p.drawPath(p2); + p.end(); + + const int p2Pixels = getPaintedPixels(image, Qt::white); + + QCOMPARE(p1Pixels + p2Pixels, w * h); + } +} + +void tst_QPainter::drawPath3() +{ +#if !defined(Q_OS_WINCE) && !defined(Q_OS_SYMBIAN) + QImage imgA(400, 400, QImage::Format_RGB32); +#else + QImage imgA(100, 100, QImage::Format_RGB32); +#endif + imgA.fill(0xffffff); + QImage imgB = imgA; + + QPainterPath path; + for (int y = 0; y < imgA.height(); ++y) { + for (int x = 0; x < imgA.width(); ++x) { + if ((x + y) & 1) { + imgA.setPixel(x, y, 0); + path.addRect(x, y, 1, 1); + } + } + } + + QPainter p(&imgB); + p.setPen(Qt::NoPen); + p.setBrush(Qt::black); + + p.drawPath(path); + p.end(); + + QVERIFY(imgA == imgB); + + imgA.invertPixels(); + imgB.fill(0xffffff); + + p.begin(&imgB); + p.setPen(Qt::NoPen); + p.setBrush(Qt::black); + + QRectF rect(0, 0, imgA.width(), imgA.height()); + path.addRect(rect.adjusted(-10, -10, 10, 10)); + p.drawPath(path); + p.end(); + + QVERIFY(imgA == imgB); + + path.setFillRule(Qt::WindingFill); + imgB.fill(0xffffff); + + p.begin(&imgB); + p.setPen(Qt::NoPen); + p.setBrush(Qt::black); + QRect clip = rect.adjusted(10, 10, -10, -10).toRect(); + p.setClipRect(clip); + p.drawPath(path); + p.end(); + + QCOMPARE(getPaintedPixels(imgB, Qt::white), clip.width() * clip.height()); +} + +void tst_QPainter::drawEllipse_data() +{ + QTest::addColumn("size"); + QTest::addColumn("usePen"); + + // The current drawEllipse algorithm (drawEllipse_midpoint_i in + // qpaintengine_raster.cpp) draws ellipses that are too wide if the + // ratio between width and hight is too large/small (task 114874). Those + // ratios are therefore currently avoided. + for (int w = 10; w < 128; w += 7) { + for (int h = w/2; h < qMin(2*w, 128); h += 13) { + QString s = QString("%1x%2").arg(w).arg(h); + QTest::newRow(QString("%1 with pen").arg(s).toLatin1()) << QSize(w, h) << true; + QTest::newRow(QString("%1 no pen").arg(s).toLatin1()) << QSize(w, h) << false; + } + } +} + +void tst_QPainter::drawEllipse() +{ + QFETCH(QSize, size); + QFETCH(bool, usePen); + + const int offset = 10; + QRect rect(QPoint(offset, offset), size); + + QImage image(size.width() + 2 * offset, size.height() + 2 * offset, + QImage::Format_ARGB32_Premultiplied); + image.fill(QColor(Qt::white).rgb()); + + QPainter p(&image); + p.setPen(usePen ? QPen(Qt::black) : QPen(Qt::NoPen)); + p.setBrush(Qt::black); + p.drawEllipse(rect); + p.end(); + + QPixmap pixmap = QPixmap::fromImage(image); + + const QRect painted = getPaintedSize(pixmap, Qt::white); + + QCOMPARE(painted.x(), rect.x()); + QCOMPARE(painted.y(), rect.y() + (usePen ? 0 : 1)); + QCOMPARE(painted.width(), size.width() + (usePen ? 1 : 0)); + QCOMPARE(painted.height(), size.height() + (usePen ? 1 : -1)); +} + +void tst_QPainter::drawClippedEllipse_data() +{ + QTest::addColumn("rect"); + + for (int w = 20; w < 128; w += 7) { + for (int h = w/2; h < qMin(2*w, 128); h += 13) { + QString s = QString("%1x%2").arg(w).arg(h); + QTest::newRow(QString("%1 top").arg(s).toLatin1()) << QRect(0, -h/2, w, h); + QTest::newRow(QString("%1 topright").arg(s).toLatin1()) << QRect(w/2, -h/2, w, h); + QTest::newRow(QString("%1 right").arg(s).toLatin1()) << QRect(w/2, 0, w, h); + QTest::newRow(QString("%1 bottomright").arg(s).toLatin1()) << QRect(w/2, h/2, w, h); + QTest::newRow(QString("%1 bottom").arg(s).toLatin1()) << QRect(0, h/2, w, h); + QTest::newRow(QString("%1 bottomleft").arg(s).toLatin1()) << QRect(-w/2, h/2, w, h); + QTest::newRow(QString("%1 left").arg(s).toLatin1()) << QRect(-w/2, 0, w, h); + QTest::newRow(QString("%1 topleft").arg(s).toLatin1()) << QRect(-w/2, -h/2, w, h); + } + } +} + +void tst_QPainter::drawClippedEllipse() +{ + QFETCH(QRect, rect); +#if defined(Q_OS_WINCE) || defined(Q_OS_SYMBIAN) + if (sizeof(qreal) != sizeof(double)) + QSKIP("Test only works for qreal==double", SkipAll); +#endif + QImage image(rect.width() + 1, rect.height() + 1, + QImage::Format_ARGB32_Premultiplied); + QRect expected = QRect(rect.x(), rect.y(), rect.width()+1, rect.height()+1) + & QRect(0, 0, image.width(), image.height()); + + + image.fill(QColor(Qt::white).rgb()); + QPainter p(&image); + p.drawEllipse(rect); + p.end(); + + QPixmap pixmap = QPixmap::fromImage(image); + const QRect painted = getPaintedSize(pixmap, Qt::white); + + QCOMPARE(painted.x(), expected.x()); + QCOMPARE(painted.y(), expected.y()); + QCOMPARE(painted.width(), expected.width()); + QCOMPARE(painted.height(), expected.height()); + +} + +void tst_QPainter::drawRoundRect() +{ + QFETCH(QRect, rect); + QFETCH(bool, usePen); + +#ifdef Q_WS_MAC + if (QTest::currentDataTag() == QByteArray("rect(6, 12, 3, 14) with pen") || + QTest::currentDataTag() == QByteArray("rect(6, 17, 3, 25) with pen") || + QTest::currentDataTag() == QByteArray("rect(10, 6, 10, 3) with pen") || + QTest::currentDataTag() == QByteArray("rect(10, 12, 10, 14) with pen") || + QTest::currentDataTag() == QByteArray("rect(13, 45, 17, 80) with pen") || + QTest::currentDataTag() == QByteArray("rect(13, 50, 17, 91) with pen") || + QTest::currentDataTag() == QByteArray("rect(17, 6, 24, 3) with pen") || + QTest::currentDataTag() == QByteArray("rect(24, 12, 38, 14) with pen")) + QSKIP("The Mac paint engine is off-by-one on certain rect sizes", SkipSingle); +#endif + QPixmap pixmap(rect.x() + rect.width() + 10, + rect.y() + rect.height() + 10); + { + pixmap.fill(Qt::white); + QPainter p(&pixmap); + p.setPen(usePen ? QPen(Qt::black) : QPen(Qt::NoPen)); + p.setBrush(Qt::black); + p.drawRoundRect(rect); + p.end(); + + int increment = usePen ? 1 : 0; + + const QRect painted = getPaintedSize(pixmap, Qt::white); + QCOMPARE(painted.width(), rect.width() + increment); + QCOMPARE(painted.height(), rect.height() + increment); + } + +#ifdef QT3_SUPPORT + { + pixmap.fill(Qt::white); + Q3Painter p(&pixmap); + p.setPen(usePen ? QPen(Qt::black) : QPen(Qt::NoPen)); + p.setBrush(Qt::black); + p.drawRoundRect(rect); + p.end(); + + const QRect painted = getPaintedSize(pixmap, Qt::white); + + QCOMPARE(painted.width(), rect.width()); + QCOMPARE(painted.height(), rect.height()); + } +#endif +} + +Q_DECLARE_METATYPE(QImage::Format) + +void tst_QPainter::qimageFormats_data() +{ + QTest::addColumn("format"); + QTest::newRow("QImage::Format_RGB32") << QImage::Format_RGB32; + QTest::newRow("QImage::Format_ARGB32") << QImage::Format_ARGB32; + QTest::newRow("QImage::Format_ARGB32_Premultiplied") << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("QImage::Format_RGB16") << QImage::Format_RGB16; + QTest::newRow("Qimage::Format_ARGB8565_Premultiplied") << QImage::Format_ARGB8565_Premultiplied; + QTest::newRow("Qimage::Format_RGB666") << QImage::Format_RGB666; + QTest::newRow("Qimage::Format_RGB555") << QImage::Format_RGB555; + QTest::newRow("Qimage::Format_ARGB8555_Premultiplied") << QImage::Format_ARGB8555_Premultiplied; + QTest::newRow("Qimage::Format_RGB888") << QImage::Format_RGB888; +} + +/* + Tests that QPainter can paint on various QImage formats. +*/ +void tst_QPainter::qimageFormats() +{ + QFETCH(QImage::Format, format); + + const QSize size(100, 100); + QImage image(size, format); + image.fill(0); + + const QColor testColor(Qt::red); + QPainter p(&image); + QVERIFY(p.isActive()); + p.setBrush(QBrush(testColor)); + p.drawRect(QRect(QPoint(0,0), size)); + QCOMPARE(image.pixel(50, 50), testColor.rgb()); +} + +void tst_QPainter::fillData() +{ + QTest::addColumn("rect"); + QTest::addColumn("usePen"); + + for (int w = 3; w < 50; w += 7) { + for (int h = 3; h < 50; h += 11) { + int x = w/2 + 5; + int y = h/2 + 5; + QTest::newRow(QString("rect(%1, %2, %3, %4) with pen").arg(x).arg(y).arg(w).arg(h).toLatin1()) + << QRect(x, y, w, h) << true; + QTest::newRow(QString("rect(%1, %2, %3, %4) no pen").arg(x).arg(y).arg(w).arg(h).toLatin1()) + << QRect(x, y, w, h) << false; + } + } +} + +/* + Test that drawline works properly after setWindow has been called. +*/ +void tst_QPainter::setWindow() +{ + QPixmap pixmap(600, 600); + pixmap.fill(QColor(Qt::white)); + + QPainter painter(&pixmap); + painter.setWindow(0, 0, 3, 3); + painter.drawLine(1, 1, 2, 2); + + const QRect painted = getPaintedSize(pixmap, Qt::white); + QVERIFY(195 < painted.y() && painted.y() < 205); // correct value is around 200 + QVERIFY(195 < painted.height() && painted.height() < 205); // correct value is around 200 +} + +void tst_QPainter::combinedMatrix() +{ + QPixmap pm(64, 64); + + QPainter p(&pm); + p.setWindow(0, 0, 1, 1); + p.setViewport(32, 0, 32, 32); + + p.translate(0.5, 0.5); + + QMatrix cm = p.combinedMatrix(); + + QPointF pt = QPointF(0, 0) * cm; + + QCOMPARE(pt.x(), 48.0); + QCOMPARE(pt.y(), 16.0); +} + +void tst_QPainter::textOnTransparentImage() +{ + bool foundPixel = false; + QImage image(10, 10, QImage::Format_ARGB32_Premultiplied); + image.fill(qRgba(0, 0, 0, 0)); // transparent + { + QPainter painter(&image); + painter.setPen(QColor(255, 255, 255)); + painter.drawText(0, 10, "W"); + } + for (int x = 0; x < image.width(); ++x) + for (int y = 0; y < image.height(); ++y) + if (image.pixel(x, y) != 0) + foundPixel = true; + QVERIFY(foundPixel); +} + +void tst_QPainter::renderHints() +{ + QImage img(1, 1, QImage::Format_RGB32); + + QPainter p(&img); + + // Turn off all... + p.setRenderHints(QPainter::RenderHints(0xffffffff), false); + QCOMPARE(p.renderHints(), QPainter::RenderHints(0)); + + // Single set/get + p.setRenderHint(QPainter::Antialiasing); + QVERIFY(p.renderHints() & QPainter::Antialiasing); + + p.setRenderHint(QPainter::Antialiasing, false); + QVERIFY(!(p.renderHints() & QPainter::Antialiasing)); + + // Multi set/get + p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + QVERIFY(p.renderHints() & (QPainter::Antialiasing | QPainter::SmoothPixmapTransform)); + + p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, false); + QVERIFY(!(p.renderHints() & (QPainter::Antialiasing | QPainter::SmoothPixmapTransform))); +} + +int countPixels(const QImage &img, const QRgb &color) +{ + int count = 0; + for (int y = 0; y < img.height(); ++y) { + for (int x = 0; x < img.width(); ++x) { + count += ((img.pixel(x, y) & 0xffffff) == color); + } + } + return count; +} + +template +void testClipping(QImage &img) +{ + img.fill(0x0); + QPainterPath a, b; + a.addRect(QRect(2, 2, 4, 4)); + b.addRect(QRect(4, 4, 4, 4)); + + QPainter p(&img); + p.setClipPath(a); + p.setClipPath(b, Qt::UniteClip); + + p.setClipping(false); + p.setPen(Qt::NoPen); + p.setBrush(QColor(0xff0000)); + p.drawRect(T(0, 0, 10, 10)); + + p.setClipping(true); + p.setBrush(QColor(0x00ff00)); + p.drawRect(T(0, 0, 10, 10)); + + QCOMPARE(countPixels(img, 0xff0000), 72); + QCOMPARE(countPixels(img, 0x00ff00), 28); + + p.end(); + img.fill(0x0); + p.begin(&img); + p.setClipPath(a); + p.setClipPath(b, Qt::IntersectClip); + + p.setClipping(false); + p.setPen(Qt::NoPen); + p.setBrush(QColor(0xff0000)); + p.drawRect(T(0, 0, 10, 10)); + + p.setClipping(true); + p.setBrush(QColor(0x00ff00)); + p.drawRect(T(0, 0, 10, 10)); + + QCOMPARE(countPixels(img, 0xff0000), 96); + QCOMPARE(countPixels(img, 0x00ff00), 4); +} + +void tst_QPainter::disableEnableClipping() +{ + QImage img(10, 10, QImage::Format_RGB32); + + testClipping(img); + testClipping(img); +} + +void tst_QPainter::setClipRect() +{ + QImage img(10, 10, QImage::Format_RGB32); + // simple test to let valgrind check for buffer overflow + { + QPainter p(&img); + p.setClipRect(-10, -10, 100, 100); + p.fillRect(-10, -10, 100, 100, QBrush(QColor(Qt::red))); + } + + // rects with negative width/height + { + QPainter p(&img); + p.setClipRect(QRect(10, 10, -10, 10)); + QVERIFY(p.clipRegion().isEmpty()); + p.setClipRect(QRect(10, 10, 10, -10)); + QVERIFY(p.clipRegion().isEmpty()); + p.setClipRect(QRectF(10.5, 10.5, -10.5, 10.5)); + QVERIFY(p.clipRegion().isEmpty()); + p.setClipRect(QRectF(10.5, 10.5, 10.5, -10.5)); + QVERIFY(p.clipRegion().isEmpty()); + } +} + +/* + This tests the two different clipping approaches in QRasterPaintEngine, + one when using a QRegion and one when using a QPainterPath. They should + give equal results. +*/ +void tst_QPainter::setEqualClipRegionAndPath_data() +{ + QTest::addColumn("deviceSize"); + QTest::addColumn("region"); + + QTest::newRow("empty") << QSize(100, 100) << QRegion(); + QTest::newRow("simple rect") << QSize(100, 100) + << QRegion(QRect(5, 5, 10, 10)); + + QVector rects; + QRegion region; + + rects << QRect(5, 5, 10, 10) << QRect(20, 20, 10, 10); + region.setRects(rects.constData(), rects.size()); + QTest::newRow("two rects") << QSize(100, 100) << region; + + rects.clear(); + rects << QRect(5, 5, 10, 10) << QRect(20, 5, 10, 10); + region.setRects(rects.constData(), rects.size()); + QTest::newRow("two x-adjacent rects") << QSize(100, 100) << region; + + rects.clear(); + rects << QRect(0, 0, 10, 100) << QRect(12, 0, 10, 100); + region.setRects(rects.constData(), rects.size()); + QTest::newRow("two x-adjacent rects 2") << QSize(100, 100) << region; + + rects.clear(); + rects << QRect(0, 0, 10, 100) << QRect(12, 0, 10, 100); + region.setRects(rects.constData(), rects.size()); + QTest::newRow("two x-adjacent rects 3") << QSize(50, 50) << region; + + rects.clear(); + rects << QRect(0, 0, 10, 100) << QRect(12, 0, 10, 100); + region.setRects(rects.constData(), rects.size()); + QTest::newRow("two x-adjacent rects 4") << QSize(101, 101) << region; + + region = QRegion(QRect(0, 0, 200, 200), QRegion::Ellipse); + + QTest::newRow("ellipse") << QSize(190, 200) << region; + + region ^= QRect(50, 50, 50, 50); + QTest::newRow("ellipse 2") << QSize(200, 200) << region; +} + +void tst_QPainter::setEqualClipRegionAndPath() +{ + QFETCH(QSize, deviceSize); + QFETCH(QRegion, region); + + QPainterPath path; + path.addRegion(region); + + QImage img1(deviceSize.width(), deviceSize.height(), + QImage::Format_ARGB32); + QImage img2(deviceSize.width(), deviceSize.height(), + QImage::Format_ARGB32); + img1.fill(0x12345678); + img2.fill(0x12345678); + + { + QPainter p(&img1); + p.setClipRegion(region); + p.fillRect(0, 0, img1.width(), img1.height(), QColor(Qt::red)); + } + { + QPainter p(&img2); + p.setClipPath(path); + p.fillRect(0, 0, img2.width(), img2.height(), QColor(Qt::red)); + } + +#if 0 + if (img1 != img2) { + img1.save("setEqualClipRegionAndPath_1.xpm", "XPM"); + img2.save("setEqualClipRegionAndPath_2.xpm", "XPM"); + } +#endif + QCOMPARE(img1, img2); + +#if 0 + // rotated + img1.fill(0x12345678); + img2.fill(0x12345678); + + { + QPainter p(&img1); + p.rotate(25); + p.setClipRegion(region); + p.fillRect(0, 0, img1.width(), img1.height(), QColor(Qt::red)); + } + { + QPainter p(&img2); + p.rotate(25); + p.setClipPath(path); + p.fillRect(0, 0, img2.width(), img2.height(), QColor(Qt::red)); + } + +#if 1 + if (img1 != img2) { + img1.save("setEqualClipRegionAndPath_1.xpm", "XPM"); + img2.save("setEqualClipRegionAndPath_2.xpm", "XPM"); + } +#endif + QCOMPARE(img1, img2); +#endif + + // simple uniteclip + img1.fill(0x12345678); + img2.fill(0x12345678); + { + QPainter p(&img1); + p.setClipRegion(region); + p.setClipRegion(region, Qt::UniteClip); + p.fillRect(0, 0, img1.width(), img1.height(), QColor(Qt::red)); + } + { + QPainter p(&img2); + p.setClipPath(path); + p.setClipPath(path, Qt::UniteClip); + p.fillRect(0, 0, img2.width(), img2.height(), QColor(Qt::red)); + } + QCOMPARE(img1, img2); + img1.fill(0x12345678); + img2.fill(0x12345678); + { + QPainter p(&img1); + p.setClipPath(path); + p.setClipRegion(region, Qt::UniteClip); + p.fillRect(0, 0, img1.width(), img1.height(), QColor(Qt::red)); + } + { + QPainter p(&img2); + p.setClipRegion(region); + p.setClipPath(path, Qt::UniteClip); + p.fillRect(0, 0, img2.width(), img2.height(), QColor(Qt::red)); + } +#if 0 + if (img1 != img2) { + img1.save("setEqualClipRegionAndPath_1.xpm", "XPM"); + img2.save("setEqualClipRegionAndPath_2.xpm", "XPM"); + } +#endif + QCOMPARE(img1, img2); + + // simple intersectclip + img1.fill(0x12345678); + img2.fill(0x12345678); + { + QPainter p(&img1); + p.setClipRegion(region); + p.setClipRegion(region, Qt::IntersectClip); + p.fillRect(0, 0, img1.width(), img1.height(), QColor(Qt::red)); + } + { + QPainter p(&img2); + p.setClipPath(path); + p.setClipPath(path, Qt::IntersectClip); + p.fillRect(0, 0, img2.width(), img2.height(), QColor(Qt::red)); + } +#if 0 + if (img1 != img2) { + img1.save("setEqualClipRegionAndPath_1.png", "PNG"); + img2.save("setEqualClipRegionAndPath_2.png", "PNG"); + } +#endif + QCOMPARE(img1, img2); + + img1.fill(0x12345678); + img2.fill(0x12345678); + { + QPainter p(&img1); + p.setClipPath(path); + p.setClipRegion(region, Qt::IntersectClip); + p.fillRect(0, 0, img1.width(), img1.height(), QColor(Qt::red)); + } + { + QPainter p(&img2); + p.setClipRegion(region); + p.setClipPath(path, Qt::IntersectClip); + p.fillRect(0, 0, img2.width(), img2.height(), QColor(Qt::red)); + } +#if 0 + if (img1 != img2) { + img1.save("setEqualClipRegionAndPath_1.xpm", "XPM"); + img2.save("setEqualClipRegionAndPath_2.xpm", "XPM"); + } +#endif + QCOMPARE(img1, img2); + +} + +void tst_QPainter::clippedFillPath_data() +{ + QTest::addColumn("imageSize"); + QTest::addColumn("path"); + QTest::addColumn("clipRect"); + QTest::addColumn("brush"); + QTest::addColumn("pen"); + + QLinearGradient gradient(QPoint(0, 0), QPoint(100, 100)); + gradient.setColorAt(0, Qt::red); + gradient.setColorAt(1, Qt::blue); + + + QPen pen2(QColor(223, 223, 0, 223)); + pen2.setWidth(2); + + QPainterPath path; + path.addRect(QRect(15, 15, 50, 50)); + QTest::newRow("simple rect 0") << QSize(100, 100) << path + << QRect(15, 15, 49, 49) + << QBrush(Qt::NoBrush) + << QPen(Qt::black); + QTest::newRow("simple rect 1") << QSize(100, 100) << path + << QRect(15, 15, 50, 50) + << QBrush(Qt::NoBrush) + << QPen(Qt::black); + QTest::newRow("simple rect 2") << QSize(100, 100) << path + << QRect(15, 15, 51, 51) + << QBrush(Qt::NoBrush) + << QPen(Qt::black); + QTest::newRow("simple rect 3") << QSize(100, 100) << path + << QRect(15, 15, 51, 51) + << QBrush(QColor(Qt::blue)) + << QPen(Qt::NoPen); + QTest::newRow("simple rect 4") << QSize(100, 100) << path + << QRect(15, 15, 51, 51) + << QBrush(gradient) + << pen2; + + path = QPainterPath(); + path.addEllipse(QRect(15, 15, 50, 50)); + QTest::newRow("ellipse 0") << QSize(100, 100) << path + << QRect(15, 15, 49, 49) + << QBrush(Qt::NoBrush) + << QPen(Qt::black); + QTest::newRow("ellipse 1") << QSize(100, 100) << path + << QRect(15, 15, 50, 50) + << QBrush(Qt::NoBrush) + << QPen(Qt::black); + QTest::newRow("ellipse 2") << QSize(100, 100) << path + << QRect(15, 15, 51, 51) + << QBrush(Qt::NoBrush) + << QPen(Qt::black); + QTest::newRow("ellipse 3") << QSize(100, 100) << path + << QRect(15, 15, 51, 51) + << QBrush(QColor(Qt::blue)) + << QPen(Qt::NoPen); + QTest::newRow("ellipse 4") << QSize(100, 100) << path + << QRect(15, 15, 51, 51) + << QBrush(gradient) + << pen2; + + path = QPainterPath(); + path.addRoundRect(QRect(15, 15, 50, 50), 20); + QTest::newRow("round rect 0") << QSize(100, 100) << path + << QRect(15, 15, 49, 49) + << QBrush(Qt::NoBrush) + << QPen(Qt::black); + QTest::newRow("round rect 1") << QSize(100, 100) << path + << QRect(15, 15, 50, 50) + << QBrush(Qt::NoBrush) + << QPen(Qt::black); + QTest::newRow("round rect 2") << QSize(100, 100) << path + << QRect(15, 15, 51, 51) + << QBrush(Qt::NoBrush) + << QPen(Qt::black); + QTest::newRow("round rect 3") << QSize(100, 100) << path + << QRect(15, 15, 51, 51) + << QBrush(QColor(Qt::blue)) + << QPen(Qt::NoPen); + QTest::newRow("round rect 4") << QSize(100, 100) << path + << QRect(15, 15, 51, 51) + << QBrush(gradient) + << pen2; + + path = QPainterPath(); + path.moveTo(15, 50); + path.cubicTo(40, 50, 40, 15, 65, 50); + path.lineTo(15, 50); + QTest::newRow("cubic 0") << QSize(100, 100) << path + << QRect(15, 15, 49, 49) + << QBrush(Qt::NoBrush) + << QPen(Qt::black); + QTest::newRow("cubic 1") << QSize(100, 100) << path + << QRect(15, 15, 50, 50) + << QBrush(Qt::NoBrush) + << QPen(Qt::black); + QTest::newRow("cubic 2") << QSize(100, 100) << path + << QRect(15, 15, 51, 51) + << QBrush(Qt::NoBrush) + << QPen(Qt::black); + QTest::newRow("cubic 3") << QSize(100, 100) << path + << QRect(15, 15, 51, 51) + << QBrush(QColor(Qt::blue)) + << QPen(Qt::NoPen); + QTest::newRow("cubic 4") << QSize(100, 100) << path + << QRect(15, 15, 51, 51) + << QBrush(gradient) + << pen2; +} + +void tst_QPainter::clippedFillPath() +{ + QFETCH(QSize, imageSize); + QFETCH(QPainterPath, path); + QFETCH(QRect, clipRect); + QPainterPath clipPath; + clipPath.addRect(clipRect); + QFETCH(QBrush, brush); + QFETCH(QPen, pen); + + const int width = imageSize.width(); + const int height = imageSize.height(); + + QImage clippedRect(width, height, QImage::Format_ARGB32_Premultiplied); + clippedRect.fill(0x12345678); + { + QPainter painter(&clippedRect); + painter.setPen(pen); + painter.setBrush(brush); + painter.setClipRect(clipRect); + painter.drawPath(path); + } + + QImage clippedPath(width, height, QImage::Format_ARGB32_Premultiplied); + clippedPath.fill(0x12345678); + { + QPainter painter(&clippedPath); + painter.setPen(pen); + painter.setBrush(brush); + painter.setClipPath(clipPath); + painter.drawPath(path); + } + +#if 0 + if (clippedRect != clippedPath) { + clippedRect.save(QString("clippedRect.png"), "PNG"); + clippedPath.save(QString("clippedPath.png"), "PNG"); + } +#endif + QCOMPARE(clippedRect, clippedPath); + + // repeat with antialiasing + + clippedRect.fill(0x12345678); + { + QPainter painter(&clippedRect); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(pen); + painter.setBrush(brush); + painter.setClipRect(clipRect); + painter.drawPath(path); + } + + clippedPath.fill(0x12345678); + { + QPainter painter(&clippedPath); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(pen); + painter.setBrush(brush); + painter.setClipPath(clipPath); + painter.drawPath(path); + } + +#if 1 + if (clippedRect != clippedPath) { + clippedRect.save(QString("clippedRect.png"), "PNG"); + clippedPath.save(QString("clippedPath.png"), "PNG"); + } +#endif + QCOMPARE(clippedRect, clippedPath); + +} + +void tst_QPainter::clippedLines_data() +{ + QTest::addColumn("imageSize"); + QTest::addColumn("line"); + QTest::addColumn("clipRect"); + QTest::addColumn("pen"); + + QPen pen2(QColor(223, 223, 0, 223)); + pen2.setWidth(2); + + QVector lines; + lines << QLineF(15, 15, 65, 65) + << QLineF(14, 14, 66, 66) + << QLineF(16, 16, 64, 64) + << QLineF(65, 65, 15, 15) + << QLineF(66, 66, 14, 14) + << QLineF(64, 64, 14, 14) + << QLineF(15, 50, 15, 64) + << QLineF(15, 50, 15, 65) + << QLineF(15, 50, 15, 66) + << QLineF(15, 50, 64, 50) + << QLineF(15, 50, 65, 50) + << QLineF(15, 50, 66, 50); + + foreach (QLineF line, lines) { + QString desc = QString("line (%1, %2, %3, %4) %5").arg(line.x1()) + .arg(line.y1()).arg(line.x2()).arg(line.y2()); + QTest::newRow(qPrintable(desc.arg(0))) << QSize(100, 100) << line + << QRect(15, 15, 49, 49) + << QPen(Qt::black); + QTest::newRow(qPrintable(desc.arg(1))) << QSize(100, 100) << line + << QRect(15, 15, 50, 50) + << QPen(Qt::black); + QTest::newRow(qPrintable(desc.arg(2))) << QSize(100, 100) << line + << QRect(15, 15, 51, 51) + << QPen(Qt::black); + QTest::newRow(qPrintable(desc.arg(3))) << QSize(100, 100) << line + << QRect(15, 15, 51, 51) + << QPen(Qt::NoPen); + QTest::newRow(qPrintable(desc.arg(4))) << QSize(100, 100) << line + << QRect(15, 15, 51, 51) + << pen2; + } +} + +void tst_QPainter::clippedLines() +{ + QFETCH(QSize, imageSize); + QFETCH(QLineF, line); + QFETCH(QRect, clipRect); + QPainterPath clipPath; + clipPath.addRect(clipRect); + QFETCH(QPen, pen); + + const int width = imageSize.width(); + const int height = imageSize.height(); + + QImage clippedRect(width, height, QImage::Format_ARGB32_Premultiplied); + clippedRect.fill(0x12345678); + { + QPainter painter(&clippedRect); + painter.setPen(pen); + painter.setClipRect(clipRect); + painter.drawLine(line); + painter.drawLine(line.toLine()); + } + + QImage clippedPath(width, height, QImage::Format_ARGB32_Premultiplied); + clippedPath.fill(0x12345678); + { + QPainter painter(&clippedPath); + painter.setPen(pen); + painter.setClipPath(clipPath); + painter.drawLine(line); + painter.drawLine(line.toLine()); + } + +#if 0 + if (clippedRect != clippedPath) { + clippedRect.save(QString("clippedRect.png"), "PNG"); + clippedPath.save(QString("clippedPath.png"), "PNG"); + } +#endif + QCOMPARE(clippedRect, clippedPath); + + // repeat with antialiasing + clippedRect.fill(0x12345678); + { + QPainter painter(&clippedRect); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(pen); + painter.setClipRect(clipRect); + painter.drawLine(line); + painter.drawLine(line.toLine()); + } + + clippedPath.fill(0x12345678); + { + QPainter painter(&clippedPath); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(pen); + painter.setClipPath(clipPath); + painter.drawLine(line); + painter.drawLine(line.toLine()); + } + +#if 0 + if (clippedRect != clippedPath) { + clippedRect.save(QString("clippedRect.png"), "PNG"); + clippedPath.save(QString("clippedPath.png"), "PNG"); + } +#endif + QCOMPARE(clippedRect, clippedPath); +} + +void tst_QPainter::clippedPolygon_data() +{ + clippedFillPath_data(); +}; + +void tst_QPainter::clippedPolygon() +{ + QFETCH(QSize, imageSize); + QFETCH(QPainterPath, path); + QPolygonF polygon = path.toFillPolygon(); + QFETCH(QRect, clipRect); + QPainterPath clipPath; + clipPath.addRect(clipRect); + QFETCH(QPen, pen); + QFETCH(QBrush, brush); + + const int width = imageSize.width(); + const int height = imageSize.height(); + + QImage clippedRect(width, height, QImage::Format_ARGB32_Premultiplied); + clippedRect.fill(0x12345678); + { + QPainter painter(&clippedRect); + painter.setPen(pen); + painter.setBrush(brush); + painter.setClipRect(clipRect); + painter.drawPolygon(polygon); + painter.drawPolygon(polygon.toPolygon()); + } + + QImage clippedPath(width, height, QImage::Format_ARGB32_Premultiplied); + clippedPath.fill(0x12345678); + { + QPainter painter(&clippedPath); + painter.setPen(pen); + painter.setBrush(brush); + painter.setClipRect(clipRect); + painter.drawPolygon(polygon); + painter.drawPolygon(polygon.toPolygon()); + } + +#if 0 + if (clippedRect != clippedPath) { + clippedRect.save(QString("clippedRect.png"), "PNG"); + clippedPath.save(QString("clippedPath.png"), "PNG"); + } +#endif + QCOMPARE(clippedRect, clippedPath); + + // repeat with antialiasing + + clippedRect.fill(0x12345678); + { + QPainter painter(&clippedRect); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(pen); + painter.setBrush(brush); + painter.setClipRect(clipRect); + painter.drawPolygon(polygon); + painter.drawPolygon(polygon.toPolygon()); + } + + clippedPath.fill(0x12345678); + { + QPainter painter(&clippedPath); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(pen); + painter.setBrush(brush); + painter.setClipRect(clipRect); + painter.drawPolygon(polygon); + painter.drawPolygon(polygon.toPolygon()); + } + +#if 0 + if (clippedRect != clippedPath) { + clippedRect.save(QString("clippedRect.png"), "PNG"); + clippedPath.save(QString("clippedPath.png"), "PNG"); + } +#endif + QCOMPARE(clippedRect, clippedPath); +} + +// this just draws some text that should be clipped in the raster +// paint engine. +void tst_QPainter::clippedText() +{ + for (char ch = 'A'; ch < 'Z'; ++ch) { + //qDebug() << ch; + QFont f; + f.setPixelSize(24); + QFontMetrics metrics(f); + QRect textRect = metrics.boundingRect(QChar(ch)); + + if (textRect.width() <= 8) + continue; + if (textRect.height() <= 8) + continue; + + QRect imageRect = textRect.adjusted(4, 4, -4, -4); + + QImage image(imageRect.size(), QImage::Format_ARGB32_Premultiplied); + + image.fill(qRgba(255, 255, 255, 255)); + { + QPainter painter(&image); + painter.setFont(f); + painter.setPen(Qt::black); + + painter.drawText(0, 0, QChar(ch)); + } + + image.fill(qRgba(255, 255, 255, 255)); + { + QPainter painter(&image); + painter.setFont(f); + painter.setPen(Qt::black); + + painter.drawText(-imageRect.topLeft(), QChar(ch)); + } + + bool foundPixel = false; + for (int x = 0; x < image.width(); ++x) + for (int y = 0; y < image.height(); ++y) + if (image.pixel(x, y) != 0) + foundPixel = true; + // can't QVERIFY(foundPixel) as sometimes all pixels are clipped + // away. For example for 'O' + // just call /some/ function to prevent the compiler from optimizing + // foundPixel away + QString::number(foundPixel); + + //image.save(QString("debug") + ch + ".xpm"); + } + + QVERIFY(true); // reached, don't trigger any valgrind errors +} + +void tst_QPainter::setOpacity_data() +{ + QTest::addColumn("destFormat"); + QTest::addColumn("srcFormat"); + + QTest::newRow("ARGB32P on ARGB32P") << QImage::Format_ARGB32_Premultiplied + << QImage::Format_ARGB32_Premultiplied; + + QTest::newRow("ARGB32 on ARGB32") << QImage::Format_ARGB32 + << QImage::Format_ARGB32; + + QTest::newRow("RGB32 on RGB32") << QImage::Format_RGB32 + << QImage::Format_RGB32; + + QTest::newRow("RGB16 on RGB16") << QImage::Format_RGB16 + << QImage::Format_RGB16; + + QTest::newRow("ARGB8565_Premultiplied on ARGB8565_Premultiplied") << QImage::Format_ARGB8565_Premultiplied + << QImage::Format_ARGB8565_Premultiplied; + + QTest::newRow("RGB555 on RGB555") << QImage::Format_RGB555 + << QImage::Format_RGB555; + + QTest::newRow("RGB666 on RGB666") << QImage::Format_RGB666 + << QImage::Format_RGB666; + + QTest::newRow("ARGB8555_Premultiplied on ARGB8555_Premultiplied") << QImage::Format_ARGB8555_Premultiplied + << QImage::Format_ARGB8555_Premultiplied; + + QTest::newRow("RGB888 on RGB888") << QImage::Format_RGB888 + << QImage::Format_RGB888; + + QTest::newRow("RGB32 on RGB16") << QImage::Format_RGB16 + << QImage::Format_RGB32; + + QTest::newRow("RGB32 on ARGB8565_Premultiplied") << QImage::Format_ARGB8565_Premultiplied + << QImage::Format_RGB32; + + QTest::newRow("RGB32 on RGB666") << QImage::Format_RGB666 + << QImage::Format_RGB32; + + QTest::newRow("RGB32 on RGB555") << QImage::Format_RGB555 + << QImage::Format_RGB32; + + QTest::newRow("RGB32 on ARGB8555_Premultiplied") << QImage::Format_ARGB8555_Premultiplied + << QImage::Format_RGB32; + + QTest::newRow("RGB32 on RGB888") << QImage::Format_RGB888 + << QImage::Format_RGB32; + + QTest::newRow("RGB16 on RGB32") << QImage::Format_RGB32 + << QImage::Format_RGB16; + + QTest::newRow("ARGB8565_Premultiplied on RGB32") << QImage::Format_RGB32 + << QImage::Format_ARGB8565_Premultiplied; + + QTest::newRow("RGB666 on RGB32") << QImage::Format_RGB32 + << QImage::Format_RGB666; + + QTest::newRow("RGB555 on RGB32") << QImage::Format_RGB32 + << QImage::Format_RGB555; + + QTest::newRow("ARGB8555_Premultiplied on RGB32") << QImage::Format_RGB32 + << QImage::Format_ARGB8555_Premultiplied; + + QTest::newRow("RGB888 on RGB32") << QImage::Format_RGB32 + << QImage::Format_RGB888; + + QTest::newRow("RGB555 on RGB888") << QImage::Format_RGB888 + << QImage::Format_RGB555; + + QTest::newRow("RGB666 on RGB888") << QImage::Format_RGB888 + << QImage::Format_RGB666; + + QTest::newRow("RGB444 on RGB444") << QImage::Format_RGB444 + << QImage::Format_RGB444; +} + +void tst_QPainter::setOpacity() +{ + QFETCH(QImage::Format, destFormat); + QFETCH(QImage::Format, srcFormat); + + const QSize imageSize(12, 12); + const QRect imageRect(QPoint(0, 0), imageSize); + QColor destColor = Qt::black; + QColor srcColor = Qt::white; + + QImage dest(imageSize, destFormat); + QImage src(imageSize, srcFormat); + + QPainter p; + p.begin(&dest); + p.fillRect(imageRect, destColor); + p.end(); + + p.begin(&src); + p.fillRect(imageRect, srcColor); + p.end(); + + p.begin(&dest); + p.setOpacity(0.5); + p.drawImage(imageRect, src, imageRect); + p.end(); + + QImage expected(imageSize, destFormat); + p.begin(&expected); + p.fillRect(imageRect, QColor(127, 127, 127)); + p.end(); + + QCOMPARE(dest, expected); +} + +void tst_QPainter::drawhelper_blend_untransformed_data() +{ + setOpacity_data(); +} + +void tst_QPainter::drawhelper_blend_untransformed() +{ + QFETCH(QImage::Format, destFormat); + QFETCH(QImage::Format, srcFormat); + + const int size = 128; + const QSize imageSize(size, size); + const QRect paintRect(1, 0, size - 2, size); // needs alignment and tailing + + QColor destColor(127, 127, 127); + QColor srcColor(Qt::white); + + QImage dest(imageSize, destFormat); + QImage src(imageSize, srcFormat); + + QPainter p; + p.begin(&src); + p.fillRect(paintRect, srcColor); + p.end(); + + QList opacities = (QList() << 0.0 << 0.1 << 0.01 << 0.4 + << 0.5 << 0.6 << 0.9 << 1.0); + foreach (qreal opacity, opacities) { + p.begin(&dest); + p.fillRect(paintRect, destColor); + + p.setOpacity(opacity); + p.drawImage(paintRect, src, paintRect); + p.end(); + + // sanity check: make sure all pixels are equal + QImage expected(size - 2, size, destFormat); + p.begin(&expected); + p.fillRect(0, 0, expected.width(), expected.height(), + QColor(dest.pixel(1, 0))); + p.end(); + + const QImage subDest(dest.bits() + dest.depth() / 8, + dest.width() - 2, dest.height(), + dest.bytesPerLine(), dest.format()); + + if (dest.format() == QImage::Format_ARGB8565_Premultiplied || + dest.format() == QImage::Format_ARGB8555_Premultiplied) { + // Test skipped due to rounding errors... + continue; + } +#if 0 + if (subDest != expected) { + qDebug() << "size" << size << "opacity" << opacity; + for (int j = 0; j < expected.height(); ++j) { + for (int i = 0; i < expected.width(); ++i) { + if (expected.pixel(i,j) != subDest.pixel(i,j)) + qDebug() << i << j << hex << expected.pixel(i, j) + << subDest.pixel(i, j); + } + } + } +#endif + QCOMPARE(subDest, expected); + } +} + +void tst_QPainter::drawhelper_blend_tiled_untransformed_data() +{ + setOpacity_data(); +} + +void tst_QPainter::drawhelper_blend_tiled_untransformed() +{ + QFETCH(QImage::Format, destFormat); + QFETCH(QImage::Format, srcFormat); + + const int size = 128; + const QSize imageSize(size, size); + const QRect paintRect(1, 0, size - 2, size); // needs alignment and tailing + + QColor destColor(127, 127, 127); + QColor srcColor(Qt::white); + + QImage dest(imageSize, destFormat); + QImage src(imageSize / 2, srcFormat); + + QPainter p; + p.begin(&src); + p.fillRect(QRect(QPoint(0, 0), imageSize/ 2), srcColor); + p.end(); + + const QBrush brush(src); + + QList opacities = (QList() << 0.0 << 0.1 << 0.01 << 0.4 + << 0.5 << 0.6 << 0.9 << 1.0); + foreach (qreal opacity, opacities) { + p.begin(&dest); + p.fillRect(paintRect, destColor); + + p.setOpacity(opacity); + p.fillRect(paintRect, brush); + p.end(); + + // sanity check: make sure all pixels are equal + QImage expected(size - 2, size, destFormat); + p.begin(&expected); + p.fillRect(0, 0, expected.width(), expected.height(), + QColor(dest.pixel(1, 0))); + p.end(); + + const QImage subDest(dest.bits() + dest.depth() / 8, + dest.width() - 2, dest.height(), + dest.bytesPerLine(), dest.format()); + + if (dest.format() == QImage::Format_ARGB8565_Premultiplied || + dest.format() == QImage::Format_ARGB8555_Premultiplied) { + // Skipping test due to rounding errors. Test needs rewrite + continue; + } +#if 0 + if (subDest != expected) { + qDebug() << "size" << size << "opacity" << opacity; + for (int j = 0; j < expected.height(); ++j) { + for (int i = 0; i < expected.width(); ++i) { + if (expected.pixel(i,j) != subDest.pixel(i,j)) + qDebug() << i << j << hex << expected.pixel(i, j) + << subDest.pixel(i, j); + } + } + } +#endif + QCOMPARE(subDest, expected); + } +} + +static QPaintEngine::PaintEngineFeatures no_porter_duff() +{ + QPaintEngine::PaintEngineFeatures features = QPaintEngine::AllFeatures; + return features & ~QPaintEngine::PorterDuff; +} + +class DummyPaintEngine : public QPaintEngine, public QPaintDevice +{ +public: + DummyPaintEngine() : QPaintEngine(no_porter_duff()) {} + virtual bool begin(QPaintDevice *) { return true; } + virtual bool end() { return true; } + + virtual void updateState(const QPaintEngineState &) {} + virtual void drawPixmap(const QRectF &, const QPixmap &, const QRectF &) {} + + virtual Type type() const { return User; } + + virtual QPaintEngine *paintEngine() const { return (QPaintEngine *)this; } + + virtual int metric(PaintDeviceMetric metric) const { Q_UNUSED(metric); return 0; }; +}; + +static bool success; + +void porterDuff_warningChecker(QtMsgType type, const char *msg) +{ + if (type == QtWarningMsg && msg == QLatin1String("QPainter::setCompositionMode: PorterDuff modes not supported on device")) + success = false; +} + +void tst_QPainter::porterDuff_warning() +{ + QtMsgHandler old = qInstallMsgHandler(porterDuff_warningChecker); + DummyPaintEngine dummy; + QPainter p(&dummy); + + success = true; + p.setCompositionMode(QPainter::CompositionMode_Source); + QVERIFY(success); + + success = true; + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + QVERIFY(success); + + success = true; + p.setCompositionMode(QPainter::CompositionMode_DestinationOver); + QVERIFY(!success); + + QVERIFY(qInstallMsgHandler(old) == porterDuff_warningChecker); +} + +class quint24 +{ +public: + inline quint24(quint32 v) + { + data[0] = qBlue(v); + data[1] = qGreen(v); + data[2] = qRed(v); + } + + inline operator quint32 () + { + return qRgb(data[2], data[1], data[0]); + } + + inline bool operator==(const quint24 &v) const { + return (data[0] == v.data[0] && data[1] == v.data[1] && data[2] == v.data[2]); + } + + uchar data[3]; +} Q_PACKED; + +void tst_QPainter::drawhelper_blend_color() +{ + QImage dest(32, 32, QImage::Format_ARGB8555_Premultiplied); + dest.fill(0xff000000); + + { + QPainter p(&dest); + p.fillRect(0, 0, dest.width(), dest.height(), QColor(255, 0, 0, 127)); + } + + QImage expected(32, 32, QImage::Format_ARGB8555_Premultiplied); + expected.fill(0xff3c007f); + + QCOMPARE(dest.pixel(1, 1), expected.pixel(1, 1)); + QCOMPARE(dest, expected); +} + +class ViewportTestWidget : public QWidget +{ +public: + ViewportTestWidget(QWidget *parent = 0) : QWidget(parent), hasPainted(false) {} + QSize sizeHint() const { + return QSize(100, 100); + } + + QRect viewport; + bool hasPainted; + +protected: + void paintEvent(QPaintEvent *) { + hasPainted = true; + QPainter p(this); + viewport = p.viewport(); + } +}; + +void tst_QPainter::childWidgetViewport() +{ + QWidget parent; + parent.setAutoFillBackground(true); + parent.resize(200, 200); + ViewportTestWidget child(&parent); + child.setAutoFillBackground(true); + parent.show(); + parent.update(); + qApp->processEvents(); + + if (child.hasPainted) { + QCOMPARE(child.viewport, QRect(QPoint(0, 0), child.sizeHint())); + } else { + qWarning("Failed to ensure that paintEvent has been run. Could not run test."); + } +} + +void tst_QPainter::fillRect_objectBoundingModeGradient() +{ + QImage a(10, 10, QImage::Format_ARGB32_Premultiplied); + a.fill(0x0); + QImage b = a; + + QLinearGradient g(QPoint(0, 0), QPoint(0, 1)); + g.setColorAt(0, Qt::red); + g.setColorAt(1, Qt::blue); + g.setCoordinateMode(QGradient::ObjectBoundingMode); + + QPainter p(&a); + p.fillRect(QRect(0, 0, a.width(), a.height()), g); + p.end(); + + QPainterPath path; + path.addRect(0, 0, a.width(), a.height()); + + p.begin(&b); + p.fillPath(path, g); + p.end(); + + QCOMPARE(a, b); +} + +void tst_QPainter::fillRect_stretchToDeviceMode() +{ + QImage img(64, 64, QImage::Format_ARGB32_Premultiplied); + + QLinearGradient g(QPoint(0, 0), QPoint(0, 1)); + g.setCoordinateMode(QGradient::StretchToDeviceMode); + + QPainter p(&img); + p.fillRect(img.rect(), g); + p.end(); + + for (int i = 1; i < img.height(); ++i) + QVERIFY(img.pixel(0, i) != img.pixel(0, i-1)); +} + +void tst_QPainter::monoImages() +{ + Qt::GlobalColor colorPairs[][2] = { + { Qt::white, Qt::black }, + { Qt::color0, Qt::color1 }, + { Qt::red, Qt::blue } + }; + + const int numColorPairs = sizeof(colorPairs) / sizeof(QRgb[2]); + + QImage transparent(2, 2, QImage::Format_ARGB32_Premultiplied); + transparent.fill(0x0); + + for (int i = 1; i < QImage::NImageFormats; ++i) { + for (int j = 0; j < numColorPairs; ++j) { + const QImage::Format format = QImage::Format(i); + if (format == QImage::Format_Indexed8) + continue; + + QImage img(2, 2, format); + + if (img.numColors() > 0) { + img.setColor(0, QColor(colorPairs[j][0]).rgba()); + img.setColor(1, QColor(colorPairs[j][1]).rgba()); + } + + img.fill(0x0); + QPainter p(&img); + p.fillRect(0, 0, 2, 2, colorPairs[j][0]); + p.fillRect(0, 0, 1, 1, colorPairs[j][1]); + p.fillRect(1, 1, 1, 1, colorPairs[j][1]); + p.end(); + + QImage original = img; + + p.begin(&img); + p.drawImage(0, 0, transparent); + p.end(); + + // drawing a transparent image on top of another image + // should not change the image + QCOMPARE(original, img); + + if (img.numColors() == 0) + continue; + + for (int k = 0; k < 2; ++k) { + QPainter p(&img); + p.fillRect(0, 0, 2, 2, colorPairs[j][k]); + p.end(); + + QImage argb32p(2, 2, QImage::Format_ARGB32_Premultiplied); + p.begin(&argb32p); + p.fillRect(0, 0, 2, 2, colorPairs[j][k]); + p.end(); + + QCOMPARE(argb32p, img.convertToFormat(argb32p.format())); + + // drawing argb32p image on mono image + p.begin(&img); + p.drawImage(0, 0, argb32p); + p.end(); + + QCOMPARE(argb32p, img.convertToFormat(argb32p.format())); + + // drawing mono image on argb32p image + p.begin(&argb32p); + p.drawImage(0, 0, img); + p.end(); + + QCOMPARE(argb32p, img.convertToFormat(argb32p.format())); + } + } + } +} + +#if !defined(Q_OS_IRIX) && !defined(Q_OS_AIX) && !defined(Q_CC_MSVC) && !defined(Q_OS_SOLARIS) && !defined(Q_OS_SYMBIAN) +#include + +static const QString fpeExceptionString(int exception) +{ +#ifdef FE_INEXACT + if (exception & FE_INEXACT) + return QLatin1String("Inexact result"); +#endif + if (exception & FE_UNDERFLOW) + return QLatin1String("Underflow"); + if (exception & FE_OVERFLOW) + return QLatin1String("Overflow"); + if (exception & FE_DIVBYZERO) + return QLatin1String("Divide by zero"); + if (exception & FE_INVALID) + return QLatin1String("Invalid operation"); + return QLatin1String("No exception"); +} + +class FpExceptionChecker +{ +public: + FpExceptionChecker(int exceptionMask) + : m_exceptionMask(exceptionMask) + { + feclearexcept(m_exceptionMask); + } + + ~FpExceptionChecker() + { + const int exceptions = fetestexcept(m_exceptionMask); + QVERIFY2(!exceptions, qPrintable(QLatin1String("Floating point exception: ") + fpeExceptionString(exceptions))); + } + +private: + int m_exceptionMask; +}; + +void fpe_rasterizeLine_task232012() +{ + FpExceptionChecker checker(FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID | FE_DIVBYZERO); + QImage img(128, 128, QImage::Format_ARGB32_Premultiplied); + img.fill(0x0); + QPainter p(&img); + + p.setBrush(Qt::black); + p.drawRect(QRectF(0, 0, 5, 0)); + p.drawRect(QRectF(0, 0, 0, 5)); +} + +void fpe_pixmapTransform() +{ + FpExceptionChecker checker(FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID | FE_DIVBYZERO); + + QImage img(128, 128, QImage::Format_ARGB32_Premultiplied); + + QPainter p(&img); + + const qreal scaleFactor = 0.001; + const int translateDistance = 1000000; + + p.setPen(Qt::red); + p.setBrush(QBrush(Qt::red,Qt::Dense6Pattern)); + + for (int i = 0; i < 2; ++i) { + p.setRenderHint(QPainter::SmoothPixmapTransform, i); + + p.resetTransform(); + p.scale(1.1, 1.1); + p.translate(translateDistance, 0); + p.drawRect(-translateDistance, 0, 100, 100); + + p.resetTransform(); + p.scale(scaleFactor, scaleFactor); + p.drawRect(QRectF(0, 0, 1 / scaleFactor, 1 / scaleFactor)); + } +} + +void fpe_zeroLengthLines() +{ + FpExceptionChecker checker(FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID | FE_DIVBYZERO); + + QImage img(128, 128, QImage::Format_ARGB32_Premultiplied); + + QPainter p(&img); + + p.setPen(QPen(Qt::black, 3)); + p.drawLine(64, 64, 64, 64); +} + +void fpe_divByZero() +{ + FpExceptionChecker checker(FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID | FE_DIVBYZERO); + + QImage img(128, 128, QImage::Format_ARGB32_Premultiplied); + + QPainter p(&img); + + p.setRenderHint(QPainter::Antialiasing); + + p.drawRect(QRectF(10, 10, 100, 0)); + p.drawRect(QRectF(10, 10, 0, 100)); + + p.drawRect(QRect(10, 10, 100, 0)); + p.drawRect(QRect(10, 10, 0, 100)); + + p.fillRect(QRectF(10, 10, 100, 0), Qt::black); + p.fillRect(QRectF(10, 10, 0, 100), Qt::black); + + p.fillRect(QRect(10, 10, 100, 0), Qt::black); + p.fillRect(QRect(10, 10, 0, 100), Qt::black); +} + +void fpe_steepSlopes() +{ + FpExceptionChecker checker(FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID | FE_DIVBYZERO); + + QImage img(1024, 1024, QImage::Format_ARGB32_Premultiplied); + + QFETCH(QTransform, transform); + QFETCH(QLineF, line); + QFETCH(bool, antialiased); + + QPainter p(&img); + + p.setPen(QPen(Qt::black, 1)); + p.setRenderHint(QPainter::Antialiasing, antialiased); + p.setTransform(transform); + + QEXPECT_FAIL("steep line 3 aa", "needs to be fixed", Continue); + p.drawLine(line); +} + +void fpe_radialGradients() +{ + FpExceptionChecker checker(FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID | FE_DIVBYZERO); + + QImage img(21, 21, QImage::Format_ARGB32_Premultiplied); + img.fill(0); + + double m = img.width() * 0.5; + + QPainter p(&img); + p.setRenderHints(QPainter::Antialiasing); + p.setPen(Qt::NoPen); + p.setBrush(QRadialGradient(m, m, m)); + p.drawEllipse(img.rect()); +} + +#define FPE_TEST(x) \ +void tst_QPainter::x() \ +{ \ + ::x(); \ +} +#else +#define FPE_TEST(x) \ +void tst_QPainter::x() \ +{ \ + QSKIP("Floating point exception checking (fenv.h) not available", SkipAll); \ +} +#endif + +FPE_TEST(fpe_rasterizeLine_task232012) +FPE_TEST(fpe_pixmapTransform) +FPE_TEST(fpe_zeroLengthLines) +FPE_TEST(fpe_divByZero) +FPE_TEST(fpe_steepSlopes) +FPE_TEST(fpe_radialGradients) + +void tst_QPainter::fpe_steepSlopes_data() +{ + QTest::addColumn("transform"); + QTest::addColumn("line"); + QTest::addColumn("antialiased"); + + { + const qreal dsin = 0.000014946676875461832484392500630665523431162000633776187896728515625; + const qreal dcos = 0.9999999998882984630910186751862056553363800048828125; + + const QTransform transform = QTransform(QMatrix(dcos, dsin, -dsin, dcos, 64, 64)); + const QLineF line(2, 2, 2, 6); + + QTest::newRow("task 207147 aa") << transform << line << true; + QTest::newRow("task 207147 no aa") << transform << line << false; + } + + { + QTransform transform; + transform.rotate(0.0000001); + const QLineF line(5, 5, 10, 5); + + QTest::newRow("task 166702 aa") << transform << line << true; + QTest::newRow("task 166702 no aa") << transform << line << false; + } + + { + const QTransform transform; + const QLineF line(2.5, 2.5, 2.5 + 1/256., 60000.5); + + QTest::newRow("steep line aa") << transform << line << true; + QTest::newRow("steep line no aa") << transform << line << false; + } + + { + const QTransform transform; + const QLineF line(2.5, 2.5, 2.5 + 1/256., 1024); + + QTest::newRow("steep line 2 aa") << transform << line << true; + QTest::newRow("steep line 2 no aa") << transform << line << false; + } + + { + const QTransform transform; + const QLineF line(2.5, 2.5, 2.5 + 1/64., 1024); + + QTest::newRow("steep line 3 aa") << transform << line << true; + QTest::newRow("steep line 3 no aa") << transform << line << false; + } +} + +qreal randf() +{ + return rand() / (RAND_MAX + 1.0); +} + +QPointF randInRect(const QRectF &rect) +{ + const qreal x = rect.left() + rect.width() * randf(); + const qreal y = rect.top() + rect.height() * randf(); + + return QPointF(x, y); +} + +void tst_QPainter::rasterizer_asserts() +{ + QImage img(64, 64, QImage::Format_ARGB32_Premultiplied); + + QRectF middle(QPointF(0, 0), img.size()); + QRectF left = middle.translated(-middle.width(), 0); + QRectF right = middle.translated(middle.width(), 0); + + QPainter p(&img); + img.fill(Qt::white); + p.setCompositionMode(QPainter::CompositionMode_Destination); + for (int i = 0; i < 100000; ++i) { + QPainterPath path; + path.moveTo(randInRect(middle)); + path.lineTo(randInRect(left)); + path.lineTo(randInRect(right)); + + p.fillPath(path, Qt::black); + } +} + +void tst_QPainter::rasterizer_negativeCoords() +{ + QImage img(64, 64, QImage::Format_ARGB32_Premultiplied); + img.fill(0x0); + + QImage original = img; + + QPainter p(&img); + p.rotate(90); + p.fillRect(0, 0, 70, 50, Qt::black); + + // image should not have changed + QCOMPARE(img.pixel(0, 0), 0x0U); + QCOMPARE(img, original); +} + +void tst_QPainter::blendOverFlow_data() +{ + QTest::addColumn("format"); + QTest::addColumn("width"); + QTest::addColumn("height"); + + QImage::Format format = QImage::Format_ARGB8555_Premultiplied; + QTest::newRow("555,1,1") << format << 1 << 1; + QTest::newRow("555,2,2") << format << 2 << 2; + QTest::newRow("555,10,10") << format << 10 << 10; + + format = QImage::Format_ARGB8565_Premultiplied; + QTest::newRow("565,1,1") << format << 1 << 1; + QTest::newRow("565,2,2") << format << 2 << 2; + QTest::newRow("565,10,10") << format << 10 << 10; +} + +void tst_QPainter::blendOverFlow() +{ + QFETCH(QImage::Format, format); + QFETCH(int, width); + QFETCH(int, height); + + QImage dest(width, height, format); + QImage src(width, height, format); + + { + QPainter p(&dest); + p.fillRect(0, 0, width, height, Qt::green); + } + QImage expected = dest; + + { + QPainter p(&src); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, width, height, QColor(0, 255, 0, 6)); + } + + { + QPainter p(&dest); + p.drawImage(0, 0, src); + } + + QCOMPARE(dest.pixel(0, 0), expected.pixel(0, 0)); + QCOMPARE(dest, expected); +} + +void tst_QPainter::largeImagePainting_data() +{ + QTest::addColumn("width"); + QTest::addColumn("height"); + QTest::addColumn("antialiased"); + + QTest::newRow("tall") << 1 << 32767 << false; + QTest::newRow("tall aa") << 1 << 32767 << true; + QTest::newRow("wide") << 32767 << 1 << false; + QTest::newRow("wide aa") << 32767 << 1 << true; +} + +void tst_QPainter::largeImagePainting() +{ + QPainterPath path; + path.addRect(0, 0, 1, 1); + path.addRect(2, 0, 1, 1); + path.addRect(0, 2, 1, 1); + + QFETCH(int, width); + QFETCH(int, height); + QFETCH(bool, antialiased); + + QImage img(width, height, QImage::Format_ARGB32_Premultiplied); + img.fill(0x0); + + QPainter p(&img); + p.setPen(Qt::NoPen); + p.setBrush(Qt::white); + + p.setRenderHint(QPainter::Antialiasing, antialiased); + + for (int i = 0; i < img.width(); i += 4) { + p.drawPath(path); + p.translate(4, 0); + } + + p.resetMatrix(); + + for (int i = 4; i < img.height(); i += 4) { + p.translate(0, 4); + p.drawPath(path); + } + + for (int i = 0; i < img.width(); ++i) { + if (i % 2) + QCOMPARE(img.pixel(i, 0), 0x0U); + else + QCOMPARE(img.pixel(i, 0), 0xffffffffU); + } + + for (int i = 1; i < img.height(); ++i) { + if (i % 2) + QCOMPARE(img.pixel(0, i), 0x0U); + else + QCOMPARE(img.pixel(0, i), 0xffffffffU); + } +} + +void tst_QPainter::imageScaling_task206785() +{ + QImage src(32, 2, QImage::Format_ARGB32_Premultiplied); + src.fill(0xffffffff); + + QImage dst(128, 128, QImage::Format_ARGB32_Premultiplied); + + QImage expected(128, 128, QImage::Format_ARGB32_Premultiplied); + expected.fill(0xffffffff); + + for (int i = 1; i < 5; ++i) { + qreal scale = i / qreal(5); + + dst.fill(0xff000000); + + QPainter p(&dst); + p.scale(dst.width() / qreal(src.width()), scale); + + for (int y = 0; y * scale < dst.height(); ++y) + p.drawImage(0, y, src); + + p.end(); + + QCOMPARE(dst, expected); + } +} + +#define FOR_EACH_NEIGHBOR_8 for (int dx = -1; dx <= 1; ++dx) for (int dy = -1; dy <= 1; ++dy) if (dx != 0 || dy != 0) +#define FOR_EACH_NEIGHBOR_4 for (int dx = -1; dx <= 1; ++dx) for (int dy = -1; dy <= 1; ++dy) if ((dx == 0) != (dy == 0)) + +uint qHash(const QPoint &point) +{ + return qHash(qMakePair(point.x(), point.y())); +} + +bool verifyOutlineFillConsistency(const QImage &img, QRgb outside, QRgb inside, QRgb outline) +{ + if (img.pixel(img.width() / 2, img.height() / 2) != inside) + return false; + + int x = img.width() / 2; + int y = img.height() / 2; + + while (img.pixel(++x, y) == inside) + ; + + if (img.pixel(x, y) != outline) + return false; + + QQueue discovered; + discovered.enqueue(QPoint(x, y)); + + QVector visited(img.width() * img.height()); + visited.fill(false); + + while (!discovered.isEmpty()) { + QPoint p = discovered.dequeue(); + QRgb pixel = img.pixel(p.x(), p.y()); + + bool &v = visited[p.y() * img.width() + p.x()]; + if (v) + continue; + v = true; + + if (pixel == outline) { + FOR_EACH_NEIGHBOR_8 { + QPoint x(p.x() + dx, p.y() + dy); + discovered.enqueue(x); + } + } else { + FOR_EACH_NEIGHBOR_4 { + if ((dx == 0) == (dy == 0)) + continue; + QRgb neighbor = img.pixel(p.x() + dx, p.y() + dy); + if (pixel == inside && neighbor == outside || + pixel == outside && neighbor == inside) + return false; + } + } + } + + return true; +} + +#undef FOR_EACH_NEIGHBOR_8 +#undef FOR_EACH_NEIGHBOR_4 + +void tst_QPainter::outlineFillConsistency() +{ + QImage dst(256, 256, QImage::Format_ARGB32_Premultiplied); + + QPolygonF poly; + poly << QPointF(5, -100) << QPointF(-70, 20) << QPointF(95, 25); + + QPen pen(Qt::red); + QBrush brush(Qt::black); + + QRgb background = 0xffffffff; + for (int i = 0; i < 360; ++i) { + dst.fill(background); + + QPainter p(&dst); + p.translate(dst.width() / 2, dst.height() / 2); + + QPolygonF copy = poly; + for (int j = 0; j < copy.size(); ++j) + copy[j] = QTransform().rotate(i).map(copy[j]); + + p.setPen(pen); + p.setBrush(brush); + p.drawPolygon(copy); + p.end(); + +#if 0 + if (!verifyOutlineFillConsistency(dst, background, p.brush().color().rgba(), p.pen().color().rgba())) + dst.save(QString("outlineFillConsistency-%1.png").arg(i)); +#endif + + QVERIFY(verifyOutlineFillConsistency(dst, background, brush.color().rgba(), pen.color().rgba())); + } +} + +void tst_QPainter::drawImage_task217400_data() +{ + QTest::addColumn("format"); + + QTest::newRow("444") << QImage::Format_ARGB4444_Premultiplied; + QTest::newRow("555") << QImage::Format_ARGB8555_Premultiplied; + QTest::newRow("565") << QImage::Format_ARGB8565_Premultiplied; +// QTest::newRow("666") << QImage::Format_ARGB6666_Premultiplied; + QTest::newRow("888p") << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("888") << QImage::Format_ARGB32; +} + +void tst_QPainter::drawImage_task217400() +{ + QFETCH(QImage::Format, format); + + const QImage src = QImage(QString(SRCDIR) + "/task217400.png") + .convertToFormat(format); + QVERIFY(!src.isNull()); + + QImage expected(src.size(), format); + { + QPainter p(&expected); + p.fillRect(0, 0, expected.width(), expected.height(), Qt::white); + p.drawImage(0, 0, src); + } + + for (int i = 1; i <= 4; ++i) { + QImage dest(src.width() + i, src.height(), format); + { + QPainter p(&dest); + p.fillRect(0, 0, dest.width(), dest.height(), Qt::white); + p.drawImage(i, 0, src); + } + + const QImage result = dest.copy(i, 0, src.width(), src.height()); + +#if 0 + if (result != expected) { + qDebug("i=%i", i); + result.save("result.png"); + expected.save("expected.png"); + } +#endif + QCOMPARE(result, expected); + } +} + +void tst_QPainter::drawImage_task258776() +{ + QImage src(16, 16, QImage::Format_RGB888); + QImage dest(33, 33, QImage::Format_RGB888); + src.fill(0x00ff00); + dest.fill(0x0000ff); + + QPainter painter(&dest); + painter.drawImage(QRectF(0.499, 0.499, 32, 32), src, QRectF(0, 0, 16, 16)); + painter.end(); + + QImage expected(33, 33, QImage::Format_RGB32); + expected.fill(0xff0000); + + painter.begin(&expected); + painter.drawImage(QRectF(0, 0, 32, 32), src); + painter.end(); + + dest = dest.convertToFormat(QImage::Format_RGB32); + + dest.save("dest.png"); + expected.save("expected.png"); + QCOMPARE(dest, expected); +} + +void tst_QPainter::clipRectSaveRestore() +{ + QImage img(64, 64, QImage::Format_ARGB32_Premultiplied); + img.fill(0x0); + + QPainter p(&img); + p.setClipRect(QRect(0, 0, 10, 10)); + p.save(); + p.setClipRect(QRect(5, 5, 5, 5), Qt::IntersectClip); + p.restore(); + p.fillRect(0, 0, 64, 64, Qt::black); + p.end(); + + QCOMPARE(img.pixel(0, 0), QColor(Qt::black).rgba()); +} + +void tst_QPainter::clippedImage() +{ + QImage img(16, 16, QImage::Format_ARGB32_Premultiplied); + img.fill(0x0); + + QImage src(16, 16, QImage::Format_RGB32); + src.fill(QColor(Qt::red).rgba()); + + QPainter p(&img); + p.setClipRect(QRect(1, 1, 14, 14)); + p.drawImage(0, 0, src); + p.end(); + + QCOMPARE(img.pixel(0, 0), 0x0U); + QCOMPARE(img.pixel(1, 1), src.pixel(1, 1)); +} + +void tst_QPainter::stateResetBetweenQPainters() +{ + QImage img(16, 16, QImage::Format_ARGB32); + + { + QPainter p(&img); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, 16, 16, Qt::red); + } + + { + QPainter p2(&img); + p2.fillRect(0, 0, 16, 16, QColor(0, 0, 255, 63)); + } + + img.save("foo.png"); + + QVERIFY(img.pixel(0, 0) != qRgba(0, 0, 255, 63)); + QVERIFY(qRed(img.pixel(0, 0)) > 0); // We didn't erase the red channel... + QVERIFY(qBlue(img.pixel(0, 0)) < 255); // We blended the blue channel +} + +void tst_QPainter::drawRect_task215378() +{ + QImage img(11, 11, QImage::Format_ARGB32_Premultiplied); + img.fill(QColor(Qt::white).rgba()); + + QPainter p(&img); + p.setPen(QColor(127, 127, 127, 127)); + p.drawRect(0, 0, 10, 10); + p.end(); + + QCOMPARE(img.pixel(0, 0), img.pixel(1, 0)); + QCOMPARE(img.pixel(0, 0), img.pixel(0, 1)); + QVERIFY(img.pixel(0, 0) != img.pixel(1, 1)); +} + +void tst_QPainter::drawRect_task247505() +{ + QImage a(10, 10, QImage::Format_ARGB32_Premultiplied); + a.fill(0); + QImage b = a; + + QPainter p(&a); + p.setPen(Qt::NoPen); + p.setBrush(Qt::black); + p.drawRect(QRectF(10, 0, -10, 10)); + p.end(); + p.begin(&b); + p.setPen(Qt::NoPen); + p.setBrush(Qt::black); + p.drawRect(QRectF(0, 0, 10, 10)); + p.end(); + + QCOMPARE(a, b); +} + +void tst_QPainter::drawImage_data() +{ + QTest::addColumn("x"); + QTest::addColumn("y"); + QTest::addColumn("w"); + QTest::addColumn("h"); + QTest::addColumn("srcFormat"); + QTest::addColumn("dstFormat"); + + for (int srcFormat = QImage::Format_Mono; srcFormat < QImage::NImageFormats; ++srcFormat) { + for (int dstFormat = QImage::Format_Mono; dstFormat < QImage::NImageFormats; ++dstFormat) { + if (dstFormat == QImage::Format_Indexed8) + continue; + for (int odd_x = 0; odd_x <= 1; ++odd_x) { + for (int odd_width = 0; odd_width <= 1; ++odd_width) { + QString description = + QString("srcFormat %1, dstFormat %2, odd x: %3, odd width: %4") + .arg(srcFormat).arg(dstFormat).arg(odd_x).arg(odd_width); + + QTest::newRow(qPrintable(description)) << (10 + odd_x) << 10 << (20 + odd_width) << 20 + << QImage::Format(srcFormat) + << QImage::Format(dstFormat); + } + } + } + } +} + +bool verifyImage(const QImage &img, int x, int y, int w, int h, uint background) +{ + int imgWidth = img.width(); + int imgHeight = img.height(); + for (int i = 0; i < imgHeight; ++i) { + for (int j = 0; j < imgWidth; ++j) { + uint pixel = img.pixel(j, i); + bool outside = j < x || j >= (x + w) || i < y || i >= (y + h); + if (outside != (pixel == background)) { + //printf("%d %d, expected %x, got %x, outside: %d\n", x, y, background, pixel, outside); + return false; + } + } + } + + return true; +} + +void tst_QPainter::drawImage() +{ + QFETCH(int, x); + QFETCH(int, y); + QFETCH(int, w); + QFETCH(int, h); + QFETCH(QImage::Format, srcFormat); + QFETCH(QImage::Format, dstFormat); + + QImage dst(40, 40, QImage::Format_RGB32); + dst.fill(0xffffffff); + + dst = dst.convertToFormat(dstFormat); + uint background = dst.pixel(0, 0); + + QImage src(w, h, QImage::Format_RGB32); + src.fill(0xff000000); + src = src.convertToFormat(srcFormat); + + QPainter p(&dst); + p.drawImage(x, y, src); + p.end(); + + QVERIFY(verifyImage(dst, x, y, w, h, background)); +} + +void tst_QPainter::imageCoordinateLimit() +{ + QImage img(64, 40000, QImage::Format_MonoLSB); + QPainter p(&img); + p.drawText(10, 36000, QLatin1String("foo")); + p.setPen(QPen(Qt::black, 2)); + p.drawLine(10, 0, 60, 40000); + + p.setRenderHint(QPainter::Antialiasing); + p.drawLine(10, 0, 60, 40000); +} + + +void tst_QPainter::imageBlending_data() +{ + QTest::addColumn("sourceFormat"); + QTest::addColumn("destFormat"); + QTest::addColumn("error"); + + int error_rgb565 = ((1<<3) + (1<<2) + (1<<3)); + QTest::newRow("rgb565_on_rgb565") << QImage::Format_RGB16 + << QImage::Format_RGB16 + << 0; + QTest::newRow("argb8565_on_rgb565") << QImage::Format_ARGB8565_Premultiplied + << QImage::Format_RGB16 + << error_rgb565; + + QTest::newRow("rgb32_on_rgb565") << QImage::Format_RGB32 + << QImage::Format_RGB16 + << error_rgb565; + + QTest::newRow("argb32pm_on_rgb565") << QImage::Format_ARGB32_Premultiplied + << QImage::Format_RGB16 + << error_rgb565; +} + +int diffColor(quint32 ap, quint32 bp) +{ + int a = qAlpha(ap) - qAlpha(bp); + int r = qRed(ap) - qRed(bp); + int b = qBlue(ap) - qBlue(bp); + int g = qBlue(ap) - qBlue(bp); + + return qAbs(a) + qAbs(r) + qAbs(g) + qAbs(b); +} + +// this test assumes premultiplied pixels... + +void tst_QPainter::imageBlending() +{ + QFETCH(QImage::Format, sourceFormat); + QFETCH(QImage::Format, destFormat); + QFETCH(int, error); + + QImage dest; + { + QImage orig_dest(6, 6, QImage::Format_ARGB32_Premultiplied); + orig_dest.fill(0); + QPainter p(&orig_dest); + p.fillRect(0, 0, 6, 3, QColor::fromRgbF(1, 0, 0)); + p.fillRect(3, 0, 3, 6, QColor::fromRgbF(0, 0, 1, 0.5)); + p.end(); + dest = orig_dest.convertToFormat(destFormat); + + // An image like this: (r = red, m = magenta, b = light alpha blue, 0 = transparent) + // r r r m m m + // r r r m m m + // r r r m m m + // 0 0 0 b b b + // 0 0 0 b b b + // 0 0 0 b b b + } + + QImage source; + { + QImage orig_source(6, 6, QImage::Format_ARGB32_Premultiplied); + orig_source.fill(0); + QPainter p(&orig_source); + p.fillRect(1, 1, 4, 4, QColor::fromRgbF(0, 1, 0, 0.5)); + p.fillRect(2, 2, 2, 2, QColor::fromRgbF(0, 1, 0)); + p.end(); + source = orig_source.convertToFormat(sourceFormat); + + // An image like this: (0 = transparent, . = green at 0.5 alpha, g = opaque green. + // 0 0 0 0 0 0 + // 0 . . . . 0 + // 0 . g g . 0 + // 0 . g g . 0 + // 0 . . . . 0 + // 0 0 0 0 0 0 + } + + QPainter p(&dest); + p.drawImage(0, 0, source); + p.end(); + + // resulting image: + // r r r m m m + // r r. r. m. m. m + // r r. g g m. m + // 0 . g g b. b + // 0 . . b. b. b + // 0 0 0 b b b + + // the g pixels, always green.. + QVERIFY(diffColor(dest.pixel(2, 2), 0xff00ff00) <= error); // g + + if (source.hasAlphaChannel()) { + QVERIFY(diffColor(dest.pixel(0, 0), 0xffff0000) <= error); // r + QVERIFY(diffColor(dest.pixel(5, 0), 0xff7f007f) <= error); // m + QVERIFY(diffColor(dest.pixel(1, 1), 0xff7f7f00) <= error); // r. + QVERIFY(diffColor(dest.pixel(4, 1), 0xff3f7f3f) <= error); // m. + if (dest.hasAlphaChannel()) { + QVERIFY(diffColor(dest.pixel(1, 3), 0x7f007f00) <= error); // . + QVERIFY(diffColor(dest.pixel(4, 3), 0x7f007f3f) <= error); // b. + QVERIFY(diffColor(dest.pixel(4, 3), 0x7f007f3f) <= error); // b. + QVERIFY(diffColor(dest.pixel(4, 4), 0x7f00007f) <= error); // b + QVERIFY(diffColor(dest.pixel(4, 0), 0) <= 0); // 0 + } + } else { + QVERIFY(diffColor(dest.pixel(0, 0), 0xff000000) <= 0); + QVERIFY(diffColor(dest.pixel(1, 1), 0xff007f00) <= error); + } +} + +void tst_QPainter::imageBlending_clipped() +{ + QImage src(20, 20, QImage::Format_RGB16); + QPainter p(&src); + p.fillRect(src.rect(), Qt::red); + p.end(); + + QImage dst(40, 20, QImage::Format_RGB16); + p.begin(&dst); + p.fillRect(dst.rect(), Qt::white); + p.end(); + + QImage expected = dst; + + p.begin(&dst); + p.setClipRect(QRect(23, 0, 20, 20)); + + // should be completely clipped + p.drawImage(QRectF(3, 0, 20, 20), src); + p.end(); + + // dst should be left unchanged + QCOMPARE(dst, expected); +} + +void tst_QPainter::paintOnNullPixmap() +{ + QPixmap pix(16, 16); + + QPixmap textPixmap; + QPainter p(&textPixmap); + p.drawPixmap(10, 10, pix); + p.end(); + + QPixmap textPixmap2(16,16); + p.begin(&textPixmap2); + p.end(); +} + +void tst_QPainter::checkCompositionMode() +{ + QImage refImage(50,50,QImage::Format_ARGB32); + QPainter painter(&refImage); + painter.fillRect(QRect(0,0,50,50),Qt::blue); + + QImage testImage(50,50,QImage::Format_ARGB32); + QPainter p(&testImage); + p.fillRect(QRect(0,0,50,50),Qt::red); + p.save(); + p.setCompositionMode(QPainter::CompositionMode_SourceOut); + p.restore(); + p.fillRect(QRect(0,0,50,50),Qt::blue); + + QCOMPARE(refImage.pixel(20,20),testImage.pixel(20,20)); +} + +static QLinearGradient inverseGradient(QLinearGradient g) +{ + QLinearGradient g2 = g; + + QGradientStops stops = g.stops(); + + QGradientStops inverse; + foreach (QGradientStop stop, stops) + inverse << QGradientStop(1 - stop.first, stop.second); + + g2.setStops(inverse); + return g2; +} + +void tst_QPainter::linearGradientSymmetry() +{ + QImage a(64, 8, QImage::Format_ARGB32_Premultiplied); + QImage b(64, 8, QImage::Format_ARGB32_Premultiplied); + + a.fill(0); + b.fill(0); + + QLinearGradient gradient(QRectF(b.rect()).topLeft(), QRectF(b.rect()).topRight()); + gradient.setColorAt(0.0, Qt::blue); + gradient.setColorAt(0.2, QColor(220, 220, 220, 0)); + gradient.setColorAt(0.6, Qt::red); + gradient.setColorAt(0.9, QColor(220, 220, 220, 255)); + gradient.setColorAt(1.0, Qt::black); + + QPainter pa(&a); + pa.fillRect(a.rect(), gradient); + pa.end(); + + QPainter pb(&b); + pb.fillRect(b.rect(), inverseGradient(gradient)); + pb.end(); + + b = b.mirrored(true); + QCOMPARE(a, b); +} + +void tst_QPainter::gradientInterpolation() +{ + QImage image(256, 8, QImage::Format_ARGB32_Premultiplied); + QPainter painter; + + QLinearGradient gradient(QRectF(image.rect()).topLeft(), QRectF(image.rect()).topRight()); + gradient.setColorAt(0.0, QColor(255, 0, 0, 0)); + gradient.setColorAt(1.0, Qt::blue); + + image.fill(0); + painter.begin(&image); + painter.fillRect(image.rect(), gradient); + painter.end(); + + const QRgb *line = reinterpret_cast(image.scanLine(3)); + + for (int i = 0; i < 256; ++i) { + QCOMPARE(qAlpha(line[i]), qBlue(line[i])); // bright blue + QVERIFY(qAbs(qAlpha(line[i]) - i) < 3); // linear alpha + QCOMPARE(qRed(line[i]), 0); // no red component + QCOMPARE(qGreen(line[i]), 0); // no green component + } + + gradient.setInterpolationMode(QGradient::ComponentInterpolation); + + image.fill(0); + painter.begin(&image); + painter.fillRect(image.rect(), gradient); + painter.end(); + + for (int i = 1; i < 256; ++i) { + if (i < 128) { + QVERIFY(qRed(line[i]) >= qBlue(line[i])); // red is dominant + } else { + QVERIFY(qRed(line[i]) <= qBlue(line[i])); // blue is dominant + } + QVERIFY((qRed(line[i]) - 0.5) * (qAlpha(line[i - 1]) - 0.5) <= (qRed(line[i - 1]) + 0.5) * (qAlpha(line[i]) + 0.5)); // decreasing red + QVERIFY((qBlue(line[i]) + 0.5) * (qAlpha(line[i - 1]) + 0.5) >= (qBlue(line[i - 1]) - 0.5) * (qAlpha(line[i]) - 0.5)); // increasing blue + QVERIFY(qAbs(qAlpha(line[i]) - i) < 3); // linear alpha + QCOMPARE(qGreen(line[i]), 0); // no green component + } +} + +void tst_QPainter::drawPolygon() +{ + QImage img(128, 128, QImage::Format_ARGB32_Premultiplied); + + QPainterPathStroker stroker; + stroker.setWidth(1.5); + + QPainterPath path; + path.moveTo(2, 34); + path.lineTo(34, 2); + + QPolygonF poly = stroker.createStroke(path).toFillPolygon(); + + img.fill(0xffffffff); + QPainter p(&img); + p.setRenderHint(QPainter::Antialiasing); + p.setBrush(Qt::red); + p.setPen(Qt::NoPen); + p.drawPolygon(poly); + p.translate(64, 64); + p.drawPolygon(poly); + p.end(); + + QImage a = img.copy(); + + img.fill(0xffffffff); + p.begin(&img); + p.setRenderHint(QPainter::Antialiasing); + p.setBrush(Qt::red); + p.setPen(Qt::NoPen); + p.translate(64, 64); + p.drawPolygon(poly); + p.resetTransform(); + p.drawPolygon(poly); + p.end(); + + QCOMPARE(a, img); +} + +void tst_QPainter::inactivePainter() +{ + // This test succeeds if it doesn't segfault. + + QPainter p; + QPainterPath path; + QRegion region(QRect(20, 20, 60, 40)); + QPolygonF polygon(QVector() << QPointF(0, 0) << QPointF(12, 0) << QPointF(8, 6)); + path.addPolygon(polygon); + + p.save(); + p.restore(); + + p.background(); + p.setBackground(QBrush(Qt::blue)); + + p.brush(); + p.setBrush(Qt::red); + p.setBrush(Qt::NoBrush); + p.setBrush(QBrush(Qt::white, Qt::DiagCrossPattern)); + + p.backgroundMode(); + p.setBackgroundMode(Qt::OpaqueMode); + + p.boundingRect(QRectF(0, 0, 100, 20), Qt::AlignCenter, QLatin1String("Hello, World!")); + p.boundingRect(QRect(0, 0, 100, 20), Qt::AlignCenter, QLatin1String("Hello, World!")); + + p.brushOrigin(); + p.setBrushOrigin(QPointF(12, 34)); + p.setBrushOrigin(QPoint(12, 34)); + + p.clipPath(); + p.clipRegion(); + p.hasClipping(); + p.setClipPath(path); + p.setClipRect(QRectF(42, 42, 42, 42)); + p.setClipRect(QRect(42, 42, 42, 42)); + p.setClipRegion(region); + p.setClipping(true); + + p.combinedMatrix(); + p.combinedTransform(); + + p.compositionMode(); + p.setCompositionMode(QPainter::CompositionMode_Plus); + + p.device(); + p.deviceMatrix(); + p.deviceTransform(); + + p.font(); + p.setFont(QFont(QLatin1String("Times"), 24)); + + p.fontInfo(); + p.fontMetrics(); + + p.layoutDirection(); + p.setLayoutDirection(Qt::RightToLeft); + + p.opacity(); + p.setOpacity(0.75); + + p.pen(); + p.setPen(QPen(Qt::red)); + p.setPen(Qt::green); + p.setPen(Qt::NoPen); + + p.renderHints(); + p.setRenderHint(QPainter::Antialiasing, true); + p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, false); + + p.resetMatrix(); + p.resetTransform(); + p.rotate(1); + p.scale(2, 2); + p.shear(-1, 1); + p.translate(3, 14); + + p.viewTransformEnabled(); + p.setViewTransformEnabled(true); + + p.viewport(); + p.setViewport(QRect(10, 10, 620, 460)); + + p.window(); + p.setWindow(QRect(10, 10, 620, 460)); + + p.worldMatrix(); + p.setWorldMatrix(QMatrix().translate(43, 21), true); + p.setWorldMatrixEnabled(true); + + p.transform(); + p.setTransform(QTransform().translate(12, 34), true); + + p.worldTransform(); + p.setWorldTransform(QTransform().scale(0.5, 0.5), true); +} + +bool testCompositionMode(int src, int dst, int expected, QPainter::CompositionMode op) +{ + QImage actual(1, 1, QImage::Format_ARGB32_Premultiplied); + actual.fill(QColor(dst, dst, dst).rgb()); + + QPainter p(&actual); + p.setCompositionMode(op); + p.fillRect(0, 0, 1, 1, QColor(src, src, src)); + p.end(); + + if (qRed(actual.pixel(0, 0)) != expected) { + qDebug("Fail: mode %d, src[%d] dst [%d] actual [%d] expected [%d]", op, + src, dst, qRed(actual.pixel(0, 0)), expected); + return false; + } else { + return true; + } +} + +void tst_QPainter::extendedBlendModes() +{ + QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_Plus)); + QVERIFY(testCompositionMode( 0, 0, 0, QPainter::CompositionMode_Plus)); + QVERIFY(testCompositionMode(127, 128, 255, QPainter::CompositionMode_Plus)); + QVERIFY(testCompositionMode(127, 0, 127, QPainter::CompositionMode_Plus)); + QVERIFY(testCompositionMode( 0, 127, 127, QPainter::CompositionMode_Plus)); + QVERIFY(testCompositionMode(255, 0, 255, QPainter::CompositionMode_Plus)); + QVERIFY(testCompositionMode( 0, 255, 255, QPainter::CompositionMode_Plus)); + QVERIFY(testCompositionMode(128, 128, 255, QPainter::CompositionMode_Plus)); + + QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_Multiply)); + QVERIFY(testCompositionMode( 0, 0, 0, QPainter::CompositionMode_Multiply)); + QVERIFY(testCompositionMode(127, 255, 127, QPainter::CompositionMode_Multiply)); + QVERIFY(testCompositionMode(255, 127, 127, QPainter::CompositionMode_Multiply)); + QVERIFY(testCompositionMode( 63, 255, 63, QPainter::CompositionMode_Multiply)); + QVERIFY(testCompositionMode(255, 63, 63, QPainter::CompositionMode_Multiply)); + QVERIFY(testCompositionMode(127, 127, 63, QPainter::CompositionMode_Multiply)); + + QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_Screen)); + QVERIFY(testCompositionMode( 0, 0, 0, QPainter::CompositionMode_Screen)); + QVERIFY(testCompositionMode( 63, 255, 255, QPainter::CompositionMode_Screen)); + QVERIFY(testCompositionMode(255, 63, 255, QPainter::CompositionMode_Screen)); + QVERIFY(testCompositionMode( 63, 0, 63, QPainter::CompositionMode_Screen)); + QVERIFY(testCompositionMode( 0, 63, 63, QPainter::CompositionMode_Screen)); + QVERIFY(testCompositionMode(127, 127, 191, QPainter::CompositionMode_Screen)); + + QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_Overlay)); + QVERIFY(testCompositionMode( 0, 0, 0, QPainter::CompositionMode_Overlay)); + QVERIFY(testCompositionMode( 63, 63, 31, QPainter::CompositionMode_Overlay)); + QVERIFY(testCompositionMode( 63, 255, 255, QPainter::CompositionMode_Overlay)); + + QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_Darken)); + QVERIFY(testCompositionMode( 0, 0, 0, QPainter::CompositionMode_Darken)); + QVERIFY(testCompositionMode( 63, 63, 63, QPainter::CompositionMode_Darken)); + QVERIFY(testCompositionMode( 63, 255, 63, QPainter::CompositionMode_Darken)); + QVERIFY(testCompositionMode(255, 63, 63, QPainter::CompositionMode_Darken)); + QVERIFY(testCompositionMode( 63, 127, 63, QPainter::CompositionMode_Darken)); + QVERIFY(testCompositionMode(127, 63, 63, QPainter::CompositionMode_Darken)); + + QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_Lighten)); + QVERIFY(testCompositionMode( 0, 0, 0, QPainter::CompositionMode_Lighten)); + QVERIFY(testCompositionMode( 63, 63, 63, QPainter::CompositionMode_Lighten)); + QVERIFY(testCompositionMode( 63, 255, 255, QPainter::CompositionMode_Lighten)); + QVERIFY(testCompositionMode(255, 63, 255, QPainter::CompositionMode_Lighten)); + QVERIFY(testCompositionMode( 63, 127, 127, QPainter::CompositionMode_Lighten)); + QVERIFY(testCompositionMode(127, 63, 127, QPainter::CompositionMode_Lighten)); + + QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_ColorDodge)); + QVERIFY(testCompositionMode( 0, 0, 0, QPainter::CompositionMode_ColorDodge)); + QVERIFY(testCompositionMode( 63, 127, 169, QPainter::CompositionMode_ColorDodge)); + QVERIFY(testCompositionMode(191, 127, 255, QPainter::CompositionMode_ColorDodge)); + QVERIFY(testCompositionMode(127, 191, 255, QPainter::CompositionMode_ColorDodge)); + + QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_ColorBurn)); + QVERIFY(testCompositionMode( 0, 0, 0, QPainter::CompositionMode_ColorBurn)); + QVERIFY(testCompositionMode(127, 127, 0, QPainter::CompositionMode_ColorBurn)); + QVERIFY(testCompositionMode(128, 128, 2, QPainter::CompositionMode_ColorBurn)); + QVERIFY(testCompositionMode(191, 127, 84, QPainter::CompositionMode_ColorBurn)); + + QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_HardLight)); + QVERIFY(testCompositionMode( 0, 0, 0, QPainter::CompositionMode_HardLight)); + QVERIFY(testCompositionMode(127, 127, 127, QPainter::CompositionMode_HardLight)); + QVERIFY(testCompositionMode( 63, 63, 31, QPainter::CompositionMode_HardLight)); + QVERIFY(testCompositionMode(127, 63, 63, QPainter::CompositionMode_HardLight)); + + QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_SoftLight)); + QVERIFY(testCompositionMode( 0, 0, 0, QPainter::CompositionMode_SoftLight)); + QVERIFY(testCompositionMode(127, 127, 127, QPainter::CompositionMode_SoftLight)); + QVERIFY(testCompositionMode( 63, 63, 86, QPainter::CompositionMode_SoftLight)); + QVERIFY(testCompositionMode(127, 63, 63, QPainter::CompositionMode_SoftLight)); + + QVERIFY(testCompositionMode(255, 255, 0, QPainter::CompositionMode_Difference)); + QVERIFY(testCompositionMode( 0, 0, 0, QPainter::CompositionMode_Difference)); + QVERIFY(testCompositionMode(255, 0, 255, QPainter::CompositionMode_Difference)); + QVERIFY(testCompositionMode(127, 127, 0, QPainter::CompositionMode_Difference)); + QVERIFY(testCompositionMode(127, 128, 1, QPainter::CompositionMode_Difference)); + QVERIFY(testCompositionMode(127, 63, 64, QPainter::CompositionMode_Difference)); + QVERIFY(testCompositionMode( 0, 127, 127, QPainter::CompositionMode_Difference)); + + QVERIFY(testCompositionMode(255, 255, 0, QPainter::CompositionMode_Exclusion)); + QVERIFY(testCompositionMode( 0, 0, 0, QPainter::CompositionMode_Exclusion)); + QVERIFY(testCompositionMode(255, 0, 255, QPainter::CompositionMode_Exclusion)); + QVERIFY(testCompositionMode(127, 127, 127, QPainter::CompositionMode_Exclusion)); + QVERIFY(testCompositionMode( 63, 127, 127, QPainter::CompositionMode_Exclusion)); + QVERIFY(testCompositionMode( 63, 63, 95, QPainter::CompositionMode_Exclusion)); + QVERIFY(testCompositionMode(191, 191, 96, QPainter::CompositionMode_Exclusion)); +} + +void tst_QPainter::zeroOpacity() +{ + QImage source(1, 1, QImage::Format_ARGB32_Premultiplied); + source.fill(0xffffffff); + + QImage target(1, 1, QImage::Format_RGB32); + target.fill(0xff000000); + + QPainter p(&target); + p.setOpacity(0.0); + p.drawImage(0, 0, source); + p.end(); + + QCOMPARE(target.pixel(0, 0), 0xff000000); +} + +void tst_QPainter::clippingBug() +{ + QImage img(32, 32, QImage::Format_ARGB32_Premultiplied); + img.fill(0); + + QImage expected = img; + QPainter p(&expected); + p.fillRect(1, 1, 30, 30, Qt::red); + p.end(); + + QPainterPath path; + path.addRect(1, 1, 30, 30); + path.addRect(1, 1, 30, 30); + path.addRect(1, 1, 30, 30); + + p.begin(&img); + p.setClipPath(path); + p.fillRect(0, 0, 32, 32, Qt::red); + p.end(); + + QCOMPARE(img, expected); +} + +void tst_QPainter::emptyClip() +{ + QImage img(64, 64, QImage::Format_ARGB32_Premultiplied); + QPainter p(&img); + p.setRenderHints(QPainter::Antialiasing); + p.setClipRect(0, 32, 64, 0); + p.fillRect(0, 0, 64, 64, Qt::white); + + QPainterPath path; + path.lineTo(64, 0); + path.lineTo(64, 64); + path.lineTo(40, 64); + path.lineTo(40, 80); + path.lineTo(0, 80); + + p.fillPath(path, Qt::green); +} + +void tst_QPainter::drawImage_1x1() +{ + QImage source(1, 1, QImage::Format_ARGB32_Premultiplied); + source.fill(0xffffffff); + + QImage img(32, 32, QImage::Format_ARGB32_Premultiplied); + img.fill(0xff000000); + QPainter p(&img); + p.drawImage(QRectF(0.9, 0.9, 32, 32), source); + p.end(); + + QImage expected = img; + expected.fill(0xff000000); + p.begin(&expected); + p.fillRect(1, 1, 31, 31, Qt::white); + p.end(); + + QCOMPARE(img, expected); +} + +void tst_QPainter::taskQT4444_dontOverflowDashOffset() +{ + QPainter p; + + QPen pen; + pen.setWidth(2); + pen.setStyle(Qt::DashDotLine); + + QPointF point[4]; + point[0] = QPointF(182.50868749707968,347.78457234212630); + point[1] = QPointF(182.50868749707968,107.22501998401277); + point[2] = QPointF(182.50868749707968,107.22501998401277); + point[3] = QPointF(520.46600762283651,107.22501998401277); + + QImage crashImage(QSize(1000, 120), QImage::Format_ARGB32_Premultiplied); + p.begin(&crashImage); + p.setPen(pen); + p.drawLines(point, 2); + p.end(); + + QVERIFY(true); // Don't crash +} + +void tst_QPainter::painterBegin() +{ + QImage nullImage; + QImage indexed8Image(16, 16, QImage::Format_Indexed8); + QImage rgb32Image(16, 16, QImage::Format_RGB32); + QImage argb32Image(16, 16, QImage::Format_ARGB32_Premultiplied); + + QPainter p; + + // Painting on null image should fail. + QVERIFY(!p.begin(&nullImage)); + + // Check that the painter is not messed up by using it on another image. + QVERIFY(p.begin(&rgb32Image)); + QVERIFY(p.end()); + + // If painting on indexed8 image fails, the painter state should still be OK. + if (p.begin(&indexed8Image)) + QVERIFY(p.end()); + QVERIFY(p.begin(&rgb32Image)); + QVERIFY(p.end()); + + // Try opening a painter on the two different images. + QVERIFY(p.begin(&rgb32Image)); + QVERIFY(!p.begin(&argb32Image)); + QVERIFY(p.end()); + + // Try opening two painters on the same image. + QVERIFY(p.begin(&rgb32Image)); + QPainter q; + QVERIFY(!q.begin(&rgb32Image)); + QVERIFY(!q.end()); + QVERIFY(p.end()); + + // Try ending an inactive painter. + QVERIFY(!p.end()); +} + +QTEST_MAIN(tst_QPainter) +#include "tst_qpainter.moc"