[calligra/calligra/2.6] krita: Highly optimized Displacement option for the Experiment PaintOp
Dmitry Kazakov
dimula73 at gmail.com
Sun Dec 2 07:15:14 UTC 2012
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 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/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<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();
}
};
More information about the kimageshop
mailing list