[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 ¢er,
> 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 ¢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 at kde.org
> https://mail.kde.org/mailman/listinfo/kimageshop
More information about the kimageshop
mailing list