[calligra/krita-chili-kazakov] krita: [FEATURE] Implemented Wash mode for the liquify transform tool

Dmitry Kazakov dimula73 at gmail.com
Fri Oct 17 13:16:04 UTC 2014


Git commit 16d55c02bb56c766892717d3508f7f13620f947d by Dmitry Kazakov.
Committed on 17/10/2014 at 13:15.
Pushed by dkazakov into branch 'krita-chili-kazakov'.

[FEATURE] Implemented Wash mode for the liquify transform tool

There is a fundamental difference between Wash mode and BuildUp mode of
the liquify brush. When you paint on the same place with in BuildUp
(default) mode it adds deformations infinitely, until you hardly see the
original piece. When you use Wash mode, all deformations are limited
by the Amount value, that is they will not exceed this level. You can also
control how fast the deformations will rush to the maximum level by
adjusting 'Flow' parameter, which is available only in this mode.

CCMAIL:kimageshop at kde.org

M  +78   -10   krita/image/kis_liquify_transform_worker.cpp
M  +9    -3    krita/image/kis_liquify_transform_worker.h
M  +6    -6    krita/image/tests/kis_liquify_transform_worker_test.cpp
M  +6    -5    krita/plugins/tools/tool_transform2/kis_liquify_paintop.cpp
M  +5    -0    krita/plugins/tools/tool_transform2/kis_liquify_properties.cpp
M  +20   -1    krita/plugins/tools/tool_transform2/kis_liquify_properties.h
M  +47   -0    krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp
M  +2    -0    krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h
M  +22   -5    krita/plugins/tools/tool_transform2/wdg_tool_transform.ui

http://commits.kde.org/calligra/16d55c02bb56c766892717d3508f7f13620f947d

diff --git a/krita/image/kis_liquify_transform_worker.cpp b/krita/image/kis_liquify_transform_worker.cpp
index a8ed648..3e4c850 100644
--- a/krita/image/kis_liquify_transform_worker.cpp
+++ b/krita/image/kis_liquify_transform_worker.cpp
@@ -45,9 +45,22 @@ struct KisLiquifyTransformWorker::Private
     struct MapIndexesOp;
 
     template <class ProcessOp>
