Git commit 32050085533d88e924b2d00337e995a6e75d3cb0 by Dmitry Kazakov. Committed on 25/11/2012 at 10:46. Pushed by dkazakov into branch 'calligra/2.6'. 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@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/32050085533d88e924b2d00337e995a6e75d3cb0 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 ¢er, QVector points) { + Q_ASSERT(points.size()); + Q_ASSERT(!(points.size() & 1)); + QVector triangles; QRect totalRect; @@ -117,13 +120,40 @@ QRegion splitTriangles(const QPointF ¢er, 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@kde.org https://mail.kde.org/mailman/listinfo/kimageshop