diff --git a/plugins/paintops/watercolor/CMakeLists.txt b/plugins/paintops/watercolor/CMakeLists.txt index 6620c735ca..81e93fdee2 100644 --- a/plugins/paintops/watercolor/CMakeLists.txt +++ b/plugins/paintops/watercolor/CMakeLists.txt @@ -1,11 +1,14 @@ set(kritawatercolorpaintop_SOURCES kis_splat.cpp kis_wetmap.cpp kis_splat_generator.cpp ) +add_subdirectory(tests) + add_library(kritawatercolorpaintop MODULE ${kritawatercolorpaintop_SOURCES}) +generate_export_header(kritawatercolorpaintop EXPORT_MACRO_NAME WATERCOLORPAINT_EXPORT) target_link_libraries(kritawatercolorpaintop kritalibpaintop) install(TARGETS kritawatercolorpaintop DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/paintops/watercolor/kis_wetmap.cpp b/plugins/paintops/watercolor/kis_wetmap.cpp index 77f3ab8264..64924dbd16 100644 --- a/plugins/paintops/watercolor/kis_wetmap.cpp +++ b/plugins/paintops/watercolor/kis_wetmap.cpp @@ -1,84 +1,100 @@ /* This file is part of the KDE project * * Copyright (C) 2017 Grigory Tantsevov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_wetmap.h" #include "KoColorSpaceRegistry.h" #include "kis_sequential_iterator.h" #include +#include + +using namespace Arithmetic; KisWetMap::KisWetMap() { m_wetMap = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb16()); } +KisWetMap::~KisWetMap() +{ + delete m_wetMap; +} + // Adding water in circle with the given center and radius void KisWetMap::addWater(QPoint pos, qreal radius) { QRect rect(pos - QPointF(radius, radius).toPoint(), pos + QPointF(radius, radius).toPoint()); KisSequentialIterator it(m_wetMap, rect); do { - qint16 *mydata = reinterpret_cast(it.rawData()); - mydata[0] = 255; + //qDebug() << it.x() << it.y() << it.x() * it.x() + it.y() * it.y() << radius * radius; QPoint place(it.x(), it.y()); QVector2D vec(place - pos); - - vec.normalize(); - - mydata[1] = 255 * vec.length(); - mydata[2] = 255 * vec.length(); + if ((vec.x() * vec.x() + vec.y() * vec.y()) <= (radius * radius)) { + quint16 *mydata = reinterpret_cast(it.rawData()); + vec.normalize(); + mydata[0] = unitValue(); + mydata[3] = unitValue(); + mydata[1] = unitValue() * vec.x(); + mydata[2] = unitValue() * vec.y(); + } } while (it.nextPixel()); } // Updating wetmap for simulating drying process void KisWetMap::update() { - KisSequentialIterator it(m_wetMap, m_wetMap->exactBounds()); + if (m_wetMap->exactBounds().size() != QSize(0, 0)) { + KisSequentialIterator it(m_wetMap, m_wetMap->exactBounds()); - do { - qint16 *mydata = reinterpret_cast(it.rawData()); - if (mydata[0] > 0) { // If there some water - mydata[0]--; // The "evaporated" part of the water - } else { // If there is no water - mydata[1] = 0; // Remove speed vector - mydata[2] = 0; - } - } while (it.nextPixel()); + do { + quint16 *mydata = reinterpret_cast(it.rawData()); + if (mydata[0] > 255) { // If there some water + mydata[0] -= 256; // The "evaporated" part of the water + } else { // If there is no water + mydata[1] = zeroValue(); // Remove speed vector + mydata[2] = zeroValue(); + mydata[3] = zeroValue(); + mydata[0] = zeroValue(); + } + if (m_wetMap->exactBounds().size() == QSize(0, 0)) + break; + } while (it.nextPixel()); + } } // Returns water count in point (x, y) int KisWetMap::getWater(int x, int y) { KisSequentialIterator it(m_wetMap, m_wetMap->exactBounds()); while (it.x() != x && it.y() != y) it.nextPixel(); - qint16 *mydata = reinterpret_cast(it.rawData()); + quint16 *mydata = reinterpret_cast(it.rawData()); return mydata[0]; } // Returns paint device for visualizing wet map KisPaintDeviceSP KisWetMap::getPaintDevice() { return m_wetMap; } diff --git a/plugins/paintops/watercolor/kis_wetmap.h b/plugins/paintops/watercolor/kis_wetmap.h index 3069b0d961..ea83820594 100644 --- a/plugins/paintops/watercolor/kis_wetmap.h +++ b/plugins/paintops/watercolor/kis_wetmap.h @@ -1,46 +1,43 @@ /* This file is part of the KDE project * * Copyright (C) 2017 Grigory Tantsevov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_WETMAP_H #define KIS_WETMAP_H #include "kritawatercolorpaintop_export.h" #include "kis_paint_device.h" class WATERCOLORPAINT_EXPORT KisWetMap { public: KisWetMap(); + ~KisWetMap(); void addWater(QPoint pos, qreal radius); void update(); int getWater(int x, int y); KisPaintDeviceSP getPaintDevice(); private: KisPaintDeviceSP m_wetMap; - float* m_waterVelocitiesX; - float* m_waterVelocitiesY; - int m_width; - int m_height; }; #endif diff --git a/plugins/paintops/watercolor/tests/CMakeLists.txt b/plugins/paintops/watercolor/tests/CMakeLists.txt new file mode 100644 index 0000000000..90d180ffd7 --- /dev/null +++ b/plugins/paintops/watercolor/tests/CMakeLists.txt @@ -0,0 +1,15 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories(${CMAKE_SOURCE_DIR}/.. ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(kis_watercolorop_test.cpp + TEST_NAME krita-paintop-WetMapTest + LINK_LIBRARIES kritaimage kritalibpaintop kritawatercolorpaintop Qt5::Test) +add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}") + +#krita_add_broken_unit_test(kis_watercolorop_test.cpp ../../../../sdk/tests/stroke_testing_utils.cpp +# TEST_NAME krita-plugins-KisWaterColorOpTest +# LINK_LIBRARIES kritaimage kritawatercolorpaintop kritalibpaintop Qt5::Test) diff --git a/plugins/paintops/watercolor/tests/kis_brushop_test.cpp b/plugins/paintops/watercolor/tests/kis_brushop_test.cpp new file mode 100644 index 0000000000..8979adeaa9 --- /dev/null +++ b/plugins/paintops/watercolor/tests/kis_brushop_test.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2013 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kis_brushop_test.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class TestBrushOp : public TestUtil::QImageBasedTest +{ +public: + TestBrushOp(const QString &presetFileName, const QString &prefix = "simple") + : QImageBasedTest("brushop") { + m_presetFileName = presetFileName; + m_prefix = prefix; + } + + virtual ~TestBrushOp() {} + + void test() { + test(false, false, 0.0); + test(false, false, 10.0); + test(false, false, 20.0); + + test(true, false, 0.0); + test(true, false, 10.0); + test(true, false, 20.0); + + test(false, true, 0.0); + test(false, true, 10.0); + test(false, true, 20.0); + + test(true, true, 0.0); + test(true, true, 10.0); + test(true, true, 20.0); + } + + void test(bool mirrorX, bool mirrorY, qreal rotation) { + test(mirrorX, mirrorY, rotation, false, false); + test(mirrorX, mirrorY, rotation, true, false); + test(mirrorX, mirrorY, rotation, false, true); + test(mirrorX, mirrorY, rotation, true, true); + } + + void test(bool mirrorX, bool mirrorY, qreal rotation, bool mirrorDabX, bool mirrorDabY) { + test(mirrorX, mirrorY, rotation, mirrorDabX, mirrorDabY, 0.0); + test(mirrorX, mirrorY, rotation, mirrorDabX, mirrorDabY, 360.0 - 10.0); + test(mirrorX, mirrorY, rotation, mirrorDabX, mirrorDabY, 360.0 - 20.0); + } + + void test(bool mirrorX, bool mirrorY, qreal rotation, bool mirrorDabX, bool mirrorDabY, qreal dabRotation) { + + KisSurrogateUndoStore *undoStore = new KisSurrogateUndoStore(); + KisImageSP image = createTrivialImage(undoStore); + image->initialRefreshGraph(); + + KisNodeSP paint1 = findNode(image->root(), "paint1"); + + QVERIFY(paint1->extent().isEmpty()); + + KisPainter gc(paint1->paintDevice()); + + QScopedPointer manager( + utils::createResourceManager(image, 0, m_presetFileName)); + + KisPaintOpPresetSP preset = + manager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); + + preset->settings()->setCanvasRotation(rotation); + preset->settings()->setCanvasMirroring(mirrorY, mirrorX); + + + if (mirrorDabX || mirrorDabY) { + KisPaintOpSettingsSP settings = preset->settings()->clone(); + + KisPressureMirrorOption mirrorOption; + mirrorOption.readOptionSetting(settings); + + mirrorOption.setChecked(true); + mirrorOption.setCurveUsed(false); + + mirrorOption.enableHorizontalMirror(mirrorDabX); + mirrorOption.enableVerticalMirror(mirrorDabY); + + mirrorOption.writeOptionSetting(settings.data()); + + preset->setSettings(settings); + } + + if (dabRotation != 0.0) { + KisPaintOpSettingsSP settings = preset->settings()->clone(); + + KisPressureRotationOption rotationOption; + rotationOption.readOptionSetting(settings); + + rotationOption.setChecked(true); + rotationOption.setCurveUsed(false); + + rotationOption.setValue(dabRotation / 360.0); + + rotationOption.writeOptionSetting(settings.data()); + + preset->setSettings(settings); + } + + + QString testName = + QString("%7_cmY_%1_cmX_%2_cR_%3_dmX_%4_dmY_%5_dR_%6") + .arg(mirrorY) + .arg(mirrorX) + .arg(rotation) + .arg(mirrorDabX) + .arg(mirrorDabY) + .arg(std::fmod(360.0 - dabRotation, 360.0)) + .arg(m_prefix); + + KisResourcesSnapshotSP resources = + new KisResourcesSnapshot(image, + paint1, + manager.data()); + + resources->setupPainter(&gc); + + doPaint(gc); + + checkOneLayer(image, paint1, testName); + } + + virtual void doPaint(KisPainter &gc) { + KisPaintInformation pi(QPointF(100, 100), 1.0); + + KisDistanceInformation dist; + gc.paintAt(pi, &dist); + } + + QString m_presetFileName; + QString m_prefix; +}; + +class TestBrushOpLines : public TestBrushOp +{ +public: + TestBrushOpLines(const QString &presetFileName) + : TestBrushOp(presetFileName) { + } + + void doPaint(KisPainter &gc) override { + + QVector vector; + + vector << KisPaintInformation(QPointF(100, 100)); + vector << KisPaintInformation(QPointF(200, 150)); + vector << KisPaintInformation(QPointF(100, 350)); + + KisDistanceInformation dist; + + for (int i = 1; i < vector.size(); i++) { + gc.paintLine(vector[i - 1], vector[i], &dist); + } + } +}; + +class TestBrushOpPressureLines : public TestBrushOp +{ +public: + TestBrushOpPressureLines(const QString &presetFileName, const QString &prefix) + : TestBrushOp(presetFileName, prefix) { + } + + void doPaint(KisPainter &gc) override { + + QVector vector; + + vector << KisPaintInformation(QPointF(0, 0), 0.2); + vector << KisPaintInformation(QPointF(200, 50), 1.0); + vector << KisPaintInformation(QPointF(100, 250), 0.0); + vector << KisPaintInformation(QPointF(200, 150), 1.0); + vector << KisPaintInformation(QPointF(100, 350), 1.0); + + KisDistanceInformation dist; + + for (int i = 1; i < vector.size(); i++) { + gc.paintLine(vector[i - 1], vector[i], &dist); + } + } +}; + +#include +void KisBrushOpTest::initTestCase() +{ + KoResourcePaths::addResourceDir("kis_brushes", QString(SYSTEM_RESOURCES_DATA_DIR) + "/brushes"); +} + +void KisBrushOpTest::testRotationMirroring() +{ + TestBrushOp t("LR_simple.kpp"); + t.test(); +} + +void KisBrushOpTest::testRotationMirroringDrawingAngle() +{ + TestBrushOpLines t("LR_drawing_angle.kpp"); + t.test(); +} + +void KisBrushOpTest::testMagicSeven() +{ + /** + * A special preset that forces Qt to bug: + * mask size: 56 + * brush size: 7 + * therefore scale is: 0.125 + * which causes QTransform work as a pure Translate in the mipmap + */ + + TestBrushOpPressureLines t("magic_seven.kpp", "magicseven"); + t.test(); +} + +QTEST_MAIN(KisBrushOpTest) diff --git a/plugins/paintops/watercolor/tests/kis_brushop_test.h b/plugins/paintops/watercolor/tests/kis_brushop_test.h new file mode 100644 index 0000000000..313a5e6a41 --- /dev/null +++ b/plugins/paintops/watercolor/tests/kis_brushop_test.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __KIS_BRUSHOP_TEST_H +#define __KIS_BRUSHOP_TEST_H + +#include + +class KisBrushOpTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + + void testRotationMirroring(); + void testRotationMirroringDrawingAngle(); + void testMagicSeven(); +}; + +#endif /* __KIS_BRUSHOP_TEST_H */ diff --git a/plugins/paintops/watercolor/tests/kis_watercolorop_test.cpp b/plugins/paintops/watercolor/tests/kis_watercolorop_test.cpp new file mode 100644 index 0000000000..845da12cad --- /dev/null +++ b/plugins/paintops/watercolor/tests/kis_watercolorop_test.cpp @@ -0,0 +1,128 @@ +#include "kis_watercolorop_test.h" + +#include +#include + +#include "plugins/paintops/watercolor/kis_wetmap.h" + +#include +#include +#include + +class TestWetMap : public TestUtil::QImageBasedTest +{ +public: + TestWetMap() : QImageBasedTest("WetMap") {} + + void test() { + QVector pos; + QVector radius; + + pos.push_back(QPoint(10, 10)); + radius.push_back(5.5); + pos.push_back(QPoint(50, 50)); + radius.push_back(25); + pos.push_back(QPoint(63, 60)); + radius.push_back(19.36); + test(pos, radius); + + test(256, QString("Full_Drying")); + test(128, QString("Half_Drying")); + test(64, QString("Quarter_Drying")); + + pos.clear(); + radius.clear(); + for (int i = 0; i < 3; i++) + radius.push_back(50); + pos.push_back(QPoint(50, 50)); + pos.push_back(QPoint(100, 50)); + pos.push_back(QPoint(150, 50)); + QVector addingTime; + addingTime.push_back(0); + addingTime.push_back(64); + addingTime.push_back(128); + test(256, pos, radius, addingTime, "One_Line"); + test(300, pos, radius, addingTime, "One_Line_Overtime"); + + pos.clear(); + pos.push_back(QPoint(50, 50)); + pos.push_back(QPoint(125, 50)); + pos.push_back(QPoint(100, 125)); + test(256, pos, radius, addingTime, "Triange"); + test(300, pos, radius, addingTime, "Triange_Overtime"); + + test(0); + test(64); + test(128); + test(256); + + test(300); + } + + // Testing adding watter on wetmap + void test(QVector pos, QVector radius) { + + KisWetMap *wetMap = new KisWetMap(); + for (int i = 0; i < pos.size(); i++) { + wetMap->addWater(pos.at(i), radius.at(i)); + } + QVERIFY(TestUtil::checkQImage(wetMap->getPaintDevice()->convertToQImage(0), QString("WaterColor"), + QString("WetMap"), QString("Adding_Water"))); + delete wetMap; + } + + // Testing drying + void test(int time, QString prefix) { + KisWetMap *wetMap = new KisWetMap(); + QPoint pos(50, 50); + qreal radius = 25; + + wetMap->addWater(pos, radius); + + for (int i = 0; i < time; i++) { + wetMap->update(); + } + + QVERIFY(TestUtil::checkQImage(wetMap->getPaintDevice()->convertToQImage(0), QString("WaterColor"), + QString("WetMap"), prefix)); + delete wetMap; + } + + // Tesitng multiple actions with wetmap + void test(int fullWorkTime, QVector pos, QVector radius, + QVector addingTime, QString prefix) { + KisWetMap *wetMap = new KisWetMap(); + int posI; + for (int i = 0; i <= fullWorkTime; i++) { + if ((posI = addingTime.indexOf(i)) != -1) { + wetMap->addWater(pos.at(posI), radius.at(posI)); + } + wetMap->update(); + } + + QVERIFY(TestUtil::checkQImage(wetMap->getPaintDevice()->convertToQImage(0), QString("WaterColor"), + QString("WetMap_Multiple_Actions"), prefix)); + delete wetMap; + } + + // Testing reading watercount + void test(int time) { + KisWetMap *wetMap = new KisWetMap(); + wetMap->addWater(QPoint(50, 50), 50); + for (int i = 0; i < time; i++) + wetMap->update(); + int realVal = wetMap->getWater(50, 50); + int expectedVal = 65535 - 256 * time; + if (expectedVal < 0) + expectedVal = 0; + QCOMPARE(realVal, expectedVal); + } +}; + +void WetMapTest::testWetMap() +{ + TestWetMap t; + t.test(); +} + +QTEST_MAIN(WetMapTest) diff --git a/plugins/paintops/watercolor/kis_wetmap.h b/plugins/paintops/watercolor/tests/kis_watercolorop_test.h similarity index 65% copy from plugins/paintops/watercolor/kis_wetmap.h copy to plugins/paintops/watercolor/tests/kis_watercolorop_test.h index 3069b0d961..cd185c3204 100644 --- a/plugins/paintops/watercolor/kis_wetmap.h +++ b/plugins/paintops/watercolor/tests/kis_watercolorop_test.h @@ -1,46 +1,33 @@ /* This file is part of the KDE project * * Copyright (C) 2017 Grigory Tantsevov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -#ifndef KIS_WETMAP_H -#define KIS_WETMAP_H +#ifndef __KIS_WATERCOLOROP_TEST_H +#define __KIS_WATERCOLOROP_TEST_H -#include "kritawatercolorpaintop_export.h" +#include -#include "kis_paint_device.h" - -class WATERCOLORPAINT_EXPORT KisWetMap +class WetMapTest : public QObject { -public: - KisWetMap(); - - void addWater(QPoint pos, qreal radius); - void update(); - int getWater(int x, int y); - - KisPaintDeviceSP getPaintDevice(); -private: - KisPaintDeviceSP m_wetMap; - float* m_waterVelocitiesX; - float* m_waterVelocitiesY; - int m_width; - int m_height; + Q_OBJECT +private Q_SLOTS: + void testWetMap(); }; -#endif +#endif /* __KIS_WATERCOLOROP_TEST_H */