+    void processTransformedPixelsBuildUp(ProcessOp op,
+                                         const QPointF &base,
+                                         qreal sigma);
+
+    template <class ProcessOp>
+    void processTransformedPixelsWash(ProcessOp op,
+                                      const QPointF &base,
+                                      qreal sigma,
+                                      qreal flow);
+
+    template <class ProcessOp>
     void processTransformedPixels(ProcessOp op,
                                   const QPointF &base,
-                                  qreal sigma);
+                                  qreal sigma,
+                                  bool useWashMode,
+                                  qreal flow);
 };
 
 KisLiquifyTransformWorker::KisLiquifyTransformWorker(const QRect &srcBounds,
@@ -159,9 +172,9 @@ void KisLiquifyTransformWorker::undoPoints(const QPointF &base,
 
 template <class ProcessOp>
 void KisLiquifyTransformWorker::Private::
-processTransformedPixels(ProcessOp op,
-                         const QPointF &base,
-                         qreal sigma)
+processTransformedPixelsBuildUp(ProcessOp op,
+                                const QPointF &base,
+                                qreal sigma)
 {
     const qreal maxDist = ProcessOp::maxDistCoeff * sigma;
     QRectF clipRect(base.x() - maxDist, base.y() - maxDist,
@@ -182,6 +195,55 @@ processTransformedPixels(ProcessOp op,
     }
 }
 
+template <class ProcessOp>
+void KisLiquifyTransformWorker::Private::
+processTransformedPixelsWash(ProcessOp op,
+                             const QPointF &base,
+                             qreal sigma,
+                             qreal flow)
+{
+    const qreal maxDist = ProcessOp::maxDistCoeff * sigma;
+    QRectF clipRect(base.x() - maxDist, base.y() - maxDist,
+                    2 * maxDist, 2 * maxDist);
+
+    QVector<QPointF>::iterator it = transformedPoints.begin();
+    QVector<QPointF>::iterator end = transformedPoints.end();
+
+    QVector<QPointF>::iterator refIt = originalPoints.begin();
+    KIS_ASSERT_RECOVER_RETURN(originalPoints.size() ==
+                              transformedPoints.size());
+
+    for (; it != end; ++it, ++refIt) {
+        if (!clipRect.contains(*it)) continue;
+
+        QPointF diff = *refIt - base;
+        qreal dist = KisAlgebra2D::norm(diff);
+        if (dist > maxDist) continue;
+
+        const qreal lambda = exp(-0.5 * pow2(dist / sigma));
+        QPointF dstPt = op(*refIt, base, diff, lambda);
+
+        if (kisDistance(dstPt, *refIt) > kisDistance(*it, *refIt)) {
+            *it = (1.0 - flow) * (*it) + flow * dstPt;
+        }
+    }
+}
+
+template <class ProcessOp>
+void KisLiquifyTransformWorker::Private::
+processTransformedPixels(ProcessOp op,
+                         const QPointF &base,
+                         qreal sigma,
+                         bool useWashMode,
+                         qreal flow)
+{
+    if (useWashMode) {
+        processTransformedPixelsWash(op, base, sigma, flow);
+    } else {
+        processTransformedPixelsBuildUp(op, base, sigma);
+    }
+}
+
 struct TranslateOp
 {
     TranslateOp(const QPointF &offset) : m_offset(offset) {}
@@ -248,26 +310,32 @@ struct RotateOp
 
 void KisLiquifyTransformWorker::translatePoints(const QPointF &base,
                                                 const QPointF &offset,
-                                                qreal sigma)
+                                                qreal sigma,
+                                                bool useWashMode,
+                                                qreal flow)
 {
     TranslateOp op(offset);
-    m_d->processTransformedPixels(op, base, sigma);
+    m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
 }
 
 void KisLiquifyTransformWorker::scalePoints(const QPointF &base,
                                             qreal scale,
-                                            qreal sigma)
+                                            qreal sigma,
+                                            bool useWashMode,
+                                            qreal flow)
 {
     ScaleOp op(scale);
-    m_d->processTransformedPixels(op, base, sigma);
+    m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
 }
 
 void KisLiquifyTransformWorker::rotatePoints(const QPointF &base,
                                              qreal angle,
-                                             qreal sigma)
+                                             qreal sigma,
+                                             bool useWashMode,
+                                             qreal flow)
 {
     RotateOp op(angle);
-    m_d->processTransformedPixels(op, base, sigma);
+    m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
 }
 
 struct KisLiquifyTransformWorker::Private::MapIndexesOp {
diff --git a/krita/image/kis_liquify_transform_worker.h b/krita/image/kis_liquify_transform_worker.h
index 8977097..b6f269b 100644
--- a/krita/image/kis_liquify_transform_worker.h
+++ b/krita/image/kis_liquify_transform_worker.h
@@ -45,15 +45,21 @@ public:
 
     void translatePoints(const QPointF &base,
                          const QPointF &offset,
-                         qreal sigma);
+                         qreal sigma,
+                         bool useWashMode,
+                         qreal flow);
 
     void scalePoints(const QPointF &base,
                      qreal scale,
-                     qreal sigma);
+                     qreal sigma,
+                     bool useWashMode,
+                     qreal flow);
 
     void rotatePoints(const QPointF &base,
                       qreal angle,
-                      qreal sigma);
+                      qreal sigma,
+                      bool useWashMode,
+                      qreal flow);
 
     void undoPoints(const QPointF &base,
                     qreal amount,
diff --git a/krita/image/tests/kis_liquify_transform_worker_test.cpp b/krita/image/tests/kis_liquify_transform_worker_test.cpp
index 507c0a4..180efac 100644
--- a/krita/image/tests/kis_liquify_transform_worker_test.cpp
+++ b/krita/image/tests/kis_liquify_transform_worker_test.cpp
@@ -148,11 +148,11 @@ void KisLiquifyTransformWorkerTest::testPoints()
     QBENCHMARK_ONCE {
         worker.translatePoints(QPointF(100,100),
                                QPointF(50, 0),
-                               50);
+                               50, false, 0.2);
 
         worker.scalePoints(QPointF(400,100),
                            0.9,
-                           50);
+                           50, false, 0.2);
 
         worker.undoPoints(QPointF(400,100),
                            1.0,
@@ -160,15 +160,15 @@ void KisLiquifyTransformWorkerTest::testPoints()
 
         worker.scalePoints(QPointF(400,300),
                            0.5,
-                           50);
+                           50, false, 0.2);
 
         worker.scalePoints(QPointF(100,300),
                            -0.5,
-                           30);
+                           30, false, 0.2);
 
         worker.rotatePoints(QPointF(100,500),
                             M_PI / 4,
-                            50);
+                            50, false, 0.2);
     }
 
     worker.run(dev);
