Commit 2212073d authored by Dmitry Kazakov's avatar Dmitry Kazakov

Optimized the speed of the Polygonal Gradient (ready for testing!)

Now it is calculated in two stages: first a bspline grid, then
the real gradient is interpolated using the grid.

einspline library yet doesn't compile on Windows, but speaking truly
the whole branch doesn't compile on Windows :) This issue will be fixed
later by porting einspline into c++.
parent f020728a
......@@ -72,8 +72,8 @@ void KisGradientBenchmark::benchmarkGradient()
fillPainter.setOpacity(OPACITY_OPAQUE_U8);
// default
fillPainter.setCompositeOp(COMPOSITE_OVER);
fillPainter.paintGradient(QPointF(0,0), QPointF(3000,3000), KisGradientPainter::GradientShapeBiLinear, KisGradientPainter::GradientRepeatNone, true, false, 0, 0, GMP_IMAGE_WIDTH,GMP_IMAGE_HEIGHT);
fillPainter.setGradientShape(KisGradientPainter::GradientShapeBiLinear);
fillPainter.paintGradient(QPointF(0,0), QPointF(3000,3000), KisGradientPainter::GradientRepeatNone, true, false, 0, 0, GMP_IMAGE_WIDTH,GMP_IMAGE_HEIGHT);
fillPainter.deleteTransaction();
}
......
......@@ -139,6 +139,7 @@ set(kritaimage_LIB_SRCS
kis_safe_transform.cpp
kis_gradient_painter.cc
kis_gradient_shape_strategy.cpp
kis_cached_gradient_shape_strategy.cpp
kis_polygonal_gradient_shape_strategy.cpp
kis_iterator_ng.cpp
kis_async_merger.cpp
......@@ -205,9 +206,9 @@ set(kritaimage_LIB_SRCS
kis_transaction_data.cpp
kis_transform_worker.cc
kis_perspectivetransform_worker.cpp
# bsplines/kis_bspline_1d.cpp
# bsplines/kis_bspline_2d.cpp
# bsplines/kis_nu_bspline_2d.cpp
bsplines/kis_bspline_1d.cpp
bsplines/kis_bspline_2d.cpp
bsplines/kis_nu_bspline_2d.cpp
kis_warptransform_worker.cc
kis_cage_transform_worker.cpp
kis_liquify_transform_worker.cpp
......@@ -261,15 +262,15 @@ set(kritaimage_LIB_SRCS
)
set(einspline_SRCS
# bsplines/einspline/bspline_create.c
# bsplines/einspline/bspline_data.c
# bsplines/einspline/multi_bspline_create.c
# bsplines/einspline/nubasis.c
# bsplines/einspline/nubspline_create.c
# bsplines/einspline/nugrid.c
bsplines/einspline/bspline_create.c
bsplines/einspline/bspline_data.c
bsplines/einspline/multi_bspline_create.c
bsplines/einspline/nubasis.c
bsplines/einspline/nubspline_create.c
bsplines/einspline/nugrid.c
)
#SET_SOURCE_FILES_PROPERTIES(${einspline_SRCS} PROPERTIES COMPILE_FLAGS -std=c99)
SET_SOURCE_FILES_PROPERTIES(${einspline_SRCS} PROPERTIES COMPILE_FLAGS -std=c99)
kde4_add_library(kritaimage SHARED ${kritaimage_LIB_SRCS} ${einspline_SRCS})
......
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_cached_gradient_shape_strategy.h"
#include <QRect>
#include "bsplines/kis_bspline_2d.h"
#include <cmath>
#include <boost/function.hpp>
#include <boost/bind.hpp>
using namespace KisBSplines;
struct KisCachedGradientShapeStrategy::Private
{
QRect rc;
qreal xStep;
qreal yStep;
QScopedPointer<KisGradientShapeStrategy> baseStrategy;
QScopedPointer<KisBSpline2D> spline;
};
KisCachedGradientShapeStrategy::KisCachedGradientShapeStrategy(const QRect &rc,
qreal xStep,
qreal yStep,
KisGradientShapeStrategy *baseStrategy)
: KisGradientShapeStrategy(),
m_d(new Private())
{
m_d->rc = rc;
m_d->xStep = xStep;
m_d->yStep = yStep;
m_d->baseStrategy.reset(baseStrategy);
qreal xStart = rc.x();
qreal yStart = rc.y();
qreal xEnd = rc.x() + rc.width();
qreal yEnd = rc.y() + rc.height();
int numSamplesX = std::ceil(qreal(rc.width()) / xStep);
int numSamplesY = std::ceil(qreal(rc.height()) / yStep);
m_d->spline.reset(new KisBSpline2D(xStart, xEnd, numSamplesX, Natural,
yStart, yEnd, numSamplesY, Natural));
boost::function<qreal(qreal, qreal)> valueOp =
boost::bind(&KisGradientShapeStrategy::valueAt, m_d->baseStrategy.data(), _1, _2);
m_d->spline->initializeSpline(valueOp);
}
KisCachedGradientShapeStrategy::~KisCachedGradientShapeStrategy()
{
}
double KisCachedGradientShapeStrategy::valueAt(double x, double y) const
{
return m_d->spline->value(x, y);
}
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_CACHED_GRADIENT_SHAPE_STRATEGY_H
#define __KIS_CACHED_GRADIENT_SHAPE_STRATEGY_H
#include "kis_gradient_shape_strategy.h"
#include "krita_export.h"
#include <QScopedPointer>
class QRect;
class KRITAIMAGE_EXPORT KisCachedGradientShapeStrategy : public KisGradientShapeStrategy
{
public:
KisCachedGradientShapeStrategy(const QRect &rc, qreal xStep, qreal yStep, KisGradientShapeStrategy *baseStrategy);
~KisCachedGradientShapeStrategy();
double valueAt(double x, double y) const;
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif /* __KIS_CACHED_GRADIENT_SHAPE_STRATEGY_H */
......@@ -198,5 +198,12 @@ inline QRect kisEnsureInRect(QRect rc, const QRect &bounds)
return rc;
}
#include <QSharedPointer>
template <class T>
inline QSharedPointer<T> toQShared(T* ptr) {
return QSharedPointer<T>(ptr);
}
#endif // KISGLOBAL_H_
......@@ -35,6 +35,8 @@
#include "kis_progress_update_helper.h"
#include "kis_gradient_shape_strategy.h"
#include "kis_polygonal_gradient_shape_strategy.h"
#include "kis_cached_gradient_shape_strategy.h"
#include "krita_utils.h"
class CachedGradient : public KoAbstractGradient
......@@ -495,26 +497,79 @@ double GradientRepeatAlternateStrategy::valueAt(double t) const
}
}
struct KisGradientPainter::Private
{
enumGradientShape shape;
QSharedPointer<KisGradientShapeStrategy> precalculatedShapeStrategy;
};
KisGradientPainter::KisGradientPainter()
: KisPainter()
: m_d(new Private())
{
}
KisGradientPainter::KisGradientPainter(KisPaintDeviceSP device)
: KisPainter(device)
: KisPainter(device),
m_d(new Private())
{
}
KisGradientPainter::KisGradientPainter(KisPaintDeviceSP device, KisSelectionSP selection)
: KisPainter(device, selection)
: KisPainter(device, selection),
m_d(new Private())
{
}
#include <QTime>
KisGradientPainter::~KisGradientPainter()
{
}
void KisGradientPainter::setGradientShape(enumGradientShape shape)
{
m_d->shape = shape;
}
/**
* TODO: make this call happen asyncronously when the user does nothing
*/
void KisGradientPainter::precalculateShape()
{
if (m_d->precalculatedShapeStrategy) return;
QPolygonF polygon;
if (selection()) {
if (!selection()->outlineCacheValid()) {
selection()->recalculateOutlineCache();
}
KIS_ASSERT_RECOVER_RETURN(selection()->outlineCacheValid());
KIS_ASSERT_RECOVER_RETURN(!selection()->outlineCache().isEmpty());
QPainterPath path = selection()->outlineCache();
polygon = path.toFillPolygons().first();
} else {
polygon = QPolygonF(QRectF(device()->defaultBounds()->bounds()));
}
// TODO: implement UI for exponent option
const qreal exponent = 2.0;
KisGradientShapeStrategy *strategy =
new KisPolygonalGradientShapeStrategy(polygon, exponent);
const QRect selectionRect = polygon.boundingRect().toAlignedRect();
const qreal step =
qMin(8.0, KritaUtils::maxDimensionPortion(selectionRect, 0.01, 3.0));
m_d->precalculatedShapeStrategy =
toQShared(new KisCachedGradientShapeStrategy(selectionRect, step, step, strategy));
Q_CHECK_PTR(m_d->precalculatedShapeStrategy);
}
bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
const QPointF& gradientVectorEnd,
enumGradientShape shape,
enumGradientRepeat repeat,
double antiAliasThreshold,
bool reverseGradient,
......@@ -529,52 +584,34 @@ bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
QRect requestedRect(startx, starty, width, height);
KisGradientShapeStrategy *shapeStrategy = 0;
QSharedPointer<KisGradientShapeStrategy> shapeStrategy;
switch (shape) {
precalculateShape();
switch (m_d->shape) {
case GradientShapeLinear:
shapeStrategy = new LinearGradientStrategy(gradientVectorStart, gradientVectorEnd);
shapeStrategy = toQShared(new LinearGradientStrategy(gradientVectorStart, gradientVectorEnd));
break;
case GradientShapeBiLinear:
shapeStrategy = new BiLinearGradientStrategy(gradientVectorStart, gradientVectorEnd);
shapeStrategy = toQShared(new BiLinearGradientStrategy(gradientVectorStart, gradientVectorEnd));
break;
case GradientShapeRadial:
shapeStrategy = new RadialGradientStrategy(gradientVectorStart, gradientVectorEnd);
shapeStrategy = toQShared(new RadialGradientStrategy(gradientVectorStart, gradientVectorEnd));
break;
case GradientShapeSquare:
shapeStrategy = new SquareGradientStrategy(gradientVectorStart, gradientVectorEnd);
shapeStrategy = toQShared(new SquareGradientStrategy(gradientVectorStart, gradientVectorEnd));
break;
case GradientShapeConical:
shapeStrategy = new ConicalGradientStrategy(gradientVectorStart, gradientVectorEnd);
shapeStrategy = toQShared(new ConicalGradientStrategy(gradientVectorStart, gradientVectorEnd));
break;
case GradientShapeConicalSymetric:
shapeStrategy = new ConicalSymetricGradientStrategy(gradientVectorStart, gradientVectorEnd);
shapeStrategy = toQShared(new ConicalSymetricGradientStrategy(gradientVectorStart, gradientVectorEnd));
break;
case GradientShapePolygonal:
QPolygonF polygon;
if (selection()) {
if (!selection()->outlineCacheValid()) {
selection()->recalculateOutlineCache();
}
KIS_ASSERT_RECOVER(selection()->outlineCacheValid()) { return false; }
KIS_ASSERT_RECOVER(!selection()->outlineCache().isEmpty()) { return false; }
QPainterPath path = selection()->outlineCache();
polygon = path.toFillPolygons().first();
} else {
polygon = QPolygonF(QRectF(requestedRect));
}
// TODO: implement UI for exponent option
const qreal exponent = 2.0;
shapeStrategy = new KisPolygonalGradientShapeStrategy(gradientVectorStart, gradientVectorEnd, polygon, exponent);
shapeStrategy = m_d->precalculatedShapeStrategy;
repeat = GradientRepeatNone;
break;
}
Q_CHECK_PTR(shapeStrategy);
GradientRepeatStrategy *repeatStrategy = 0;
......@@ -637,6 +674,5 @@ bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
bitBlt(startx, starty, dev, startx, starty, width, height);
delete shapeStrategy;
return true;
}
......@@ -18,6 +18,8 @@
#ifndef KIS_GRADIENT_PAINTER_H_
#define KIS_GRADIENT_PAINTER_H_
#include <QScopedPointer>
#include <KoColor.h>
#include "kis_global.h"
......@@ -40,6 +42,8 @@ public:
KisGradientPainter(KisPaintDeviceSP device);
KisGradientPainter(KisPaintDeviceSP device, KisSelectionSP selection);
~KisGradientPainter();
enum enumGradientShape {
GradientShapeLinear,
GradientShapeBiLinear,
......@@ -56,33 +60,15 @@ public:
GradientRepeatAlternate
};
class Configuration
{
public:
const KoAbstractGradient* gradient;
KoColor fgColor;
quint8 opacity;
const KoCompositeOp* compositeOp;
KisTransaction* transaction;
void setGradientShape(enumGradientShape shape);
QPointF vectorStart;
QPointF vectorEnd;
KisGradientPainter::enumGradientShape shape;
KisGradientPainter::enumGradientRepeat repeat;
double antiAliasThreshold;
bool reverse;
};
void precalculateShape();
/**
* Paint a gradient in the rect between startx, starty, width and height.
*/
bool paintGradient(const QPointF& gradientVectorStart,
const QPointF& gradientVectorEnd,
enumGradientShape shape,
enumGradientRepeat repeat,
double antiAliasThreshold,
bool reverseGradient,
......@@ -91,5 +77,8 @@ public:
qint32 width,
qint32 height);
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif //KIS_GRADIENT_PAINTER_H_
......@@ -19,6 +19,10 @@
#include "kis_gradient_shape_strategy.h"
KisGradientShapeStrategy::KisGradientShapeStrategy()
{
}
KisGradientShapeStrategy::KisGradientShapeStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
: m_gradientVectorStart(gradientVectorStart),
m_gradientVectorEnd(gradientVectorEnd)
......
......@@ -25,6 +25,7 @@
class KisGradientShapeStrategy
{
public:
KisGradientShapeStrategy();
KisGradientShapeStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
virtual ~KisGradientShapeStrategy();
......
......@@ -40,6 +40,10 @@ namespace Private {
qreal getDisnormedGradientValue(const QPointF &pt, const QPolygonF &selection, qreal exponent)
{
// FIXME: exponent = 2.0
// We explicitly use pow2() and sqrt() functions here
// for efficiency reasons.
const qreal minHiLevel = std::pow(0.5, 1.0 / exponent);
qreal ptWeightNode = 0.0;
......@@ -71,10 +75,17 @@ namespace Private {
}
hi = qMax(minHiLevel, hi);
ptWeightNode += 1.0 / std::pow(hi, exponent);
// disabled for efficiency reasons
// ptWeightNode += 1.0 / std::pow(hi, exponent);
ptWeightNode += 1.0 / pow2(hi);
}
return 1.0 / std::pow(ptWeightNode, 1.0 / exponent);
// disabled for efficiency reasons
// return 1.0 / std::pow(ptWeightNode, 1.0 / exponent);
return 1.0 / std::sqrt(ptWeightNode);
}
#ifdef HAVE_GSL
......@@ -197,12 +208,9 @@ namespace Private {
}
KisPolygonalGradientShapeStrategy::KisPolygonalGradientShapeStrategy(const QPointF& gradientVectorStart,
const QPointF& gradientVectorEnd,
const QPolygonF &selection,
KisPolygonalGradientShapeStrategy::KisPolygonalGradientShapeStrategy(const QPolygonF &selection,
qreal exponent)
: KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd),
m_exponent(exponent)
: m_exponent(exponent)
{
QPainterPath p;
p.addPolygon(selection);
......
......@@ -28,9 +28,7 @@
class KRITAIMAGE_EXPORT KisPolygonalGradientShapeStrategy : public KisGradientShapeStrategy
{
public:
KisPolygonalGradientShapeStrategy(const QPointF& gradientVectorStart,
const QPointF& gradientVectorEnd,
const QPolygonF &selection,
KisPolygonalGradientShapeStrategy(const QPolygonF &selection,
qreal exponent);
~KisPolygonalGradientShapeStrategy();
......
......@@ -471,9 +471,9 @@ target_link_libraries(KisPerspectiveTransformWorkerTest ${KDE4_KDEUI_LIBS} krit
########### next target ###############
#set(kis_bsplines_test_SRCS kis_bsplines_test.cpp )
#kde4_add_unit_test(KisBSplinesTest TESTNAME krita-image-KisBSplinesTest ${kis_bsplines_test_SRCS})
#target_link_libraries(KisBSplinesTest ${KDE4_KDEUI_LIBS} kritaimage ${QT_QTTEST_LIBRARY})
set(kis_bsplines_test_SRCS kis_bsplines_test.cpp )
kde4_add_unit_test(KisBSplinesTest TESTNAME krita-image-KisBSplinesTest ${kis_bsplines_test_SRCS})
target_link_libraries(KisBSplinesTest ${KDE4_KDEUI_LIBS} kritaimage ${QT_QTTEST_LIBRARY})
########### next target ###############
......
......@@ -34,12 +34,6 @@
#include "testutil.h"
void KisGradientPainterTest::testCreation()
{
KisGradientPainter test;
}
void KisGradientPainterTest::testSimplifyPath()
{
QPolygonF selectionPolygon;
......@@ -96,9 +90,10 @@ void testShapedGradientPainterImpl(const QPolygonF &selectionPolygon,
KisGradientPainter gc(dev, selection);
gc.setGradient(gradient.data());
gc.setGradientShape(KisGradientPainter::GradientShapePolygonal);
gc.paintGradient(selectionPolygon.boundingRect().topLeft(),
selectionPolygon.boundingRect().bottomRight(),
KisGradientPainter::GradientShapePolygonal,
KisGradientPainter::GradientRepeatNone,
0,
false,
......@@ -138,5 +133,64 @@ void KisGradientPainterTest::testShapedGradientPainterNonRegular()
testShapedGradientPainterImpl(selectionPolygon, "nonregular_shape");
}
#include "kis_polygonal_gradient_shape_strategy.h"
#include "kis_cached_gradient_shape_strategy.h"
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/variance.hpp>
#include <boost/accumulators/statistics/min.hpp>
#include <boost/accumulators/statistics/max.hpp>
using namespace boost::accumulators;
void KisGradientPainterTest::testCachedStrategy()
{
QPolygonF selectionPolygon;
selectionPolygon << QPointF(100, 100);
selectionPolygon << QPointF(200, 120);
selectionPolygon << QPointF(170, 140);
selectionPolygon << QPointF(200, 180);
selectionPolygon << QPointF(30, 220);
QRect rc = selectionPolygon.boundingRect().toAlignedRect();
KisGradientShapeStrategy *strategy =
new KisPolygonalGradientShapeStrategy(selectionPolygon, 2.0);
KisCachedGradientShapeStrategy cached(rc, 4, 4, strategy);
accumulator_set<qreal, stats<tag::variance, tag::max, tag::min> > accum;
const qreal maxRelError = 5.0 / 256;
for (int y = rc.y(); y <= rc.bottom(); y++) {
for (int x = rc.x(); x <= rc.right(); x++) {
if (!selectionPolygon.containsPoint(QPointF(x, y), Qt::OddEvenFill)) continue;
qreal ref = strategy->valueAt(x, y);
qreal value = cached.valueAt(x, y);
if (ref == 0.0) continue;
qreal relError = (ref - value)/* / ref*/;
accum(relError);
if (relError > maxRelError) {
qDebug() << ppVar(x) << ppVar(y) << ppVar(value) << ppVar(ref) << ppVar(relError);
}
}
}
qDebug() << ppVar(count(accum));
qDebug() << ppVar(mean(accum));
qDebug() << ppVar(variance(accum));
qDebug() << ppVar((min)(accum));
qDebug() << ppVar((max)(accum));
qreal maxError = qMax(qAbs((min)(accum)), qAbs((max)(accum)));
QVERIFY(maxError < maxRelError);
}
QTEST_KDEMAIN(KisGradientPainterTest, GUI)
#include "kis_gradient_painter_test.moc"
......@@ -26,11 +26,12 @@ class KisGradientPainterTest : public QObject
Q_OBJECT
private slots:
void testCreation();
void testSimplifyPath();
void testShapedGradientPainterRect();
void testShapedGradientPainterNonRegular();
void testCachedStrategy();
};
#endif
......@@ -165,7 +165,8 @@ void KisToolGradient::endPrimaryAction(KoPointerEvent *event)
updater->start(100, i18nc("@info:progress", "Gradient..."));
painter.setProgress(updater->startSubtask());
painter.paintGradient(m_startPos, m_endPos, m_shape, m_repeat, m_antiAliasThreshold, m_reverse, 0, 0, image->width(), image->height());
painter.setGradientShape(m_shape);
painter.paintGradient(m_startPos, m_endPos, m_repeat, m_antiAliasThreshold, m_reverse, 0, 0, image->width(), image->height());
painter.endTransaction(undoAdapter);
undoAdapter->endMacro();
......
......@@ -425,9 +425,9 @@ void KisScratchPad::fillGradient()
KisGradientPainter painter(paintDevice);
painter.setGradient(gradient);
painter.setGradientShape(KisGradientPainter::GradientShapeLinear);
painter.paintGradient(gradientRect.topLeft(),
gradientRect.bottomRight(),
KisGradientPainter::GradientShapeLinear,
KisGradientPainter::GradientRepeatNone,
0.2, false,
gradientRect.left(), gradientRect.top(),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment