[calligra] krita: Highly optimized Displacement option for the Experiment PaintOp

Lukast dev lukast.dev at gmail.com
Mon Nov 26 17:04:44 UTC 2012


Man, you are awesome!

2012/11/25 Dmitry Kazakov <dimula73 at gmail.com>:
> Git commit c955e6ef5c88490043a181bf4344c44c0f03dafb by Dmitry Kazakov.
> Committed on 25/11/2012 at 10:46.
> Pushed by dkazakov into branch 'master'.
>
> Highly optimized Displacement option for the Experiment PaintOp
>
> This patch re-enables the Displacement option for the Experiment
> PaintOp as was requested by Lukas. The new implementation has two
> kinds of optimizations:
> 1) It updates big shapes (128+ pixes) using diffs of paths. It means
>    that the whole shape is not rendered every time. Only the changed
>    part of it is rendered. This gives up to 2 times better performance
>    for huge shapes of 2000+ px in size.
> 2) The path is incrementally simplified. That is the elements smaller
>    than 1% of the shape are truncated to a single line.
>
> CCMAIL:kimageshop at kde.org
>
> M  +2    -3    krita/image/kis_painter.cc
> M  +174  -18   krita/plugins/paintops/experiment/kis_experiment_paintop.cpp
> M  +12   -1    krita/plugins/paintops/experiment/kis_experiment_paintop.h
> M  +0    -5    krita/plugins/paintops/experiment/kis_experimentop_option.cpp
>
> http://commits.kde.org/calligra/c955e6ef5c88490043a181bf4344c44c0f03dafb
>
> diff --git a/krita/image/kis_painter.cc b/krita/image/kis_painter.cc
> index b11d87f..f748810 100644
> --- a/krita/image/kis_painter.cc
> +++ b/krita/image/kis_painter.cc
> @@ -1218,9 +1218,8 @@ void KisPainter::fillPainterPath(const QPainterPath& path, const QRect &requeste
>          }
>      }
>
> -    // The strokes for the outline may have already added updated the dirtyrect, but it can't hurt,
> -    // and if we're painting without outlines, then there will be no dirty rect. Let's do it ourselves...
> -    bitBlt(fillRect.x(), fillRect.y(), d->polygon, fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height());
> +    QRect bltRect = !requestedRect.isEmpty() ? requestedRect : fillRect;
> +    bitBlt(bltRect.x(), bltRect.y(), d->polygon, bltRect.x(), bltRect.y(), bltRect.width(), bltRect.height());
>  }
>
>  void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen)
> diff --git a/krita/plugins/paintops/experiment/kis_experiment_paintop.cpp b/krita/plugins/paintops/experiment/kis_experiment_paintop.cpp
> index 2116780..be7cf83 100644
> --- a/krita/plugins/paintops/experiment/kis_experiment_paintop.cpp
> +++ b/krita/plugins/paintops/experiment/kis_experiment_paintop.cpp
> @@ -39,8 +39,8 @@ KisExperimentPaintOp::KisExperimentPaintOp(const KisExperimentPaintOpSettings *s
>
>      m_experimentOption.readOptionSetting(settings);
>
> -    // not implemented
> -    // m_displacement = (m_experimentOption.displacement * 0.01 * 14) + 1; // 1..15 [7 default according alchemy]
> +    m_displaceEnabled = m_experimentOption.isDisplacementEnabled;
> +    m_displaceCoeff = (m_experimentOption.displacement * 0.01 * 14) + 1; // 1..15 [7 default according alchemy]
>
>      m_speedEnabled = m_experimentOption.isSpeedEnabled;
>      m_speedMultiplier = (m_experimentOption.speed * 0.01 * 35); // 0..35 [15 default according alchemy]
> @@ -75,6 +75,9 @@ bool checkInTriangle(const QRectF &rect,
>  QRegion splitTriangles(const QPointF &center,
>                         QVector<QPointF> points)
>  {
> +    Q_ASSERT(points.size());
> +    Q_ASSERT(!(points.size() & 1));
> +
>      QVector<QPolygonF> triangles;
>      QRect totalRect;
>
> @@ -117,13 +120,40 @@ QRegion splitTriangles(const QPointF &center,
>      return dirtyRegion;
>  }
>
> -void KisExperimentPaintOp::paintTriangles()
> +QRegion splitPath(QPainterPath path)
>  {
> -    Q_ASSERT(m_savedPoints.size());
> -    Q_ASSERT(!(m_savedPoints.size() & 1));
> +    QRect totalRect = path.boundingRect().toAlignedRect();
> +    totalRect.adjusted(-1,-1,1,1);
> +
> +    const int step = 64;
> +    const int right = totalRect.x() + totalRect.width();
> +    const int bottom = totalRect.y() + totalRect.height();
> +
> +    QRegion dirtyRegion;
> +
> +
> +    for (int y = totalRect.y(); y < bottom;) {
> +        int nextY = qMin((y + step) & ~(step-1), bottom);
> +
> +        for (int x = totalRect.x(); x < right;) {
> +            int nextX = qMin((x + step) & ~(step-1), right);
> +
> +            QRect rect(x, y, nextX - x, nextY - y);
> +
> +            if(path.intersects(rect)) {
> +                dirtyRegion |= rect;
> +            }
>
> -    QRegion changedRegion = splitTriangles(m_center, m_savedPoints);
> +            x = nextX;
> +        }
> +        y = nextY;
> +    }
>
> +    return dirtyRegion;
> +}
> +
> +void KisExperimentPaintOp::paintRegion(const QRegion &changedRegion)
> +{
>      if (m_useMirroring) {
>          foreach(const QRect &rect, changedRegion.rects()) {
>              m_originalPainter->fillPainterPath(m_path, rect);
> @@ -214,6 +244,19 @@ KisDistanceInformation KisExperimentPaintOp::paintLine(const KisPaintInformation
>              m_savedPoints << pos2;
>          }
>
> +        if (m_displaceEnabled) {
> +            if (m_path.elementCount() % 16 == 0) {
> +                QRectF bounds = m_path.boundingRect();
> +                m_path = applyDisplace(m_path, m_displaceCoeff - length);
> +                bounds |= m_path.boundingRect();
> +
> +                qreal threshold = simplifyThreshold(bounds);
> +                m_path = trySimplifyPath(m_path, threshold);
> +            }
> +            else {
> +                m_path = applyDisplace(m_path, m_displaceCoeff - length);
> +            }
> +        }
>
>          /**
>           * Refresh rate at least 25fps
> @@ -222,20 +265,51 @@ KisDistanceInformation KisExperimentPaintOp::paintLine(const KisPaintInformation
>          const int elapsedTime = pi2.currentTime() - m_lastPaintTime;
>
>          QRect pathBounds = m_path.boundingRect().toRect();
> -        int distanceThreshold = qMax(pathBounds.width(), pathBounds.height());
> +        int distanceMetric = qMax(pathBounds.width(), pathBounds.height());
> +
> +        if(elapsedTime > timeThreshold ||
> +           (!m_displaceEnabled &&
> +            m_savedUpdateDistance > distanceMetric / 8)) {
> +
> +            if (m_displaceEnabled) {
> +                /**
> +                 * Rendering the path with diff'ed rects is up to two
> +                 * times more efficient for really huge shapes (tested
> +                 * on 2000+ px shapes), however for smaller ones doing
> +                 * paths arithmetics eats too much time. That's why we
> +                 * choose the method on the base of the size of the
> +                 * shape.
> +                 */
> +                const int pathSizeThreshold = 128;
> +
> +                QRegion changedRegion;
> +                if (distanceMetric < pathSizeThreshold) {
> +
> +                    QRectF changedRect = m_path.boundingRect().toRect() |
> +                        m_lastPaintedPath.boundingRect().toRect();
> +                    changedRect.adjust(-1,-1,1,1);
> +
> +                    changedRegion = changedRect.toRect();
> +                } else {
> +                    QPainterPath diff1 = m_path - m_lastPaintedPath;
> +                    QPainterPath diff2 = m_lastPaintedPath - m_path;
> +
> +                    changedRegion = splitPath(diff1 | diff2);
> +                }
>
> -        if(!m_savedPoints.isEmpty() &&
> -           (m_savedUpdateDistance > distanceThreshold / 8 ||
> -            elapsedTime > timeThreshold)) {
> +                paintRegion(changedRegion);
> +                m_lastPaintedPath = m_path;
> +            } else if (!m_savedPoints.isEmpty()) {
> +                QRegion changedRegion = splitTriangles(m_center, m_savedPoints);
> +                paintRegion(changedRegion);
> +            }
>
> -            paintTriangles();
>              m_savedPoints.clear();
>              m_savedUpdateDistance = 0;
>              m_lastPaintTime = pi2.currentTime();
>          }
>      }
>
> -
>      return kdi;
>  }
>
> @@ -246,9 +320,92 @@ qreal KisExperimentPaintOp::paintAt(const KisPaintInformation& info)
>      return 1.0;
>  }
>
> -#if 0
> -// the displacement is not implemented yet
> -// this implementation takes too much time to be user-ready
> +bool tryMergePoints(QPainterPath &path,
> +                    const QPointF &startPoint,
> +                    const QPointF &endPoint,
> +                    qreal &distance,
> +                    qreal distanceThreshold,
> +                    bool lastSegment)
> +{
> +    qreal length = (endPoint - startPoint).manhattanLength();
> +
> +    if (lastSegment || length > distanceThreshold) {
> +        if (distance != 0) {
> +            path.lineTo(startPoint);
> +        }
> +        distance = 0;
> +        return false;
> +    }
> +
> +    distance += length;
> +
> +    if (distance > distanceThreshold) {
> +        path.lineTo(endPoint);
> +        distance = 0;
> +    }
> +
> +    return true;
> +}
> +
> +qreal KisExperimentPaintOp::simplifyThreshold(const QRectF &bounds)
> +{
> +    qreal maxDimension = qMax(bounds.width(), bounds.height());
> +    return qMax(0.01 * maxDimension, 1.0);
> +}
> +
> +QPainterPath KisExperimentPaintOp::trySimplifyPath(const QPainterPath &path, qreal lengthThreshold)
> +{
> +    QPainterPath newPath;
> +    QPointF startPoint;
> +    qreal distance = 0;
> +
> +    int count = path.elementCount();
> +    for (int i = 0; i < count; i++){
> +        QPainterPath::Element e = path.elementAt(i);
> +        QPointF endPoint = QPointF(e.x, e.y);
> +
> +        switch(e.type){
> +        case QPainterPath::MoveToElement:
> +            newPath.moveTo(endPoint);
> +            break;
> +        case QPainterPath::LineToElement:
> +            if (!tryMergePoints(newPath, startPoint, endPoint,
> +                                distance, lengthThreshold, i == count - 1)) {
> +
> +                newPath.lineTo(endPoint);
> +            }
> +            break;
> +        case QPainterPath::CurveToElement:{
> +            Q_ASSERT(i + 2 < count);
> +
> +            if (!tryMergePoints(newPath, startPoint, endPoint,
> +                                distance, lengthThreshold, i == count - 1)) {
> +
> +                e = path.elementAt(i + 1);
> +                Q_ASSERT(e.type == QPainterPath::CurveToDataElement);
> +                QPointF ctrl1 = QPointF(e.x, e.y);
> +                e = path.elementAt(i + 2);
> +                Q_ASSERT(e.type == QPainterPath::CurveToDataElement);
> +                QPointF ctrl2 = QPointF(e.x, e.y);
> +                newPath.cubicTo(ctrl1, ctrl2, endPoint);
> +            }
> +
> +            i += 2;
> +        }
> +        }
> +        startPoint = endPoint;
> +    }
> +
> +    return newPath;
> +}
> +
> +QPointF KisExperimentPaintOp::getAngle(const QPointF& p1, const QPointF& p2, qreal distance)
> +{
> +    QPointF diff = p1 - p2;
> +    qreal realLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
> +    return realLength > 0.5 ? p1 + diff * distance / realLength : p1;
> +}
> +
>  QPainterPath KisExperimentPaintOp::applyDisplace(const QPainterPath& path, int speed)
>  {
>      QPointF lastPoint = path.currentPosition();
> @@ -279,10 +436,10 @@ QPainterPath KisExperimentPaintOp::applyDisplace(const QPainterPath& path, int s
>                  curveElementCounter++;
>
>                  if (curveElementCounter == 1){
> -                    ctrl1 = QPointF(e.x,e.y);
> +                    ctrl1 = getAngle(QPointF(e.x,e.y),lastPoint,speed);
>                  }
>                  else if (curveElementCounter == 2){
> -                    ctrl2 = QPointF(e.x,e.y);
> +                    ctrl2 = getAngle(QPointF(e.x,e.y),lastPoint,speed);
>                      newPath.cubicTo(ctrl1,ctrl2,endPoint);
>                  }
>                  break;
> @@ -293,5 +450,4 @@ QPainterPath KisExperimentPaintOp::applyDisplace(const QPainterPath& path, int s
>
>      return newPath;
>  }
> -#endif
>
> diff --git a/krita/plugins/paintops/experiment/kis_experiment_paintop.h b/krita/plugins/paintops/experiment/kis_experiment_paintop.h
> index 8c3a588..37e6fca 100644
> --- a/krita/plugins/paintops/experiment/kis_experiment_paintop.h
> +++ b/krita/plugins/paintops/experiment/kis_experiment_paintop.h
> @@ -42,10 +42,21 @@ public:
>      virtual qreal paintAt(const KisPaintInformation& info);
>
>  private:
> -    void paintTriangles();
> +    void paintRegion(const QRegion &changedRegion);
>      QPointF speedCorrectedPosition(const KisPaintInformation& pi1,
>                                     const KisPaintInformation& pi2);
>
> +
> +    static qreal simplifyThreshold(const QRectF &bounds);
> +    static QPainterPath trySimplifyPath(const QPainterPath &path, qreal lengthThreshold);
> +    static QPointF getAngle(const QPointF& p1, const QPointF& p2, qreal distance);
> +    static QPainterPath applyDisplace(const QPainterPath& path, int speed);
> +
> +
> +    bool m_displaceEnabled;
> +    int m_displaceCoeff;
> +    QPainterPath m_lastPaintedPath;
> +
>      bool m_speedEnabled;
>      int m_speedMultiplier;
>      qreal m_savedSpeedCoeff;
> diff --git a/krita/plugins/paintops/experiment/kis_experimentop_option.cpp b/krita/plugins/paintops/experiment/kis_experimentop_option.cpp
> index 5d6a7d9..afdb1be 100644
> --- a/krita/plugins/paintops/experiment/kis_experimentop_option.cpp
> +++ b/krita/plugins/paintops/experiment/kis_experimentop_option.cpp
> @@ -41,11 +41,6 @@ public:
>          displaceStrength->setSuffix(QChar(Qt::Key_Percent));
>          displaceStrength->setValue(42.0);
>          displaceStrength->setSingleStep(1.0);
> -
> -        // HINT: Displace capabilities are not implemented yet
> -        lblPostprocessing->hide();
> -        displaceCHBox->hide();
> -        displaceStrength->hide();
>      }
>  };
>
> _______________________________________________
> kimageshop mailing list
> kimageshop at kde.org
> https://mail.kde.org/mailman/listinfo/kimageshop


More information about the kimageshop mailing list