@@ -198,7 +198,7 @@ void KisLiquifyTransformWorkerTest::testPointsQImage()
 
     worker.translatePoints(QPointF(100,100),
                            QPointF(50, 0),
-                           50);
+                           50, false, 0.2);
 
     QRect rc = dev->exactBounds();
     dev->setX(50);
diff --git a/krita/plugins/tools/tool_transform2/kis_liquify_paintop.cpp b/krita/plugins/tools/tool_transform2/kis_liquify_paintop.cpp
index 478dc73..a7e07c7 100644
--- a/krita/plugins/tools/tool_transform2/kis_liquify_paintop.cpp
+++ b/krita/plugins/tools/tool_transform2/kis_liquify_paintop.cpp
@@ -121,32 +121,33 @@ KisSpacingInformation KisLiquifyPaintop::paintAt(const KisPaintInformation &pi)
         pi.pressure() * reverseCoeff * m_d->props.amount():
         reverseCoeff * m_d->props.amount();
 
-
+    const bool useWashMode = m_d->props.useWashMode();
+    const qreal flow = m_d->props.flow();
 
     switch (m_d->props.mode()) {
     case KisLiquifyProperties::MOVE: {
         const qreal offsetLength = size * amount;
         m_d->worker->translatePoints(pi.pos(),
                                      pi.drawingDirectionVector() * offsetLength,
-                                     size);
+                                     size, useWashMode, flow);
 
         break;
     }
     case KisLiquifyProperties::SCALE:
         m_d->worker->scalePoints(pi.pos(),
                                  amount,
-                                 size);
+                                 size, useWashMode, flow);
         break;
     case KisLiquifyProperties::ROTATE:
         m_d->worker->rotatePoints(pi.pos(),
                                   2.0 * M_PI * amount,
-                                  size);
+                                  size, useWashMode, flow);
         break;
     case KisLiquifyProperties::OFFSET: {
         const qreal offsetLength = size * amount;
         m_d->worker->translatePoints(pi.pos(),
                                      KisAlgebra2D::rightUnitNormal(pi.drawingDirectionVector()) * offsetLength,
-                                     size);
+                                     size, useWashMode, flow);
         break;
     }
     case KisLiquifyProperties::UNDO:
diff --git a/krita/plugins/tools/tool_transform2/kis_liquify_properties.cpp b/krita/plugins/tools/tool_transform2/kis_liquify_properties.cpp
index ad497fe..8f468a4 100644
--- a/krita/plugins/tools/tool_transform2/kis_liquify_properties.cpp
+++ b/krita/plugins/tools/tool_transform2/kis_liquify_properties.cpp
@@ -61,6 +61,9 @@ void KisLiquifyProperties::saveMode() const
     cfg.writeEntry("sizeHasPressure", m_sizeHasPressure);
     cfg.writeEntry("amountHasPressure", m_amountHasPressure);
     cfg.writeEntry("reverseDirection", m_reverseDirection);
+    cfg.writeEntry("useWashMode", m_useWashMode);
+    cfg.writeEntry("flow", m_flow);
+
 }
 
 void KisLiquifyProperties::loadMode()
@@ -74,4 +77,6 @@ void KisLiquifyProperties::loadMode()
     m_sizeHasPressure = cfg.readEntry("sizeHasPressure", m_sizeHasPressure);
     m_amountHasPressure = cfg.readEntry("amountHasPressure", m_amountHasPressure);
     m_reverseDirection = cfg.readEntry("reverseDirection", m_reverseDirection);
+    m_useWashMode = cfg.readEntry("useWashMode", m_useWashMode);
+    m_flow = cfg.readEntry("flow", m_flow);
 }
diff --git a/krita/plugins/tools/tool_transform2/kis_liquify_properties.h b/krita/plugins/tools/tool_transform2/kis_liquify_properties.h
index ab888aa..08e32c0 100644
--- a/krita/plugins/tools/tool_transform2/kis_liquify_properties.h
+++ b/krita/plugins/tools/tool_transform2/kis_liquify_properties.h
@@ -39,7 +39,9 @@ public:
           m_spacing(0.2),
           m_sizeHasPressure(false),
           m_amountHasPressure(false),
-          m_reverseDirection(false)
+          m_reverseDirection(false),
+          m_useWashMode(false),
+          m_flow(0.2)
     {
     }
 
@@ -100,6 +102,20 @@ public:
         m_reverseDirection = value;
     }
 
+    bool useWashMode() const {
+        return m_useWashMode;
+    }
+    void setUseWashMode(bool value) {
+        m_useWashMode = value;
+    }
+
+    qreal flow() const {
+        return m_flow;
+    }
+    void setFlow(qreal value) {
+        m_flow = value;
+    }
+
     void saveMode() const;
     void loadMode();
 
@@ -111,6 +127,9 @@ private:
     bool m_sizeHasPressure;
     bool m_amountHasPressure;
     bool m_reverseDirection;
+
+    bool m_useWashMode;
+    qreal m_flow;
 };
 
 #endif /* __KIS_LIQUIFY_PROPERTIES_H */
diff --git a/krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp b/krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp
index ca1623f..841e0a2 100644
--- a/krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp
+++ b/krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp
@@ -144,6 +144,15 @@ KisToolTransformConfigWidget::KisToolTransformConfigWidget(TransformTransactionP
     connect(liquifyAmountSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifyAmountChanged(qreal)));
     liquifyAmountSlider->setToolTip(i18nc("@info:tooltip", "Amount of the deformation you get"));
 
+    liquifyFlowSlider->setRange(0.0, 1.0, 2);
+    liquifyFlowSlider->setValue(1.0);
+    connect(liquifyFlowSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifyFlowChanged(qreal)));
+    liquifyFlowSlider->setToolTip(i18nc("@info:tooltip", "When in non-buildup mode, shows how fast the deformation limit is reached."));
+
+    liquifyBuildUpBox->setChecked(true);
+    connect(liquifyBuildUpBox, SIGNAL(toggled(bool)), this, SLOT(liquifyBuildUpChanged(bool)));
+    liquifyBuildUpBox->setToolTip(i18nc("@info:tooltip", "Switch between Build Up and Wash mode of painting. Build Up mode adds deformations one on top of the other without any limits. Wash mode gradually deforms the piece to the selected deformation level."));
+
     liquifySpacingSlider->setRange(0.0, 3.0, 2);
     liquifySizeSlider->setExponentRatio(3);
     liquifySpacingSlider->setSingleStep(0.01);
@@ -265,8 +274,12 @@ void KisToolTransformConfigWidget::updateLiquifyControls()
     KisLiquifyProperties *props =
         config->liquifyProperties();
 
+    const bool useWashMode = props->useWashMode();
+
     liquifySizeSlider->setValue(props->size());
     liquifyAmountSlider->setValue(props->amount());
+    liquifyFlowSlider->setValue(props->flow());
+    liquifyBuildUpBox->setChecked(!useWashMode);
     liquifySpacingSlider->setValue(props->spacing());
     liquifySizePressureBox->setChecked(props->sizeHasPressure());
     liquifyAmountPressureBox->setChecked(props->amountHasPressure());
@@ -279,7 +292,14 @@ void KisToolTransformConfigWidget::updateLiquifyControls()
     bool canInverseDirection =
         mode != KisLiquifyProperties::UNDO;
 
+    bool canUseWashMode = mode != KisLiquifyProperties::UNDO;
+
     liquifyReverseDirectionChk->setEnabled(canInverseDirection);
+    liquifyFlowSlider->setEnabled(canUseWashMode && useWashMode);
+    liquifyBuildUpBox->setEnabled(canUseWashMode);
+
+    const qreal maxAmount = canUseWashMode ? 5.0 : 1.0;
+    liquifyAmountSlider->setRange(0.0, maxAmount, 2);
 
     unblockUiSlots();
 }
@@ -330,6 +350,33 @@ void KisToolTransformConfigWidget::liquifyAmountChanged(qreal value)
     notifyConfigChanged();
 }
 
+void KisToolTransformConfigWidget::liquifyFlowChanged(qreal value)
+{
+    if (m_uiSlotsBlocked) return;
+
+    ToolTransformArgs *config = m_transaction->currentConfig();
+    KisLiquifyProperties *props =
+        config->liquifyProperties();
+
+    props->setFlow(value);
+    notifyConfigChanged();
+}
+
+void KisToolTransformConfigWidget::liquifyBuildUpChanged(bool value)
+{
+    if (m_uiSlotsBlocked) return;
+
+    ToolTransformArgs *config = m_transaction->currentConfig();
+    KisLiquifyProperties *props =
+        config->liquifyProperties();
+
+    props->setUseWashMode(!value);
+    notifyConfigChanged();
+
+    // we need to enable/disable flow slider
+    updateLiquifyControls();
+}
+
 void KisToolTransformConfigWidget::liquifySpacingChanged(qreal value)
 {
     if (m_uiSlotsBlocked) return;
diff --git a/krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h b/krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h
index 94cf109..6dc79f1 100644
--- a/krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h
+++ b/krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h
@@ -90,6 +90,8 @@ public slots:
 
     void liquifySizeChanged(qreal value);
     void liquifyAmountChanged(qreal value);
+    void liquifyFlowChanged(qreal value);
+    void liquifyBuildUpChanged(bool value);
     void liquifySpacingChanged(qreal value);
     void liquifySizePressureChanged(bool value);
     void liquifyAmountPressureChanged(bool value);
diff --git a/krita/plugins/tools/tool_transform2/wdg_tool_transform.ui b/krita/plugins/tools/tool_transform2/wdg_tool_transform.ui
index 47f13ea..52284c9 100755
--- a/krita/plugins/tools/tool_transform2/wdg_tool_transform.ui
+++ b/krita/plugins/tools/tool_transform2/wdg_tool_transform.ui
@@ -1054,7 +1054,7 @@ big!</string>
              <bool>true</bool>
             </property>
             <attribute name="buttonGroup">
-             <string notr="true">buttonGroup</string>
+             <string>buttonGroup</string>
             </attribute>
            </widget>
           </item>
@@ -1120,7 +1120,7 @@ big!</string>
              <bool>true</bool>
             </property>
             <attribute name="buttonGroup">
-             <string notr="true">buttonGroup</string>
+             <string>buttonGroup</string>
             </attribute>
            </widget>
           </item>
@@ -1373,17 +1373,17 @@ big!</string>
            </property>
           </widget>
          </item>
-         <item row="2" column="0">
+         <item row="3" column="0">
           <widget class="QLabel" name="lblSpacing">
            <property name="text">
             <string>Spacing:</string>
            </property>
           </widget>
          </item>
-         <item row="2" column="1">
+         <item row="3" column="1">
           <widget class="KisDoubleSliderSpinBox" name="liquifySpacingSlider" native="true"/>
          </item>
-         <item row="3" column="1">
+         <item row="4" column="1">
           <widget class="QPushButton" name="liquifyReverseDirectionChk">
            <property name="sizePolicy">
             <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@@ -1399,6 +1399,23 @@ big!</string>
            </property>
           </widget>
          </item>
+         <item row="2" column="0">
+          <widget class="QLabel" name="lblFlow">
+           <property name="text">
+            <string>Flow:</string>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="1">
+          <widget class="KisDoubleSliderSpinBox" name="liquifyFlowSlider" native="true"/>
+         </item>
+         <item row="2" column="2">
+          <widget class="QCheckBox" name="liquifyBuildUpBox">
+           <property name="text">
+            <string>Build Up</string>
+           </property>
+          </widget>
+         </item>
         </layout>
        </item>
        <item>


More information about the kimageshop mailing list