diff --git a/libs/image/brushengine/kis_paintop.cc b/libs/image/brushengine/kis_paintop.cc index 0f7f583633..7b6e6cc4b4 100644 --- a/libs/image/brushengine/kis_paintop.cc +++ b/libs/image/brushengine/kis_paintop.cc @@ -1,171 +1,185 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004,2007,2010 Cyrille Berger * * 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_paintop.h" #include #include #include #include #include "kis_painter.h" #include "kis_layer.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_global.h" #include "kis_datamanager.h" #include #include #include "kis_vec.h" #include "kis_perspective_math.h" #include "kis_fixed_paint_device.h" #include "kis_paintop_utils.h" #define BEZIER_FLATNESS_THRESHOLD 0.5 #include #include struct Q_DECL_HIDDEN KisPaintOp::Private { Private(KisPaintOp *_q) : q(_q), dab(0), fanCornersEnabled(false), fanCornersStep(1.0) {} KisPaintOp *q; KisFixedPaintDeviceSP dab; KisPainter* painter; bool fanCornersEnabled; qreal fanCornersStep; }; KisPaintOp::KisPaintOp(KisPainter * painter) : d(new Private(this)) { d->painter = painter; } KisPaintOp::~KisPaintOp() { d->dab.clear(); delete d; } KisFixedPaintDeviceSP KisPaintOp::cachedDab() { return cachedDab(d->painter->device()->colorSpace()); } KisFixedPaintDeviceSP KisPaintOp::cachedDab(const KoColorSpace *cs) { if (!d->dab || *d->dab->colorSpace() != *cs) { d->dab = new KisFixedPaintDevice(cs); } return d->dab; } void KisPaintOp::setFanCornersInfo(bool fanCornersEnabled, qreal fanCornersStep) { d->fanCornersEnabled = fanCornersEnabled; d->fanCornersStep = fanCornersStep; } void KisPaintOp::splitCoordinate(qreal coordinate, qint32 *whole, qreal *fraction) { const qint32 i = std::floor(coordinate); const qreal f = coordinate - i; *whole = i; *fraction = f; } static void paintBezierCurve(KisPaintOp *paintOp, const KisPaintInformation &pi1, const KisVector2D &control1, const KisVector2D &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { LineEquation line = LineEquation::Through(toKisVector2D(pi1.pos()), toKisVector2D(pi2.pos())); qreal d1 = line.absDistance(control1); qreal d2 = line.absDistance(control2); if ((d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) || qIsNaN(d1) || qIsNaN(d2)) { paintOp->paintLine(pi1, pi2, currentDistance); } else { // Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508 KisVector2D l2 = (toKisVector2D(pi1.pos()) + control1) / 2; KisVector2D h = (control1 + control2) / 2; KisVector2D l3 = (l2 + h) / 2; KisVector2D r3 = (control2 + toKisVector2D(pi2.pos())) / 2; KisVector2D r2 = (h + r3) / 2; KisVector2D l4 = (l3 + r2) / 2; KisPaintInformation middlePI = KisPaintInformation::mix(toQPointF(l4), 0.5, pi1, pi2); paintBezierCurve(paintOp, pi1, l2, l3, middlePI, currentDistance); paintBezierCurve(paintOp, middlePI, r2, r3, pi2, currentDistance); } } void KisPaintOp::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { return ::paintBezierCurve(this, pi1, toKisVector2D(control1), toKisVector2D(control2), pi2, currentDistance); } void KisPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { KisPaintOpUtils::paintLine(*this, pi1, pi2, currentDistance, d->fanCornersEnabled, d->fanCornersStep); } void KisPaintOp::paintAt(const KisPaintInformation& info, KisDistanceInformation *currentDistance) { Q_ASSERT(currentDistance); KisPaintInformation pi(info); pi.paintAt(*this, currentDistance); } +void KisPaintOp::updateSpacing(const KisPaintInformation &info, + KisDistanceInformation ¤tDistance) const +{ + KisPaintInformation pi(info); + KisSpacingInformation spacingInfo; + { + KisPaintInformation::DistanceInformationRegistrar r + = pi.registerDistanceInformation(¤tDistance); + spacingInfo = updateSpacingImpl(pi); + } + + currentDistance.setSpacing(spacingInfo); +} + KisPainter* KisPaintOp::painter() const { return d->painter; } KisPaintDeviceSP KisPaintOp::source() const { return d->painter->device(); } diff --git a/libs/image/brushengine/kis_paintop.h b/libs/image/brushengine/kis_paintop.h index adec15f84f..b6bbee5cc2 100644 --- a/libs/image/brushengine/kis_paintop.h +++ b/libs/image/brushengine/kis_paintop.h @@ -1,129 +1,142 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004,2010 Cyrille Berger * * 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_PAINTOP_H_ #define KIS_PAINTOP_H_ #include #include "kis_shared.h" #include "kis_types.h" #include class QPointF; class KoColorSpace; class KisPainter; class KisPaintInformation; /** * KisPaintOp are use by tools to draw on a paint device. A paintop takes settings * and input information, like pressure, tilt or motion and uses that to draw pixels */ class KRITAIMAGE_EXPORT KisPaintOp : public KisShared { struct Private; public: KisPaintOp(KisPainter * painter); virtual ~KisPaintOp(); /** * Paint at the subpixel point pos using the specified paint * information.. * * The distance between two calls of the paintAt is always * specified by spacing, which is automatically saved into the * current distance information object */ void paintAt(const KisPaintInformation& info, KisDistanceInformation *currentDistance); + /** + * Updates the spacing in currentDistance based on the provided information. Note that the + * spacing is updated automatically in the paintAt method, so there is no need to call this + * method if paintAt has just been called. + */ + void updateSpacing(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) + const; + /** * Draw a line between pos1 and pos2 using the currently set brush and color. * If savedDist is less than zero, the brush is painted at pos1 before being * painted along the line using the spacing setting. * * @return the drag distance, that is the remains of the distance * between p1 and p2 not covered because the currenlty set brush * has a spacing greater than that distance. */ virtual void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance); /** * Draw a Bezier curve between pos1 and pos2 using control points 1 and 2. * If savedDist is less than zero, the brush is painted at pos1 before being * painted along the curve using the spacing setting. * @return the drag distance, that is the remains of the distance between p1 and p2 not covered * because the currenlty set brush has a spacing greater than that distance. */ virtual void paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance); /** * Whether this paintop can paint. Can be false in case that some setting isn't read correctly. * @return if paintop is ready for painting, default is true */ virtual bool canPaint() const { return true; } /** * Split the coordinate into whole + fraction, where fraction is always >= 0. */ static void splitCoordinate(qreal coordinate, qint32 *whole, qreal *fraction); protected: friend class KisPaintInformation; /** - * The implementation of painting of a dab + * The implementation of painting of a dab and updating spacing */ virtual KisSpacingInformation paintAt(const KisPaintInformation& info) = 0; + /** + * Implementation of a spacing update + */ + virtual KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const = 0; + KisFixedPaintDeviceSP cachedDab(); KisFixedPaintDeviceSP cachedDab(const KoColorSpace *cs); /** * Return the painter this paintop is owned by */ KisPainter* painter() const; /** * Return the paintdevice the painter this paintop is owned by */ KisPaintDeviceSP source() const; private: friend class KisPressureRotationOption; void setFanCornersInfo(bool fanCornersEnabled, qreal fanCornersStep); private: Private* const d; }; #endif // KIS_PAINTOP_H_ diff --git a/libs/image/brushengine/kis_paintop_utils.h b/libs/image/brushengine/kis_paintop_utils.h index 58d5d1ba6e..0c180ddc2b 100644 --- a/libs/image/brushengine/kis_paintop_utils.h +++ b/libs/image/brushengine/kis_paintop_utils.h @@ -1,198 +1,215 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * 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_PAINTOP_UTILS_H #define __KIS_PAINTOP_UTILS_H #include "kis_global.h" #include "kis_paint_information.h" +#include "kis_distance_information.h" namespace KisPaintOpUtils { template bool paintFan(PaintOp &op, const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance, qreal fanCornersStep) { const qreal angleStep = fanCornersStep; const qreal initialAngle = currentDistance->lastDrawingAngle(); const qreal finalAngle = pi2.drawingAngleSafe(*currentDistance); const qreal fullDistance = shortestAngularDistance(initialAngle, finalAngle); qreal lastAngle = initialAngle; int i = 0; while (shortestAngularDistance(lastAngle, finalAngle) > angleStep) { lastAngle = incrementInDirection(lastAngle, angleStep, finalAngle); qreal t = angleStep * i++ / fullDistance; QPointF pt = pi1.pos() + t * (pi2.pos() - pi1.pos()); KisPaintInformation pi = KisPaintInformation::mix(pt, t, pi1, pi2); pi.overrideDrawingAngle(lastAngle); pi.paintAt(op, currentDistance); } return i; } template void paintLine(PaintOp &op, const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance, bool fanCornersEnabled, qreal fanCornersStep) { QPointF end = pi2.pos(); qreal endTime = pi2.currentTime(); KisPaintInformation pi = pi1; qreal t = 0.0; while ((t = currentDistance->getNextPointPosition(pi.pos(), end, pi.currentTime(), endTime)) >= 0.0) { pi = KisPaintInformation::mix(t, pi, pi2); if (fanCornersEnabled && currentDistance->hasLastPaintInformation()) { paintFan(op, currentDistance->lastPaintInformation(), pi, currentDistance, fanCornersStep); } /** * A bit complicated part to ensure the registration * of the distance information is done in right order */ pi.paintAt(op, currentDistance); } + + /* + * Perform a spacing update between dabs if appropriate. Typically, this will not happen if the + * above loop actually painted anything. This is because the getNextPointPosition() call before + * the paint operation will reset the accumulators in currentDistance and therefore make + * needsSpacingUpdate() false. The temporal distance between pi1 and pi2 is typically too small + * for the accumulators to build back up enough to require a spacing update after that. + * (The accumulated time value is updated not during the paint operation, but during the call to + * getNextPointPosition(), that is, updated during every paintLine() call.) + */ + if (currentDistance->needsSpacingUpdate()) { + op.updateSpacing(pi2, *currentDistance); + } } /** * A special class containing the previous position of the cursor for * the sake of painting the outline of the paint op. The main purpose * of this class is to ensure that the saved point does not equal to * the current one, which would cause a outline flicker. To echieve * this the class stores two previosly requested points instead of the * last one. */ class PositionHistory { public: /** * \return the previously used point, which is guaranteed not to * be equal to \p pt and updates the history if needed */ QPointF pushThroughHistory(const QPointF &pt) { QPointF result; const qreal pointSwapThreshold = 7.0; /** * We check x *and* y separately, because events generated by * a mouse device tend to come separately for x and y offsets. * Efficienty generating the 'stairs' pattern. */ if (qAbs(pt.x() - m_second.x()) > pointSwapThreshold && qAbs(pt.y() - m_second.y()) > pointSwapThreshold) { result = m_second; m_first = m_second; m_second = pt; } else { result = m_first; } return result; } private: QPointF m_first; QPointF m_second; }; bool checkSizeTooSmall(qreal scale, qreal width, qreal height) { return scale * width < 0.01 || scale * height < 0.01; } inline qreal calcAutoSpacing(qreal value, qreal coeff) { return coeff * (value < 1.0 ? value : sqrt(value)); } QPointF calcAutoSpacing(const QPointF &pt, qreal coeff, qreal lodScale) { const qreal invLodScale = 1.0 / lodScale; const QPointF lod0Point = invLodScale * pt; return lodScale * QPointF(calcAutoSpacing(lod0Point.x(), coeff), calcAutoSpacing(lod0Point.y(), coeff)); } KisSpacingInformation effectiveSpacing(qreal dabWidth, qreal dabHeight, qreal extraScale, qreal rateExtraScale, bool distanceSpacingEnabled, bool isotropicSpacing, qreal rotation, bool axesFlipped, qreal spacingVal, bool autoSpacingActive, qreal autoSpacingCoeff, bool timedSpacingEnabled, qreal timedSpacingInterval, qreal lodScale) { QPointF spacing; if (!isotropicSpacing) { if (autoSpacingActive) { spacing = calcAutoSpacing(QPointF(dabWidth, dabHeight), autoSpacingCoeff, lodScale); } else { spacing = QPointF(dabWidth, dabHeight); spacing *= spacingVal; } } else { qreal significantDimension = qMax(dabWidth, dabHeight); if (autoSpacingActive) { significantDimension = calcAutoSpacing(significantDimension, autoSpacingCoeff); } else { significantDimension *= spacingVal; } spacing = QPointF(significantDimension, significantDimension); rotation = 0.0; axesFlipped = false; } spacing *= extraScale; + qreal scaledInterval = rateExtraScale <= 0.0 ? LONG_TIME : + timedSpacingInterval / rateExtraScale; + return KisSpacingInformation(distanceSpacingEnabled, spacing, rotation, axesFlipped, - timedSpacingEnabled, timedSpacingInterval / rateExtraScale); + timedSpacingEnabled, scaledInterval); } } #endif /* __KIS_PAINTOP_UTILS_H */ diff --git a/libs/image/kis_distance_information.cpp b/libs/image/kis_distance_information.cpp index 8e167627a7..20dba8196a 100644 --- a/libs/image/kis_distance_information.cpp +++ b/libs/image/kis_distance_information.cpp @@ -1,400 +1,557 @@ /* * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2013 Dmitry Kazakov * * 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 #include #include "kis_debug.h" #include #include #include #include "kis_algebra_2d.h" +#include "kis_dom_utils.h" #include "kis_lod_transform.h" const qreal MIN_DISTANCE_SPACING = 0.5; // Smallest allowed interval when timed spacing is enabled, in milliseconds. const qreal MIN_TIMED_INTERVAL = 0.5; // Largest allowed interval when timed spacing is enabled, in milliseconds. -const qreal MAX_TIMED_INTERVAL = 1000.0; +const qreal MAX_TIMED_INTERVAL = LONG_TIME; struct Q_DECL_HIDDEN KisDistanceInformation::Private { Private() : accumDistance(), accumTime(0.0), + spacingUpdateInterval(LONG_TIME), lastDabInfoValid(false), lastPaintInfoValid(false), lockedDrawingAngle(0.0), hasLockedDrawingAngle(false), totalDistance(0.0) {} QPointF accumDistance; qreal accumTime; KisSpacingInformation spacing; + qreal spacingUpdateInterval; QPointF lastPosition; qreal lastTime; qreal lastAngle; bool lastDabInfoValid; KisPaintInformation lastPaintInformation; bool lastPaintInfoValid; qreal lockedDrawingAngle; bool hasLockedDrawingAngle; qreal totalDistance; }; +KisDistanceInitInfo::KisDistanceInitInfo() + : m_hasLastInfo(false) + , m_lastPosition() + , m_lastTime(0.0) + , m_lastAngle(0.0) + , m_spacingUpdateInterval(LONG_TIME) +{ +} + +KisDistanceInitInfo::KisDistanceInitInfo(qreal spacingUpdateInterval) + : m_hasLastInfo(false) + , m_lastPosition() + , m_lastTime(0.0) + , m_lastAngle(0.0) + , m_spacingUpdateInterval(spacingUpdateInterval) +{ +} + +KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, + qreal lastAngle) + : m_hasLastInfo(true) + , m_lastPosition(lastPosition) + , m_lastTime(lastTime) + , m_lastAngle(lastAngle) + , m_spacingUpdateInterval(LONG_TIME) +{ +} + +KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, + qreal lastAngle, qreal spacingUpdateInterval) + : m_hasLastInfo(true) + , m_lastPosition(lastPosition) + , m_lastTime(lastTime) + , m_lastAngle(lastAngle) + , m_spacingUpdateInterval(spacingUpdateInterval) +{ +} + +bool KisDistanceInitInfo::operator==(const KisDistanceInitInfo &other) const +{ + if (m_spacingUpdateInterval != other.m_spacingUpdateInterval + || m_hasLastInfo != other.m_hasLastInfo) + { + return false; + } + if (m_hasLastInfo) { + if (m_lastPosition != other.m_lastPosition || m_lastTime != other.m_lastTime + || m_lastAngle != other.m_lastAngle) + { + return false; + } + } + + return true; +} + +bool KisDistanceInitInfo::operator!=(const KisDistanceInitInfo &other) const +{ + return !(*this == other); +} + +KisDistanceInformation KisDistanceInitInfo::makeDistInfo() +{ + if (m_hasLastInfo) { + return KisDistanceInformation(m_lastPosition, m_lastTime, m_lastAngle, + m_spacingUpdateInterval); + } + else { + return KisDistanceInformation(m_spacingUpdateInterval); + } +} + +void KisDistanceInitInfo::toXML(QDomDocument &doc, QDomElement &elt) const +{ + elt.setAttribute("spacingUpdateInterval", QString::number(m_spacingUpdateInterval, 'g', 15)); + if (m_hasLastInfo) { + QDomElement lastInfoElt = doc.createElement("LastInfo"); + lastInfoElt.setAttribute("lastPosX", QString::number(m_lastPosition.x(), 'g', 15)); + lastInfoElt.setAttribute("lastPosY", QString::number(m_lastPosition.y(), 'g', 15)); + lastInfoElt.setAttribute("lastTime", QString::number(m_lastTime, 'g', 15)); + lastInfoElt.setAttribute("lastAngle", QString::number(m_lastAngle, 'g', 15)); + elt.appendChild(lastInfoElt); + } +} + +KisDistanceInitInfo KisDistanceInitInfo::fromXML(const QDomElement &elt) +{ + const qreal spacingUpdateInterval = qreal(KisDomUtils::toDouble(elt.attribute("spacingUpdateInterval", + QString::number(LONG_TIME, 'g', 15)))); + const QDomElement lastInfoElt = elt.firstChildElement("LastInfo"); + const bool hasLastInfo = !lastInfoElt.isNull(); + + if (hasLastInfo) { + const qreal lastPosX = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosX", + "0.0"))); + const qreal lastPosY = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosY", + "0.0"))); + const qreal lastTime = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastTime", + "0.0"))); + const qreal lastAngle = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastAngle", + "0.0"))); + + return KisDistanceInitInfo(QPointF(lastPosX, lastPosY), lastTime, lastAngle, + spacingUpdateInterval); + } + else { + return KisDistanceInitInfo(spacingUpdateInterval); + } +} + KisDistanceInformation::KisDistanceInformation() : m_d(new Private) { } +KisDistanceInformation::KisDistanceInformation(qreal spacingUpdateInterval) + : m_d(new Private) +{ + m_d->spacingUpdateInterval = spacingUpdateInterval; +} + KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition, qreal lastTime, qreal lastAngle) : m_d(new Private) { m_d->lastPosition = lastPosition; m_d->lastTime = lastTime; m_d->lastAngle = lastAngle; m_d->lastDabInfoValid = true; } +KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition, + qreal lastTime, + qreal lastAngle, + qreal spacingUpdateInterval) + : KisDistanceInformation(lastPosition, lastTime, lastAngle) +{ + m_d->spacingUpdateInterval = spacingUpdateInterval; +} + KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs) : m_d(new Private(*rhs.m_d)) { } KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs, int levelOfDetail) : m_d(new Private(*rhs.m_d)) { KIS_ASSERT_RECOVER_NOOP(!m_d->lastPaintInfoValid && "The distance information " "should be cloned before the " "actual painting is started"); KisLodTransform t(levelOfDetail); m_d->lastPosition = t.map(m_d->lastPosition); } KisDistanceInformation& KisDistanceInformation::operator=(const KisDistanceInformation &rhs) { *m_d = *rhs.m_d; return *this; } void KisDistanceInformation::overrideLastValues(const QPointF &lastPosition, qreal lastTime, qreal lastAngle) { m_d->lastPosition = lastPosition; m_d->lastTime = lastTime; m_d->lastAngle = lastAngle; m_d->lastDabInfoValid = true; } KisDistanceInformation::~KisDistanceInformation() { delete m_d; } const KisSpacingInformation& KisDistanceInformation::currentSpacing() const { return m_d->spacing; } +void KisDistanceInformation::setSpacing(const KisSpacingInformation &spacing) +{ + m_d->spacing = spacing; +} + +bool KisDistanceInformation::needsSpacingUpdate() const +{ + // Only require spacing updates between dabs if timed spacing is enabled. + return m_d->spacing.isTimedSpacingEnabled() && m_d->accumTime >= m_d->spacingUpdateInterval; +} + bool KisDistanceInformation::hasLastDabInformation() const { return m_d->lastDabInfoValid; } QPointF KisDistanceInformation::lastPosition() const { return m_d->lastPosition; } qreal KisDistanceInformation::lastTime() const { return m_d->lastTime; } qreal KisDistanceInformation::lastDrawingAngle() const { return m_d->lastAngle; } bool KisDistanceInformation::hasLastPaintInformation() const { return m_d->lastPaintInfoValid; } const KisPaintInformation& KisDistanceInformation::lastPaintInformation() const { return m_d->lastPaintInformation; } bool KisDistanceInformation::isStarted() const { return m_d->lastPaintInfoValid; } void KisDistanceInformation::registerPaintedDab(const KisPaintInformation &info, const KisSpacingInformation &spacing) { m_d->totalDistance += KisAlgebra2D::norm(info.pos() - m_d->lastPosition); m_d->lastPaintInformation = info; m_d->lastPaintInfoValid = true; m_d->lastAngle = nextDrawingAngle(info.pos()); m_d->lastPosition = info.pos(); m_d->lastTime = info.currentTime(); m_d->lastDabInfoValid = true; m_d->spacing = spacing; } qreal KisDistanceInformation::getNextPointPosition(const QPointF &start, const QPointF &end, qreal startTime, qreal endTime) { // Compute interpolation factor based on distance. qreal distanceFactor = -1.0; if (m_d->spacing.isDistanceSpacingEnabled()) { distanceFactor = m_d->spacing.isIsotropic() ? getNextPointPositionIsotropic(start, end) : getNextPointPositionAnisotropic(start, end); } // Compute interpolation factor based on time. qreal timeFactor = -1.0; if (m_d->spacing.isTimedSpacingEnabled()) { timeFactor = getNextPointPositionTimed(startTime, endTime); } // Return the distance-based or time-based factor, whichever is smallest. if (distanceFactor < 0.0) { return timeFactor; } else if (timeFactor < 0.0) { return distanceFactor; } else { return qMin(distanceFactor, timeFactor); } } qreal KisDistanceInformation::getNextPointPositionIsotropic(const QPointF &start, const QPointF &end) { qreal distance = m_d->accumDistance.x(); qreal spacing = qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().x()); if (start == end) { return -1; } qreal dragVecLength = QVector2D(end - start).length(); qreal nextPointDistance = spacing - distance; qreal t; - if (nextPointDistance <= dragVecLength) { + // nextPointDistance can sometimes be negative if the spacing info has been modified since the + // last interpolation attempt. In that case, have a point painted immediately. + if (nextPointDistance <= 0.0) { + resetAccumulators(); + t = 0.0; + } + else if (nextPointDistance <= dragVecLength) { t = nextPointDistance / dragVecLength; resetAccumulators(); } else { t = -1; m_d->accumDistance.rx() += dragVecLength; } return t; } qreal KisDistanceInformation::getNextPointPositionAnisotropic(const QPointF &start, const QPointF &end) { if (start == end) { return -1; } qreal a_rev = 1.0 / qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().x()); qreal b_rev = 1.0 / qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().y()); qreal x = m_d->accumDistance.x(); qreal y = m_d->accumDistance.y(); + qreal gamma = pow2(x * a_rev) + pow2(y * b_rev) - 1; + + // If the distance accumulator is already past the spacing ellipse, have a point painted + // immediately. This can happen if the spacing info has been modified since the last + // interpolation attempt. + if (gamma >= 0.0) { + resetAccumulators(); + return 0.0; + } + static const qreal eps = 2e-3; // < 0.2 deg qreal currentRotation = m_d->spacing.rotation(); if (m_d->spacing.coordinateSystemFlipped()) { currentRotation = 2 * M_PI - currentRotation; } QPointF diff = end - start; if (currentRotation > eps) { QTransform rot; // since the ellipse is symmetrical, the sign // of rotation doesn't matter rot.rotateRadians(currentRotation); diff = rot.map(diff); } qreal dx = qAbs(diff.x()); qreal dy = qAbs(diff.y()); qreal alpha = pow2(dx * a_rev) + pow2(dy * b_rev); qreal beta = x * dx * a_rev * a_rev + y * dy * b_rev * b_rev; - qreal gamma = pow2(x * a_rev) + pow2(y * b_rev) - 1; - qreal D_4 = pow2(beta) - alpha * gamma; qreal t = -1.0; if (D_4 >= 0) { qreal k = (-beta + qSqrt(D_4)) / alpha; if (k >= 0.0 && k <= 1.0) { t = k; resetAccumulators(); } else { m_d->accumDistance += KisAlgebra2D::abs(diff); } } else { warnKrita << "BUG: No solution for elliptical spacing equation has been found. This shouldn't have happened."; } return t; } qreal KisDistanceInformation::getNextPointPositionTimed(qreal startTime, qreal endTime) { // If start time is not before end time, do not interpolate. if (!(startTime < endTime)) { return -1.0; } qreal timedSpacingInterval = qBound(MIN_TIMED_INTERVAL, m_d->spacing.timedSpacingInterval(), MAX_TIMED_INTERVAL); qreal nextPointInterval = timedSpacingInterval - m_d->accumTime; - // Note: nextPointInterval SHOULD always be positive, but I wasn't sure if floating point - // roundoff error might make it nonpositive in some cases, so I included this check. + qreal t = -1.0; + + // nextPointInterval can sometimes be negative if the spacing info has been modified since the + // last interpolation attempt. In that case, have a point painted immediately. if (nextPointInterval <= 0.0) { resetAccumulators(); - return 0.0; + t = 0.0; } else if (nextPointInterval <= endTime - startTime) { resetAccumulators(); - return nextPointInterval / (endTime - startTime); + t = nextPointInterval / (endTime - startTime); } else { m_d->accumTime += endTime - startTime; - return -1.0; + t = -1.0; } + + return t; } void KisDistanceInformation::resetAccumulators() { m_d->accumDistance = QPointF(); m_d->accumTime = 0.0; } bool KisDistanceInformation::hasLockedDrawingAngle() const { return m_d->hasLockedDrawingAngle; } qreal KisDistanceInformation::lockedDrawingAngle() const { return m_d->lockedDrawingAngle; } void KisDistanceInformation::setLockedDrawingAngle(qreal angle) { m_d->hasLockedDrawingAngle = true; m_d->lockedDrawingAngle = angle; } qreal KisDistanceInformation::nextDrawingAngle(const QPointF &nextPos, bool considerLockedAngle) const { if (!m_d->lastDabInfoValid) { warnKrita << "KisDistanceInformation::nextDrawingAngle()" << "No last dab data"; return 0.0; } // Compute the drawing angle. If the new position is the same as the previous position, an angle // can't be computed. In that case, act as if the angle is the same as in the previous dab. return drawingAngleImpl(m_d->lastPosition, nextPos, considerLockedAngle, m_d->lastAngle); } QPointF KisDistanceInformation::nextDrawingDirectionVector(const QPointF &nextPos, bool considerLockedAngle) const { if (!m_d->lastDabInfoValid) { warnKrita << "KisDistanceInformation::nextDrawingDirectionVector()" << "No last dab data"; return QPointF(1.0, 0.0); } // Compute the direction vector. If the new position is the same as the previous position, a // direction can't be computed. In that case, act as if the direction is the same as in the // previous dab. return drawingDirectionVectorImpl(m_d->lastPosition, nextPos, considerLockedAngle, m_d->lastAngle); } qreal KisDistanceInformation::scalarDistanceApprox() const { return m_d->totalDistance; } qreal KisDistanceInformation::drawingAngleImpl(const QPointF &start, const QPointF &end, bool considerLockedAngle, qreal defaultAngle) const { if (m_d->hasLockedDrawingAngle && considerLockedAngle) { return m_d->lockedDrawingAngle; } // If the start and end positions are the same, we can't compute an angle. In that case, use the // provided default. return KisAlgebra2D::directionBetweenPoints(start, end, defaultAngle); } QPointF KisDistanceInformation::drawingDirectionVectorImpl(const QPointF &start, const QPointF &end, bool considerLockedAngle, qreal defaultAngle) const { if (m_d->hasLockedDrawingAngle && considerLockedAngle) { return QPointF(cos(m_d->lockedDrawingAngle), sin(m_d->lockedDrawingAngle)); } // If the start and end positions are the same, we can't compute a drawing direction. In that // case, use the provided default. if (KisAlgebra2D::fuzzyPointCompare(start, end)) { return QPointF(cos(defaultAngle), sin(defaultAngle)); } const QPointF diff(end - start); return KisAlgebra2D::normalize(diff); } diff --git a/libs/image/kis_distance_information.h b/libs/image/kis_distance_information.h index 742ea1d2b0..9a64340f86 100644 --- a/libs/image/kis_distance_information.h +++ b/libs/image/kis_distance_information.h @@ -1,254 +1,335 @@ /* * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2013 Dmitry Kazakov * * 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_DISTANCE_INFORMATION_H_ #define _KIS_DISTANCE_INFORMATION_H_ #include #include +#include +#include #include "kritaimage_export.h" class KisPaintInformation; +class KisDistanceInformation; +/** + * A time in milliseconds that is assumed to be longer than any stroke (or other paint operation) + * will ever last. This is used instead of infinity to avoid potential errors. The value is + * approximately ten years. + */ +const qreal LONG_TIME = 320000000000.0; /** * This structure contains information about the desired spacing * requested by the paintAt call */ class KisSpacingInformation { public: explicit KisSpacingInformation() : m_distanceSpacingEnabled(true) , m_distanceSpacing(0.0, 0.0) , m_timedSpacingEnabled(false) , m_timedSpacingInterval(0.0) , m_rotation(0.0) , m_coordinateSystemFlipped(false) { } explicit KisSpacingInformation(qreal isotropicSpacing) : m_distanceSpacingEnabled(true) , m_distanceSpacing(isotropicSpacing, isotropicSpacing) , m_timedSpacingEnabled(false) , m_timedSpacingInterval(0.0) , m_rotation(0.0) , m_coordinateSystemFlipped(false) { } explicit KisSpacingInformation(const QPointF &anisotropicSpacing, qreal rotation, bool coordinateSystemFlipped) : m_distanceSpacingEnabled(true) , m_distanceSpacing(anisotropicSpacing) , m_timedSpacingEnabled(false) , m_timedSpacingInterval(0.0) , m_rotation(rotation) , m_coordinateSystemFlipped(coordinateSystemFlipped) { } explicit KisSpacingInformation(qreal isotropicSpacing, qreal timedSpacingInterval) : m_distanceSpacingEnabled(true) , m_distanceSpacing(isotropicSpacing, isotropicSpacing) , m_timedSpacingEnabled(true) , m_timedSpacingInterval(timedSpacingInterval) , m_rotation(0.0) , m_coordinateSystemFlipped(false) { } explicit KisSpacingInformation(const QPointF &anisotropicSpacing, qreal rotation, bool coordinateSystemFlipped, qreal timedSpacingInterval) : m_distanceSpacingEnabled(true) , m_distanceSpacing(anisotropicSpacing) , m_timedSpacingEnabled(true) , m_timedSpacingInterval(timedSpacingInterval) , m_rotation(rotation) , m_coordinateSystemFlipped(coordinateSystemFlipped) { } explicit KisSpacingInformation(bool distanceSpacingEnabled, qreal isotropicSpacing, bool timedSpacingEnabled, qreal timedSpacingInterval) : m_distanceSpacingEnabled(distanceSpacingEnabled) , m_distanceSpacing(isotropicSpacing, isotropicSpacing) , m_timedSpacingEnabled(timedSpacingEnabled) , m_timedSpacingInterval(timedSpacingInterval) , m_rotation(0.0) , m_coordinateSystemFlipped(false) { } explicit KisSpacingInformation(bool distanceSpacingEnabled, const QPointF &anisotropicSpacing, qreal rotation, bool coordinateSystemFlipped, bool timedSpacingEnabled, qreal timedSpacingInterval) : m_distanceSpacingEnabled(distanceSpacingEnabled) , m_distanceSpacing(anisotropicSpacing) , m_timedSpacingEnabled(timedSpacingEnabled) , m_timedSpacingInterval(timedSpacingInterval) , m_rotation(rotation) , m_coordinateSystemFlipped(coordinateSystemFlipped) { } /** * @return True if and only if distance-based spacing is enabled. */ inline bool isDistanceSpacingEnabled() const { return m_distanceSpacingEnabled; } inline QPointF distanceSpacing() const { return m_distanceSpacing; } /** * @return True if and only if time-based spacing is enabled. */ inline bool isTimedSpacingEnabled() const { return m_timedSpacingEnabled; } /** - * @return The desired maximum amount of time between dabs, in milliseconds. Returns a time of - * approximately 1 year if time-based spacing is disabled. + * @return The desired maximum amount of time between dabs, in milliseconds. Returns LONG_TIME + * if time-based spacing is disabled. */ inline qreal timedSpacingInterval() const { return isTimedSpacingEnabled() ? m_timedSpacingInterval : - 32000000000.0; + LONG_TIME; } inline bool isIsotropic() const { return m_distanceSpacing.x() == m_distanceSpacing.y(); } inline qreal scalarApprox() const { return isIsotropic() ? m_distanceSpacing.x() : QVector2D(m_distanceSpacing).length(); } inline qreal rotation() const { return m_rotation; } bool coordinateSystemFlipped() const { return m_coordinateSystemFlipped; } private: // Distance-based spacing bool m_distanceSpacingEnabled; QPointF m_distanceSpacing; // Time-based spacing (interval is in milliseconds) bool m_timedSpacingEnabled; qreal m_timedSpacingInterval; qreal m_rotation; bool m_coordinateSystemFlipped; }; +/** + * Represents some information that can be used to initialize a KisDistanceInformation object. The + * main purpose of this class is to allow serialization of KisDistanceInformation initial settings + * to XML. + */ +class KRITAIMAGE_EXPORT KisDistanceInitInfo { + +public: + + /** + * Creates a KisDistanceInitInfo with no initial last dab information, and spacing update + * interval set to LONG_TIME. + */ + explicit KisDistanceInitInfo(); + + /** + * Creates a KisDistanceInitInfo with no initial last dab information, and the specified spacing + * update interval. + */ + explicit KisDistanceInitInfo(qreal spacingUpdateInterval); + + /** + * Creates a KisDistanceInitInfo with the specified last dab information, and spacing update + * interval set to LONG_TIME. + */ + explicit KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, qreal lastAngle); + + /** + * Creates a KisDistanceInitInfo with the specified last dab information and spacing update + * interval. + */ + explicit KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, qreal lastAngle, + qreal spacingUpdateInterval); + + bool operator==(const KisDistanceInitInfo &other) const; + + bool operator!=(const KisDistanceInitInfo &other) const; + + /** + * Constructs a KisDistanceInformation with initial settings based on this object. + */ + KisDistanceInformation makeDistInfo(); + + void toXML(QDomDocument &doc, QDomElement &elt) const; + + static KisDistanceInitInfo fromXML(const QDomElement &elt); + +private: + // Indicates whether lastPosition, lastTime, and lastAngle are valid or not. + bool m_hasLastInfo; + + QPointF m_lastPosition; + qreal m_lastTime; + qreal m_lastAngle; + + qreal m_spacingUpdateInterval; +}; + /** * This structure is used as return value of paintLine to contain * information that is needed to be passed for the next call. */ class KRITAIMAGE_EXPORT KisDistanceInformation { public: KisDistanceInformation(); + KisDistanceInformation(qreal spacingUpdateInterval); KisDistanceInformation(const QPointF &lastPosition, qreal lastTime, qreal lastAngle); + /** + * @param spacingUpdateInterval The amount of time allowed between spacing updates, in + * milliseconds. Only used when timed spacing is enabled. + */ + KisDistanceInformation(const QPointF &lastPosition, qreal lastTime, qreal lastAngle, + qreal spacingUpdateInterval); KisDistanceInformation(const KisDistanceInformation &rhs); KisDistanceInformation(const KisDistanceInformation &rhs, int levelOfDetail); KisDistanceInformation& operator=(const KisDistanceInformation &rhs); ~KisDistanceInformation(); const KisSpacingInformation& currentSpacing() const; + void setSpacing(const KisSpacingInformation &spacing); + /** + * Returns true if this KisDistanceInformation should have its spacing information updated + * immediately (regardless of whether a dab is ready to be painted). + */ + bool needsSpacingUpdate() const; + bool hasLastDabInformation() const; QPointF lastPosition() const; qreal lastTime() const; qreal lastDrawingAngle() const; bool hasLastPaintInformation() const; const KisPaintInformation& lastPaintInformation() const; void registerPaintedDab(const KisPaintInformation &info, const KisSpacingInformation &spacing); qreal getNextPointPosition(const QPointF &start, const QPointF &end, qreal startTime, qreal endTime); /** * \return true if at least one dab has been painted with this * distance information */ bool isStarted() const; bool hasLockedDrawingAngle() const; qreal lockedDrawingAngle() const; void setLockedDrawingAngle(qreal angle); /** * Computes the next drawing angle assuming that the next painting position will be nextPos. * This method should not be called when hasLastDabInformation() is false. */ qreal nextDrawingAngle(const QPointF &nextPos, bool considerLockedAngle = true) const; /** * Returns a unit vector pointing in the direction that would have been indicated by a call to * nextDrawingAngle. This method should not be called when hasLastDabInformation() is false. */ QPointF nextDrawingDirectionVector(const QPointF &nextPos, bool considerLockedAngle = true) const; qreal scalarDistanceApprox() const; void overrideLastValues(const QPointF &lastPosition, qreal lastTime, qreal lastAngle); private: qreal getNextPointPositionIsotropic(const QPointF &start, const QPointF &end); qreal getNextPointPositionAnisotropic(const QPointF &start, const QPointF &end); qreal getNextPointPositionTimed(qreal startTime, qreal endTime); void resetAccumulators(); qreal drawingAngleImpl(const QPointF &start, const QPointF &end, bool considerLockedAngle = true, qreal defaultAngle = 0.0) const; QPointF drawingDirectionVectorImpl(const QPointF &start, const QPointF &end, bool considerLockedAngle = true, qreal defaultAngle = 0.0) const; private: struct Private; Private * const m_d; }; #endif diff --git a/libs/image/recorder/kis_recorded_path_paint_action.cpp b/libs/image/recorder/kis_recorded_path_paint_action.cpp index db57127038..a4951a5194 100644 --- a/libs/image/recorder/kis_recorded_path_paint_action.cpp +++ b/libs/image/recorder/kis_recorded_path_paint_action.cpp @@ -1,260 +1,292 @@ /* * Copyright (c) 2007 Cyrille Berger * * 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 //MSVC requires that Vc come first #include "recorder/kis_recorded_path_paint_action.h" #include #include #include #include #include #include "kis_node.h" #include "kis_mask_generator.h" #include "kis_painter.h" #include #include "kis_paintop_registry.h" #include "recorder/kis_recorded_action_factory_registry.h" #include "kis_transaction.h" #include "kis_undo_adapter.h" #include #include #include "kis_paint_device.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_node_query_path.h" #include struct Q_DECL_HIDDEN KisRecordedPathPaintAction::Private { struct BezierCurveSlice { enum Type { Point, Line, Curve }; Type type; KisPaintInformation point1; QPointF control1; QPointF control2; KisPaintInformation point2; }; + + Private(const KisDistanceInitInfo &startDistInfo) : + startDistInfo(startDistInfo) {} + QList curveSlices; + + // Information about distance and spacing at the start of the action. + KisDistanceInitInfo startDistInfo; }; KisRecordedPathPaintAction::KisRecordedPathPaintAction( const KisNodeQueryPath& path, - const KisPaintOpPresetSP preset) + const KisPaintOpPresetSP preset, + const KisDistanceInitInfo& startDistInfo) : KisRecordedPaintAction("PathPaintAction", i18n("Path"), path, preset) - , d(new Private) + , d(new Private(startDistInfo)) { } KisRecordedPathPaintAction::KisRecordedPathPaintAction(const KisRecordedPathPaintAction& rhs) : KisRecordedPaintAction(rhs), d(new Private(*rhs.d)) { } KisRecordedPathPaintAction::~KisRecordedPathPaintAction() { delete d; } +KisDistanceInitInfo KisRecordedPathPaintAction::getInitDistInfo() const +{ + return d->startDistInfo; +} + +void KisRecordedPathPaintAction::setInitDistInfo(const KisDistanceInitInfo &startDistInfo) +{ + d->startDistInfo = startDistInfo; +} + void KisRecordedPathPaintAction::addPoint(const KisPaintInformation& info) { Private::BezierCurveSlice slice; slice.type = Private::BezierCurveSlice::Point; slice.point1 = info; d->curveSlices.append(slice); } void KisRecordedPathPaintAction::addLine(const KisPaintInformation& point1, const KisPaintInformation& point2) { Private::BezierCurveSlice slice; slice.type = Private::BezierCurveSlice::Line; slice.point1 = point1; slice.point2 = point2; d->curveSlices.append(slice); } void KisRecordedPathPaintAction::addPolyLine(const QList& points) { QPointF previousPoint = points[0]; for(int i = 1; i < points.size(); ++i) { QPointF pt = points[i]; addLine(KisPaintInformation(previousPoint), KisPaintInformation(pt)); previousPoint = pt; } } void KisRecordedPathPaintAction::addCurve(const KisPaintInformation& point1, const QPointF& control1, const QPointF& control2, const KisPaintInformation& point2) { Private::BezierCurveSlice slice; slice.type = Private::BezierCurveSlice::Curve; slice.point1 = point1; slice.control1 = control1; slice.control2 = control2; slice.point2 = point2; d->curveSlices.append(slice); } void KisRecordedPathPaintAction::playPaint(const KisPlayInfo&, KisPainter* painter) const { dbgImage << "play path paint action with " << d->curveSlices.size() << " slices"; if (d->curveSlices.size() <= 0) return; - KisDistanceInformation savedDist; + + KisDistanceInformation savedDist = d->startDistInfo.makeDistInfo(); Q_FOREACH (const Private::BezierCurveSlice &slice, d->curveSlices) { switch(slice.type) { case Private::BezierCurveSlice::Point: painter->paintAt(slice.point1, &savedDist); break; case Private::BezierCurveSlice::Line: painter->paintLine(slice.point1, slice.point2, &savedDist); break; case Private::BezierCurveSlice::Curve: painter->paintBezierCurve(slice.point1, slice.control1, slice.control2, slice.point2, &savedDist); break; } } } void KisRecordedPathPaintAction::toXML(QDomDocument& doc, QDomElement& elt, KisRecordedActionSaveContext* context) const { KisRecordedPaintAction::toXML(doc, elt, context); QDomElement waypointsElt = doc.createElement("Slices"); Q_FOREACH (const Private::BezierCurveSlice & slice, d->curveSlices) { switch(slice.type) { case Private::BezierCurveSlice::Point: { QDomElement infoElt = doc.createElement("Point"); slice.point1.toXML(doc, infoElt); waypointsElt.appendChild(infoElt); break; } case Private::BezierCurveSlice::Line: { QDomElement infoElt = doc.createElement("Line"); // Point1 QDomElement point1Elt = doc.createElement("Point1"); slice.point1.toXML(doc, point1Elt); infoElt.appendChild(point1Elt); // Point2 QDomElement point2Elt = doc.createElement("Point2"); slice.point2.toXML(doc, point2Elt); infoElt.appendChild(point2Elt); waypointsElt.appendChild(infoElt); break; } case Private::BezierCurveSlice::Curve: { QDomElement infoElt = doc.createElement("Curve"); // Point1 QDomElement point1Elt = doc.createElement("Point1"); slice.point1.toXML(doc, point1Elt); infoElt.appendChild(point1Elt); // Control1 QDomElement control1Elt = doc.createElement("Control1"); control1Elt.setAttribute("x", KisDomUtils::toString(slice.control1.x())); control1Elt.setAttribute("y", KisDomUtils::toString(slice.control1.y())); infoElt.appendChild(control1Elt); // Control2 QDomElement control2Elt = doc.createElement("Control2"); control2Elt.setAttribute("x", KisDomUtils::toString(slice.control2.x())); control2Elt.setAttribute("y", KisDomUtils::toString(slice.control2.y())); infoElt.appendChild(control2Elt); // Point2 QDomElement point2Elt = doc.createElement("Point2"); slice.point2.toXML(doc, point2Elt); infoElt.appendChild(point2Elt); waypointsElt.appendChild(infoElt); } } } elt.appendChild(waypointsElt); + + QDomElement initDistElt = doc.createElement("StartDistInfo"); + d->startDistInfo.toXML(doc, initDistElt); + elt.appendChild(initDistElt); } KisRecordedAction* KisRecordedPathPaintAction::clone() const { return new KisRecordedPathPaintAction(*this); } KisRecordedPathPaintActionFactory::KisRecordedPathPaintActionFactory() : KisRecordedPaintActionFactory("PathPaintAction") { } KisRecordedPathPaintActionFactory::~KisRecordedPathPaintActionFactory() { } KisRecordedAction* KisRecordedPathPaintActionFactory::fromXML(const QDomElement& elt, const KisRecordedActionLoadContext* context) { KisNodeQueryPath pathnode = nodeQueryPathFromXML(elt); // Decode pressets KisPaintOpPresetSP paintOpPreset = paintOpPresetFromXML(elt); - KisRecordedPathPaintAction* rplpa = new KisRecordedPathPaintAction(pathnode, paintOpPreset); + KisRecordedPathPaintAction* rplpa = new KisRecordedPathPaintAction(pathnode, paintOpPreset, + KisDistanceInitInfo()); setupPaintAction(rplpa, elt, context); QDomElement wpElt = elt.firstChildElement("Slices"); if (!wpElt.isNull()) { QDomNode nWp = wpElt.firstChild(); while (!nWp.isNull()) { QDomElement eWp = nWp.toElement(); if (!eWp.isNull()) { if( eWp.tagName() == "Point") { rplpa->addPoint(KisPaintInformation::fromXML(eWp)); } else if(eWp.tagName() == "Line") { rplpa->addLine(KisPaintInformation::fromXML(eWp.firstChildElement("Point1")), KisPaintInformation::fromXML(eWp.firstChildElement("Point2"))); } else if( eWp.tagName() == "Curve") { QDomElement control1Elt = eWp.firstChildElement("Control1"); QDomElement control2Elt = eWp.firstChildElement("Control2"); rplpa->addCurve(KisPaintInformation::fromXML(eWp.firstChildElement("Point1")), QPointF(KisDomUtils::toDouble(control1Elt.attribute("x", "0.0")), KisDomUtils::toDouble(control1Elt.attribute("y", "0.0"))), QPointF(KisDomUtils::toDouble(control2Elt.attribute("x", "0.0")), KisDomUtils::toDouble(control2Elt.attribute("y", "0.0"))), KisPaintInformation::fromXML(eWp.firstChildElement("Point2"))); } else { dbgImage << "Unsupported <" << eWp.tagName() << " /> element"; } } nWp = nWp.nextSibling(); } } else { dbgImage << "Warning: no found"; } + + QDomElement initDistInfoElt = elt.firstChildElement("StartDistInfo"); + if (!initDistInfoElt.isNull()) { + rplpa->setInitDistInfo(KisDistanceInitInfo::fromXML(initDistInfoElt)); + } else { + dbgImage << "Warning: no found"; + } + return rplpa; } diff --git a/libs/image/recorder/kis_recorded_path_paint_action.h b/libs/image/recorder/kis_recorded_path_paint_action.h index 94172cf8aa..607eb47ccc 100644 --- a/libs/image/recorder/kis_recorded_path_paint_action.h +++ b/libs/image/recorder/kis_recorded_path_paint_action.h @@ -1,77 +1,87 @@ /* * Copyright (c) 2007 Cyrille Berger * * 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_RECORDED_PATH_PAINT_ACTIONS_H_ #define _KIS_RECORDED_PATH_PAINT_ACTIONS_H_ #include "recorder/kis_recorded_action.h" #include "recorder/kis_recorded_paint_action.h" #include "kis_types.h" class KisPaintInformation; class KisPainter; +class KisDistanceInitInfo; #include /** * This class will record the painting of a bezier curve. */ class KRITAIMAGE_EXPORT KisRecordedPathPaintAction : public KisRecordedPaintAction { public: + /** + * @param startDist - Provides initial information related to distance and spacing, which can + * have an effect on how the path is painted. + */ KisRecordedPathPaintAction(const KisNodeQueryPath& path, - const KisPaintOpPresetSP paintOpPreset); + const KisPaintOpPresetSP paintOpPreset, + const KisDistanceInitInfo& startDistInfo); KisRecordedPathPaintAction(const KisRecordedPathPaintAction&); ~KisRecordedPathPaintAction() override; + KisDistanceInitInfo getInitDistInfo() const; + + void setInitDistInfo(const KisDistanceInitInfo &startDistInfo); + void addPoint(const KisPaintInformation& info); void addLine(const KisPaintInformation& point1, const KisPaintInformation& point2); void addPolyLine(const QList& points); void addCurve(const KisPaintInformation& point1, const QPointF& control1, const QPointF& control2, const KisPaintInformation& point2); void toXML(QDomDocument& doc, QDomElement& elt, KisRecordedActionSaveContext* ) const override; KisRecordedAction* clone() const override; protected: void playPaint(const KisPlayInfo& info, KisPainter* painter) const override; private: struct Private; Private* const d; }; class KisRecordedPathPaintActionFactory : public KisRecordedPaintActionFactory { public: KisRecordedPathPaintActionFactory(); ~KisRecordedPathPaintActionFactory() override; KisRecordedAction* fromXML(const QDomElement& elt, const KisRecordedActionLoadContext*) override; }; #endif diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt index 8eb9ffd5f2..9c3b5f62ba 100644 --- a/libs/image/tests/CMakeLists.txt +++ b/libs/image/tests/CMakeLists.txt @@ -1,237 +1,238 @@ # cmake in some versions for some not yet known reasons fails to run automoc # on random targets (changing target names already has an effect) # As temporary workaround skipping build of tests on these versions for now # See https://mail.kde.org/pipermail/kde-buildsystem/2015-June/010819.html # extend range of affected cmake versions as needed if(NOT ${CMAKE_VERSION} VERSION_LESS 3.1.3 AND NOT ${CMAKE_VERSION} VERSION_GREATER 3.2.3) message(WARNING "Skipping krita/image/tests, CMake in at least versions 3.1.3 - 3.2.3 seems to have a problem with automoc. \n(FRIENDLY REMINDER: PLEASE DON'T BREAK THE TESTS!)") set (HAVE_FAILING_CMAKE TRUE) else() set (HAVE_FAILING_CMAKE FALSE) endif() include_directories( ${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_BINARY_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/brushengine ${CMAKE_SOURCE_DIR}/libs/image/tiles3 ${CMAKE_SOURCE_DIR}/libs/image/tiles3/swap ${CMAKE_SOURCE_DIR}/sdk/tests ) include_Directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ) if(HAVE_VC) include_directories(${Vc_INCLUDE_DIR}) endif() include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() set(KisRandomGeneratorDemoSources kis_random_generator_demo.cpp kimageframe.cpp) ki18n_wrap_ui(KisRandomGeneratorDemoSources kis_random_generator_demo.ui) add_executable(KisRandomGeneratorDemo ${KisRandomGeneratorDemoSources}) target_link_libraries(KisRandomGeneratorDemo kritaimage) ecm_mark_as_test(KisRandomGeneratorDemo) ecm_add_tests( kis_base_node_test.cpp kis_fast_math_test.cpp kis_node_test.cpp kis_node_facade_test.cpp kis_fixed_paint_device_test.cpp kis_layer_test.cpp kis_effect_mask_test.cpp kis_iterator_test.cpp kis_painter_test.cpp kis_selection_test.cpp kis_count_visitor_test.cpp kis_projection_test.cpp kis_properties_configuration_test.cpp kis_transaction_test.cpp kis_pixel_selection_test.cpp kis_group_layer_test.cpp kis_paint_layer_test.cpp kis_adjustment_layer_test.cpp kis_annotation_test.cpp kis_change_profile_visitor_test.cpp kis_clone_layer_test.cpp kis_colorspace_convert_visitor_test.cpp kis_convolution_painter_test.cpp kis_crop_processing_visitor_test.cpp kis_processing_applicator_test.cpp kis_datamanager_test.cpp kis_fill_painter_test.cpp kis_filter_configuration_test.cpp kis_filter_test.cpp kis_filter_processing_information_test.cpp kis_filter_registry_test.cpp kis_filter_strategy_test.cpp kis_gradient_painter_test.cpp kis_image_commands_test.cpp kis_image_test.cpp kis_image_signal_router_test.cpp kis_iterators_ng_test.cpp kis_iterator_benchmark.cpp kis_updater_context_test.cpp kis_simple_update_queue_test.cpp kis_stroke_test.cpp kis_simple_stroke_strategy_test.cpp kis_stroke_strategy_undo_command_based_test.cpp kis_strokes_queue_test.cpp kis_macro_test.cpp kis_mask_test.cpp kis_math_toolbox_test.cpp kis_name_server_test.cpp kis_node_commands_test.cpp kis_node_graph_listener_test.cpp kis_node_visitor_test.cpp kis_paint_information_test.cpp + kis_distance_information_test.cpp kis_paintop_test.cpp kis_pattern_test.cpp kis_recorded_action_factory_registry_test.cpp kis_recorded_action_test.cpp kis_recorded_filter_action_test.cpp kis_selection_mask_test.cpp kis_shared_ptr_test.cpp kis_bsplines_test.cpp kis_warp_transform_worker_test.cpp kis_liquify_transform_worker_test.cpp kis_transparency_mask_test.cpp kis_types_test.cpp kis_vec_test.cpp kis_filter_config_widget_test.cpp kis_mask_generator_test.cpp kis_cubic_curve_test.cpp kis_node_query_path_test.cpp kis_fixed_point_maths_test.cpp kis_filter_weights_buffer_test.cpp kis_filter_weights_applicator_test.cpp kis_fill_interval_test.cpp kis_fill_interval_map_test.cpp kis_scanline_fill_test.cpp kis_psd_layer_style_test.cpp kis_layer_style_projection_plane_test.cpp kis_lod_capable_layer_offset_test.cpp kis_algebra_2d_test.cpp kis_marker_painter_test.cpp kis_lazy_brush_test.cpp kis_colorize_mask_test.cpp NAME_PREFIX "krita-image-" LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(kis_layer_style_filter_environment_test.cpp TEST_NAME kritaimage-layer_style_filter_environment_test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) ecm_add_test(kis_asl_parser_test.cpp TEST_NAME kritalibpsd-asl_parser_test LINK_LIBRARIES kritapsd kritapigment kritawidgetutils kritacommand Qt5::Xml Qt5::Test) # ecm_add_test(kis_dom_utils_test.cpp # TEST_NAME krita-image-DomUtils-Test # LINK_LIBRARIES kritaimage Qt5::Test) # kisdoc dep # kis_transform_worker_test.cpp # TEST_NAME krita-image-KisTransformWorkerTest #LINK_LIBRARIES kritaimage Qt5::Test) # kisdoc # kis_perspective_transform_worker_test.cpp # TEST_NAME krita-image-KisPerspectiveTransformWorkerTest #LINK_LIBRARIES kritaimage Qt5::Test) # kis_cs_conversion_test.cpp # TEST_NAME krita-image-KisCsConversionTest # LINK_LIBRARIES kritaimage Qt5::Test) # kisdoc # kis_processings_test.cpp # TEST_NAME krita-image-KisProcessingsTest #LINK_LIBRARIES kritaimage Qt5::Test) # image/tests cannot use stuff that needs kisdocument # kis_projection_leaf_test.cpp # TEST_NAME kritaimage-projection_leaf_test # LINK_LIBRARIES kritaimage Qt5::Test) if (NOT HAVE_FAILING_CMAKE) krita_add_broken_unit_test(kis_paint_device_test.cpp TEST_NAME krita-image-KisPaintDeviceTest LINK_LIBRARIES kritaimage kritaodf Qt5::Test) else() message(WARNING "Skipping KisPaintDeviceTest!!!!!!!!!!!!!!") endif() if (NOT HAVE_FAILING_CMAKE) krita_add_broken_unit_test(kis_filter_mask_test.cpp TEST_NAME krita-image-KisFilterMaskTest LINK_LIBRARIES kritaimage Qt5::Test) else() message(WARNING "Skipping KisFilterMaskTest!!!!!!!!!!!!!!") endif() krita_add_broken_unit_test(kis_transform_mask_test.cpp TEST_NAME krita-image-KisTransformMaskTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_histogram_test.cpp TEST_NAME krita-image-KisHistogramTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_walkers_test.cpp TEST_NAME krita-image-KisWalkersTest LINK_LIBRARIES kritaimage Qt5::Test) #krita_add_broken_unit_test(kis_async_merger_test.cpp # TEST_NAME krita-image-KisAsyncMergerTest # LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_update_scheduler_test.cpp TEST_NAME krita-image-KisUpdateSchedulerTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_queues_progress_updater_test.cpp TEST_NAME krita-image-KisQueuesProgressUpdaterTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_cage_transform_worker_test.cpp TEST_NAME krita-image-KisCageTransformWorkerTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_meta_data_test.cpp TEST_NAME krita-image-KisMetaDataTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_random_generator_test.cpp TEST_NAME krita-image-KisRandomGeneratorTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_keyframing_test.cpp TEST_NAME krita-image-Keyframing-Test LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_image_animation_interface_test.cpp TEST_NAME krita-image-ImageAnimationInterface-Test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) krita_add_broken_unit_test(kis_onion_skin_compositor_test.cpp TEST_NAME krita-image-OnionSkinCompositor-Test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) krita_add_broken_unit_test(kis_layer_styles_test.cpp TEST_NAME krita-image-LayerStylesTest LINK_LIBRARIES kritaimage Qt5::Test) diff --git a/libs/image/tests/kis_distance_information_test.cpp b/libs/image/tests/kis_distance_information_test.cpp new file mode 100644 index 0000000000..30fa1d03f9 --- /dev/null +++ b/libs/image/tests/kis_distance_information_test.cpp @@ -0,0 +1,159 @@ +#include "kis_distance_information_test.h" + +#include +#include +#include +#include + +#include "kis_algebra_2d.h" +#include "kis_distance_information.h" +#include "kis_paint_information.h" + +void KisDistanceInformationTest::testInitInfo() +{ + // Test equality checking operators. + testInitInfoEquality(); + + // Test XML cloning. + testInitInfoXMLClone(); +} + +void KisDistanceInformationTest::testInterpolation() +{ + // Set up a scenario for interpolation. + + QPointF startPos; + QPointF endPos(100.0, -50.0); + qreal dist = KisAlgebra2D::norm(endPos - startPos); + + qreal startTime = 0.0; + qreal endTime = 1000.0; + qreal interval = endTime - startTime; + + KisPaintInformation p1(startPos, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, startTime, 0.0); + KisPaintInformation p2(endPos, 1.0, 0.0, 0.0, 5.0, 0.0, 1.0, endTime, 0.0); + + // Test interpolation with various spacing settings. + + static const qreal interpTolerance = 0.000001; + + KisDistanceInformation dist1; + dist1.setSpacing(KisSpacingInformation(dist/10.0)); + testInterpolationImpl(p1, p2, dist1, 1.0/10.0, false, interpTolerance); + + KisDistanceInformation dist2(interval/2.0); + dist2.setSpacing(KisSpacingInformation(dist*2.0, interval*1.5)); + testInterpolationImpl(p1, p2, dist2, -1.0, true, interpTolerance); + + KisDistanceInformation dist3(interval*1.1); + dist3.setSpacing(KisSpacingInformation(dist/40.0)); + testInterpolationImpl(p1, p2, dist3, 1.0/40.0, false, interpTolerance); + + KisDistanceInformation dist4; + dist4.setSpacing(KisSpacingInformation(false, 1.0, false, 1.0)); + testInterpolationImpl(p1, p2, dist4, -1.0, false, interpTolerance); + + KisDistanceInformation dist5; + dist5.setSpacing(KisSpacingInformation(false, 1.0, true, interval/20.0)); + testInterpolationImpl(p1, p2, dist5, 1.0/20.0, false, interpTolerance); + + KisDistanceInformation dist6; + dist6.setSpacing(KisSpacingInformation(true, dist/10.0, true, interval/15.0)); + testInterpolationImpl(p1, p2, dist6, 1.0/15.0, false, interpTolerance); + + KisDistanceInformation dist7; + dist7.setSpacing(KisSpacingInformation(true, dist/15.0, true, interval/10.0)); + testInterpolationImpl(p1, p2, dist7, 1.0/15.0, false, interpTolerance); + + KisDistanceInformation dist8; + dist8.setSpacing(KisSpacingInformation(true, dist * 2.0, true, interval * 1.5)); + testInterpolationImpl(p1, p2, dist8, -1.0, false, interpTolerance); + + KisDistanceInformation dist9; + qreal a = 50.0; + qreal b = 25.0; + dist9.setSpacing(KisSpacingInformation(QPointF(a * 2.0, b * 2.0), 0.0, false)); + // Compute the expected interpolation factor; we are using anisotropic spacing here. + qreal angle = KisAlgebra2D::directionBetweenPoints(startPos, endPos, 0.0); + qreal cosTermSqrt = qCos(angle) / a; + qreal sinTermSqrt = qSin(angle) / b; + qreal spacingDist = 2.0 / qSqrt(cosTermSqrt * cosTermSqrt + sinTermSqrt * sinTermSqrt); + qreal expectedInterp = spacingDist / dist; + testInterpolationImpl(p1, p2, dist9, expectedInterp, false, interpTolerance); +} + +void KisDistanceInformationTest::testInitInfoEquality() const +{ + KisDistanceInitInfo info1; + KisDistanceInitInfo info2; + QVERIFY(info1 == info2); + QVERIFY(!(info1 != info2)); + + KisDistanceInitInfo info3(0.1); + KisDistanceInitInfo info4(0.1); + QVERIFY(info3 == info4); + QVERIFY(!(info3 != info4)); + + KisDistanceInitInfo info5(QPointF(1.1, -10.7), 100.0, 3.3); + KisDistanceInitInfo info6(QPointF(1.1, -10.7), 100.0, 3.3); + QVERIFY(info5 == info6); + QVERIFY(!(info5 != info6)); + + KisDistanceInitInfo info7(QPointF(-12.3, 24.0), 104.0, 5.0, 20.1); + KisDistanceInitInfo info8(QPointF(-12.3, 24.0), 104.0, 5.0, 20.1); + QVERIFY(info7 == info8); + QVERIFY(!(info7 != info8)); + + QVERIFY(info1 != info3); + QVERIFY(info1 != info5); + QVERIFY(info1 != info7); + QVERIFY(info3 != info5); + QVERIFY(info3 != info7); + QVERIFY(info5 != info7); +} + +void KisDistanceInformationTest::testInitInfoXMLClone() const +{ + // Note: Numeric values used here must be values that get serialized to XML exactly (e.g. small + // integers). Otherwise, roundoff error in serialization may cause a failure. + + KisDistanceInitInfo info1; + QDomDocument doc; + QDomElement elt1 = doc.createElement("Test1"); + info1.toXML(doc, elt1); + KisDistanceInitInfo clone1 = KisDistanceInitInfo::fromXML(elt1); + QVERIFY(clone1 == info1); + + KisDistanceInitInfo info2(40.0); + QDomElement elt2 = doc.createElement("Test2"); + info2.toXML(doc, elt2); + KisDistanceInitInfo clone2 = KisDistanceInitInfo::fromXML(elt2); + QVERIFY(clone2 == info2); + + KisDistanceInitInfo info3(QPointF(-8.0, -5.0), 0.0, 60.0); + QDomElement elt3 = doc.createElement("Test3"); + info3.toXML(doc, elt3); + KisDistanceInitInfo clone3 = KisDistanceInitInfo::fromXML(elt3); + QVERIFY(clone3 == info3); + + KisDistanceInitInfo info4(QPointF(0.0, 9.0), 10.0, 6.0, 1.0); + QDomElement elt4 = doc.createElement("Test4"); + info4.toXML(doc, elt4); + KisDistanceInitInfo clone4 = KisDistanceInitInfo::fromXML(elt4); + QVERIFY(clone4 == info4); +} + +void KisDistanceInformationTest::testInterpolationImpl(const KisPaintInformation &p1, + const KisPaintInformation &p2, + KisDistanceInformation &dist, + qreal interpFactor, + bool needSpacingUpdate, + qreal interpTolerance) const +{ + qreal actualInterpFactor = dist.getNextPointPosition(p1.pos(), p2.pos(), p1.currentTime(), + p2.currentTime()); + QVERIFY(qAbs(interpFactor - actualInterpFactor) <= interpTolerance); + QCOMPARE(dist.needsSpacingUpdate(), needSpacingUpdate); +} + +QTEST_MAIN(KisDistanceInformationTest) diff --git a/libs/image/tests/kis_distance_information_test.h b/libs/image/tests/kis_distance_information_test.h new file mode 100644 index 0000000000..12718bb946 --- /dev/null +++ b/libs/image/tests/kis_distance_information_test.h @@ -0,0 +1,32 @@ +#ifndef KIS_DISTANCE_INFORMATION_TEST_H +#define KIS_DISTANCE_INFORMATION_TEST_H + +#include + +class KisPaintInformation; +class KisDistanceInformation; + +class KisDistanceInformationTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testInitInfo(); + void testInterpolation(); + +private: + void testInitInfoEquality() const; + void testInitInfoXMLClone() const; + + /** + * Performs one interpolation using the specified KisDistanceInformation and checks the results. + * @param interpFactor The interpolation factor that the KisDistanceInformation is expected to + * return. + * @param needSpacingUpdate Indicates whether the KisDistanceInformation is expected to need a + * spacing update after the interpolation. + */ + void testInterpolationImpl(const KisPaintInformation &p1, const KisPaintInformation &p2, + KisDistanceInformation &dist, qreal interpFactor, + bool needSpacingUpdate, qreal interpTolerance) const; +}; + +#endif // KIS_DISTANCE_INFORMATION_TEST_H diff --git a/libs/image/tests/kis_paintop_test.cpp b/libs/image/tests/kis_paintop_test.cpp index b0b90eee8c..2111c968fc 100644 --- a/libs/image/tests/kis_paintop_test.cpp +++ b/libs/image/tests/kis_paintop_test.cpp @@ -1,47 +1,54 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * 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_paintop_test.h" #include #include "kis_paintop.h" #include "kis_painter.h" #include "kis_paint_device.h" class TestPaintOp : public KisPaintOp { public: TestPaintOp(KisPainter * gc) : KisPaintOp(gc) { } +protected: + KisSpacingInformation paintAt(const KisPaintInformation&) override { return KisSpacingInformation(0.0); } + KisSpacingInformation updateSpacingImpl(const KisPaintInformation&) const override + { + return KisSpacingInformation(0.0); + } + }; void KisPaintopTest::testCreation() { KisPainter p; TestPaintOp test(&p); } QTEST_MAIN(KisPaintopTest) diff --git a/libs/ui/tool/kis_recording_adapter.cpp b/libs/ui/tool/kis_recording_adapter.cpp index 1489b3649c..62074e02d4 100644 --- a/libs/ui/tool/kis_recording_adapter.cpp +++ b/libs/ui/tool/kis_recording_adapter.cpp @@ -1,83 +1,84 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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_recording_adapter.h" #include "kis_image.h" #include "kis_node.h" #include #include "recorder/kis_action_recorder.h" #include "recorder/kis_recorded_path_paint_action.h" #include "recorder/kis_node_query_path.h" KisRecordingAdapter::KisRecordingAdapter() : m_pathPaintAction(0) { } KisRecordingAdapter::~KisRecordingAdapter() { } -void KisRecordingAdapter::startStroke(KisImageWSP image, KisResourcesSnapshotSP resources) +void KisRecordingAdapter::startStroke(KisImageWSP image, KisResourcesSnapshotSP resources, + const KisDistanceInitInfo &startDistInfo) { Q_ASSERT(!m_pathPaintAction); Q_ASSERT(!m_image); m_image = image; m_pathPaintAction = new KisRecordedPathPaintAction( - KisNodeQueryPath::absolutePath(resources->currentNode()), 0); + KisNodeQueryPath::absolutePath(resources->currentNode()), 0, startDistInfo); resources->setupPaintAction(m_pathPaintAction); } void KisRecordingAdapter::endStroke() { Q_ASSERT(m_pathPaintAction); Q_ASSERT(m_image); m_image->actionRecorder()->addAction(*m_pathPaintAction); delete m_pathPaintAction; m_pathPaintAction = 0; m_image = 0; } void KisRecordingAdapter::addPoint(const KisPaintInformation &pi) { m_pathPaintAction->addPoint(pi); } void KisRecordingAdapter::addLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2) { m_pathPaintAction->addLine(pi1, pi2); } void KisRecordingAdapter::addCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2) { m_pathPaintAction->addCurve(pi1, control1, control2, pi2); } diff --git a/libs/ui/tool/kis_recording_adapter.h b/libs/ui/tool/kis_recording_adapter.h index a868f2eed8..76b8752465 100644 --- a/libs/ui/tool/kis_recording_adapter.h +++ b/libs/ui/tool/kis_recording_adapter.h @@ -1,52 +1,54 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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_RECORDING_ADAPTER_H #define __KIS_RECORDING_ADAPTER_H #include "kis_types.h" #include "kis_resources_snapshot.h" class KisRecordedPathPaintAction; class KisPaintInformation; +class KisDistanceInitInfo; class KisRecordingAdapter { public: KisRecordingAdapter(); ~KisRecordingAdapter(); - void startStroke(KisImageWSP image, KisResourcesSnapshotSP resources); + void startStroke(KisImageWSP image, KisResourcesSnapshotSP resources, + const KisDistanceInitInfo &startDist); void endStroke(); void addPoint(const KisPaintInformation &pi); void addLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2); void addCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2); private: KisImageWSP m_image; KisRecordedPathPaintAction *m_pathPaintAction; }; #endif /* __KIS_RECORDING_ADAPTER_H */ diff --git a/libs/ui/tool/kis_tool_freehand_helper.cpp b/libs/ui/tool/kis_tool_freehand_helper.cpp index b1a61b33ba..b4bc6e3317 100644 --- a/libs/ui/tool/kis_tool_freehand_helper.cpp +++ b/libs/ui/tool/kis_tool_freehand_helper.cpp @@ -1,963 +1,969 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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_tool_freehand_helper.h" #include #include #include #include #include #include "kis_algebra_2d.h" #include "kis_distance_information.h" #include "kis_painting_information_builder.h" #include "kis_recording_adapter.h" #include "kis_image.h" #include "kis_painter.h" #include #include #include "kis_update_time_monitor.h" #include "kis_stabilized_events_sampler.h" #include "KisStabilizerDelayedPaintHelper.h" #include "kis_config.h" #include //#define DEBUG_BEZIER_CURVES // Factor by which to scale the airbrush timer's interval, relative to the actual airbrushing rate. // Setting this less than 1 makes the timer-generated pseudo-events happen faster than the desired // airbrush rate, which can improve responsiveness. const qreal AIRBRUSH_INTERVAL_FACTOR = 0.5; +// The amount of time, in milliseconds, to allow between updates of the spacing information. Only +// used when airbrushing. +const qreal SPACING_UPDATE_INTERVAL = 50.0; + struct KisToolFreehandHelper::Private { KisPaintingInformationBuilder *infoBuilder; KisRecordingAdapter *recordingAdapter; KisStrokesFacade *strokesFacade; KUndo2MagicString transactionText; bool haveTangent; QPointF previousTangent; bool hasPaintAtLeastOnce; QTime strokeTime; QTimer strokeTimeoutTimer; QVector painterInfos; KisResourcesSnapshotSP resources; KisStrokeId strokeId; KisPaintInformation previousPaintInformation; KisPaintInformation olderPaintInformation; KisSmoothingOptionsSP smoothingOptions; // Timer used to generate paint updates periodically even without input events. This is only // used for paintops that depend on timely updates even when the cursor is not moving, e.g. for // airbrushing effects. QTimer airbrushingTimer; QList history; QList distanceHistory; // Keeps track of past cursor positions. This is used to determine the drawing angle when // drawing the brush outline or starting a stroke. KisPaintOpUtils::PositionHistory lastCursorPos; // Stabilizer data QQueue stabilizerDeque; QTimer stabilizerPollTimer; KisStabilizedEventsSampler stabilizedSampler; KisStabilizerDelayedPaintHelper stabilizerDelayedPaintHelper; int canvasRotation; bool canvasMirroredH; qreal effectiveSmoothnessDistance() const; }; KisToolFreehandHelper::KisToolFreehandHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText, KisRecordingAdapter *recordingAdapter, KisSmoothingOptions *smoothingOptions) : m_d(new Private()) { m_d->infoBuilder = infoBuilder; m_d->recordingAdapter = recordingAdapter; m_d->transactionText = transactionText; m_d->smoothingOptions = KisSmoothingOptionsSP( smoothingOptions ? smoothingOptions : new KisSmoothingOptions()); m_d->canvasRotation = 0; m_d->strokeTimeoutTimer.setSingleShot(true); connect(&m_d->strokeTimeoutTimer, SIGNAL(timeout()), SLOT(finishStroke())); connect(&m_d->airbrushingTimer, SIGNAL(timeout()), SLOT(doAirbrushing())); connect(&m_d->stabilizerPollTimer, SIGNAL(timeout()), SLOT(stabilizerPollAndPaint())); m_d->stabilizerDelayedPaintHelper.setPaintLineCallback( [this](const KisPaintInformation &pi1, const KisPaintInformation &pi2) { paintLine(pi1, pi2); }); m_d->stabilizerDelayedPaintHelper.setUpdateOutlineCallback( [this]() { emit requestExplicitUpdateOutline(); }); } KisToolFreehandHelper::~KisToolFreehandHelper() { delete m_d; } void KisToolFreehandHelper::setSmoothness(KisSmoothingOptionsSP smoothingOptions) { m_d->smoothingOptions = smoothingOptions; } KisSmoothingOptionsSP KisToolFreehandHelper::smoothingOptions() const { return m_d->smoothingOptions; } QPainterPath KisToolFreehandHelper::paintOpOutline(const QPointF &savedCursorPos, const KoPointerEvent *event, const KisPaintOpSettingsSP globalSettings, KisPaintOpSettings::OutlineMode mode) const { KisPaintOpSettingsSP settings = globalSettings; KisPaintInformation info = m_d->infoBuilder->hover(savedCursorPos, event); QPointF prevPoint = m_d->lastCursorPos.pushThroughHistory(savedCursorPos); qreal startAngle = KisAlgebra2D::directionBetweenPoints(prevPoint, savedCursorPos, 0); info.setCanvasRotation(m_d->canvasRotation); info.setCanvasHorizontalMirrorState( m_d->canvasMirroredH ); KisDistanceInformation distanceInfo(prevPoint, 0, startAngle); if (!m_d->painterInfos.isEmpty()) { settings = m_d->resources->currentPaintOpPreset()->settings(); if (m_d->stabilizerDelayedPaintHelper.running() && m_d->stabilizerDelayedPaintHelper.hasLastPaintInformation()) { info = m_d->stabilizerDelayedPaintHelper.lastPaintInformation(); } else { info = m_d->previousPaintInformation; } /** * When LoD mode is active it may happen that the helper has * already started a stroke, but it painted noting, because * all the work is being calculated by the scaled-down LodN * stroke. So at first we try to fetch the data from the lodN * stroke ("buddy") and then check if there is at least * something has been painted with this distance information * object. */ KisDistanceInformation *buddyDistance = m_d->painterInfos.first()->buddyDragDistance(); if (buddyDistance) { /** * Tiny hack alert: here we fetch the distance information * directly from the LodN stroke. Ideally, we should * upscale its data, but here we just override it with our * local copy of the coordinates. */ distanceInfo = *buddyDistance; distanceInfo.overrideLastValues(prevPoint, 0, startAngle); } else if (m_d->painterInfos.first()->dragDistance->isStarted()) { distanceInfo = *m_d->painterInfos.first()->dragDistance; } } KisPaintInformation::DistanceInformationRegistrar registrar = info.registerDistanceInformation(&distanceInfo); QPainterPath outline = settings->brushOutline(info, mode); if (m_d->resources && m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER && m_d->smoothingOptions->useDelayDistance()) { const qreal R = m_d->smoothingOptions->delayDistance() / m_d->resources->effectiveZoom(); outline.addEllipse(info.pos(), R, R); } return outline; } void KisToolFreehandHelper::cursorMoved(const QPointF &cursorPos) { m_d->lastCursorPos.pushThroughHistory(cursorPos); } void KisToolFreehandHelper::initPaint(KoPointerEvent *event, const QPointF &pixelCoords, KoCanvasResourceManager *resourceManager, KisImageWSP image, KisNodeSP currentNode, KisStrokesFacade *strokesFacade, KisNodeSP overrideNode, KisDefaultBoundsBaseSP bounds) { QPointF prevPoint = m_d->lastCursorPos.pushThroughHistory(pixelCoords); m_d->strokeTime.start(); KisPaintInformation pi = m_d->infoBuilder->startStroke(event, elapsedStrokeTime(), resourceManager); qreal startAngle = KisAlgebra2D::directionBetweenPoints(prevPoint, pixelCoords, 0.0); initPaintImpl(startAngle, pi, resourceManager, image, currentNode, strokesFacade, overrideNode, bounds); } bool KisToolFreehandHelper::isRunning() const { return m_d->strokeId; } void KisToolFreehandHelper::initPaintImpl(qreal startAngle, const KisPaintInformation &pi, KoCanvasResourceManager *resourceManager, KisImageWSP image, KisNodeSP currentNode, KisStrokesFacade *strokesFacade, KisNodeSP overrideNode, KisDefaultBoundsBaseSP bounds) { Q_UNUSED(overrideNode); m_d->strokesFacade = strokesFacade; m_d->haveTangent = false; m_d->previousTangent = QPointF(); m_d->hasPaintAtLeastOnce = false; m_d->previousPaintInformation = pi; + KisDistanceInitInfo startDistInfo(m_d->previousPaintInformation.pos(), + m_d->previousPaintInformation.currentTime(), + startAngle, + SPACING_UPDATE_INTERVAL); + KisDistanceInformation startDist = startDistInfo.makeDistInfo(); + createPainters(m_d->painterInfos, - m_d->previousPaintInformation.pos(), - m_d->previousPaintInformation.currentTime(), - startAngle); + startDist); m_d->resources = new KisResourcesSnapshot(image, currentNode, resourceManager, bounds); if(overrideNode) { m_d->resources->setCurrentNode(overrideNode); } if(m_d->recordingAdapter) { - m_d->recordingAdapter->startStroke(image, m_d->resources); + m_d->recordingAdapter->startStroke(image, m_d->resources, startDistInfo); } KisStrokeStrategy *stroke = new FreehandStrokeStrategy(m_d->resources->needsIndirectPainting(), m_d->resources->indirectPaintingCompositeOp(), m_d->resources, m_d->painterInfos, m_d->transactionText); m_d->strokeId = m_d->strokesFacade->startStroke(stroke); m_d->history.clear(); m_d->distanceHistory.clear(); if(m_d->resources->needsAirbrushing()) { m_d->airbrushingTimer.setInterval(computeAirbrushTimerInterval()); m_d->airbrushingTimer.start(); } if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) { stabilizerStart(m_d->previousPaintInformation); } // If airbrushing, paint an initial dab immediately. This is a workaround for an issue where // some paintops (Dyna, Particle, Sketch) might never initialize their spacing information until // paintAt is called. if (m_d->resources->needsAirbrushing()) { paintAt(pi); } } void KisToolFreehandHelper::paintBezierSegment(KisPaintInformation pi1, KisPaintInformation pi2, QPointF tangent1, QPointF tangent2) { if (tangent1.isNull() || tangent2.isNull()) return; const qreal maxSanePoint = 1e6; QPointF controlTarget1; QPointF controlTarget2; // Shows the direction in which control points go QPointF controlDirection1 = pi1.pos() + tangent1; QPointF controlDirection2 = pi2.pos() - tangent2; // Lines in the direction of the control points QLineF line1(pi1.pos(), controlDirection1); QLineF line2(pi2.pos(), controlDirection2); // Lines to check whether the control points lay on the opposite // side of the line QLineF line3(controlDirection1, controlDirection2); QLineF line4(pi1.pos(), pi2.pos()); QPointF intersection; if (line3.intersect(line4, &intersection) == QLineF::BoundedIntersection) { qreal controlLength = line4.length() / 2; line1.setLength(controlLength); line2.setLength(controlLength); controlTarget1 = line1.p2(); controlTarget2 = line2.p2(); } else { QLineF::IntersectType type = line1.intersect(line2, &intersection); if (type == QLineF::NoIntersection || intersection.manhattanLength() > maxSanePoint) { intersection = 0.5 * (pi1.pos() + pi2.pos()); // dbgKrita << "WARINING: there is no intersection point " // << "in the basic smoothing algoriths"; } controlTarget1 = intersection; controlTarget2 = intersection; } // shows how near to the controlTarget the value raises qreal coeff = 0.8; qreal velocity1 = QLineF(QPointF(), tangent1).length(); qreal velocity2 = QLineF(QPointF(), tangent2).length(); if (velocity1 == 0.0 || velocity2 == 0.0) { velocity1 = 1e-6; velocity2 = 1e-6; warnKrita << "WARNING: Basic Smoothing: Velocity is Zero! Please report a bug:" << ppVar(velocity1) << ppVar(velocity2); } qreal similarity = qMin(velocity1/velocity2, velocity2/velocity1); // the controls should not differ more than 50% similarity = qMax(similarity, qreal(0.5)); // when the controls are symmetric, their size should be smaller // to avoid corner-like curves coeff *= 1 - qMax(qreal(0.0), similarity - qreal(0.8)); Q_ASSERT(coeff > 0); QPointF control1; QPointF control2; if (velocity1 > velocity2) { control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1; coeff *= similarity; control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2; } else { control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2; coeff *= similarity; control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1; } paintBezierCurve(pi1, control1, control2, pi2); } qreal KisToolFreehandHelper::Private::effectiveSmoothnessDistance() const { const qreal effectiveSmoothnessDistance = !smoothingOptions->useScalableDistance() ? smoothingOptions->smoothnessDistance() : smoothingOptions->smoothnessDistance() / resources->effectiveZoom(); return effectiveSmoothnessDistance; } void KisToolFreehandHelper::paintEvent(KoPointerEvent *event) { KisPaintInformation info = m_d->infoBuilder->continueStroke(event, elapsedStrokeTime()); info.setCanvasRotation( m_d->canvasRotation ); info.setCanvasHorizontalMirrorState( m_d->canvasMirroredH ); KisUpdateTimeMonitor::instance()->reportMouseMove(info.pos()); paint(info); } void KisToolFreehandHelper::paint(KisPaintInformation &info) { /** * Smooth the coordinates out using the history and the * distance. This is a heavily modified version of an algo used in * Gimp and described in https://bugs.kde.org/show_bug.cgi?id=281267 and * http://www24.atwiki.jp/sigetch_2007/pages/17.html. The main * differences are: * * 1) It uses 'distance' instead of 'velocity', since time * measurements are too unstable in realworld environment * * 2) There is no 'Quality' parameter, since the number of samples * is calculated automatically * * 3) 'Tail Aggressiveness' is used for controling the end of the * stroke * * 4) The formila is a little bit different: 'Distance' parameter * stands for $3 \Sigma$ */ if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING && m_d->smoothingOptions->smoothnessDistance() > 0.0) { { // initialize current distance QPointF prevPos; if (!m_d->history.isEmpty()) { const KisPaintInformation &prevPi = m_d->history.last(); prevPos = prevPi.pos(); } else { prevPos = m_d->previousPaintInformation.pos(); } qreal currentDistance = QVector2D(info.pos() - prevPos).length(); m_d->distanceHistory.append(currentDistance); } m_d->history.append(info); qreal x = 0.0; qreal y = 0.0; if (m_d->history.size() > 3) { const qreal sigma = m_d->effectiveSmoothnessDistance() / 3.0; // '3.0' for (3 * sigma) range qreal gaussianWeight = 1 / (sqrt(2 * M_PI) * sigma); qreal gaussianWeight2 = sigma * sigma; qreal distanceSum = 0.0; qreal scaleSum = 0.0; qreal pressure = 0.0; qreal baseRate = 0.0; Q_ASSERT(m_d->history.size() == m_d->distanceHistory.size()); for (int i = m_d->history.size() - 1; i >= 0; i--) { qreal rate = 0.0; const KisPaintInformation nextInfo = m_d->history.at(i); double distance = m_d->distanceHistory.at(i); Q_ASSERT(distance >= 0.0); qreal pressureGrad = 0.0; if (i < m_d->history.size() - 1) { pressureGrad = nextInfo.pressure() - m_d->history.at(i + 1).pressure(); const qreal tailAgressiveness = 40.0 * m_d->smoothingOptions->tailAggressiveness(); if (pressureGrad > 0.0 ) { pressureGrad *= tailAgressiveness * (1.0 - nextInfo.pressure()); distance += pressureGrad * 3.0 * sigma; // (3 * sigma) --- holds > 90% of the region } } if (gaussianWeight2 != 0.0) { distanceSum += distance; rate = gaussianWeight * exp(-distanceSum * distanceSum / (2 * gaussianWeight2)); } if (m_d->history.size() - i == 1) { baseRate = rate; } else if (baseRate / rate > 100) { break; } scaleSum += rate; x += rate * nextInfo.pos().x(); y += rate * nextInfo.pos().y(); if (m_d->smoothingOptions->smoothPressure()) { pressure += rate * nextInfo.pressure(); } } if (scaleSum != 0.0) { x /= scaleSum; y /= scaleSum; if (m_d->smoothingOptions->smoothPressure()) { pressure /= scaleSum; } } if ((x != 0.0 && y != 0.0) || (x == info.pos().x() && y == info.pos().y())) { info.setPos(QPointF(x, y)); if (m_d->smoothingOptions->smoothPressure()) { info.setPressure(pressure); } m_d->history.last() = info; } } } if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::SIMPLE_SMOOTHING || m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING) { // Now paint between the coordinates, using the bezier curve interpolation if (!m_d->haveTangent) { m_d->haveTangent = true; m_d->previousTangent = (info.pos() - m_d->previousPaintInformation.pos()) / qMax(qreal(1.0), info.currentTime() - m_d->previousPaintInformation.currentTime()); } else { QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) / qMax(qreal(1.0), info.currentTime() - m_d->olderPaintInformation.currentTime()); if (newTangent.isNull() || m_d->previousTangent.isNull()) { paintLine(m_d->previousPaintInformation, info); } else { paintBezierSegment(m_d->olderPaintInformation, m_d->previousPaintInformation, m_d->previousTangent, newTangent); } m_d->previousTangent = newTangent; } m_d->olderPaintInformation = m_d->previousPaintInformation; m_d->strokeTimeoutTimer.start(100); } else if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::NO_SMOOTHING){ paintLine(m_d->previousPaintInformation, info); } if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) { m_d->stabilizedSampler.addEvent(info); } else { m_d->previousPaintInformation = info; } if(m_d->airbrushingTimer.isActive()) { m_d->airbrushingTimer.start(); } } void KisToolFreehandHelper::endPaint() { if (!m_d->hasPaintAtLeastOnce) { paintAt(m_d->previousPaintInformation); } else if (m_d->smoothingOptions->smoothingType() != KisSmoothingOptions::NO_SMOOTHING) { finishStroke(); } m_d->strokeTimeoutTimer.stop(); if(m_d->airbrushingTimer.isActive()) { m_d->airbrushingTimer.stop(); } if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) { stabilizerEnd(); } /** * There might be some timer events still pending, so * we should cancel them. Use this flag for the purpose. * Please note that we are not in MT here, so no mutex * is needed */ m_d->painterInfos.clear(); m_d->strokesFacade->endStroke(m_d->strokeId); m_d->strokeId.clear(); if(m_d->recordingAdapter) { m_d->recordingAdapter->endStroke(); } } void KisToolFreehandHelper::cancelPaint() { if (!m_d->strokeId) return; m_d->strokeTimeoutTimer.stop(); if (m_d->airbrushingTimer.isActive()) { m_d->airbrushingTimer.stop(); } if (m_d->stabilizerPollTimer.isActive()) { m_d->stabilizerPollTimer.stop(); } if (m_d->stabilizerDelayedPaintHelper.running()) { m_d->stabilizerDelayedPaintHelper.cancel(); } // see a comment in endPaint() m_d->painterInfos.clear(); m_d->strokesFacade->cancelStroke(m_d->strokeId); m_d->strokeId.clear(); if(m_d->recordingAdapter) { //FIXME: not implemented //m_d->recordingAdapter->cancelStroke(); } } int KisToolFreehandHelper::elapsedStrokeTime() const { return m_d->strokeTime.elapsed(); } void KisToolFreehandHelper::stabilizerStart(KisPaintInformation firstPaintInfo) { // FIXME: Ugly hack, this is no a "distance" in any way int sampleSize = qRound(m_d->effectiveSmoothnessDistance()); sampleSize = qMax(3, sampleSize); // Fill the deque with the current value repeated until filling the sample m_d->stabilizerDeque.clear(); for (int i = sampleSize; i > 0; i--) { m_d->stabilizerDeque.enqueue(firstPaintInfo); } // Poll and draw regularly KisConfig cfg; int stabilizerSampleSize = cfg.stabilizerSampleSize(); m_d->stabilizerPollTimer.setInterval(stabilizerSampleSize); m_d->stabilizerPollTimer.start(); int delayedPaintInterval = cfg.stabilizerDelayedPaintInterval(); if (delayedPaintInterval < stabilizerSampleSize) { m_d->stabilizerDelayedPaintHelper.start(delayedPaintInterval, firstPaintInfo); } m_d->stabilizedSampler.clear(); m_d->stabilizedSampler.addEvent(firstPaintInfo); } KisPaintInformation KisToolFreehandHelper::getStabilizedPaintInfo(const QQueue &queue, const KisPaintInformation &lastPaintInfo) { KisPaintInformation result(lastPaintInfo.pos(), lastPaintInfo.pressure(), lastPaintInfo.xTilt(), lastPaintInfo.yTilt(), lastPaintInfo.rotation(), lastPaintInfo.tangentialPressure(), lastPaintInfo.perspective(), elapsedStrokeTime(), lastPaintInfo.drawingSpeed()); if (queue.size() > 1) { QQueue::const_iterator it = queue.constBegin(); QQueue::const_iterator end = queue.constEnd(); /** * The first point is going to be overridden by lastPaintInfo, skip it. */ it++; int i = 2; if (m_d->smoothingOptions->stabilizeSensors()) { while (it != end) { qreal k = qreal(i - 1) / i; // coeff for uniform averaging result = KisPaintInformation::mixWithoutTime(k, *it, result); it++; i++; } } else{ while (it != end) { qreal k = qreal(i - 1) / i; // coeff for uniform averaging result = KisPaintInformation::mixOnlyPosition(k, *it, result); it++; i++; } } } return result; } void KisToolFreehandHelper::stabilizerPollAndPaint() { KisStabilizedEventsSampler::iterator it; KisStabilizedEventsSampler::iterator end; std::tie(it, end) = m_d->stabilizedSampler.range(); QVector delayedPaintTodoItems; for (; it != end; ++it) { KisPaintInformation sampledInfo = *it; bool canPaint = true; if (m_d->smoothingOptions->useDelayDistance()) { const qreal R = m_d->smoothingOptions->delayDistance() / m_d->resources->effectiveZoom(); QPointF diff = sampledInfo.pos() - m_d->previousPaintInformation.pos(); qreal dx = sqrt(pow2(diff.x()) + pow2(diff.y())); if (!(dx > R)) { if (m_d->resources->needsAirbrushing()) { sampledInfo.setPos(m_d->previousPaintInformation.pos()); } else { canPaint = false; } } } if (canPaint) { KisPaintInformation newInfo = getStabilizedPaintInfo(m_d->stabilizerDeque, sampledInfo); if (m_d->stabilizerDelayedPaintHelper.running()) { delayedPaintTodoItems.append(newInfo); } else { paintLine(m_d->previousPaintInformation, newInfo); } m_d->previousPaintInformation = newInfo; // Push the new entry through the queue m_d->stabilizerDeque.dequeue(); m_d->stabilizerDeque.enqueue(sampledInfo); } else if (m_d->stabilizerDeque.head().pos() != m_d->previousPaintInformation.pos()) { QQueue::iterator it = m_d->stabilizerDeque.begin(); QQueue::iterator end = m_d->stabilizerDeque.end(); while (it != end) { *it = m_d->previousPaintInformation; ++it; } } } m_d->stabilizedSampler.clear(); if (m_d->stabilizerDelayedPaintHelper.running()) { m_d->stabilizerDelayedPaintHelper.update(delayedPaintTodoItems); } else { emit requestExplicitUpdateOutline(); } } void KisToolFreehandHelper::stabilizerEnd() { // Stop the timer m_d->stabilizerPollTimer.stop(); // Finish the line if (m_d->smoothingOptions->finishStabilizedCurve()) { // Process all the existing events first stabilizerPollAndPaint(); // Draw the finish line with pending events and a time override m_d->stabilizedSampler.addFinishingEvent(m_d->stabilizerDeque.size()); stabilizerPollAndPaint(); } if (m_d->stabilizerDelayedPaintHelper.running()) { m_d->stabilizerDelayedPaintHelper.end(); } } const KisPaintOp* KisToolFreehandHelper::currentPaintOp() const { return !m_d->painterInfos.isEmpty() ? m_d->painterInfos.first()->painter->paintOp() : 0; } void KisToolFreehandHelper::finishStroke() { if (m_d->haveTangent) { m_d->haveTangent = false; QPointF newTangent = (m_d->previousPaintInformation.pos() - m_d->olderPaintInformation.pos()) / (m_d->previousPaintInformation.currentTime() - m_d->olderPaintInformation.currentTime()); paintBezierSegment(m_d->olderPaintInformation, m_d->previousPaintInformation, m_d->previousTangent, newTangent); } } void KisToolFreehandHelper::doAirbrushing() { // Check that the stroke hasn't ended. if (!m_d->painterInfos.isEmpty()) { // Add a new painting update at a point identical to the previous one, except for the time // and speed information. const KisPaintInformation &prevPaint = m_d->previousPaintInformation; KisPaintInformation nextPaint(prevPaint.pos(), prevPaint.pressure(), prevPaint.xTilt(), prevPaint.yTilt(), prevPaint.rotation(), prevPaint.tangentialPressure(), prevPaint.perspective(), elapsedStrokeTime(), 0.0); paint(nextPaint); } } int KisToolFreehandHelper::computeAirbrushTimerInterval() const { qreal realInterval = m_d->resources->airbrushingInterval() * AIRBRUSH_INTERVAL_FACTOR; return qMax(1, qFloor(realInterval)); } void KisToolFreehandHelper::paintAt(int painterInfoId, const KisPaintInformation &pi) { m_d->hasPaintAtLeastOnce = true; m_d->strokesFacade->addJob(m_d->strokeId, new FreehandStrokeStrategy::Data(m_d->resources->currentNode(), painterInfoId, pi)); if(m_d->recordingAdapter) { m_d->recordingAdapter->addPoint(pi); } } void KisToolFreehandHelper::paintLine(int painterInfoId, const KisPaintInformation &pi1, const KisPaintInformation &pi2) { m_d->hasPaintAtLeastOnce = true; m_d->strokesFacade->addJob(m_d->strokeId, new FreehandStrokeStrategy::Data(m_d->resources->currentNode(), painterInfoId, pi1, pi2)); if(m_d->recordingAdapter) { m_d->recordingAdapter->addLine(pi1, pi2); } } void KisToolFreehandHelper::paintBezierCurve(int painterInfoId, const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2) { #ifdef DEBUG_BEZIER_CURVES KisPaintInformation tpi1; KisPaintInformation tpi2; tpi1 = pi1; tpi2 = pi2; tpi1.setPressure(0.3); tpi2.setPressure(0.3); paintLine(tpi1, tpi2); tpi1.setPressure(0.6); tpi2.setPressure(0.3); tpi1.setPos(pi1.pos()); tpi2.setPos(control1); paintLine(tpi1, tpi2); tpi1.setPos(pi2.pos()); tpi2.setPos(control2); paintLine(tpi1, tpi2); #endif m_d->hasPaintAtLeastOnce = true; m_d->strokesFacade->addJob(m_d->strokeId, new FreehandStrokeStrategy::Data(m_d->resources->currentNode(), painterInfoId, pi1, control1, control2, pi2)); if(m_d->recordingAdapter) { m_d->recordingAdapter->addCurve(pi1, control1, control2, pi2); } } void KisToolFreehandHelper::createPainters(QVector &painterInfos, - const QPointF &lastPosition, - int lastTime, - qreal lastAngle) + const KisDistanceInformation &startDist) { - painterInfos << new PainterInfo(lastPosition, lastTime, lastAngle); + painterInfos << new PainterInfo(startDist); } void KisToolFreehandHelper::paintAt(const KisPaintInformation &pi) { paintAt(0, pi); } void KisToolFreehandHelper::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2) { paintLine(0, pi1, pi2); } void KisToolFreehandHelper::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2) { paintBezierCurve(0, pi1, control1, control2, pi2); } int KisToolFreehandHelper::canvasRotation() { return m_d->canvasRotation; } void KisToolFreehandHelper::setCanvasRotation(int rotation) { m_d->canvasRotation = rotation; } bool KisToolFreehandHelper::canvasMirroredH() { return m_d->canvasMirroredH; } void KisToolFreehandHelper::setCanvasHorizontalMirrorState(bool mirrored) { m_d->canvasMirroredH = mirrored; } diff --git a/libs/ui/tool/kis_tool_freehand_helper.h b/libs/ui/tool/kis_tool_freehand_helper.h index 21431b5eba..dcf07292b5 100644 --- a/libs/ui/tool/kis_tool_freehand_helper.h +++ b/libs/ui/tool/kis_tool_freehand_helper.h @@ -1,164 +1,162 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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_TOOL_FREEHAND_HELPER_H #define __KIS_TOOL_FREEHAND_HELPER_H #include #include "kis_types.h" #include "kritaui_export.h" #include #include "kis_default_bounds.h" #include #include "kis_smoothing_options.h" #include "strokes/freehand_stroke.h" class KoPointerEvent; class KoCanvasResourceManager; class KisPaintingInformationBuilder; class KisRecordingAdapter; class KisStrokesFacade; class KisPostExecutionUndoAdapter; class KisPaintOp; class KRITAUI_EXPORT KisToolFreehandHelper : public QObject { Q_OBJECT protected: typedef FreehandStrokeStrategy::PainterInfo PainterInfo; public: KisToolFreehandHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText = KUndo2MagicString(), KisRecordingAdapter *recordingAdapter = 0, KisSmoothingOptions *smoothingOptions = 0); ~KisToolFreehandHelper() override; void setSmoothness(KisSmoothingOptionsSP smoothingOptions); KisSmoothingOptionsSP smoothingOptions() const; bool isRunning() const; void cursorMoved(const QPointF &cursorPos); /** * @param pixelCoords - The position of the KoPointerEvent, in pixel coordinates. */ void initPaint(KoPointerEvent *event, const QPointF &pixelCoords, KoCanvasResourceManager *resourceManager, KisImageWSP image, KisNodeSP currentNode, KisStrokesFacade *strokesFacade, KisNodeSP overrideNode = 0, KisDefaultBoundsBaseSP bounds = 0); void paintEvent(KoPointerEvent *event); void endPaint(); const KisPaintOp* currentPaintOp() const; QPainterPath paintOpOutline(const QPointF &savedCursorPos, const KoPointerEvent *event, const KisPaintOpSettingsSP globalSettings, KisPaintOpSettings::OutlineMode mode) const; int canvasRotation(); void setCanvasRotation(int rotation = 0); bool canvasMirroredH(); void setCanvasHorizontalMirrorState (bool mirrored = false); Q_SIGNALS: /** * The signal is emitted when the outline should be updated * explicitly by the tool. Used by Stabilizer option, because it * paints on internal timer events instead of the on every paint() * event */ void requestExplicitUpdateOutline(); protected: void cancelPaint(); int elapsedStrokeTime() const; void initPaintImpl(qreal startAngle, const KisPaintInformation &pi, KoCanvasResourceManager *resourceManager, KisImageWSP image, KisNodeSP node, KisStrokesFacade *strokesFacade, KisNodeSP overrideNode = 0, KisDefaultBoundsBaseSP bounds = 0); protected: virtual void createPainters(QVector &painterInfos, - const QPointF &lastPosition, - int lastTime, - qreal lastAngle); + const KisDistanceInformation &startDist); // lo-level methods for painting primitives void paintAt(int painterInfoId, const KisPaintInformation &pi); void paintLine(int painterInfoId, const KisPaintInformation &pi1, const KisPaintInformation &pi2); void paintBezierCurve(int painterInfoId, const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2); // hi-level methods for painting primitives virtual void paintAt(const KisPaintInformation &pi); virtual void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2); virtual void paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2); private: void paint(KisPaintInformation &info); void paintBezierSegment(KisPaintInformation pi1, KisPaintInformation pi2, QPointF tangent1, QPointF tangent2); void stabilizerStart(KisPaintInformation firstPaintInfo); void stabilizerEnd(); KisPaintInformation getStabilizedPaintInfo(const QQueue &queue, const KisPaintInformation &lastPaintInfo); int computeAirbrushTimerInterval() const; private Q_SLOTS: void finishStroke(); void doAirbrushing(); void stabilizerPollAndPaint(); private: struct Private; Private * const m_d; }; #endif /* __KIS_TOOL_FREEHAND_HELPER_H */ diff --git a/libs/ui/tool/kis_tool_multihand_helper.cpp b/libs/ui/tool/kis_tool_multihand_helper.cpp index 3d78d78e9d..106e4ff78d 100644 --- a/libs/ui/tool/kis_tool_multihand_helper.cpp +++ b/libs/ui/tool/kis_tool_multihand_helper.cpp @@ -1,149 +1,147 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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_tool_multihand_helper.h" #include #include "kis_painter.h" struct KisToolMultihandHelper::Private { QVector transformations; }; KisToolMultihandHelper::KisToolMultihandHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText, KisRecordingAdapter *recordingAdapter) : KisToolFreehandHelper(infoBuilder, transactionText, recordingAdapter) , d(new Private) { } KisToolMultihandHelper::~KisToolMultihandHelper() { delete d; } void KisToolMultihandHelper::setupTransformations(const QVector &transformations) { d->transformations = transformations; } void KisToolMultihandHelper::createPainters(QVector &painterInfos, - const QPointF &lastPosition, - int lastTime, - qreal lastAngle) + const KisDistanceInformation &startDist) { for (int i = 0; i < d->transformations.size(); i++) { - painterInfos << new PainterInfo(lastPosition, lastTime, lastAngle); + painterInfos << new PainterInfo(startDist); } } void KisToolMultihandHelper::paintAt(const KisPaintInformation &pi) { for (int i = 0; i < d->transformations.size(); i++) { const QTransform &transform = d->transformations[i]; KisPaintInformation __pi = pi; QLineF rotateme(QPointF (0.0,0.0), QPointF (10.0,10.0)); rotateme.setAngle(__pi.canvasRotation()); QLineF rotated = transform.map(rotateme); __pi.setPos(transform.map(__pi.pos())); __pi.setCanvasRotation(rotated.angle()); if (__pi.canvasMirroredH()) { __pi.setCanvasRotation(180-__pi.canvasRotation()); __pi.setCanvasRotation(__pi.canvasRotation()+180); } paintAt(i, __pi); } } void KisToolMultihandHelper::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2) { for (int i = 0; i < d->transformations.size(); i++) { const QTransform &transform = d->transformations[i]; KisPaintInformation __pi1 = pi1; KisPaintInformation __pi2 = pi2; __pi1.setPos(transform.map(__pi1.pos())); __pi2.setPos(transform.map(__pi2.pos())); QLineF rotateme(QPointF (0.0,0.0), QPointF (10.0,10.0)); rotateme.setAngle(__pi1.canvasRotation()); QLineF rotated = transform.map(rotateme); __pi1.setCanvasRotation(rotated.angle()); rotateme.setAngle(__pi2.canvasRotation()); rotated = transform.map(rotateme); __pi2.setCanvasRotation(rotated.angle()); //check mirroring if (__pi2.canvasMirroredH()) { __pi1.setCanvasRotation(180-__pi1.canvasRotation()); __pi1.setCanvasRotation(__pi1.canvasRotation()+180); __pi2.setCanvasRotation(180-__pi2.canvasRotation()); __pi2.setCanvasRotation(__pi2.canvasRotation()+180); } paintLine(i, __pi1, __pi2); } } void KisToolMultihandHelper::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2) { for (int i = 0; i < d->transformations.size(); i++) { const QTransform &transform = d->transformations[i]; KisPaintInformation __pi1 = pi1; KisPaintInformation __pi2 = pi2; __pi1.setPos(transform.map(__pi1.pos())); __pi2.setPos(transform.map(__pi2.pos())); QLineF rotateme(QPointF (0.0,0.0), QPointF (10.0,10.0)); rotateme.setAngle(__pi1.canvasRotation()); QLineF rotated = transform.map(rotateme); __pi1.setCanvasRotation(rotated.angle()); rotateme.setAngle(__pi2.canvasRotation()); rotated = transform.map(rotateme); __pi2.setCanvasRotation(rotated.angle()); if (__pi2.canvasMirroredH()) { __pi1.setCanvasRotation(180-__pi1.canvasRotation()); __pi1.setCanvasRotation(__pi1.canvasRotation()+180); __pi2.setCanvasRotation(180-__pi2.canvasRotation()); __pi2.setCanvasRotation(__pi2.canvasRotation()+180); } QPointF __control1 = transform.map(control1); QPointF __control2 = transform.map(control2); paintBezierCurve(i, __pi1, __control1, __control2, __pi2); } } diff --git a/libs/ui/tool/kis_tool_multihand_helper.h b/libs/ui/tool/kis_tool_multihand_helper.h index d6e4e9559d..68cc72ae47 100644 --- a/libs/ui/tool/kis_tool_multihand_helper.h +++ b/libs/ui/tool/kis_tool_multihand_helper.h @@ -1,62 +1,60 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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_TOOL_MULTIHAND_HELPER_H #define __KIS_TOOL_MULTIHAND_HELPER_H #include "kis_tool_freehand_helper.h" class KRITAUI_EXPORT KisToolMultihandHelper : public KisToolFreehandHelper { Q_OBJECT public: KisToolMultihandHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText, KisRecordingAdapter *recordingAdapter = 0); ~KisToolMultihandHelper() override; void setupTransformations(const QVector &transformations); protected: void createPainters(QVector &painterInfos, - const QPointF &lastPosition, - int lastTime, - qreal lastAngle) override; + const KisDistanceInformation &startDist) override; void paintAt(const KisPaintInformation &pi) override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2) override; void paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2) override; using KisToolFreehandHelper::paintAt; using KisToolFreehandHelper::paintLine; using KisToolFreehandHelper::paintBezierCurve; private: struct Private; Private * const d; }; #endif /* __KIS_TOOL_MULTIHAND_HELPER_H */ diff --git a/libs/ui/tool/kis_tool_shape.cc b/libs/ui/tool/kis_tool_shape.cc index 306a84a914..4016231b65 100644 --- a/libs/ui/tool/kis_tool_shape.cc +++ b/libs/ui/tool/kis_tool_shape.cc @@ -1,255 +1,256 @@ /* * Copyright (c) 2005 Adrian Page * * 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_tool_shape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" #include #include KisToolShape::KisToolShape(KoCanvasBase * canvas, const QCursor & cursor) : KisToolPaint(canvas, cursor) { m_shapeOptionsWidget = 0; } KisToolShape::~KisToolShape() { // in case the widget hasn't been shown if (m_shapeOptionsWidget && !m_shapeOptionsWidget->parent()) { delete m_shapeOptionsWidget; } } void KisToolShape::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolPaint::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } int KisToolShape::flags() const { return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET |KisTool::FLAG_USES_CUSTOM_SIZE; } QWidget * KisToolShape::createOptionWidget() { m_shapeOptionsWidget = new WdgGeometryOptions(0); m_shapeOptionsWidget->cmbOutline->setCurrentIndex(KisPainter::StrokeStyleBrush); //connect two combo box event. Inherited classes can call the slots to make appropriate changes connect(m_shapeOptionsWidget->cmbOutline, SIGNAL(currentIndexChanged(int)), this, SLOT(outlineSettingChanged(int))); connect(m_shapeOptionsWidget->cmbFill, SIGNAL(currentIndexChanged(int)), this, SLOT(fillSettingChanged(int))); m_shapeOptionsWidget->cmbOutline->setCurrentIndex(m_configGroup.readEntry("outlineType", 0)); m_shapeOptionsWidget->cmbFill->setCurrentIndex(m_configGroup.readEntry("fillType", 0)); //if both settings are empty, force the outline to brush so the tool will work when first activated if ( m_shapeOptionsWidget->cmbFill->currentIndex() == 0 && m_shapeOptionsWidget->cmbOutline->currentIndex() == 0) { m_shapeOptionsWidget->cmbOutline->setCurrentIndex(1); // brush } return m_shapeOptionsWidget; } void KisToolShape::outlineSettingChanged(int value) { m_configGroup.writeEntry("outlineType", value); } void KisToolShape::fillSettingChanged(int value) { m_configGroup.writeEntry("fillType", value); } KisPainter::FillStyle KisToolShape::fillStyle(void) { if (m_shapeOptionsWidget) { return static_cast(m_shapeOptionsWidget->cmbFill->currentIndex()); } else { return KisPainter::FillStyleNone; } } KisPainter::StrokeStyle KisToolShape::strokeStyle(void) { if (m_shapeOptionsWidget) { return static_cast(m_shapeOptionsWidget->cmbOutline->currentIndex()); } else { return KisPainter::StrokeStyleNone; } } qreal KisToolShape::currentStrokeWidth() const { const qreal sizeInPx = canvas()->resourceManager()->resource(KisCanvasResourceProvider::Size).toReal(); return canvas()->unit().fromUserValue(sizeInPx); } void KisToolShape::setupPaintAction(KisRecordedPaintAction* action) { KisToolPaint::setupPaintAction(action); action->setFillStyle(fillStyle()); action->setStrokeStyle(strokeStyle()); action->setGenerator(currentGenerator()); action->setPattern(currentPattern()); action->setGradient(currentGradient()); } void KisToolShape::addShape(KoShape* shape) { KoImageCollection* imageCollection = canvas()->shapeController()->resourceManager()->imageCollection(); switch(fillStyle()) { case KisPainter::FillStyleForegroundColor: shape->setBackground(QSharedPointer(new KoColorBackground(currentFgColor().toQColor()))); break; case KisPainter::FillStyleBackgroundColor: shape->setBackground(QSharedPointer(new KoColorBackground(currentBgColor().toQColor()))); break; case KisPainter::FillStylePattern: if (imageCollection) { QSharedPointer fill(new KoPatternBackground(imageCollection)); if (currentPattern()) { fill->setPattern(currentPattern()->pattern()); shape->setBackground(fill); } } else { shape->setBackground(QSharedPointer(0)); } break; case KisPainter::FillStyleGradient: { QLinearGradient *gradient = new QLinearGradient(QPointF(0, 0), QPointF(1, 1)); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); gradient->setStops(currentGradient()->toQGradient()->stops()); QSharedPointer gradientFill(new KoGradientBackground(gradient)); shape->setBackground(gradientFill); } break; case KisPainter::FillStyleNone: default: shape->setBackground(QSharedPointer(0)); break; } KUndo2Command * cmd = canvas()->shapeController()->addShape(shape); canvas()->addCommand(cmd); } void KisToolShape::addPathShape(KoPathShape* pathShape, const KUndo2MagicString& name) { KisNodeSP node = currentNode(); if (!node || !blockUntilOperationsFinished()) { return; } // Get painting options KisPaintOpPresetSP preset = currentPaintOpPreset(); // Compute the outline KisImageSP image = this->image(); QTransform matrix; matrix.scale(image->xRes(), image->yRes()); matrix.translate(pathShape->position().x(), pathShape->position().y()); QPainterPath mapedOutline = matrix.map(pathShape->outline()); // Recorde the paint action KisRecordedPathPaintAction bezierCurvePaintAction( KisNodeQueryPath::absolutePath(node), - preset ); + preset, + KisDistanceInitInfo()); bezierCurvePaintAction.setPaintColor(currentFgColor()); QPointF lastPoint, nextPoint; int elementCount = mapedOutline.elementCount(); for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = mapedOutline.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: if (i != 0) { qFatal("Unhandled"); // XXX: I am not sure the tool can produce such element, deal with it when it can } lastPoint = QPointF(element.x, element.y); break; case QPainterPath::LineToElement: nextPoint = QPointF(element.x, element.y); bezierCurvePaintAction.addLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint)); lastPoint = nextPoint; break; case QPainterPath::CurveToElement: nextPoint = QPointF(mapedOutline.elementAt(i + 2).x, mapedOutline.elementAt(i + 2).y); bezierCurvePaintAction.addCurve(KisPaintInformation(lastPoint), QPointF(mapedOutline.elementAt(i).x, mapedOutline.elementAt(i).y), QPointF(mapedOutline.elementAt(i + 1).x, mapedOutline.elementAt(i + 1).y), KisPaintInformation(nextPoint)); lastPoint = nextPoint; break; default: continue; } } image->actionRecorder()->addAction(bezierCurvePaintAction); if (node->hasEditablePaintDevice()) { KisFigurePaintingToolHelper helper(name, image, node, canvas()->resourceManager(), strokeStyle(), fillStyle()); helper.paintPainterPath(mapedOutline); } else if (node->inherits("KisShapeLayer")) { pathShape->normalize(); addShape(pathShape); } notifyModified(); } diff --git a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp index f32730134f..ba003cb9a4 100644 --- a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp +++ b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp @@ -1,317 +1,316 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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_painter_based_stroke_strategy.h" #include #include #include #include "kis_painter.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_transaction.h" #include "kis_image.h" #include #include "kis_undo_stores.h" KisPainterBasedStrokeStrategy::PainterInfo::PainterInfo() : painter(new KisPainter()), dragDistance(new KisDistanceInformation()), m_parentPainterInfo(0), m_childPainterInfo(0) { } -KisPainterBasedStrokeStrategy::PainterInfo::PainterInfo(const QPointF &lastPosition, int lastTime, - qreal lastAngle) +KisPainterBasedStrokeStrategy::PainterInfo::PainterInfo(const KisDistanceInformation &startDist) : painter(new KisPainter()), - dragDistance(new KisDistanceInformation(lastPosition, lastTime, lastAngle)), + dragDistance(new KisDistanceInformation(startDist)), m_parentPainterInfo(0), m_childPainterInfo(0) { } KisPainterBasedStrokeStrategy::PainterInfo::PainterInfo(PainterInfo *rhs, int levelOfDetail) : painter(new KisPainter()), dragDistance(new KisDistanceInformation(*rhs->dragDistance, levelOfDetail)), m_parentPainterInfo(rhs) { rhs->m_childPainterInfo = this; } KisPainterBasedStrokeStrategy::PainterInfo::~PainterInfo() { if (m_parentPainterInfo) { m_parentPainterInfo->m_childPainterInfo = 0; } delete(painter); delete(dragDistance); } KisDistanceInformation* KisPainterBasedStrokeStrategy::PainterInfo::buddyDragDistance() { return m_childPainterInfo ? m_childPainterInfo->dragDistance : 0; } KisPainterBasedStrokeStrategy::KisPainterBasedStrokeStrategy(const QString &id, const KUndo2MagicString &name, KisResourcesSnapshotSP resources, QVector painterInfos,bool useMergeID) : KisSimpleStrokeStrategy(id, name), m_resources(resources), m_painterInfos(painterInfos), m_transaction(0), m_useMergeID(useMergeID) { init(); } KisPainterBasedStrokeStrategy::KisPainterBasedStrokeStrategy(const QString &id, const KUndo2MagicString &name, KisResourcesSnapshotSP resources, PainterInfo *painterInfo,bool useMergeID) : KisSimpleStrokeStrategy(id, name), m_resources(resources), m_painterInfos(QVector() << painterInfo), m_transaction(0), m_useMergeID(useMergeID) { init(); } void KisPainterBasedStrokeStrategy::init() { enableJob(KisSimpleStrokeStrategy::JOB_INIT); enableJob(KisSimpleStrokeStrategy::JOB_FINISH); enableJob(KisSimpleStrokeStrategy::JOB_CANCEL, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); enableJob(KisSimpleStrokeStrategy::JOB_SUSPEND); enableJob(KisSimpleStrokeStrategy::JOB_RESUME); } KisPainterBasedStrokeStrategy::KisPainterBasedStrokeStrategy(const KisPainterBasedStrokeStrategy &rhs, int levelOfDetail) : KisSimpleStrokeStrategy(rhs), m_resources(rhs.m_resources), m_transaction(rhs.m_transaction), m_useMergeID(rhs.m_useMergeID) { Q_FOREACH (PainterInfo *info, rhs.m_painterInfos) { m_painterInfos.append(new PainterInfo(info, levelOfDetail)); } KIS_ASSERT_RECOVER_NOOP( !rhs.m_transaction && !rhs.m_targetDevice && !rhs.m_activeSelection && "After the stroke has been started, no copying must happen"); } KisPaintDeviceSP KisPainterBasedStrokeStrategy::targetDevice() const { return m_targetDevice; } KisSelectionSP KisPainterBasedStrokeStrategy::activeSelection() const { return m_activeSelection; } const QVector KisPainterBasedStrokeStrategy::painterInfos() const { return m_painterInfos; } void KisPainterBasedStrokeStrategy::initPainters(KisPaintDeviceSP targetDevice, KisSelectionSP selection, bool hasIndirectPainting, const QString &indirectPaintingCompositeOp) { Q_FOREACH (PainterInfo *info, m_painterInfos) { KisPainter *painter = info->painter; painter->begin(targetDevice, !hasIndirectPainting ? selection : 0); m_resources->setupPainter(painter); if(hasIndirectPainting) { painter->setCompositeOp(targetDevice->colorSpace()->compositeOp(indirectPaintingCompositeOp)); painter->setOpacity(OPACITY_OPAQUE_U8); painter->setChannelFlags(QBitArray()); } } } void KisPainterBasedStrokeStrategy::deletePainters() { Q_FOREACH (PainterInfo *info, m_painterInfos) { delete info; } m_painterInfos.clear(); } void KisPainterBasedStrokeStrategy::initStrokeCallback() { KisNodeSP node = m_resources->currentNode(); KisPaintDeviceSP paintDevice = node->paintDevice(); KisPaintDeviceSP targetDevice = paintDevice; bool hasIndirectPainting = needsIndirectPainting(); KisSelectionSP selection = m_resources->activeSelection(); if (hasIndirectPainting) { KisIndirectPaintingSupport *indirect = dynamic_cast(node.data()); if (indirect) { targetDevice = paintDevice->createCompositionSourceDevice(); targetDevice->setParentNode(node); indirect->setCurrentColor(m_resources->currentFgColor()); indirect->setTemporaryTarget(targetDevice); indirect->setTemporaryCompositeOp(m_resources->compositeOpId()); indirect->setTemporaryOpacity(m_resources->opacity()); indirect->setTemporarySelection(selection); QBitArray channelLockFlags = m_resources->channelLockFlags(); indirect->setTemporaryChannelFlags(channelLockFlags); } else { hasIndirectPainting = false; } } if(m_useMergeID){ m_transaction = new KisTransaction(name(), targetDevice,0,timedID(this->id())); } else{ m_transaction = new KisTransaction(name(), targetDevice); } initPainters(targetDevice, selection, hasIndirectPainting, indirectPaintingCompositeOp()); m_targetDevice = targetDevice; m_activeSelection = selection; // sanity check: selection should be applied only once if (selection && !m_painterInfos.isEmpty()) { KisIndirectPaintingSupport *indirect = dynamic_cast(node.data()); KIS_ASSERT_RECOVER_RETURN(hasIndirectPainting || m_painterInfos.first()->painter->selection()); KIS_ASSERT_RECOVER_RETURN(!hasIndirectPainting || !indirect->temporarySelection() || !m_painterInfos.first()->painter->selection()); } } void KisPainterBasedStrokeStrategy::finishStrokeCallback() { KisNodeSP node = m_resources->currentNode(); KisIndirectPaintingSupport *indirect = dynamic_cast(node.data()); KisPostExecutionUndoAdapter *undoAdapter = m_resources->postExecutionUndoAdapter(); QScopedPointer dumbUndoAdapter; QScopedPointer dumbUndoStore; if (!undoAdapter) { dumbUndoStore.reset(new KisDumbUndoStore()); dumbUndoAdapter.reset(new KisPostExecutionUndoAdapter(dumbUndoStore.data(), 0)); undoAdapter = dumbUndoAdapter.data(); } if (indirect && indirect->hasTemporaryTarget()) { KUndo2MagicString transactionText = m_transaction->text(); m_transaction->end(); if(m_useMergeID){ indirect->mergeToLayer(node, undoAdapter, transactionText,timedID(this->id())); } else{ indirect->mergeToLayer(node, undoAdapter, transactionText); } } else { m_transaction->commit(undoAdapter); } delete m_transaction; deletePainters(); } void KisPainterBasedStrokeStrategy::cancelStrokeCallback() { KisNodeSP node = m_resources->currentNode(); KisIndirectPaintingSupport *indirect = dynamic_cast(node.data()); bool revert = true; if (indirect) { KisPaintDeviceSP t = indirect->temporaryTarget(); if (t) { delete m_transaction; deletePainters(); QRegion region = t->region(); indirect->setTemporaryTarget(0); node->setDirty(region); revert = false; } } if (revert) { m_transaction->revert(); delete m_transaction; deletePainters(); } } void KisPainterBasedStrokeStrategy::suspendStrokeCallback() { KisNodeSP node = m_resources->currentNode(); KisIndirectPaintingSupport *indirect = dynamic_cast(node.data()); if(indirect && indirect->hasTemporaryTarget()) { indirect->setTemporaryTarget(0); } } void KisPainterBasedStrokeStrategy::resumeStrokeCallback() { KisNodeSP node = m_resources->currentNode(); KisIndirectPaintingSupport *indirect = dynamic_cast(node.data()); if(indirect) { // todo: don't ask about paint device if (node->paintDevice() != m_targetDevice) { indirect->setTemporaryTarget(m_targetDevice); indirect->setTemporaryCompositeOp(m_resources->compositeOpId()); indirect->setTemporaryOpacity(m_resources->opacity()); indirect->setTemporarySelection(m_activeSelection); } } } diff --git a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h index 56d20aa484..040559ca91 100644 --- a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h +++ b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h @@ -1,110 +1,110 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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_PAINTER_BASED_STROKE_STRATEGY_H #define __KIS_PAINTER_BASED_STROKE_STRATEGY_H #include "kis_simple_stroke_strategy.h" #include "kis_resources_snapshot.h" #include "kis_selection.h" class KisPainter; class KisDistanceInformation; class KisTransaction; class KRITAUI_EXPORT KisPainterBasedStrokeStrategy : public KisSimpleStrokeStrategy { public: /** * The distance information should be associated with each * painter individually, so we strore and manipulate with * them together using the structure PainterInfo */ class KRITAUI_EXPORT PainterInfo { public: PainterInfo(); - PainterInfo(const QPointF &lastPosition, int lastTime, qreal lastAngle); + PainterInfo(const KisDistanceInformation &startDist); PainterInfo(PainterInfo *rhs, int levelOfDetail); ~PainterInfo(); KisPainter *painter; KisDistanceInformation *dragDistance; /** * The distance inforametion of the associated LodN * stroke. Returns zero if LodN stroke has already finished * execution or does not exist. */ KisDistanceInformation* buddyDragDistance(); private: PainterInfo *m_parentPainterInfo; PainterInfo *m_childPainterInfo; }; public: KisPainterBasedStrokeStrategy(const QString &id, const KUndo2MagicString &name, KisResourcesSnapshotSP resources, QVector painterInfos,bool useMergeID = false); KisPainterBasedStrokeStrategy(const QString &id, const KUndo2MagicString &name, KisResourcesSnapshotSP resources, PainterInfo *painterInfo,bool useMergeID = false); void initStrokeCallback() override; void finishStrokeCallback() override; void cancelStrokeCallback() override; void suspendStrokeCallback() override; void resumeStrokeCallback() override; protected: KisPaintDeviceSP targetDevice() const; KisSelectionSP activeSelection() const; const QVector painterInfos() const; void setUndoEnabled(bool value); protected: KisPainterBasedStrokeStrategy(const KisPainterBasedStrokeStrategy &rhs, int levelOfDetail); private: void init(); void initPainters(KisPaintDeviceSP targetDevice, KisSelectionSP selection, bool hasIndirectPainting, const QString &indirectPaintingCompositeOp); void deletePainters(); inline int timedID(const QString &id){ return int(qHash(id)); } private: KisResourcesSnapshotSP m_resources; QVector m_painterInfos; KisTransaction *m_transaction; KisPaintDeviceSP m_targetDevice; KisSelectionSP m_activeSelection; bool m_useMergeID; }; #endif /* __KIS_PAINTER_BASED_STROKE_STRATEGY_H */ diff --git a/plugins/paintops/chalk/kis_chalk_paintop.cpp b/plugins/paintops/chalk/kis_chalk_paintop.cpp index fa43ff9910..926c8ff499 100644 --- a/plugins/paintops/chalk/kis_chalk_paintop.cpp +++ b/plugins/paintops/chalk/kis_chalk_paintop.cpp @@ -1,97 +1,102 @@ /* * Copyright (c) 2008-2010 Lukáš Tvrdý * * 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_chalk_paintop.h" #include "kis_chalk_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisChalkPaintOp::KisChalkPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image); Q_UNUSED(node); m_airbrushOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_opacityOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_properties.readOptionSetting(settings); KoColorTransformation* transfo = 0; if (m_properties.inkDepletion && m_properties.useSaturation) { transfo = painter->device()->compositionSourceColorSpace()->createColorTransformation("hsv_adjustment", QHash()); } m_chalkBrush = new ChalkBrush(&m_properties, transfo); } KisChalkPaintOp::~KisChalkPaintOp() { delete m_chalkBrush; } KisSpacingInformation KisChalkPaintOp::paintAt(const KisPaintInformation& info) { if (!painter()) return KisSpacingInformation(1.0); if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } qreal x1, y1; x1 = info.pos().x(); y1 = info.pos().y(); const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); quint8 origOpacity = m_opacityOption.apply(painter(), info); m_chalkBrush->paint(m_dab, x1, y1, painter()->paintColor(), additionalScale); QRect rc = m_dab->extent(); painter()->bitBlt(rc.x(), rc.y(), m_dab, rc.x(), rc.y(), rc.width(), rc.height()); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); + return updateSpacingImpl(info); +} + +KisSpacingInformation KisChalkPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ return KisPaintOpPluginUtils::effectiveSpacing(1.0, 1.0, true, 0.0, false, 1.0, false, 1.0, 1.0, &m_airbrushOption, nullptr, &m_rateOption, info); } diff --git a/plugins/paintops/chalk/kis_chalk_paintop.h b/plugins/paintops/chalk/kis_chalk_paintop.h index da42438918..bdbcef1636 100644 --- a/plugins/paintops/chalk/kis_chalk_paintop.h +++ b/plugins/paintops/chalk/kis_chalk_paintop.h @@ -1,52 +1,56 @@ /* * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2008, 2009 Lukáš Tvrdý * * 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_CHALK_PAINTOP_H_ #define KIS_CHALK_PAINTOP_H_ #include #include #include "chalk_brush.h" #include "kis_chalk_paintop_settings.h" #include "kis_airbrush_option.h" #include "kis_pressure_rate_option.h" class KisPainter; class KisChalkPaintOp : public KisPaintOp { public: KisChalkPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); ~KisChalkPaintOp() override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: KisPaintDeviceSP m_dab; ChalkBrush * m_chalkBrush; KisAirbrushOption m_airbrushOption; KisPressureOpacityOption m_opacityOption; KisPressureRateOption m_rateOption; ChalkProperties m_properties; }; #endif // KIS_CHALK_PAINTOP_H_ diff --git a/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp b/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp index 68b95701c3..bf4781d80e 100644 --- a/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp +++ b/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp @@ -1,287 +1,294 @@ /* * Copyright (C) 2011 Silvio Heinrich * * 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_colorsmudgeop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisColorSmudgeOp::KisColorSmudgeOp(const KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_firstRun(true) , m_image(image) , m_tempDev(painter->device()->createCompositionSourceDevice()) , m_backgroundPainter(new KisPainter(m_tempDev)) , m_smudgePainter(new KisPainter(m_tempDev)) , m_colorRatePainter(new KisPainter(m_tempDev)) , m_smudgeRateOption() , m_colorRateOption("ColorRate", KisPaintOpOption::GENERAL, false) , m_smudgeRadiusOption() { Q_UNUSED(node); Q_ASSERT(settings); Q_ASSERT(painter); m_sizeOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_smudgeRateOption.readOptionSetting(settings); m_colorRateOption.readOptionSetting(settings); m_smudgeRadiusOption.readOptionSetting(settings); m_overlayModeOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_scatterOption.readOptionSetting(settings); m_gradientOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_spacingOption.resetAllSensors(); m_smudgeRateOption.resetAllSensors(); m_colorRateOption.resetAllSensors(); m_smudgeRadiusOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_scatterOption.resetAllSensors(); m_gradientOption.resetAllSensors(); m_gradient = painter->gradient(); m_backgroundPainter->setCompositeOp(COMPOSITE_COPY); // Smudge Painter works in default COMPOSITE_OVER mode m_colorRatePainter->setCompositeOp(painter->compositeOp()->id()); m_rotationOption.applyFanCornersInfo(this); } KisColorSmudgeOp::~KisColorSmudgeOp() { delete m_backgroundPainter; delete m_colorRatePainter; delete m_smudgePainter; } void KisColorSmudgeOp::updateMask(const KisPaintInformation& info, double scale, double rotation, const QPointF &cursorPoint) { static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); m_maskDab = m_dabCache->fetchDab(cs, color, cursorPoint, KisDabShape(scale, 1.0, rotation), info, 1.0, &m_dstDabRect); // sanity check KIS_ASSERT_RECOVER_NOOP(m_dstDabRect.size() == m_maskDab->bounds().size()); } inline void KisColorSmudgeOp::getTopLeftAligned(const QPointF &pos, const QPointF &hotSpot, qint32 *x, qint32 *y) { QPointF topLeft = pos - hotSpot; qreal xFraction, yFraction; // will not be used splitCoordinate(topLeft.x(), x, &xFraction); splitCoordinate(topLeft.y(), y, &yFraction); } KisSpacingInformation KisColorSmudgeOp::paintAt(const KisPaintInformation& info) { KisBrushSP brush = m_brush; // Simple error catching if (!painter()->device() || !brush || !brush->canPaintFor(info)) { return KisSpacingInformation(1.0); } if (m_smudgeRateOption.getMode() == KisSmudgeOption::SMEARING_MODE) { /** * Disable handling of the subpixel precision. In the smudge op we * should read from the aligned areas of the image, so having * additional internal offsets, created by the subpixel precision, * will worsen the quality (at least because * QRectF(m_dstDabRect).center() will not point to the real center * of the brush anymore). * Of course, this only really matters with smearing_mode (bug:327235), * and you only notice the lack of subpixel precision in the dulling methods. */ m_dabCache->disableSubpixelPrecision(); } #if 0 //if precision KoColor colorSpaceChanger = painter()->paintColor(); const KoColorSpace* preciseColorSpace = colorSpaceChanger.colorSpace(); if (colorSpaceChanger.colorSpace()->colorDepthId().id() == "U8") { preciseColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceChanger.colorSpace()->colorModelId().id(), "U16", colorSpaceChanger.profile() ); colorSpaceChanger.convertTo(preciseColorSpace); } painter()->setPaintColor(colorSpaceChanger); #endif // get the scaling factor calculated by the size option qreal scale = m_sizeOption.apply(info); scale *= KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(info); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); KisDabShape shape(scale, 1.0, rotation); QPointF scatteredPos = m_scatterOption.apply(info, brush->maskWidth(shape, 0, 0, info), brush->maskHeight(shape, 0, 0, info)); QPointF hotSpot = brush->hotSpot(shape, info); /** * Update the brush mask. * * Upon leaving the function: * o m_maskDab stores the new mask * o m_maskBounds stores the extents of the mask paint device * o m_dstDabRect stores the destination rect where the mask is going * to be written to */ updateMask(info, scale, rotation, scatteredPos); QPointF newCenterPos = QRectF(m_dstDabRect).center(); /** * Save the center of the current dab to know where to read the * data during the next pass. We do not save scatteredPos here, * because it may differ slightly from the real center of the * brush (due to rounding effects), which will result in a * really weird quality. */ QRect srcDabRect = m_dstDabRect.translated((m_lastPaintPos - newCenterPos).toPoint()); m_lastPaintPos = newCenterPos; KisSpacingInformation spacingInfo = effectiveSpacing(scale, rotation, m_spacingOption, info); if (m_firstRun) { m_firstRun = false; return spacingInfo; } // save the old opacity value and composite mode quint8 oldOpacity = painter()->opacity(); QString oldCompositeOpId = painter()->compositeOp()->id(); qreal fpOpacity = (qreal(oldOpacity) / 255.0) * m_opacityOption.getOpacityf(info); if (m_image && m_overlayModeOption.isChecked()) { m_image->blockUpdates(); m_backgroundPainter->bitBlt(QPoint(), m_image->projection(), srcDabRect); m_image->unblockUpdates(); } else { // IMPORTANT: clear the temporary painting device to color black with zero opacity: // it will only clear the extents of the brush. m_tempDev->clear(QRect(QPoint(), m_dstDabRect.size())); } if (m_smudgeRateOption.getMode() == KisSmudgeOption::SMEARING_MODE) { m_smudgePainter->bitBlt(QPoint(), painter()->device(), srcDabRect); } else { QPoint pt = (srcDabRect.topLeft() + hotSpot).toPoint(); if (m_smudgeRadiusOption.isChecked()) { qreal effectiveSize = 0.5 * (m_dstDabRect.width() + m_dstDabRect.height()); m_smudgeRadiusOption.apply(*m_smudgePainter, info, effectiveSize, pt.x(), pt.y(), painter()->device()); KoColor color2 = m_smudgePainter->paintColor(); m_smudgePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color2); } else { KoColor color = painter()->paintColor(); // get the pixel on the canvas that lies beneath the hot spot // of the dab and fill the temporary paint device with that color KisCrossDeviceColorPickerInt colorPicker(painter()->device(), color); colorPicker.pickColor(pt.x(), pt.y(), color.data()); m_smudgePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); } } // if the user selected the color smudge option, // we will mix some color into the temporary painting device (m_tempDev) if (m_colorRateOption.isChecked()) { // this will apply the opacity (selected by the user) to copyPainter // (but fit the rate inbetween the range 0.0 to (1.0-SmudgeRate)) qreal maxColorRate = qMax(1.0 - m_smudgeRateOption.getRate(), 0.2); m_colorRateOption.apply(*m_colorRatePainter, info, 0.0, maxColorRate, fpOpacity); // paint a rectangle with the current color (foreground color) // or a gradient color (if enabled) // into the temporary painting device and use the user selected // composite mode KoColor color = painter()->paintColor(); m_gradientOption.apply(color, m_gradient, info); m_colorRatePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); } // if color is disabled (only smudge) and "overlay mode" is enabled // then first blit the region under the brush from the image projection // to the painting device to prevent a rapid build up of alpha value // if the color to be smudged is semi transparent. if (m_image && m_overlayModeOption.isChecked() && !m_colorRateOption.isChecked()) { painter()->setCompositeOp(COMPOSITE_COPY); painter()->setOpacity(OPACITY_OPAQUE_U8); m_image->blockUpdates(); painter()->bitBlt(m_dstDabRect.topLeft(), m_image->projection(), m_dstDabRect); m_image->unblockUpdates(); } // set opacity calculated by the rate option m_smudgeRateOption.apply(*painter(), info, 0.0, 1.0, fpOpacity); // then blit the temporary painting device on the canvas at the current brush position // the alpha mask (maskDab) will be used here to only blit the pixels that are in the area (shape) of the brush painter()->setCompositeOp(COMPOSITE_COPY); painter()->bitBltWithFixedSelection(m_dstDabRect.x(), m_dstDabRect.y(), m_tempDev, m_maskDab, m_dstDabRect.width(), m_dstDabRect.height()); painter()->renderMirrorMaskSafe(m_dstDabRect, m_tempDev, 0, 0, m_maskDab, !m_dabCache->needSeparateOriginal()); // restore orginal opacy and composite mode values painter()->setOpacity(oldOpacity); painter()->setCompositeOp(oldCompositeOpId); return spacingInfo; } + +KisSpacingInformation KisColorSmudgeOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); + const qreal rotation = m_rotationOption.apply(info); + return effectiveSpacing(scale, rotation, m_spacingOption, info); +} diff --git a/plugins/paintops/colorsmudge/kis_colorsmudgeop.h b/plugins/paintops/colorsmudge/kis_colorsmudgeop.h index c6f15fbf8f..8c769a6ddd 100644 --- a/plugins/paintops/colorsmudge/kis_colorsmudgeop.h +++ b/plugins/paintops/colorsmudge/kis_colorsmudgeop.h @@ -1,80 +1,83 @@ /* * Copyright (C) 2011 Silvio Heinrich * * 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_COLORSMUDGEOP_H_ #define _KIS_COLORSMUDGEOP_H_ #include #include #include #include #include #include #include #include #include #include "kis_overlay_mode_option.h" #include "kis_rate_option.h" #include "kis_smudge_option.h" #include "kis_smudge_radius_option.h" class QPointF; class KoAbstractGradient; class KisBrushBasedPaintOpSettings; class KisPainter; class KisColorSmudgeOp: public KisBrushBasedPaintOp { public: KisColorSmudgeOp(const KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image); ~KisColorSmudgeOp() override; +protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: // Sets the m_maskDab _and m_maskDabRect void updateMask(const KisPaintInformation& info, double scale, double rotation, const QPointF &cursorPoint); inline void getTopLeftAligned(const QPointF &pos, const QPointF &hotSpot, qint32 *x, qint32 *y); private: bool m_firstRun; KisImageWSP m_image; KisPaintDeviceSP m_tempDev; KisPainter* m_backgroundPainter; KisPainter* m_smudgePainter; KisPainter* m_colorRatePainter; const KoAbstractGradient* m_gradient; KisPressureSizeOption m_sizeOption; KisPressureOpacityOption m_opacityOption; KisPressureSpacingOption m_spacingOption; KisSmudgeOption m_smudgeRateOption; KisRateOption m_colorRateOption; KisSmudgeRadiusOption m_smudgeRadiusOption; KisOverlayModeOption m_overlayModeOption; KisPressureRotationOption m_rotationOption; KisPressureScatterOption m_scatterOption; KisPressureGradientOption m_gradientOption; QRect m_dstDabRect; KisFixedPaintDeviceSP m_maskDab; QPointF m_lastPaintPos; }; #endif // _KIS_COLORSMUDGEOP_H_ diff --git a/plugins/paintops/curvebrush/kis_curve_paintop.cpp b/plugins/paintops/curvebrush/kis_curve_paintop.cpp index 15faff748f..27d2cf02ff 100644 --- a/plugins/paintops/curvebrush/kis_curve_paintop.cpp +++ b/plugins/paintops/curvebrush/kis_curve_paintop.cpp @@ -1,128 +1,132 @@ /* * Copyright (c) 2008-2011 Lukáš Tvrdý * * 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_curve_paintop.h" #include #include #include #include #include "kis_global.h" #include "kis_paint_device.h" #include "kis_painter.h" #include "kis_types.h" #include KisCurvePaintOp::KisCurvePaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) , m_painter(0) { Q_ASSERT(settings); Q_UNUSED(image); Q_UNUSED(node); m_curveProperties.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_lineWidthOption.readOptionSetting(settings); m_curvesOpacityOption.readOptionSetting(settings); } KisCurvePaintOp::~KisCurvePaintOp() { delete m_painter; } KisSpacingInformation KisCurvePaintOp::paintAt(const KisPaintInformation& info) +{ + return updateSpacingImpl(info); +} + +KisSpacingInformation KisCurvePaintOp::updateSpacingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return KisSpacingInformation(1.0); } - void KisCurvePaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { Q_UNUSED(currentDistance); if (!painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } paintLine(m_dab, pi1, pi2); QRect rc = m_dab->extent(); quint8 origOpacity = m_opacityOption.apply(painter(), pi2); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); } void KisCurvePaintOp::paintLine(KisPaintDeviceSP dab, const KisPaintInformation &pi1, const KisPaintInformation &pi2) { if (!m_painter) { m_painter = new KisPainter(dab); m_painter->setPaintColor(painter()->paintColor()); } int maxPoints = m_curveProperties.curve_stroke_history_size; m_points.append(pi2.pos()); while (m_points.length() > maxPoints) { m_points.removeFirst(); } const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); const qreal lineWidth = additionalScale * m_lineWidthOption.apply(pi2, m_curveProperties.curve_line_width); QPen pen(QBrush(Qt::white), lineWidth); QPainterPath path; if (m_curveProperties.curve_paint_connection_line) { path.moveTo(pi1.pos()); path.lineTo(pi2.pos()); m_painter->drawPainterPath(path, pen); path = QPainterPath(); } if (m_points.length() >= maxPoints) { // alpha * 0.2; path.moveTo(m_points.first()); if (m_curveProperties.curve_smoothing) { path.quadTo(m_points.at(maxPoints / 2), m_points.last()); } else { // control point is at 1/3 of the history, 2/3 of the history and endpoint at 3/3 int step = maxPoints / 3; path.cubicTo(m_points.at(step), m_points.at(step + step), m_points.last()); } qreal curveOpacity = m_curvesOpacityOption.apply(pi2, m_curveProperties.curve_curves_opacity); m_painter->setOpacity(qRound(255.0 * curveOpacity)); m_painter->drawPainterPath(path, pen); m_painter->setOpacity(255); // full } } diff --git a/plugins/paintops/curvebrush/kis_curve_paintop.h b/plugins/paintops/curvebrush/kis_curve_paintop.h index b195f85471..a4ada05ce6 100644 --- a/plugins/paintops/curvebrush/kis_curve_paintop.h +++ b/plugins/paintops/curvebrush/kis_curve_paintop.h @@ -1,60 +1,64 @@ /* * Copyright (c) 2008-2011 Lukáš Tvrdý * * 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_CURVEPAINTOP_H_ #define KIS_CURVEPAINTOP_H_ #include #include #include "curve_brush.h" #include "kis_curve_line_option.h" #include "kis_curve_paintop_settings.h" #include #include "kis_linewidth_option.h" #include "kis_curves_opacity_option.h" class KisPainter; class KisCurvePaintOp : public KisPaintOp { public: KisCurvePaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisCurvePaintOp() override; - KisSpacingInformation paintAt(const KisPaintInformation& info) override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: void paintLine(KisPaintDeviceSP dab, const KisPaintInformation &pi1, const KisPaintInformation &pi2); private: KisPaintDeviceSP m_dab; KisPaintDeviceSP m_dev; CurveOption m_curveProperties; KisPressureOpacityOption m_opacityOption; KisLineWidthOption m_lineWidthOption; KisCurvesOpacityOption m_curvesOpacityOption; QList m_points; KisPainter * m_painter; }; #endif // KIS_CURVEPAINTOP_H_ diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp index 76c3f3c49c..3bff3a66e7 100644 --- a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp @@ -1,197 +1,205 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * * 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_brushop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisBrushOp::KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_opacityOption(node) , m_hsvTransformation(0) { Q_UNUSED(image); Q_ASSERT(settings); KisColorSourceOption colorSourceOption; colorSourceOption.readOptionSetting(settings); m_colorSource = colorSourceOption.createColorSource(painter); m_hsvOptions.append(KisPressureHSVOption::createHueOption()); m_hsvOptions.append(KisPressureHSVOption::createSaturationOption()); m_hsvOptions.append(KisPressureHSVOption::createValueOption()); Q_FOREACH (KisPressureHSVOption * option, m_hsvOptions) { option->readOptionSetting(settings); option->resetAllSensors(); if (option->isChecked() && !m_hsvTransformation) { m_hsvTransformation = painter->backgroundColor().colorSpace()->createColorTransformation("hsv_adjustment", QHash()); } } m_airbrushOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_flowOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_ratioOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_softnessOption.readOptionSetting(settings); m_sharpnessOption.readOptionSetting(settings); m_darkenOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_mixOption.readOptionSetting(settings); m_scatterOption.readOptionSetting(settings); m_opacityOption.resetAllSensors(); m_flowOption.resetAllSensors(); m_sizeOption.resetAllSensors(); m_ratioOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_softnessOption.resetAllSensors(); m_sharpnessOption.resetAllSensors(); m_darkenOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_scatterOption.resetAllSensors(); m_dabCache->setSharpnessPostprocessing(&m_sharpnessOption); m_rotationOption.applyFanCornersInfo(this); } KisBrushOp::~KisBrushOp() { qDeleteAll(m_hsvOptions); delete m_colorSource; delete m_hsvTransformation; } KisSpacingInformation KisBrushOp::paintAt(const KisPaintInformation& info) { if (!painter()->device()) return KisSpacingInformation(1.0); KisBrushSP brush = m_brush; Q_ASSERT(brush); if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); qreal scale = m_sizeOption.apply(info); scale *= KisLodTransform::lodToScale(painter()->device()); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); qreal rotation = m_rotationOption.apply(info); qreal ratio = m_ratioOption.apply(info); KisPaintDeviceSP device = painter()->device(); KisDabShape shape(scale, ratio, rotation); QPointF cursorPos = m_scatterOption.apply(info, brush->maskWidth(shape, 0, 0, info), brush->maskHeight(shape, 0, 0, info)); quint8 origOpacity = painter()->opacity(); m_opacityOption.setFlow(m_flowOption.apply(info)); m_opacityOption.apply(painter(), info); m_colorSource->selectColor(m_mixOption.apply(info), info); m_darkenOption.apply(m_colorSource, info); if (m_hsvTransformation) { Q_FOREACH (KisPressureHSVOption * option, m_hsvOptions) { option->apply(m_hsvTransformation, info); } m_colorSource->applyColorTransformation(m_hsvTransformation); } QRect dabRect; KisFixedPaintDeviceSP dab = m_dabCache->fetchDab(device->compositionSourceColorSpace(), m_colorSource, cursorPos, shape, info, m_softnessOption.apply(info), &dabRect); // sanity check for the size calculation code if (dab->bounds().size() != dabRect.size()) { warnKrita << "KisBrushOp: dab bounds is not dab rect. See bug 327156" << dab->bounds().size() << dabRect.size(); } painter()->bltFixed(dabRect.topLeft(), dab, dab->bounds()); painter()->renderMirrorMaskSafe(dabRect, dab, !m_dabCache->needSeparateOriginal()); painter()->setOpacity(origOpacity); return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, &m_rateOption, info); } +KisSpacingInformation KisBrushOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); + qreal rotation = m_rotationOption.apply(info); + return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, &m_rateOption, + info); +} + void KisBrushOp::paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance) { if (m_sharpnessOption.isChecked() && m_brush && (m_brush->width() == 1) && (m_brush->height() == 1)) { if (!m_lineCacheDevice) { m_lineCacheDevice = source()->createCompositionSourceDevice(); } else { m_lineCacheDevice->clear(); } KisPainter p(m_lineCacheDevice); p.setPaintColor(painter()->paintColor()); p.drawDDALine(pi1.pos(), pi2.pos()); QRect rc = m_lineCacheDevice->extent(); painter()->bitBlt(rc.x(), rc.y(), m_lineCacheDevice, rc.x(), rc.y(), rc.width(), rc.height()); //fixes Bug 338011 painter()->renderMirrorMask(rc, m_lineCacheDevice); } else { KisPaintOp::paintLine(pi1, pi2, currentDistance); } } diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.h b/plugins/paintops/defaultpaintops/brush/kis_brushop.h index 798ac4856f..0837f1b7b8 100644 --- a/plugins/paintops/defaultpaintops/brush/kis_brushop.h +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.h @@ -1,81 +1,85 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * * 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_BRUSHOP_H_ #define KIS_BRUSHOP_H_ #include "kis_brush_based_paintop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KisPainter; class KisColorSource; class KisBrushOp : public KisBrushBasedPaintOp { public: KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisBrushOp() override; - KisSpacingInformation paintAt(const KisPaintInformation& info) override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: KisColorSource *m_colorSource; KisAirbrushOption m_airbrushOption; KisPressureSizeOption m_sizeOption; KisPressureRatioOption m_ratioOption; KisPressureSpacingOption m_spacingOption; KisPressureRateOption m_rateOption; KisPressureFlowOption m_flowOption; KisFlowOpacityOption m_opacityOption; KisPressureSoftnessOption m_softnessOption; KisPressureSharpnessOption m_sharpnessOption; KisPressureDarkenOption m_darkenOption; KisPressureRotationOption m_rotationOption; KisPressureMixOption m_mixOption; KisPressureScatterOption m_scatterOption; QList m_hsvOptions; KoColorTransformation *m_hsvTransformation; KisPaintDeviceSP m_lineCacheDevice; KisPaintDeviceSP m_colorSourceDevice; }; #endif // KIS_BRUSHOP_H_ diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp index 319a11f8b7..13affff993 100644 --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp @@ -1,289 +1,294 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004,2010 Cyrille Berger * * 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_duplicateop.h" #include "kis_duplicateop_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_duplicateop_settings.h" #include "kis_duplicateop_settings_widget.h" #include "kis_duplicateop_option.h" KisDuplicateOp::KisDuplicateOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_image(image) , m_node(node) , m_settings(static_cast(const_cast(settings.data()))) { Q_ASSERT(settings); Q_ASSERT(painter); m_sizeOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_healing = settings->getBool(DUPLICATE_HEALING); m_perspectiveCorrection = settings->getBool(DUPLICATE_CORRECT_PERSPECTIVE); m_moveSourcePoint = settings->getBool(DUPLICATE_MOVE_SOURCE_POINT); m_cloneFromProjection = settings->getBool(DUPLICATE_CLONE_FROM_PROJECTION); m_srcdev = source()->createCompositionSourceDevice(); } KisDuplicateOp::~KisDuplicateOp() { } #define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x))) KisSpacingInformation KisDuplicateOp::paintAt(const KisPaintInformation& info) { if (!painter()->device()) return KisSpacingInformation(1.0); KisBrushSP brush = m_brush; if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); if (!m_duplicateStartIsSet) { m_duplicateStartIsSet = true; m_duplicateStart = info.pos(); } KisPaintDeviceSP realSourceDevice; if (m_cloneFromProjection && m_image) { realSourceDevice = m_image->projection(); } else { KisNodeSP externalSourceNode = m_settings->sourceNode(); /** * The saved layer might have been deleted by then, so check if it * still belongs to a graph */ if (!externalSourceNode || !externalSourceNode->graphListener()) { externalSourceNode = m_node; } realSourceDevice = externalSourceNode->projection(); } qreal rotation = m_rotationOption.apply(info); qreal scale = m_sizeOption.apply(info); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); KisDabShape shape(scale, 1.0, rotation); static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); QRect dstRect; KisFixedPaintDeviceSP dab = m_dabCache->fetchDab(cs, color, info.pos(), shape, info, 1.0, &dstRect); if (dstRect.isEmpty()) return KisSpacingInformation(1.0); QPoint srcPoint; if (m_moveSourcePoint) { srcPoint = (dstRect.topLeft() - m_settings->offset()).toPoint(); } else { QPointF hotSpot = brush->hotSpot(shape, info); srcPoint = (m_settings->position() - hotSpot).toPoint(); } qint32 sw = dstRect.width(); qint32 sh = dstRect.height(); // Perspective correction ? // if (m_perspectiveCorrection && m_image && m_image->perspectiveGrid()->countSubGrids() == 1) { // Matrix3qreal startM = Matrix3qreal::Identity(); // Matrix3qreal endM = Matrix3qreal::Identity(); // // First look for the grid corresponding to the start point // KisSubPerspectiveGrid* subGridStart = *m_image->perspectiveGrid()->begin(); // QRect r = QRect(0, 0, m_image->width(), m_image->height()); // if (subGridStart) { // startM = KisPerspectiveMath::computeMatrixTransfoFromPerspective(r, *subGridStart->topLeft(), *subGridStart->topRight(), *subGridStart->bottomLeft(), *subGridStart->bottomRight()); // } // // Second look for the grid corresponding to the end point // KisSubPerspectiveGrid* subGridEnd = *m_image->perspectiveGrid()->begin(); // if (subGridEnd) { // endM = KisPerspectiveMath::computeMatrixTransfoToPerspective(*subGridEnd->topLeft(), *subGridEnd->topRight(), *subGridEnd->bottomLeft(), *subGridEnd->bottomRight(), r); // } // // Compute the translation in the perspective transformation space: // QPointF positionStartPaintingT = KisPerspectiveMath::matProd(endM, QPointF(m_duplicateStart)); // QPointF duplicateStartPositionT = KisPerspectiveMath::matProd(endM, QPointF(m_duplicateStart) - QPointF(m_settings->offset())); // QPointF translat = duplicateStartPositionT - positionStartPaintingT; // KisSequentialIterator dstIt(m_srcdev, QRect(0, 0, sw, sh)); // KisRandomSubAccessorSP srcAcc = realSourceDevice->createRandomSubAccessor(); // //Action // do { // QPointF p = KisPerspectiveMath::matProd(startM, KisPerspectiveMath::matProd(endM, QPointF(dstIt.x() + dstRect.x(), dstIt.y() + dstRect.y())) + translat); // srcAcc->moveTo(p); // srcAcc->sampledOldRawData(dstIt.rawData()); // } while (dstIt.nextPixel()); // } // else { KisPainter copyPainter(m_srcdev); copyPainter.setCompositeOp(COMPOSITE_COPY); copyPainter.bitBltOldData(0, 0, realSourceDevice, srcPoint.x(), srcPoint.y(), sw, sh); copyPainter.end(); } // heal ? if (m_healing) { QRect healRect(dstRect); const bool smallWidth = healRect.width() < 3; const bool smallHeight = healRect.height() < 3; if (smallWidth || smallHeight) { healRect.adjust(-smallWidth, -smallHeight, smallWidth, smallHeight); } const int healSW = healRect.width(); const int healSH = healRect.height(); quint16 srcData[4]; quint16 tmpData[4]; QScopedArrayPointer matrix(new qreal[ 3 * healSW * healSH ]); // First divide const KoColorSpace* srcCs = realSourceDevice->colorSpace(); const KoColorSpace* tmpCs = m_srcdev->colorSpace(); KisHLineConstIteratorSP srcIt = realSourceDevice->createHLineConstIteratorNG(healRect.x(), healRect.y() , healSW); KisHLineIteratorSP tmpIt = m_srcdev->createHLineIteratorNG(0, 0, healSW); qreal* matrixIt = matrix.data(); for (int j = 0; j < healSH; j++) { for (int i = 0; i < healSW; i++) { srcCs->toLabA16(srcIt->oldRawData(), (quint8*)srcData, 1); tmpCs->toLabA16(tmpIt->rawData(), (quint8*)tmpData, 1); // Division for (int k = 0; k < 3; k++) { matrixIt[k] = srcData[k] / (qreal)qMax((int)tmpData [k], 1); } srcIt->nextPixel(); tmpIt->nextPixel(); matrixIt += 3; } srcIt->nextRow(); tmpIt->nextRow(); } // Minimize energy { int iter = 0; qreal err; QScopedArrayPointer solution(new qreal[ 3 * healSW * healSH ]); do { err = DuplicateOpUtils::minimizeEnergy(matrix.data(), solution.data(), healSW, healSH); solution.swap(matrix); iter++; } while (err > 0.00001 && iter < 100); } // Finally multiply KisHLineIteratorSP tmpIt2 = m_srcdev->createHLineIteratorNG(0, 0, healSW); matrixIt = &matrix[0]; for (int j = 0; j < healSH; j++) { for (int i = 0; i < healSW; i++) { tmpCs->toLabA16(tmpIt2->rawData(), (quint8*)tmpData, 1); // Multiplication for (int k = 0; k < 3; k++) { tmpData[k] = (int)CLAMP(matrixIt[k] * qMax((int) tmpData[k], 1), 0, 65535); } tmpCs->fromLabA16((quint8*)tmpData, tmpIt2->rawData(), 1); tmpIt2->nextPixel(); matrixIt += 3; } tmpIt2->nextRow(); } } painter()->bitBltWithFixedSelection(dstRect.x(), dstRect.y(), m_srcdev, dab, dstRect.width(), dstRect.height()); painter()->renderMirrorMaskSafe(dstRect, m_srcdev, 0, 0, dab, !m_dabCache->needSeparateOriginal()); return effectiveSpacing(scale); } + +KisSpacingInformation KisDuplicateOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + return effectiveSpacing(m_sizeOption.apply(info)); +} diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h index 19c061dbaa..a5be6cf191 100644 --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h @@ -1,77 +1,80 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * * 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_DUPLICATEOP_H_ #define KIS_DUPLICATEOP_H_ #include "kis_brush_based_paintop.h" #include #include #include #include #include #include #include "kis_duplicateop_settings.h" class KisPaintInformation; class QPointF; class KisPainter; class KisDuplicateOp : public KisBrushBasedPaintOp { public: KisDuplicateOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); ~KisDuplicateOp() override; +protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: qreal minimizeEnergy(const qreal* m, qreal* sol, int w, int h); private: KisImageSP m_image; KisNodeSP m_node; KisDuplicateOpSettingsSP m_settings; KisPaintDeviceSP m_srcdev; KisPaintDeviceSP m_target; QPointF m_duplicateStart; bool m_duplicateStartIsSet; KisPressureSizeOption m_sizeOption; KisPressureRotationOption m_rotationOption; bool m_healing; bool m_perspectiveCorrection; bool m_moveSourcePoint; bool m_cloneFromProjection; }; #endif // KIS_DUPLICATEOP_H_ diff --git a/plugins/paintops/deform/kis_deform_paintop.cpp b/plugins/paintops/deform/kis_deform_paintop.cpp index 66d8d1adf8..f1e85ee74d 100644 --- a/plugins/paintops/deform/kis_deform_paintop.cpp +++ b/plugins/paintops/deform/kis_deform_paintop.cpp @@ -1,153 +1,156 @@ /* * Copyright (c) 2008-2010 Lukáš Tvrdý * * 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_deform_paintop.h" #include "kis_deform_paintop_settings.h" #include #include #include #include #include #include "kis_global.h" #include "kis_paint_device.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_random_accessor_ng.h" #include "kis_lod_transform.h" #include #include "kis_deform_option.h" #include "kis_brush_size_option.h" #include "kis_paintop_plugin_utils.h" #include #include #ifdef Q_OS_WIN // quoting DRAND48(3) man-page: // These functions are declared obsolete by SVID 3, // which states that rand(3) should be used instead. #define drand48() (static_cast(qrand()) / static_cast(RAND_MAX)) #endif KisDeformPaintOp::KisDeformPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image); Q_UNUSED(node); Q_ASSERT(settings); m_sizeProperties.readOptionSetting(settings); m_properties.readOptionSetting(settings); m_airbrushOption.readOptionSetting(settings); // sensors m_sizeOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_deformBrush.setProperties(&m_properties); m_deformBrush.setSizeProperties(&m_sizeProperties); m_deformBrush.initDeformAction(); m_dev = source(); if ((m_sizeProperties.brush_diameter * 0.5) > 1) { m_ySpacing = m_xSpacing = m_sizeProperties.brush_diameter * 0.5 * m_sizeProperties.brush_spacing; } else { m_ySpacing = m_xSpacing = 1.0; } m_spacing = m_xSpacing; } KisDeformPaintOp::~KisDeformPaintOp() { } KisSpacingInformation KisDeformPaintOp::paintAt(const KisPaintInformation& info) { if (!painter()) return KisSpacingInformation(m_spacing); if (!m_dev) return KisSpacingInformation(m_spacing); KisFixedPaintDeviceSP dab = cachedDab(source()->compositionSourceColorSpace()); qint32 x; qreal subPixelX; qint32 y; qreal subPixelY; QPointF pt = info.pos(); if (m_sizeProperties.brush_jitter_movement_enabled) { pt.setX(pt.x() + ((m_sizeProperties.brush_diameter * drand48()) - m_sizeProperties.brush_diameter * 0.5) * m_sizeProperties.brush_jitter_movement); pt.setY(pt.y() + ((m_sizeProperties.brush_diameter * drand48()) - m_sizeProperties.brush_diameter * 0.5) * m_sizeProperties.brush_jitter_movement); } qreal rotation = m_rotationOption.apply(info); // Deform Brush is capable of working with zero scale, // so no additional checks for 'zero'ness are needed qreal scale = m_sizeOption.apply(info); rotation += m_sizeProperties.brush_rotation; scale *= m_sizeProperties.brush_scale; QPointF pos = pt - m_deformBrush.hotSpot(scale, rotation); splitCoordinate(pos.x(), &x, &subPixelX); splitCoordinate(pos.y(), &y, &subPixelY); KisFixedPaintDeviceSP mask = m_deformBrush.paintMask(dab, m_dev, scale, rotation, info.pos(), subPixelX, subPixelY, x, y ); // this happens for the first dab of the move mode, we need more information for being able to move if (!mask) { - return KisSpacingInformation(m_spacing); + return updateSpacingImpl(info); } quint8 origOpacity = m_opacityOption.apply(painter(), info); painter()->bltFixedWithFixedSelection(x, y, dab, mask, mask->bounds().width() , mask->bounds().height()); painter()->renderMirrorMask(QRect(QPoint(x, y), QSize(mask->bounds().width() , mask->bounds().height())), dab, mask); painter()->setOpacity(origOpacity); + return updateSpacingImpl(info); +} + +KisSpacingInformation KisDeformPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ return KisPaintOpPluginUtils::effectiveSpacing(1.0, 1.0, true, 0.0, false, m_spacing, false, 1.0, KisLodTransform::lodToScale(painter()->device()), &m_airbrushOption, nullptr, &m_rateOption, info); } - - diff --git a/plugins/paintops/deform/kis_deform_paintop.h b/plugins/paintops/deform/kis_deform_paintop.h index 7a65684bb3..2107156d68 100644 --- a/plugins/paintops/deform/kis_deform_paintop.h +++ b/plugins/paintops/deform/kis_deform_paintop.h @@ -1,68 +1,71 @@ /* * Copyright (c) 2008,2010 Lukáš Tvrdý * * 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_DEFORMPAINTOP_H_ #define KIS_DEFORMPAINTOP_H_ #include #include #include #include #include #include #include #include "deform_brush.h" #include "kis_deform_paintop_settings.h" #include "kis_deform_option.h" class KisPainter; class KisDeformPaintOp : public KisPaintOp { public: KisDeformPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisDeformPaintOp() override; +protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: KisPaintDeviceSP m_dab; KisPaintDeviceSP m_dev; DeformBrush m_deformBrush; DeformOption m_properties; BrushSizeOption m_sizeProperties; KisAirbrushOption m_airbrushOption; KisPressureSizeOption m_sizeOption; KisPressureOpacityOption m_opacityOption; KisPressureRotationOption m_rotationOption; KisPressureRateOption m_rateOption; qreal m_xSpacing; qreal m_ySpacing; qreal m_spacing; }; #endif // KIS_DEFORMPAINTOP_H_ diff --git a/plugins/paintops/dynadraw/kis_dyna_paintop.cpp b/plugins/paintops/dynadraw/kis_dyna_paintop.cpp index 1a868e3e5a..2c17ac8ebe 100644 --- a/plugins/paintops/dynadraw/kis_dyna_paintop.cpp +++ b/plugins/paintops/dynadraw/kis_dyna_paintop.cpp @@ -1,132 +1,137 @@ /* * Copyright (c) 2009-2010 Lukáš Tvrdý * * 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_dyna_paintop.h" #include "kis_dyna_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_dynaop_option.h" #include "filter.h" KisDynaPaintOp::KisDynaPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(node); if (image) { m_dynaBrush.setCanvasSize(image->width(), image->height()); } else { // some dummy values for scratchpad m_dynaBrush.setCanvasSize(1000, 1000); } m_properties.initWidth = settings->getDouble(DYNA_WIDTH); m_properties.action = settings->getDouble(DYNA_ACTION); m_properties.mass = settings->getDouble(DYNA_MASS); m_properties.drag = settings->getDouble(DYNA_DRAG); double angle = settings->getDouble(DYNA_ANGLE); m_properties.xAngle = cos(angle * M_PI / 180.0); m_properties.yAngle = sin(angle * M_PI / 180.0); m_properties.widthRange = settings->getDouble(DYNA_WIDTH_RANGE); m_properties.diameter = settings->getInt(DYNA_DIAMETER); m_properties.lineCount = settings->getInt(DYNA_LINE_COUNT); m_properties.lineSpacing = settings->getDouble(DYNA_LINE_SPACING); m_properties.enableLine = settings->getBool(DYNA_ENABLE_LINE); m_properties.useTwoCircles = settings->getBool(DYNA_USE_TWO_CIRCLES); m_properties.useFixedAngle = settings->getBool(DYNA_USE_FIXED_ANGLE); m_dynaBrush.setProperties(&m_properties); m_airbrushOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_rateOption.resetAllSensors(); } KisDynaPaintOp::~KisDynaPaintOp() { } void KisDynaPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { // Use superclass behavior for lines of zero length. Otherwise, airbrushing can happen faster // than it is supposed to. if (pi1.pos() == pi2.pos()) { KisPaintOp::paintLine(pi1, pi2, currentDistance); } else { doPaintLine(pi1, pi2); } } void KisDynaPaintOp::doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2) { Q_UNUSED(pi2); if (!painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } qreal x1, y1; x1 = pi1.pos().x(); y1 = pi1.pos().y(); m_dynaBrush.updateCursorPosition(pi1.pos()); m_dynaBrush.paint(m_dab, x1, y1, painter()->paintColor()); QRect rc = m_dab->extent(); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); } KisSpacingInformation KisDynaPaintOp::paintAt(const KisPaintInformation& info) { doPaintLine(info, info); + return updateSpacingImpl(info); +} + +KisSpacingInformation KisDynaPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ return KisPaintOpPluginUtils::effectiveSpacing(0.0, 0.0, true, 0.0, false, 0.0, false, 0.0, KisLodTransform::lodToScale(painter()->device()), &m_airbrushOption, nullptr, &m_rateOption, info); } diff --git a/plugins/paintops/dynadraw/kis_dyna_paintop.h b/plugins/paintops/dynadraw/kis_dyna_paintop.h index 70bace33a4..bea66cad71 100644 --- a/plugins/paintops/dynadraw/kis_dyna_paintop.h +++ b/plugins/paintops/dynadraw/kis_dyna_paintop.h @@ -1,60 +1,64 @@ /* * Copyright (c) 2009-2010 Lukáš Tvrdý * * 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_DYNA_PAINTOP_H_ #define KIS_DYNA_PAINTOP_H_ #include #include #include #include "kis_airbrush_option.h" #include "kis_pressure_rate_option.h" #include "dyna_brush.h" class KisPainter; class KisDynaPaintOpSettings; class KisDynaPaintOp : public KisPaintOp { public: KisDynaPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisDynaPaintOp() override; - KisSpacingInformation paintAt(const KisPaintInformation& info) override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; virtual bool incremental() const { return true; } +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: void doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2); private: KisDynaProperties m_properties; KisPaintDeviceSP m_dab; DynaBrush m_dynaBrush; KisAirbrushOption m_airbrushOption; KisPressureRateOption m_rateOption; }; #endif // KIS_DYNA_PAINTOP_H_ diff --git a/plugins/paintops/experiment/kis_experiment_paintop.cpp b/plugins/paintops/experiment/kis_experiment_paintop.cpp index 3f45baec80..64cdf895c7 100644 --- a/plugins/paintops/experiment/kis_experiment_paintop.cpp +++ b/plugins/paintops/experiment/kis_experiment_paintop.cpp @@ -1,336 +1,341 @@ /* * Copyright (c) 2010-2011 Lukáš Tvrdý * Copyright (c) 2012 Dmitry Kazakov * * 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_experiment_paintop.h" #include "kis_experiment_paintop_settings.h" #include #include #include #include #include #include #include KisExperimentPaintOp::KisExperimentPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image); Q_UNUSED(node); m_firstRun = true; m_experimentOption.readOptionSetting(settings); 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] m_smoothingEnabled = m_experimentOption.isSmoothingEnabled; m_smoothingThreshold = m_experimentOption.smoothing; m_useMirroring = painter->hasMirroring(); m_windingFill = m_experimentOption.windingFill; m_hardEdge = m_experimentOption.hardEdge; if (m_useMirroring) { m_originalDevice = source()->createCompositionSourceDevice(); m_originalPainter = new KisPainter(m_originalDevice); m_originalPainter->setCompositeOp(COMPOSITE_COPY); m_originalPainter->setPaintColor(painter->paintColor()); m_originalPainter->setFillStyle(KisPainter::FillStyleForegroundColor); } else { m_originalPainter = 0; } } KisExperimentPaintOp::~KisExperimentPaintOp() { delete m_originalPainter; } void KisExperimentPaintOp::paintRegion(const QRegion &changedRegion) { if (m_windingFill) { m_path.setFillRule(Qt::WindingFill); } if (m_useMirroring) { m_originalPainter->setAntiAliasPolygonFill(!m_hardEdge); Q_FOREACH (const QRect & rect, changedRegion.rects()) { m_originalPainter->fillPainterPath(m_path, rect); painter()->renderDabWithMirroringNonIncremental(rect, m_originalDevice); } } else { painter()->setFillStyle(KisPainter::FillStyleForegroundColor); painter()->setCompositeOp(COMPOSITE_COPY); painter()->setAntiAliasPolygonFill(!m_hardEdge); Q_FOREACH (const QRect & rect, changedRegion.rects()) { painter()->fillPainterPath(m_path, rect); } } } QPointF KisExperimentPaintOp::speedCorrectedPosition(const KisPaintInformation& pi1, const KisPaintInformation& pi2) { const qreal fadeFactor = 0.6; QPointF diff = pi2.pos() - pi1.pos(); qreal realLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y()); if (realLength < 0.1) return pi2.pos(); qreal coeff = 0.5 * realLength * m_speedMultiplier; m_savedSpeedCoeff = fadeFactor * m_savedSpeedCoeff + (1 - fadeFactor) * coeff; QPointF newPoint = pi1.pos() + diff * m_savedSpeedCoeff / realLength; m_savedSpeedPoint = fadeFactor * m_savedSpeedPoint + (1 - fadeFactor) * newPoint; return m_savedSpeedPoint; } void KisExperimentPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { Q_UNUSED(currentDistance); if (!painter()) return; if (m_firstRun) { m_firstRun = false; m_path.moveTo(pi1.pos()); m_path.lineTo(pi2.pos()); m_center = pi1.pos(); m_savedUpdateDistance = 0; m_lastPaintTime = 0; m_savedSpeedCoeff = 0; m_savedSpeedPoint = m_center; m_savedSmoothingDistance = 0; m_savedSmoothingPoint = m_center; } else { const QPointF pos1 = pi1.pos(); QPointF pos2 = pi2.pos(); if (m_speedEnabled) { pos2 = speedCorrectedPosition(pi1, pi2); } int length = (pos2 - pos1).manhattanLength(); m_savedUpdateDistance += length; if (m_smoothingEnabled) { m_savedSmoothingDistance += length; if (m_savedSmoothingDistance > m_smoothingThreshold) { QPointF pt = (m_savedSmoothingPoint + pos2) * 0.5; // for updates approximate curve with two lines m_savedPoints << m_path.currentPosition(); m_savedPoints << m_savedSmoothingPoint; m_savedPoints << m_savedSmoothingPoint; m_savedPoints << pt; m_path.quadTo(m_savedSmoothingPoint, pt); m_savedSmoothingPoint = pos2; m_savedSmoothingDistance = 0; } } else { m_path.lineTo(pos2); m_savedPoints << pos1; 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 = KritaUtils::trySimplifyPath(m_path, threshold); } else { m_path = applyDisplace(m_path, m_displaceCoeff - length); } } /** * Refresh rate at least 25fps */ const int timeThreshold = 40; const int elapsedTime = pi2.currentTime() - m_lastPaintTime; QRect pathBounds = m_path.boundingRect().toRect(); 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 = KritaUtils::splitPath(diff1 | diff2); } paintRegion(changedRegion); m_lastPaintedPath = m_path; } else if (!m_savedPoints.isEmpty()) { QRegion changedRegion = KritaUtils::splitTriangles(m_center, m_savedPoints); paintRegion(changedRegion); } m_savedPoints.clear(); m_savedUpdateDistance = 0; m_lastPaintTime = pi2.currentTime(); } } } KisSpacingInformation KisExperimentPaintOp::paintAt(const KisPaintInformation& info) +{ + return updateSpacingImpl(info); +} + +KisSpacingInformation KisExperimentPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return KisSpacingInformation(1.0); } 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); } 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(); QPainterPath newPath; int count = path.elementCount(); int curveElementCounter = 0; QPointF ctrl1; QPointF ctrl2; QPointF endPoint; for (int i = 0; i < count; i++) { QPainterPath::Element e = path.elementAt(i); switch (e.type) { case QPainterPath::MoveToElement: { newPath.moveTo(getAngle(QPointF(e.x, e.y), lastPoint, speed)); break; } case QPainterPath::LineToElement: { newPath.lineTo(getAngle(QPointF(e.x, e.y), lastPoint, speed)); break; } case QPainterPath::CurveToElement: { curveElementCounter = 0; endPoint = getAngle(QPointF(e.x, e.y), lastPoint, speed); break; } case QPainterPath::CurveToDataElement: { curveElementCounter++; if (curveElementCounter == 1) { ctrl1 = getAngle(QPointF(e.x, e.y), lastPoint, speed); } else if (curveElementCounter == 2) { ctrl2 = getAngle(QPointF(e.x, e.y), lastPoint, speed); newPath.cubicTo(ctrl1, ctrl2, endPoint); } break; } } }// for return newPath; } diff --git a/plugins/paintops/experiment/kis_experiment_paintop.h b/plugins/paintops/experiment/kis_experiment_paintop.h index 2fa232836f..6a36ac46da 100644 --- a/plugins/paintops/experiment/kis_experiment_paintop.h +++ b/plugins/paintops/experiment/kis_experiment_paintop.h @@ -1,86 +1,90 @@ /* * Copyright (c) 2010-2011 Lukáš Tvrdý * * 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_EXPERIMENT_PAINTOP_H_ #define KIS_EXPERIMENT_PAINTOP_H_ #include #include #include #include "kis_experiment_paintop_settings.h" #include "kis_experimentop_option.h" class QPointF; class KisPainter; class KisExperimentPaintOp : public KisPaintOp { public: KisExperimentPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); ~KisExperimentPaintOp() override; void paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance) override; + +protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: void paintRegion(const QRegion &changedRegion); QPointF speedCorrectedPosition(const KisPaintInformation& pi1, const KisPaintInformation& pi2); static qreal simplifyThreshold(const QRectF &bounds); 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_windingFill; bool m_hardEdge; bool m_speedEnabled; int m_speedMultiplier; qreal m_savedSpeedCoeff; QPointF m_savedSpeedPoint; bool m_smoothingEnabled; int m_smoothingThreshold; QPointF m_savedSmoothingPoint; int m_savedSmoothingDistance; int m_savedUpdateDistance; QVector m_savedPoints; int m_lastPaintTime; bool m_firstRun; QPointF m_center; QPainterPath m_path; ExperimentOption m_experimentOption; bool m_useMirroring; KisPainter *m_originalPainter; KisPaintDeviceSP m_originalDevice; }; #endif // KIS_EXPERIMENT_PAINTOP_H_ diff --git a/plugins/paintops/filterop/kis_filterop.cpp b/plugins/paintops/filterop/kis_filterop.cpp index 0b860b5e16..22bb27c4ef 100644 --- a/plugins/paintops/filterop/kis_filterop.cpp +++ b/plugins/paintops/filterop/kis_filterop.cpp @@ -1,142 +1,149 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * * 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_filterop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisFilterOp::KisFilterOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_filterConfiguration(0) { Q_UNUSED(node); Q_UNUSED(image); Q_ASSERT(settings); Q_ASSERT(painter); m_tmpDevice = source()->createCompositionSourceDevice(); m_sizeOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_filter = KisFilterRegistry::instance()->get(settings->getString(FILTER_ID)); m_filterConfiguration = static_cast(settings.data())->filterConfig(); m_smudgeMode = settings->getBool(FILTER_SMUDGE_MODE); m_rotationOption.applyFanCornersInfo(this); } KisFilterOp::~KisFilterOp() { } KisSpacingInformation KisFilterOp::paintAt(const KisPaintInformation& info) { if (!painter()) { return KisSpacingInformation(1.0); } if (!m_filter) { return KisSpacingInformation(1.0); } if (!source()) { return KisSpacingInformation(1.0); } KisBrushSP brush = m_brush; if (!brush) return KisSpacingInformation(1.0); if (! brush->canPaintFor(info)) return KisSpacingInformation(1.0); qreal scale = m_sizeOption.apply(info); scale *= KisLodTransform::lodToScale(painter()->device()); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); qreal rotation = m_rotationOption.apply(info); KisDabShape shape(scale, 1.0, rotation); static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); QRect dstRect; KisFixedPaintDeviceSP dab = m_dabCache->fetchDab(cs, color, info.pos(), shape, info, 1.0, &dstRect); if (dstRect.isEmpty()) return KisSpacingInformation(1.0); QRect dabRect = dab->bounds(); // sanity check Q_ASSERT(dstRect.size() == dabRect.size()); // Filter the paint device QRect neededRect = m_filter->neededRect(dstRect, m_filterConfiguration, painter()->device()->defaultBounds()->currentLevelOfDetail()); KisPainter p(m_tmpDevice); if (!m_smudgeMode) { p.setCompositeOp(COMPOSITE_COPY); } p.bitBltOldData(neededRect.topLeft() - dstRect.topLeft(), source(), neededRect); KisTransaction transaction(m_tmpDevice); m_filter->process(m_tmpDevice, dabRect, m_filterConfiguration, 0); transaction.end(); painter()->bitBltWithFixedSelection(dstRect.x(), dstRect.y(), m_tmpDevice, dab, 0, 0, dabRect.x(), dabRect.y(), dabRect.width(), dabRect.height()); painter()->renderMirrorMaskSafe(dstRect, m_tmpDevice, 0, 0, dab, !m_dabCache->needSeparateOriginal()); return effectiveSpacing(scale, rotation, info); } + +KisSpacingInformation KisFilterOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); + const qreal rotation = m_rotationOption.apply(info); + return effectiveSpacing(scale, rotation, info); +} diff --git a/plugins/paintops/filterop/kis_filterop.h b/plugins/paintops/filterop/kis_filterop.h index ce4960ac34..13300a7c55 100644 --- a/plugins/paintops/filterop/kis_filterop.h +++ b/plugins/paintops/filterop/kis_filterop.h @@ -1,55 +1,59 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * * 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_FILTEROP_H_ #define KIS_FILTEROP_H_ #include "kis_brush_based_paintop.h" #include #include class KisFilterConfiguration; class KisFilterOpSettings; class KisPaintInformation; class KisPainter; class KisFilterOp : public KisBrushBasedPaintOp { public: KisFilterOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisFilterOp() override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: KisPaintDeviceSP m_tmpDevice; KisPressureSizeOption m_sizeOption; KisPressureRotationOption m_rotationOption; KisFilterSP m_filter; KisFilterConfigurationSP m_filterConfiguration; bool m_smudgeMode; }; #endif // KIS_FILTEROP_H_ diff --git a/plugins/paintops/gridbrush/kis_grid_paintop.cpp b/plugins/paintops/gridbrush/kis_grid_paintop.cpp index b7134103ad..fba1119f60 100644 --- a/plugins/paintops/gridbrush/kis_grid_paintop.cpp +++ b/plugins/paintops/gridbrush/kis_grid_paintop.cpp @@ -1,255 +1,266 @@ /* * Copyright (c) 2009,2010 Lukáš Tvrdý (lukast.dev@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_grid_paintop.h" #include "kis_grid_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef BENCHMARK #include #endif KisGridPaintOp::KisGridPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) , m_settings(static_cast(const_cast(settings.data()))) , m_image(image) , m_node(node) { m_properties.readOptionSetting(settings); m_colorProperties.fillProperties(settings); m_xSpacing = m_properties.gridWidth * m_properties.scale; m_ySpacing = m_properties.gridHeight * m_properties.scale; m_spacing = m_xSpacing; m_dab = source()->createCompositionSourceDevice(); m_painter = new KisPainter(m_dab); m_painter->setPaintColor(painter->paintColor()); m_painter->setFillStyle(KisPainter::FillStyleForegroundColor); #ifdef BENCHMARK m_count = m_total = 0; #endif } KisGridPaintOp::~KisGridPaintOp() { delete m_painter; } KisSpacingInformation KisGridPaintOp::paintAt(const KisPaintInformation& info) { #ifdef BENCHMARK QTime time; time.start(); #endif KisRandomSourceSP randomSource = info.randomSource(); const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); if (!painter()) return KisSpacingInformation(m_spacing * additionalScale); m_dab->clear(); qreal gridWidth = m_properties.gridWidth * m_properties.scale * additionalScale; qreal gridHeight = m_properties.gridHeight * m_properties.scale * additionalScale; int divide; if (m_properties.pressureDivision) { divide = m_properties.divisionLevel * info.pressure(); } else { divide = m_properties.divisionLevel; } divide = qRound(m_properties.scale * divide); qreal posX = info.pos().x(); qreal posY = info.pos().y(); posX = posX - std::fmod(posX, gridWidth); posY = posY - std::fmod(posY, gridHeight); const QRectF dabRect(posX, posY, gridWidth, gridHeight); const QRect dabRectAligned = dabRect.toAlignedRect(); divide = qMax(1, divide); const qreal yStep = gridHeight / (qreal)divide; const qreal xStep = gridWidth / (qreal)divide; QRectF tile; KoColor color(painter()->paintColor()); QScopedPointer colorPicker; if (m_node) { colorPicker.reset(new KisCrossDeviceColorPicker(m_node->paintDevice(), color)); } qreal vertBorder = m_properties.vertBorder * additionalScale; qreal horzBorder = m_properties.horizBorder * additionalScale; if (m_properties.randomBorder) { if (vertBorder == horzBorder) { vertBorder = horzBorder = vertBorder * randomSource->generateNormalized(); } else { vertBorder *= randomSource->generateNormalized(); horzBorder *= randomSource->generateNormalized(); } } bool shouldColor = true; // fill the tile if (m_colorProperties.fillBackground) { m_dab->fill(dabRectAligned, painter()->backgroundColor()); } for (int y = 0; y < divide; y++) { for (int x = 0; x < divide; x++) { // determine the tile size tile = QRectF(dabRect.x() + x * xStep, dabRect.y() + y * yStep, xStep, yStep); tile.adjust(vertBorder, horzBorder, -vertBorder, -horzBorder); tile = tile.normalized(); // do color transformation if (shouldColor) { if (colorPicker && m_colorProperties.sampleInputColor) { colorPicker->pickOldColor(tile.center().x(), tile.center().y(), color.data()); } // mix the color with background color if (m_colorProperties.mixBgColor) { KoMixColorsOp * mixOp = source()->colorSpace()->mixColorsOp(); const quint8 *colors[2]; colors[0] = color.data(); colors[1] = painter()->backgroundColor().data(); qint16 colorWeights[2]; int MAX_16BIT = 255; qreal blend = info.pressure(); colorWeights[0] = static_cast(blend * MAX_16BIT); colorWeights[1] = static_cast((1.0 - blend) * MAX_16BIT); mixOp->mixColors(colors, colorWeights, 2, color.data()); } if (m_colorProperties.useRandomHSV) { QHash params; params["h"] = (m_colorProperties.hue / 180.0) * randomSource->generateNormalized(); params["s"] = (m_colorProperties.saturation / 100.0) * randomSource->generateNormalized(); params["v"] = (m_colorProperties.value / 100.0) * randomSource->generateNormalized(); KoColorTransformation* transfo; transfo = m_dab->colorSpace()->createColorTransformation("hsv_adjustment", params); transfo->setParameter(3, 1);//sets the type to HSV. For some reason 0 is not an option. transfo->setParameter(4, false);//sets the colorize to false. transfo->transform(color.data(), color.data() , 1); } if (m_colorProperties.useRandomOpacity) { const qreal alpha = randomSource->generateNormalized(); color.setOpacity(alpha); m_painter->setOpacity(qRound(alpha * OPACITY_OPAQUE_U8)); } if (!m_colorProperties.colorPerParticle) { shouldColor = false; } m_painter->setPaintColor(color); } // paint some element switch (m_properties.shape) { case 0: { m_painter->paintEllipse(tile); break; } case 1: { // anti-aliased version //m_painter->paintRect(tile); m_dab->fill(tile.topLeft().x(), tile.topLeft().y(), tile.width(), tile.height(), color.data()); break; } case 2: { m_painter->drawDDALine(tile.topRight(), tile.bottomLeft()); break; } case 3: { m_painter->drawLine(tile.topRight(), tile.bottomLeft()); break; } case 4: { m_painter->drawThickLine(tile.topRight(), tile.bottomLeft() , 1, 10); break; } default: { break; } } if (m_colorProperties.colorPerParticle){ color=painter()->paintColor();//reset color// } } } QRect rc = m_dab->extent(); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); #ifdef BENCHMARK int msec = time.elapsed(); dbgKrita << msec << " ms/dab " << "[average: " << m_total / (qreal)m_count << "]"; m_total += msec; m_count++; #endif - return KisSpacingInformation(m_spacing * additionalScale); + return computeSpacing(additionalScale); +} + +KisSpacingInformation KisGridPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + Q_UNUSED(info); + return computeSpacing(KisLodTransform::lodToScale(painter()->device())); +} + +KisSpacingInformation KisGridPaintOp::computeSpacing(qreal lodScale) const +{ + return KisSpacingInformation(m_spacing * lodScale); } void KisGridProperties::readOptionSetting(const KisPropertiesConfigurationSP setting) { gridWidth = qMax(1, setting->getInt(GRID_WIDTH)); gridHeight = qMax(1, setting->getInt(GRID_HEIGHT)); divisionLevel = qMax(1, setting->getInt(GRID_DIVISION_LEVEL)); pressureDivision = setting->getBool(GRID_PRESSURE_DIVISION); randomBorder = setting->getBool(GRID_RANDOM_BORDER); scale = qMax(0.1, setting->getDouble(GRID_SCALE)); vertBorder = setting->getDouble(GRID_VERTICAL_BORDER); horizBorder = setting->getDouble(GRID_HORIZONTAL_BORDER); shape = setting->getInt(GRIDSHAPE_SHAPE); } diff --git a/plugins/paintops/gridbrush/kis_grid_paintop.h b/plugins/paintops/gridbrush/kis_grid_paintop.h index ab0afccb86..847629ae54 100644 --- a/plugins/paintops/gridbrush/kis_grid_paintop.h +++ b/plugins/paintops/gridbrush/kis_grid_paintop.h @@ -1,82 +1,88 @@ /* * Copyright (c) 2009,2010 Lukáš Tvrdý (lukast.dev@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_GRID_PAINTOP_H_ #define KIS_GRID_PAINTOP_H_ //#define BENCHMARK #include #include #include #include #include "kis_grid_paintop_settings.h" class KisPainter; class KisGridProperties { public: quint16 gridWidth; quint16 gridHeight; quint16 divisionLevel; bool pressureDivision; bool randomBorder; qreal scale; qreal vertBorder; qreal horizBorder; quint8 shape; public: void readOptionSetting(const KisPropertiesConfigurationSP setting); void writeOptionSetting(KisPropertiesConfigurationSP setting); }; class KisGridPaintOp : public KisPaintOp { public: KisGridPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisGridPaintOp() override; +protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + +private: + KisSpacingInformation computeSpacing(qreal lodScale) const; + private: KisGridPaintOpSettingsSP m_settings; KisImageWSP m_image; KisPaintDeviceSP m_dab; KisPainter* m_painter; qreal m_xSpacing; qreal m_ySpacing; qreal m_spacing; KisGridProperties m_properties; KisColorProperties m_colorProperties; KisNodeSP m_node; #ifdef BENCHMARK int m_total; int m_count; #endif }; #endif // KIS_GRID_PAINTOP_H_ diff --git a/plugins/paintops/hairy/kis_hairy_paintop.cpp b/plugins/paintops/hairy/kis_hairy_paintop.cpp index 1809a37e7d..8db657b274 100644 --- a/plugins/paintops/hairy/kis_hairy_paintop.cpp +++ b/plugins/paintops/hairy/kis_hairy_paintop.cpp @@ -1,138 +1,143 @@ /* * Copyright (c) 2008-2010 Lukáš Tvrdý * * 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_hairy_paintop.h" #include "kis_hairy_paintop_settings.h" #include #include #include #include #include "kis_paint_device.h" #include "kis_painter.h" #include #include #include #include #include #include #include #include "kis_brush.h" KisHairyPaintOp::KisHairyPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image) Q_ASSERT(settings); m_dev = node ? node->paintDevice() : 0; KisBrushOption brushOption; brushOption.readOptionSettingForceCopy(settings); KisBrushSP brush = brushOption.brush(); KisFixedPaintDeviceSP dab = cachedDab(painter->device()->compositionSourceColorSpace()); if (brush->brushType() == IMAGE || brush->brushType() == PIPE_IMAGE) { dab = brush->paintDevice(source()->colorSpace(), KisDabShape(), KisPaintInformation()); } else { brush->mask(dab, painter->paintColor(), KisDabShape(), KisPaintInformation()); } m_brush.fromDabWithDensity(dab, settings->getDouble(HAIRY_BRISTLE_DENSITY) * 0.01); m_brush.setInkColor(painter->paintColor()); loadSettings(static_cast(settings.data())); m_brush.setProperties(&m_properties); m_rotationOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_rotationOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); } void KisHairyPaintOp::loadSettings(const KisBrushBasedPaintOpSettings *settings) { m_properties.inkAmount = settings->getInt(HAIRY_INK_AMOUNT); //TODO: wait for the transfer function with variable size m_properties.inkDepletionCurve = settings->getCubicCurve(HAIRY_INK_DEPLETION_CURVE).floatTransfer(m_properties.inkAmount); m_properties.inkDepletionEnabled = settings->getBool(HAIRY_INK_DEPLETION_ENABLED); m_properties.useSaturation = settings->getBool(HAIRY_INK_USE_SATURATION); m_properties.useOpacity = settings->getBool(HAIRY_INK_USE_OPACITY); m_properties.useWeights = settings->getBool(HAIRY_INK_USE_WEIGHTS); m_properties.pressureWeight = settings->getDouble(HAIRY_INK_PRESSURE_WEIGHT) / 100.0; m_properties.bristleLengthWeight = settings->getDouble(HAIRY_INK_BRISTLE_LENGTH_WEIGHT) / 100.0; m_properties.bristleInkAmountWeight = settings->getDouble(HAIRY_INK_BRISTLE_INK_AMOUNT_WEIGHT) / 100.0; m_properties.inkDepletionWeight = settings->getDouble(HAIRY_INK_DEPLETION_WEIGHT); m_properties.useSoakInk = settings->getBool(HAIRY_INK_SOAK); m_properties.useMousePressure = settings->getBool(HAIRY_BRISTLE_USE_MOUSEPRESSURE); m_properties.shearFactor = settings->getDouble(HAIRY_BRISTLE_SHEAR); m_properties.randomFactor = settings->getDouble(HAIRY_BRISTLE_RANDOM); m_properties.scaleFactor = settings->getDouble(HAIRY_BRISTLE_SCALE); m_properties.threshold = settings->getBool(HAIRY_BRISTLE_THRESHOLD); m_properties.antialias = settings->getBool(HAIRY_BRISTLE_ANTI_ALIASING); m_properties.useCompositing = settings->getBool(HAIRY_BRISTLE_USE_COMPOSITING); m_properties.connectedPath = settings->getBool(HAIRY_BRISTLE_CONNECTED); } KisSpacingInformation KisHairyPaintOp::paintAt(const KisPaintInformation& info) +{ + return updateSpacingImpl(info); +} + +KisSpacingInformation KisHairyPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return KisSpacingInformation(0.5); } void KisHairyPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { Q_UNUSED(currentDistance); if (!painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } // Hairy Brush is capable of working with zero scale, // so no additional checks for 'zero'ness are needed qreal scale = m_sizeOption.apply(pi2); scale *= KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(pi2); quint8 origOpacity = m_opacityOption.apply(painter(), pi2); m_brush.paintLine(m_dab, m_dev, pi1, pi2, scale * m_properties.scaleFactor, rotation); //QRect rc = m_dab->exactBounds(); QRect rc = m_dab->extent(); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); } diff --git a/plugins/paintops/hairy/kis_hairy_paintop.h b/plugins/paintops/hairy/kis_hairy_paintop.h index 620e8e4c17..e9de44d51d 100644 --- a/plugins/paintops/hairy/kis_hairy_paintop.h +++ b/plugins/paintops/hairy/kis_hairy_paintop.h @@ -1,59 +1,63 @@ /* * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2008-2010 Lukáš Tvrdý * * 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_HAIRYPAINTOP_H_ #define KIS_HAIRYPAINTOP_H_ #include #include #include #include #include "hairy_brush.h" #include #include #include class KisPainter; class KisBrushBasedPaintOpSettings; class KisHairyPaintOp : public KisPaintOp { public: KisHairyPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); - KisSpacingInformation paintAt(const KisPaintInformation& info) override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: KisHairyProperties m_properties; KisPaintDeviceSP m_dab; KisPaintDeviceSP m_dev; HairyBrush m_brush; KisPressureRotationOption m_rotationOption; KisPressureSizeOption m_sizeOption; KisPressureOpacityOption m_opacityOption; void loadSettings(const KisBrushBasedPaintOpSettings* settings); }; #endif // KIS_HAIRYPAINTOP_H_ diff --git a/plugins/paintops/hatching/kis_hatching_paintop.cpp b/plugins/paintops/hatching/kis_hatching_paintop.cpp index 49f17e04a7..453bd834db 100644 --- a/plugins/paintops/hatching/kis_hatching_paintop.cpp +++ b/plugins/paintops/hatching/kis_hatching_paintop.cpp @@ -1,203 +1,209 @@ /* * Copyright (c) 2008,2009 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara * * 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_hatching_paintop.h" #include "kis_hatching_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisHatchingPaintOp::KisHatchingPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_image(image) { Q_UNUSED(node); m_settings = new KisHatchingPaintOpSettings(); static_cast(settings.data())->initializeTwin(m_settings); m_hatchingBrush = new HatchingBrush(m_settings); m_crosshatchingOption.readOptionSetting(settings); m_separationOption.readOptionSetting(settings); m_thicknessOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_crosshatchingOption.resetAllSensors(); m_separationOption.resetAllSensors(); m_thicknessOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); } KisHatchingPaintOp::~KisHatchingPaintOp() { delete m_hatchingBrush; } KisSpacingInformation KisHatchingPaintOp::paintAt(const KisPaintInformation& info) { //------START SIMPLE ERROR CATCHING------- if (!painter()->device()) return KisSpacingInformation(1.0); if (!m_hatchedDab) m_hatchedDab = source()->createCompositionSourceDevice(); else m_hatchedDab->clear(); //Simple convenience renaming, I'm thinking of removing these inherited quirks KisBrushSP brush = m_brush; KisPaintDeviceSP device = painter()->device(); //Macro to catch errors Q_ASSERT(brush); //----------SIMPLE error catching code, maybe it's not even needed------ if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); //SENSOR-depending settings m_settings->crosshatchingsensorvalue = m_crosshatchingOption.apply(info); m_settings->separationsensorvalue = m_separationOption.apply(info); m_settings->thicknesssensorvalue = m_thicknessOption.apply(info); const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); const double scale = additionalScale * m_sizeOption.apply(info); if ((scale * brush->width()) <= 0.01 || (scale * brush->height()) <= 0.01) return KisSpacingInformation(1.0); KisDabShape shape(scale, 1.0, 0.0); quint8 origOpacity = m_opacityOption.apply(painter(), info); /*----Fetch the Dab----*/ static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); QRect dstRect; KisFixedPaintDeviceSP maskDab = m_dabCache->fetchDab(cs, color, info.pos(), shape, info, 1.0, &dstRect); // sanity check KIS_ASSERT_RECOVER_NOOP(dstRect.size() == maskDab->bounds().size()); /*-----Convenient renaming for the limits of the maskDab, this will be used to hatch a dab of just the right size------*/ qint32 x, y, sw, sh; dstRect.getRect(&x, &y, &sw, &sh); //------This If_block pre-fills the future m_hatchedDab with a pretty backgroundColor if (m_settings->opaquebackground) { KoColor aersh = painter()->backgroundColor(); m_hatchedDab->fill(0, 0, (sw - 1), (sh - 1), aersh.data()); //this plus yellow background = french fry brush } // Trick for moire pattern to look better bool donotbasehatch = false; /* If block describing how to stack hatching passes to generate crosshatching according to user specifications */ if (m_settings->enabledcurvecrosshatching) { if (m_settings->perpendicular) { if (m_settings->crosshatchingsensorvalue > 0.5) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(90), painter()->paintColor(), additionalScale); } else if (m_settings->minusthenplus) { if (m_settings->crosshatchingsensorvalue > 0.33) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); if (m_settings->crosshatchingsensorvalue > 0.67) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); } else if (m_settings->plusthenminus) { if (m_settings->crosshatchingsensorvalue > 0.33) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); if (m_settings->crosshatchingsensorvalue > 0.67) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); } else if (m_settings->moirepattern) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle((m_settings->crosshatchingsensorvalue) * 180), painter()->paintColor(), additionalScale); donotbasehatch = true; } } else { if (m_settings->perpendicular) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(90), painter()->paintColor(), additionalScale); } else if (m_settings->minusthenplus) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); } else if (m_settings->plusthenminus) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); } else if (m_settings->moirepattern) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-10), painter()->paintColor(), additionalScale); } } if (!donotbasehatch) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, m_settings->angle, painter()->paintColor(), additionalScale); // The most important line, the one that paints to the screen. painter()->bitBltWithFixedSelection(x, y, m_hatchedDab, maskDab, sw, sh); painter()->renderMirrorMaskSafe(QRect(QPoint(x, y), QSize(sw, sh)), m_hatchedDab, 0, 0, maskDab, !m_dabCache->needSeparateOriginal()); painter()->setOpacity(origOpacity); return effectiveSpacing(scale); } +KisSpacingInformation KisHatchingPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + const qreal scale = KisLodTransform::lodToScale(painter()->device()) * m_sizeOption.apply(info); + return effectiveSpacing(scale); +} + double KisHatchingPaintOp::spinAngle(double spin) { double tempangle = m_settings->angle + spin; qint8 factor = 1; if (tempangle < 0) factor = -1; tempangle = fabs(fmod(tempangle, 180)); if ((tempangle >= 0) && (tempangle <= 90)) return factor * tempangle; else if ((tempangle > 90) && (tempangle <= 180)) return factor * -(180 - tempangle); return 0; // this should never be executed except if NAN } diff --git a/plugins/paintops/hatching/kis_hatching_paintop.h b/plugins/paintops/hatching/kis_hatching_paintop.h index 11ad002487..4a513116b5 100644 --- a/plugins/paintops/hatching/kis_hatching_paintop.h +++ b/plugins/paintops/hatching/kis_hatching_paintop.h @@ -1,104 +1,107 @@ /* * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2008, 2009 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara Toloza * * 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_HATCHING_PAINTOP_H_ #define KIS_HATCHING_PAINTOP_H_ #include #include #include #include "hatching_brush.h" #include "kis_hatching_paintop_settings.h" #include #include #include #include #include class KisPainter; class KisHatchingPaintOp : public KisBrushBasedPaintOp { public: KisHatchingPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisHatchingPaintOp() override; - /** - * Paint a hatched dab around the mouse cursor according to - * sensor settings and user preferences. - */ - KisSpacingInformation paintAt(const KisPaintInformation& info) override; - /** * Returns a number between -90 and 90, and corresponds to the * angle that results from adding angle 'spin' to 'm_settings->angle', * corrected to coincide with the way the GUI operates. */ double spinAngle(double spin); +protected: + /** + * Paint a hatched dab around the mouse cursor according to + * sensor settings and user preferences. + */ + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: KisHatchingPaintOpSettingsSP m_settings; KisImageWSP m_image; HatchingBrush *m_hatchingBrush; /** * PaintDevice that will be filled with a single pass of * hatching by HatchingBrush::hatch */ KisPaintDeviceSP m_hatchedDab; /** * Curve to control the intensity of crosshatching * according to user preferences set in the GUI */ KisHatchingPressureCrosshatchingOption m_crosshatchingOption; /** * Curve to control the dynamics of separation with * device input */ KisHatchingPressureSeparationOption m_separationOption; /** * Curve to control the thickness of the hatching lines * with device input */ KisHatchingPressureThicknessOption m_thicknessOption; /** * Curve to control the opacity of the entire dab * with device input */ KisPressureOpacityOption m_opacityOption; /** * Curve to control the size of the entire dab * with device input */ KisPressureSizeOption m_sizeOption; }; #endif // KIS_HATCHING_PAINTOP_H_ diff --git a/plugins/paintops/particle/kis_particle_paintop.cpp b/plugins/paintops/particle/kis_particle_paintop.cpp index 45182e665a..dc0e17c310 100644 --- a/plugins/paintops/particle/kis_particle_paintop.cpp +++ b/plugins/paintops/particle/kis_particle_paintop.cpp @@ -1,113 +1,118 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * 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_particle_paintop.h" #include "kis_particle_paintop_settings.h" #include #include "kis_vec.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_particleop_option.h" #include "particle_brush.h" KisParticlePaintOp::KisParticlePaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image); Q_UNUSED(node); m_properties.particleCount = settings->getInt(PARTICLE_COUNT); m_properties.iterations = settings->getInt(PARTICLE_ITERATIONS); m_properties.gravity = settings->getDouble(PARTICLE_GRAVITY); m_properties.weight = settings->getDouble(PARTICLE_WEIGHT); m_properties.scale = QPointF(settings->getDouble(PARTICLE_SCALE_X), settings->getDouble(PARTICLE_SCALE_Y)); m_particleBrush.setProperties(&m_properties); m_particleBrush.initParticles(); m_airbrushOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_rateOption.resetAllSensors(); m_first = true; } KisParticlePaintOp::~KisParticlePaintOp() { } KisSpacingInformation KisParticlePaintOp::paintAt(const KisPaintInformation& info) { doPaintLine(info, info); + return updateSpacingImpl(info); +} + +KisSpacingInformation KisParticlePaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ return KisPaintOpPluginUtils::effectiveSpacing(0.0, 0.0, true, 0.0, false, 0.0, false, 0.0, KisLodTransform::lodToScale(painter()->device()), &m_airbrushOption, nullptr, &m_rateOption, info); } void KisParticlePaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { // Use superclass behavior for lines of zero length. Otherwise, airbrushing can happen faster // than it is supposed to. if (pi1.pos() == pi2.pos()) { KisPaintOp::paintLine(pi1, pi2, currentDistance); } else { doPaintLine(pi1, pi2); } } void KisParticlePaintOp::doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2) { if (!painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } if (m_first) { m_particleBrush.setInitialPosition(pi1.pos()); m_first = false; } m_particleBrush.draw(m_dab, painter()->paintColor(), pi2.pos()); QRect rc = m_dab->extent(); painter()->bitBlt(rc.x(), rc.y(), m_dab, rc.x(), rc.y(), rc.width(), rc.height()); painter()->renderMirrorMask(rc, m_dab); } diff --git a/plugins/paintops/particle/kis_particle_paintop.h b/plugins/paintops/particle/kis_particle_paintop.h index ab85a4355e..571d7aa197 100644 --- a/plugins/paintops/particle/kis_particle_paintop.h +++ b/plugins/paintops/particle/kis_particle_paintop.h @@ -1,56 +1,60 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * 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_PARTICLE_PAINTOP_H_ #define KIS_PARTICLE_PAINTOP_H_ #include #include #include #include #include "kis_particle_paintop_settings.h" #include "particle_brush.h" class KisPainter; class KisPaintInformation; class KisParticlePaintOp : public KisPaintOp { public: KisParticlePaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisParticlePaintOp() override; - KisSpacingInformation paintAt(const KisPaintInformation& info) override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: void doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2); private: KisParticleBrushProperties m_properties; KisPaintDeviceSP m_dab; ParticleBrush m_particleBrush; KisAirbrushOption m_airbrushOption; KisPressureRateOption m_rateOption; bool m_first; }; #endif // KIS_PARTICLE_PAINTOP_H_ diff --git a/plugins/paintops/roundmarker/kis_roundmarkerop.cpp b/plugins/paintops/roundmarker/kis_roundmarkerop.cpp index 5652db39f6..6fc6c39580 100644 --- a/plugins/paintops/roundmarker/kis_roundmarkerop.cpp +++ b/plugins/paintops/roundmarker/kis_roundmarkerop.cpp @@ -1,151 +1,164 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * 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_roundmarkerop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_marker_painter.h" #include "kis_paintop_utils.h" KisRoundMarkerOp::KisRoundMarkerOp(KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) , m_firstRun(true) , m_image(image) , m_lastRadius(1.0) { Q_UNUSED(node); Q_ASSERT(settings); Q_ASSERT(painter); m_markerOption.readOptionSetting(*settings); m_sizeOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_spacingOption.resetAllSensors(); } KisRoundMarkerOp::~KisRoundMarkerOp() { } KisSpacingInformation KisRoundMarkerOp::paintAt(const KisPaintInformation& info) { // Simple error catching if (!painter()->device()) { return KisSpacingInformation(1.0); } // get the scaling factor calculated by the size option const qreal lodScale = KisLodTransform::lodToScale(painter()->device()); const qreal scale = m_sizeOption.apply(info) * lodScale; const qreal rotation = 0; // TODO - const bool axesFlipped = false; // TODO const qreal diameter = m_markerOption.diameter * scale; qreal radius = 0.5 * diameter; if (KisPaintOpUtils::checkSizeTooSmall(scale, diameter, diameter)) return KisSpacingInformation(); KisDabShape shape(scale, 1.0, rotation); QPointF pos = info.pos(); KisMarkerPainter gc(painter()->device(), painter()->paintColor()); if (m_firstRun) { gc.fillFullCircle(pos, radius); } else { gc.fillCirclesDiff(m_lastPaintPos, m_lastRadius, pos, radius); } m_firstRun = false; m_lastPaintPos = pos; m_lastRadius = radius; QRectF dirtyRect(pos.x() - radius, pos.y() - radius, 2 * radius, 2 * radius); dirtyRect = kisGrowRect(dirtyRect, 1); painter()->addDirtyRect(dirtyRect.toAlignedRect()); // QPointF scatteredPos = // m_scatterOption.apply(info, // brush->maskWidth(shape, 0, 0, info), // brush->maskHeight(shape, 0, 0, info)); //updateMask(info, scale, rotation, scatteredPos); //QPointF newCenterPos = QRectF(m_dstDabRect).center(); /** * Save the center of the current dab to know where to read the * data during the next pass. We do not save scatteredPos here, * because it may differ slightly from the real center of the * brush (due to rounding effects), which will result in a * really weird quality. */ //QRect srcDabRect = m_dstDabRect.translated((m_lastPaintPos - newCenterPos).toPoint()); //m_lastPaintPos = newCenterPos; - qreal extraSpacingScale = 1.0; - if (m_spacingOption.isChecked()) { - extraSpacingScale = m_spacingOption.apply(info); - } - - KisSpacingInformation spacingInfo = - KisPaintOpUtils::effectiveSpacing(diameter, diameter, - extraSpacingScale, 1.0, true, true, rotation, axesFlipped, - m_markerOption.spacing, - m_markerOption.use_auto_spacing, - m_markerOption.auto_spacing_coeff, - false, - 0.0, - lodScale); + KisSpacingInformation spacingInfo = computeSpacing(info, diameter); if (m_firstRun) { m_firstRun = false; return spacingInfo; } return spacingInfo; } + +KisSpacingInformation KisRoundMarkerOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + const qreal lodScale = KisLodTransform::lodToScale(painter()->device()); + const qreal diameter = m_markerOption.diameter * m_sizeOption.apply(info) * lodScale; + + return computeSpacing(info, diameter); +} + +KisSpacingInformation KisRoundMarkerOp::computeSpacing(const KisPaintInformation &info, + qreal diameter) const +{ + const qreal rotation = 0; // TODO + const bool axesFlipped = false; // TODO + + qreal extraSpacingScale = 1.0; + if (m_spacingOption.isChecked()) { + extraSpacingScale = m_spacingOption.apply(info); + } + + return KisPaintOpUtils::effectiveSpacing(diameter, diameter, + extraSpacingScale, 1.0, true, true, rotation, + axesFlipped, m_markerOption.spacing, + m_markerOption.use_auto_spacing, + m_markerOption.auto_spacing_coeff, false, 0.0, + KisLodTransform::lodToScale(painter()->device())); +} diff --git a/plugins/paintops/roundmarker/kis_roundmarkerop.h b/plugins/paintops/roundmarker/kis_roundmarkerop.h index ded5e6eb38..977508a6f2 100644 --- a/plugins/paintops/roundmarker/kis_roundmarkerop.h +++ b/plugins/paintops/roundmarker/kis_roundmarkerop.h @@ -1,53 +1,60 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * 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_ROUNDMARKEROP_H_ #define _KIS_ROUNDMARKEROP_H_ #include #include #include #include #include #include "kis_roundmarker_option.h" class QPointF; class KisPaintOpSettings; class KisPainter; class KisRoundMarkerOp: public KisPaintOp { public: KisRoundMarkerOp(KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image); ~KisRoundMarkerOp() override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + +private: + KisSpacingInformation computeSpacing(const KisPaintInformation &info, qreal diameter) const; + private: bool m_firstRun; KisImageSP m_image; KisPaintDeviceSP m_tempDev; KisPressureSizeOption m_sizeOption; KisPressureSpacingOption m_spacingOption; QPointF m_lastPaintPos; qreal m_lastRadius; RoundMarkerOption m_markerOption; }; #endif // _KIS_ROUNDMARKEROP_H_ diff --git a/plugins/paintops/sketch/kis_sketch_paintop.cpp b/plugins/paintops/sketch/kis_sketch_paintop.cpp index ba1e89f827..7762cf457c 100644 --- a/plugins/paintops/sketch/kis_sketch_paintop.cpp +++ b/plugins/paintops/sketch/kis_sketch_paintop.cpp @@ -1,315 +1,320 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2010 Ricardo Cabello * * 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_sketch_paintop.h" #include "kis_sketch_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_lod_transform.h" #include /* * Based on Harmony project http://github.com/mrdoob/harmony/ */ // chrome : diff 0.2, sketchy : 0.3, fur: 0.5 // fur : distance / thresholdDistance // shaded: opacity per line :/ // ((1 - (d / 1000)) * 0.1 * BRUSH_PRESSURE), offset == 0 // chrome: color per line :/ //this.context.strokeStyle = "rgba(" + Math.floor(Math.random() * COLOR[0]) + ", " + Math.floor(Math.random() * COLOR[1]) + ", " + Math.floor(Math.random() * COLOR[2]) + ", " + 0.1 * BRUSH_PRESSURE + " )"; // long fur // from: count + offset * -random // to: i point - (offset * -random) + random * 2 // probability distance / thresholdDistnace // shaded: probabity : paint always - 0.0 density KisSketchPaintOp::KisSketchPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image); Q_UNUSED(node); m_airbrushOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_sketchProperties.readOptionSetting(settings); m_brushOption.readOptionSettingForceCopy(settings); m_densityOption.readOptionSetting(settings); m_lineWidthOption.readOptionSetting(settings); m_offsetScaleOption.readOptionSetting(settings); m_brush = m_brushOption.brush(); m_dabCache = new KisDabCache(m_brush); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_painter = 0; m_count = 0; } KisSketchPaintOp::~KisSketchPaintOp() { delete m_painter; delete m_dabCache; } void KisSketchPaintOp::drawConnection(const QPointF& start, const QPointF& end, double lineWidth) { if (lineWidth == 1.0) { m_painter->drawThickLine(start, end, lineWidth, lineWidth); } else { m_painter->drawLine(start, end, lineWidth, true); } } void KisSketchPaintOp::updateBrushMask(const KisPaintInformation& info, qreal scale, qreal rotation) { QRect dstRect; m_maskDab = m_dabCache->fetchDab(m_dab->colorSpace(), painter()->paintColor(), info.pos(), KisDabShape(scale, 1.0, rotation), info, 1.0, &dstRect); m_brushBoundingBox = dstRect; m_hotSpot = QPointF(0.5 * m_brushBoundingBox.width(), 0.5 * m_brushBoundingBox.height()); } void KisSketchPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { // Use superclass behavior for lines of zero length. Otherwise, airbrushing can happen faster // than it is supposed to. if (pi1.pos() == pi2.pos()) { KisPaintOp::paintLine(pi1, pi2, currentDistance); } else { doPaintLine(pi1, pi2); } } void KisSketchPaintOp::doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2) { if (!m_brush || !painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); m_painter = new KisPainter(m_dab); m_painter->setPaintColor(painter()->paintColor()); } else { m_dab->clear(); } QPointF prevMouse = pi1.pos(); QPointF mousePosition = pi2.pos(); m_points.append(mousePosition); const qreal lodAdditionalScale = KisLodTransform::lodToScale(painter()->device()); const qreal scale = lodAdditionalScale * m_sizeOption.apply(pi2); if ((scale * m_brush->width()) <= 0.01 || (scale * m_brush->height()) <= 0.01) return; const qreal currentLineWidth = qMax(0.9, lodAdditionalScale * m_lineWidthOption.apply(pi2, m_sketchProperties.lineWidth)); const qreal currentOffsetScale = m_offsetScaleOption.apply(pi2, m_sketchProperties.offset); const double rotation = m_rotationOption.apply(pi2); const double currentProbability = m_densityOption.apply(pi2, m_sketchProperties.probability); // shaded: does not draw this line, chrome does, fur does if (m_sketchProperties.makeConnection) { drawConnection(prevMouse, mousePosition, currentLineWidth); } qreal thresholdDistance = 0.0; // update the mask for simple mode only once // determine the radius if (m_count == 0 && m_sketchProperties.simpleMode) { updateBrushMask(pi2, 1.0, 0.0); //m_radius = qMax(m_maskDab->bounds().width(),m_maskDab->bounds().height()) * 0.5; m_radius = 0.5 * qMax(m_brush->width(), m_brush->height()); } if (!m_sketchProperties.simpleMode) { updateBrushMask(pi2, scale, rotation); m_radius = qMax(m_maskDab->bounds().width(), m_maskDab->bounds().height()) * 0.5; thresholdDistance = pow(m_radius, 2); } if (m_sketchProperties.simpleMode) { // update the radius according scale in simple mode thresholdDistance = pow(m_radius * scale, 2); } // determine density const qreal density = thresholdDistance * currentProbability; // probability behaviour qreal probability = 1.0 - currentProbability; QColor painterColor = painter()->paintColor().toQColor(); QColor randomColor; KoColor color(m_dab->colorSpace()); int w = m_maskDab->bounds().width(); quint8 opacityU8 = 0; quint8 * pixel; qreal distance; QPoint positionInMask; QPointF diff; int size = m_points.size(); // MAIN LOOP for (int i = 0; i < size; i++) { diff = m_points.at(i) - mousePosition; distance = diff.x() * diff.x() + diff.y() * diff.y(); // circle test bool makeConnection = false; if (m_sketchProperties.simpleMode) { if (distance < thresholdDistance) { makeConnection = true; } // mask test } else { if (m_brushBoundingBox.contains(m_points.at(i))) { positionInMask = (diff + m_hotSpot).toPoint(); uint pos = ((positionInMask.y() * w + positionInMask.x()) * m_maskDab->pixelSize()); if (pos < m_maskDab->allocatedPixels() * m_maskDab->pixelSize()) { pixel = m_maskDab->data() + pos; opacityU8 = m_maskDab->colorSpace()->opacityU8(pixel); if (opacityU8 != 0) { makeConnection = true; } } } } if (!makeConnection) { // check next point continue; } if (m_sketchProperties.distanceDensity) { probability = distance / density; } KisRandomSourceSP randomSource = pi2.randomSource(); // density check if (randomSource->generateNormalized() >= probability) { QPointF offsetPt = diff * currentOffsetScale; if (m_sketchProperties.randomRGB) { /** * Since the order of calculation of function * parameters is not defined by C++ standard, we * should generate values in an external code snippet * which has a definite order of execution. */ qreal r1 = randomSource->generateNormalized(); qreal r2 = randomSource->generateNormalized(); qreal r3 = randomSource->generateNormalized(); // some color transformation per line goes here randomColor.setRgbF(r1 * painterColor.redF(), r2 * painterColor.greenF(), r3 * painterColor.blueF()); color.fromQColor(randomColor); m_painter->setPaintColor(color); } // distance based opacity quint8 opacity = OPACITY_OPAQUE_U8; if (m_sketchProperties.distanceOpacity) { opacity *= qRound((1.0 - (distance / thresholdDistance))); } if (m_sketchProperties.randomOpacity) { opacity *= randomSource->generateNormalized(); } m_painter->setOpacity(opacity); if (m_sketchProperties.magnetify) { drawConnection(mousePosition + offsetPt, m_points.at(i) - offsetPt, currentLineWidth); } else { drawConnection(mousePosition + offsetPt, mousePosition - offsetPt, currentLineWidth); } } }// end of MAIN LOOP m_count++; QRect rc = m_dab->extent(); quint8 origOpacity = m_opacityOption.apply(painter(), pi2); painter()->bitBlt(rc.x(), rc.y(), m_dab, rc.x(), rc.y(), rc.width(), rc.height()); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); } KisSpacingInformation KisSketchPaintOp::paintAt(const KisPaintInformation& info) { doPaintLine(info, info); + return updateSpacingImpl(info); +} + +KisSpacingInformation KisSketchPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ return KisPaintOpPluginUtils::effectiveSpacing(0.0, 0.0, true, 0.0, false, 0.0, false, 0.0, KisLodTransform::lodToScale(painter()->device()), &m_airbrushOption, nullptr, &m_rateOption, info); } diff --git a/plugins/paintops/sketch/kis_sketch_paintop.h b/plugins/paintops/sketch/kis_sketch_paintop.h index 3f21bb1eb7..a52bd8417b 100644 --- a/plugins/paintops/sketch/kis_sketch_paintop.h +++ b/plugins/paintops/sketch/kis_sketch_paintop.h @@ -1,88 +1,92 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * 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_SKETCH_PAINTOP_H_ #define KIS_SKETCH_PAINTOP_H_ #include #include #include "kis_density_option.h" #include "kis_sketchop_option.h" #include "kis_sketch_paintop_settings.h" #include "kis_painter.h" #include #include #include #include #include #include "kis_linewidth_option.h" #include "kis_offset_scale_option.h" class KisDabCache; class KisSketchPaintOp : public KisPaintOp { public: KisSketchPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); ~KisSketchPaintOp() override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; + +protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: // pixel buffer KisPaintDeviceSP m_dab; // mask detection area KisFixedPaintDeviceSP m_maskDab; QRectF m_brushBoundingBox; QPointF m_hotSpot; // simple mode qreal m_radius; KisPressureOpacityOption m_opacityOption; KisPressureSizeOption m_sizeOption; KisPressureRotationOption m_rotationOption; KisPressureRateOption m_rateOption; KisDensityOption m_densityOption; KisLineWidthOption m_lineWidthOption; KisOffsetScaleOption m_offsetScaleOption; KisAirbrushOption m_airbrushOption; KisBrushOption m_brushOption; SketchProperties m_sketchProperties; QVector m_points; int m_count; KisPainter * m_painter; KisBrushSP m_brush; KisDabCache *m_dabCache; private: void drawConnection(const QPointF &start, const QPointF &end, double lineWidth); void updateBrushMask(const KisPaintInformation& info, qreal scale, qreal rotation); void doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2); }; #endif // KIS_SKETCH_PAINTOP_H_ diff --git a/plugins/paintops/spray/kis_spray_paintop.cpp b/plugins/paintops/spray/kis_spray_paintop.cpp index 7ba0c3e92b..8c8e5c8fdb 100644 --- a/plugins/paintops/spray/kis_spray_paintop.cpp +++ b/plugins/paintops/spray/kis_spray_paintop.cpp @@ -1,138 +1,148 @@ /* * Copyright (c) 2008-2012 Lukáš Tvrdý * * 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_spray_paintop.h" #include "kis_spray_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisSprayPaintOp::KisSprayPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) , m_isPresetValid(true) , m_node(node) { Q_ASSERT(settings); Q_ASSERT(painter); Q_UNUSED(image); m_airbrushOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_rotationOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_brushOption.readOptionSettingForceCopy(settings); m_colorProperties.fillProperties(settings); m_properties.readOptionSetting(settings); // first load tip properties as shape properties are dependent on diameter/scale/aspect m_shapeProperties.loadSettings(settings, m_properties.diameter * m_properties.scale, m_properties.diameter * m_properties.aspect * m_properties.scale); // TODO: what to do with proportional sizes? m_shapeDynamicsProperties.loadSettings(settings); if (!m_shapeProperties.enabled && !m_brushOption.brush()) { // in case the preset does not contain the definition for KisBrush m_isPresetValid = false; dbgKrita << "Preset is not valid. Painting is not possible. Use the preset editor to fix current brush engine preset."; } m_sprayBrush.setProperties(&m_properties, &m_colorProperties, &m_shapeProperties, &m_shapeDynamicsProperties, m_brushOption.brush()); m_sprayBrush.setFixedDab(cachedDab()); // spacing if ((m_properties.diameter * 0.5) > 1) { m_ySpacing = m_xSpacing = m_properties.diameter * 0.5 * m_properties.spacing; } else { m_ySpacing = m_xSpacing = 1.0; } m_spacing = m_xSpacing; } KisSprayPaintOp::~KisSprayPaintOp() { } KisSpacingInformation KisSprayPaintOp::paintAt(const KisPaintInformation& info) { if (!painter() || !m_isPresetValid) { return KisSpacingInformation(m_spacing); } if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } qreal rotation = m_rotationOption.apply(info); quint8 origOpacity = m_opacityOption.apply(painter(), info); // Spray Brush is capable of working with zero scale, // so no additional checks for 'zero'ness are needed const qreal scale = m_sizeOption.apply(info); - const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); + const qreal lodScale = KisLodTransform::lodToScale(painter()->device()); m_sprayBrush.paint(m_dab, m_node->paintDevice(), info, rotation, - scale, additionalScale, + scale, lodScale, painter()->paintColor(), painter()->backgroundColor()); QRect rc = m_dab->extent(); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); + return computeSpacing(info, lodScale); +} + +KisSpacingInformation KisSprayPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + return computeSpacing(info, KisLodTransform::lodToScale(painter()->device())); +} + +KisSpacingInformation KisSprayPaintOp::computeSpacing(const KisPaintInformation &info, + qreal lodScale) const +{ return KisPaintOpPluginUtils::effectiveSpacing(1.0, 1.0, true, 0.0, false, - m_spacing * additionalScale, false, 1.0, - KisLodTransform::lodToScale(painter()->device()), + m_spacing * lodScale, false, 1.0, lodScale, &m_airbrushOption, nullptr, &m_rateOption, info); } diff --git a/plugins/paintops/spray/kis_spray_paintop.h b/plugins/paintops/spray/kis_spray_paintop.h index ef4420e3d2..168418a8eb 100644 --- a/plugins/paintops/spray/kis_spray_paintop.h +++ b/plugins/paintops/spray/kis_spray_paintop.h @@ -1,66 +1,73 @@ /* * Copyright (c) 2008-2012 Lukáš Tvrdý * * 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_SPRAY_PAINTOP_H_ #define KIS_SPRAY_PAINTOP_H_ #include #include #include "spray_brush.h" #include "kis_spray_paintop_settings.h" #include "kis_brush_option.h" #include #include #include #include #include class KisPainter; class KisSprayPaintOp : public KisPaintOp { public: KisSprayPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisSprayPaintOp() override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + +private: + KisSpacingInformation computeSpacing(const KisPaintInformation &info, qreal lodScale) const; + private: KisShapeProperties m_shapeProperties; KisSprayProperties m_properties; KisShapeDynamicsProperties m_shapeDynamicsProperties; KisColorProperties m_colorProperties; KisBrushOption m_brushOption; KisPaintDeviceSP m_dab; SprayBrush m_sprayBrush; qreal m_xSpacing, m_ySpacing, m_spacing; bool m_isPresetValid; KisAirbrushOption m_airbrushOption; KisPressureRotationOption m_rotationOption; KisPressureSizeOption m_sizeOption; KisPressureOpacityOption m_opacityOption; KisPressureRateOption m_rateOption; KisNodeSP m_node; }; #endif // KIS_SPRAY_PAINTOP_H_ diff --git a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp index 2116b9872d..a355ea6a7e 100644 --- a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp +++ b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp @@ -1,235 +1,248 @@ /* * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * 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_tangent_normal_paintop.h" #include #include #include #include #include #include #include #include #include #include #include KisTangentNormalPaintOp::KisTangentNormalPaintOp(const KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image): KisBrushBasedPaintOp(settings, painter), m_opacityOption(node), m_tempDev(painter->device()->createCompositionSourceDevice()) { Q_UNUSED(image); //Init, read settings, etc// m_tangentTiltOption.readOptionSetting(settings); m_airbrushOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_flowOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_softnessOption.readOptionSetting(settings); m_sharpnessOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_scatterOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_flowOption.resetAllSensors(); m_spacingOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_softnessOption.resetAllSensors(); m_sharpnessOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_scatterOption.resetAllSensors(); m_dabCache->setSharpnessPostprocessing(&m_sharpnessOption); m_rotationOption.applyFanCornersInfo(this); } KisTangentNormalPaintOp::~KisTangentNormalPaintOp() { //destroy things here// } KisSpacingInformation KisTangentNormalPaintOp::paintAt(const KisPaintInformation& info) { /* * For the color, the precision of tilt is only 60x60, and the precision of direction and rotation are 360 and 360*90. * You can't get more precise than 8bit. Therefore, we will check if the current space is RGB, * if so we request a profile with that space and 8bit bit depth, if not, just sRGB */ KoColor currentColor = painter()->paintColor(); QString currentSpace = currentColor.colorSpace()->colorModelId().id(); const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); if (currentSpace != "RGBA") { rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); } else { rgbColorSpace = currentColor.colorSpace(); } QVector channelValues(4); qreal r, g, b; if (currentColor.colorSpace()->colorDepthId().id()=="F16" || currentColor.colorSpace()->colorDepthId().id()=="F32"){ channelValues[0] = 0.5;//red channelValues[1] = 0.5;//green channelValues[2] = 1.0;//blue channelValues[3] = 1.0;//alpha, leave alone. m_tangentTiltOption.apply(info, &r, &g, &b); channelValues[0] = r;//red channelValues[1] = g;//green channelValues[2] = b;//blue } else { channelValues[0] = 1.0;//blue channelValues[1] = 0.5;//green channelValues[2] = 0.5;//red channelValues[3] = 1.0;//alpha, leave alone. m_tangentTiltOption.apply(info, &r, &g, &b); channelValues[0] = b;//blue channelValues[1] = g;//green channelValues[2] = r;//red } quint8 data[4]; rgbColorSpace->fromNormalisedChannelsValue(data, channelValues); KoColor color(data, rgbColorSpace);//Should be default RGB(0.5,0.5,1.0) //draw stuff here, return kisspacinginformation. KisBrushSP brush = m_brush; if (!painter()->device() || !brush || !brush->canPaintFor(info)) { return KisSpacingInformation(1.0); } qreal scale = m_sizeOption.apply(info); scale *= KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(info); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); KisDabShape shape(scale, 1.0, rotation); QPointF cursorPos = m_scatterOption.apply(info, brush->maskWidth(shape, 0, 0, info), brush->maskHeight(shape, 0, 0, info)); m_maskDab = m_dabCache->fetchDab(rgbColorSpace, color, cursorPos, shape, info, m_softnessOption.apply(info), &m_dstDabRect); if (m_dstDabRect.isEmpty()) return KisSpacingInformation(1.0); QRect dabRect = m_maskDab->bounds(); // sanity check Q_ASSERT(m_dstDabRect.size() == dabRect.size()); Q_UNUSED(dabRect); quint8 oldOpacity = painter()->opacity(); QString oldCompositeOpId = painter()->compositeOp()->id(); m_opacityOption.setFlow(m_flowOption.apply(info)); m_opacityOption.apply(painter(), info); //paint with the default color? Copied this from color smudge.// //painter()->setCompositeOp(COMPOSITE_COPY); //painter()->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); painter()->bltFixed(m_dstDabRect.topLeft(), m_maskDab, m_maskDab->bounds()); painter()->renderMirrorMaskSafe(m_dstDabRect, m_maskDab, !m_dabCache->needSeparateOriginal()); // restore orginal opacity and composite mode values painter()->setOpacity(oldOpacity); painter()->setCompositeOp(oldCompositeOpId); + return computeSpacing(info, scale, rotation); +} + +KisSpacingInformation KisTangentNormalPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); + qreal rotation = m_rotationOption.apply(info); + return computeSpacing(info, scale, rotation); +} + +KisSpacingInformation KisTangentNormalPaintOp::computeSpacing(const KisPaintInformation &info, + qreal scale, qreal rotation) const +{ return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, &m_rateOption, info); } void KisTangentNormalPaintOp::paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance) { if (m_sharpnessOption.isChecked() && m_brush && (m_brush->width() == 1) && (m_brush->height() == 1)) { if (!m_lineCacheDevice) { m_lineCacheDevice = m_tempDev; } else { m_lineCacheDevice->clear(); } KisPainter p(m_lineCacheDevice); KoColor currentColor = painter()->paintColor(); QString currentSpace = currentColor.colorSpace()->colorModelId().id(); const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); if (currentSpace != "RGBA") { rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); } else { rgbColorSpace = currentColor.colorSpace(); } QVector channelValues(4); qreal r, g, b; if (currentColor.colorSpace()->colorDepthId().id()=="F16" || currentColor.colorSpace()->colorDepthId().id()=="F32"){ channelValues[0] = 0.5;//red channelValues[1] = 0.5;//green channelValues[2] = 1.0;//blue channelValues[3] = 1.0;//alpha, leave alone. m_tangentTiltOption.apply(pi2, &r, &g, &b); channelValues[0] = r;//red channelValues[1] = g;//green channelValues[2] = b;//blue } else { channelValues[0] = 1.0;//blue channelValues[1] = 0.5;//green channelValues[2] = 0.5;//red channelValues[3] = 1.0;//alpha, leave alone. m_tangentTiltOption.apply(pi2, &r, &g, &b); channelValues[0] = b;//blue channelValues[1] = g;//green channelValues[2] = r;//red } quint8 data[4]; rgbColorSpace->fromNormalisedChannelsValue(data, channelValues); KoColor color(data, rgbColorSpace); p.setPaintColor(color); p.drawDDALine(pi1.pos(), pi2.pos()); QRect rc = m_lineCacheDevice->extent(); painter()->bitBlt(rc.x(), rc.y(), m_lineCacheDevice, rc.x(), rc.y(), rc.width(), rc.height()); painter()->renderMirrorMask(rc, m_lineCacheDevice); } else { KisPaintOp::paintLine(pi1, pi2, currentDistance); } } diff --git a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.h b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.h index ea036fccf9..86bac2a741 100644 --- a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.h +++ b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.h @@ -1,74 +1,83 @@ /* * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * 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_TANGENTNORMALPAINTOP_H_ #define _KIS_TANGENTNORMALPAINTOP_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KisBrushBasedPaintOpSettings; class KisPainter; class KisTangentNormalPaintOp: public KisBrushBasedPaintOp { public: //public functions// /* Create a Tangent Normal Brush Operator*/ KisTangentNormalPaintOp(const KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image); ~KisTangentNormalPaintOp() override; + void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; + +protected: /*paint the dabs*/ KisSpacingInformation paintAt(const KisPaintInformation& info) override; - void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; + + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + +private: + KisSpacingInformation computeSpacing(const KisPaintInformation &info, qreal scale, + qreal rotation) const; + private: //private functions// KisPressureSizeOption m_sizeOption; KisFlowOpacityOption m_opacityOption; KisPressureSpacingOption m_spacingOption; KisPressureRateOption m_rateOption; KisPressureRotationOption m_rotationOption; KisPressureScatterOption m_scatterOption; KisTangentTiltOption m_tangentTiltOption; KisAirbrushOption m_airbrushOption; KisPressureSoftnessOption m_softnessOption; KisPressureSharpnessOption m_sharpnessOption; KisPressureFlowOption m_flowOption; KisFixedPaintDeviceSP m_maskDab; KisPaintDeviceSP m_tempDev; QRect m_dstDabRect; KisPaintDeviceSP m_lineCacheDevice; }; #endif // _KIS_TANGENTNORMALPAINTOP_H_ diff --git a/plugins/tools/basictools/kis_tool_line_helper.cpp b/plugins/tools/basictools/kis_tool_line_helper.cpp index c70e529f11..195a8595ae 100644 --- a/plugins/tools/basictools/kis_tool_line_helper.cpp +++ b/plugins/tools/basictools/kis_tool_line_helper.cpp @@ -1,185 +1,186 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * 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_tool_line_helper.h" #include "kis_algebra_2d.h" #include "kis_painting_information_builder.h" #include "kis_image.h" struct KisToolLineHelper::Private { Private(KisPaintingInformationBuilder *_infoBuilder) : infoBuilder(_infoBuilder), useSensors(true), enabled(true) { } QVector linePoints; KisPaintingInformationBuilder *infoBuilder; bool useSensors; bool enabled; }; KisToolLineHelper::KisToolLineHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText, KisRecordingAdapter *recordingAdapter) : KisToolFreehandHelper(infoBuilder, transactionText, recordingAdapter, new KisSmoothingOptions(false)), m_d(new Private(infoBuilder)) { } KisToolLineHelper::~KisToolLineHelper() { delete m_d; } void KisToolLineHelper::setEnabled(bool value) { m_d->enabled = value; } void KisToolLineHelper::setUseSensors(bool value) { m_d->useSensors = value; } void KisToolLineHelper::repaintLine(KoCanvasResourceManager *resourceManager, KisImageWSP image, KisNodeSP node, KisStrokesFacade *strokesFacade) { if (!m_d->enabled) return; cancelPaint(); if (m_d->linePoints.isEmpty()) return; qreal startAngle = 0.0; if (m_d->linePoints.length() > 1) { startAngle = KisAlgebra2D::directionBetweenPoints(m_d->linePoints[0].pos(), m_d->linePoints[1].pos(), 0.0); } QVector::const_iterator it = m_d->linePoints.constBegin(); QVector::const_iterator end = m_d->linePoints.constEnd(); initPaintImpl(startAngle, *it, resourceManager, image, node, strokesFacade); ++it; while (it != end) { paintLine(*(it - 1), *it); ++it; } } void KisToolLineHelper::start(KoPointerEvent *event, KoCanvasResourceManager *resourceManager) { if (!m_d->enabled) return; // Ignore the elapsed stroke time, so that the line tool will behave as if the whole stroke is // drawn at once. This should prevent any possible spurious dabs caused by airbrushing features. KisPaintInformation pi = m_d->infoBuilder->startStroke(event, 0, resourceManager); if (!m_d->useSensors) { pi = KisPaintInformation(pi.pos()); } m_d->linePoints.append(pi); } void KisToolLineHelper::addPoint(KoPointerEvent *event, const QPointF &overridePos) { if (!m_d->enabled) return; - // Ignore the elapsed stroke time. + // Ignore the elapsed stroke time, so that the line tool will behave as if the whole stroke is + // drawn at once. This should prevent any possible spurious dabs caused by airbrushing features. KisPaintInformation pi = m_d->infoBuilder->continueStroke(event, 0); if (!m_d->useSensors) { pi = KisPaintInformation(pi.pos()); } if (!overridePos.isNull()) { pi.setPos(overridePos); } if (m_d->linePoints.size() > 1) { const QPointF startPos = m_d->linePoints.first().pos(); const QPointF endPos = pi.pos(); const qreal maxDistance = kisDistance(startPos, endPos); const QPointF unit = (endPos - startPos) / maxDistance; QVector::iterator it = m_d->linePoints.begin(); ++it; while (it != m_d->linePoints.end()) { qreal dist = kisDistance(startPos, it->pos()); if (dist < maxDistance) { QPointF pos = startPos + unit * dist; it->setPos(pos); ++it; } else { it = m_d->linePoints.erase(it); } } } m_d->linePoints.append(pi); } void KisToolLineHelper::translatePoints(const QPointF &offset) { if (!m_d->enabled) return; QVector::iterator it = m_d->linePoints.begin(); while (it != m_d->linePoints.end()) { it->setPos(it->pos() + offset); ++it; } } void KisToolLineHelper::end() { if (!m_d->enabled) return; KIS_ASSERT_RECOVER_RETURN(isRunning()); endPaint(); m_d->linePoints.clear(); } void KisToolLineHelper::cancel() { if (!m_d->enabled) return; KIS_ASSERT_RECOVER_RETURN(isRunning()); cancelPaint(); m_d->linePoints.clear(); } void KisToolLineHelper::clearPaint() { if (!m_d->enabled) return; cancelPaint(); } diff --git a/plugins/tools/tool_polygon/kis_tool_polygon.cc b/plugins/tools/tool_polygon/kis_tool_polygon.cc index 6947d79eb4..246d3d8de5 100644 --- a/plugins/tools/tool_polygon/kis_tool_polygon.cc +++ b/plugins/tools/tool_polygon/kis_tool_polygon.cc @@ -1,94 +1,96 @@ /* * kis_tool_polygon.cc -- part of Krita * * Copyright (c) 2004 Michael Thaler * Copyright (c) 2009 Lukáš Tvrdý * Copyright (c) 2010 Cyrille Berger * Copyright (C) 2010 Boudewijn Rempt * * 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_tool_polygon.h" #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" #include #include #include KisToolPolygon::KisToolPolygon(KoCanvasBase *canvas) : KisToolPolylineBase(canvas, KisToolPolylineBase::PAINT, KisCursor::load("tool_polygon_cursor.png", 6, 6)) { setObjectName("tool_polygon"); setSupportOutline(true); } KisToolPolygon::~KisToolPolygon() { } void KisToolPolygon::resetCursorStyle() { KisToolPolylineBase::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolPolygon::finishPolyline(const QVector& points) { if (!blockUntilOperationsFinished()) return; if (image()) { - KisRecordedPathPaintAction linePaintAction(KisNodeQueryPath::absolutePath(currentNode()), currentPaintOpPreset()); + KisRecordedPathPaintAction linePaintAction(KisNodeQueryPath::absolutePath(currentNode()), + currentPaintOpPreset(), + KisDistanceInitInfo()); setupPaintAction(&linePaintAction); linePaintAction.addPolyLine(points.toList()); linePaintAction.addLine(KisPaintInformation(points.last()), KisPaintInformation(points.first())); image()->actionRecorder()->addAction(linePaintAction); } if (!currentNode()->inherits("KisShapeLayer")) { KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polygon"), image(), currentNode(), canvas()->resourceManager(), strokeStyle(), fillStyle()); helper.paintPolygon(points); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(points[0])); for (int i = 1; i < points.count(); i++) path->lineTo(resolutionMatrix.map(points[i])); path->close(); path->normalize(); KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor())); path->setStroke(border); addShape(path); } } diff --git a/plugins/tools/tool_polyline/kis_tool_polyline.cc b/plugins/tools/tool_polyline/kis_tool_polyline.cc index 9aee14ed37..b3bc3ebab2 100644 --- a/plugins/tools/tool_polyline/kis_tool_polyline.cc +++ b/plugins/tools/tool_polyline/kis_tool_polyline.cc @@ -1,98 +1,100 @@ /* * Copyright (c) 2004 Michael Thaler * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2009 Lukáš Tvrdý * Copyright (c) 2010 Cyrille Berger * * 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_tool_polyline.h" #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" #include #include #include KisToolPolyline::KisToolPolyline(KoCanvasBase * canvas) : KisToolPolylineBase(canvas, KisToolPolylineBase::PAINT, KisCursor::load("tool_polyline_cursor.png", 6, 6)) { setObjectName("tool_polyline"); setSupportOutline(true); } KisToolPolyline::~KisToolPolyline() { } void KisToolPolyline::resetCursorStyle() { KisToolPolylineBase::resetCursorStyle(); overrideCursorIfNotEditable(); } QWidget* KisToolPolyline::createOptionWidget() { // there are no options there return KisTool::createOptionWidget(); } void KisToolPolyline::finishPolyline(const QVector& points) { if (!blockUntilOperationsFinished()) return; if (image()) { - KisRecordedPathPaintAction linePaintAction(KisNodeQueryPath::absolutePath(currentNode()), currentPaintOpPreset()); + KisRecordedPathPaintAction linePaintAction(KisNodeQueryPath::absolutePath(currentNode()), + currentPaintOpPreset(), + KisDistanceInitInfo()); setupPaintAction(&linePaintAction); linePaintAction.addPolyLine(points.toList()); image()->actionRecorder()->addAction(linePaintAction); } if (!currentNode()->inherits("KisShapeLayer")) { KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image(), currentNode(), canvas()->resourceManager(), strokeStyle(), fillStyle()); helper.paintPolyline(points); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(points[0])); for (int i = 1; i < points.count(); i++) path->lineTo(resolutionMatrix.map(points[i])); path->normalize(); KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor())); path->setStroke(border); addShape(path); } notifyModified(); } diff --git a/plugins/tools/tool_transform2/kis_liquify_paintop.cpp b/plugins/tools/tool_transform2/kis_liquify_paintop.cpp index d35a3fcf24..0549086088 100644 --- a/plugins/tools/tool_transform2/kis_liquify_paintop.cpp +++ b/plugins/tools/tool_transform2/kis_liquify_paintop.cpp @@ -1,166 +1,192 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * 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_liquify_paintop.h" #include #include #include #include "kis_liquify_transform_worker.h" #include "kis_algebra_2d.h" #include "kis_liquify_properties.h" struct KisLiquifyPaintop::Private { Private(const KisLiquifyProperties &_props, KisLiquifyTransformWorker *_worker) : props(_props), worker(_worker) {} KisLiquifyProperties props; KisLiquifyTransformWorker *worker; }; KisLiquifyPaintop::KisLiquifyPaintop(const KisLiquifyProperties &props, KisLiquifyTransformWorker *worker) : m_d(new Private(props, worker)) { } KisLiquifyPaintop::~KisLiquifyPaintop() { } QPainterPath KisLiquifyPaintop::brushOutline(const KisLiquifyProperties &props, const KisPaintInformation &info) { const qreal diameter = props.size(); const qreal reverseCoeff = props.reverseDirection() ? -1.0 : 1.0; QPainterPath outline; outline.addEllipse(-0.5 * diameter, -0.5 * diameter, diameter, diameter); switch (props.mode()) { case KisLiquifyProperties::MOVE: case KisLiquifyProperties::SCALE: break; case KisLiquifyProperties::ROTATE: { QPainterPath p; p.lineTo(-3.0, 4.0); p.moveTo(0.0, 0.0); p.lineTo(-3.0, -4.0); QTransform S; if (diameter < 15.0) { const qreal scale = diameter / 15.0; S = QTransform::fromScale(scale, scale); } QTransform R; R.rotateRadians(-reverseCoeff * 0.5 * M_PI); QTransform T = QTransform::fromTranslate(0.5 * diameter, 0.0); p = (S * R * T).map(p); outline.addPath(p); break; } case KisLiquifyProperties::OFFSET: { qreal normalAngle = info.drawingAngle() + reverseCoeff * 0.5 * M_PI; QPainterPath p = KisAlgebra2D::smallArrow(); const qreal offset = qMax(0.8 * diameter, 15.0); QTransform R; R.rotateRadians(normalAngle); QTransform T = QTransform::fromTranslate(offset, 0.0); p = (T * R).map(p); outline.addPath(p); break; } case KisLiquifyProperties::UNDO: break; case KisLiquifyProperties::N_MODES: qFatal("Not supported mode"); } return outline; } +// TODO: Reduce code duplication between KisLiquifyPaintop and KisPaintOp. It might be possible to +// make them both subclasses of some more general base class. +void KisLiquifyPaintop::updateSpacing(const KisPaintInformation &info, + KisDistanceInformation ¤tDistance) const +{ + KisPaintInformation pi(info); + KisSpacingInformation spacingInfo; + { + KisPaintInformation::DistanceInformationRegistrar r + = pi.registerDistanceInformation(¤tDistance); + spacingInfo = updateSpacingImpl(pi); + } + + currentDistance.setSpacing(spacingInfo); +} + KisSpacingInformation KisLiquifyPaintop::paintAt(const KisPaintInformation &pi) { - static const qreal sizeToSigmaCoeff = 1.0 / 3.0; - const qreal size = sizeToSigmaCoeff * - (m_d->props.sizeHasPressure() ? - pi.pressure() * m_d->props.size(): - m_d->props.size()); + const qreal size = computeSize(pi); const qreal spacing = m_d->props.spacing() * size; const qreal reverseCoeff = m_d->props.mode() != KisLiquifyProperties::UNDO && m_d->props.reverseDirection() ? -1.0 : 1.0; const qreal amount = m_d->props.amountHasPressure() ? pi.pressure() * reverseCoeff * m_d->props.amount(): reverseCoeff * m_d->props.amount(); const bool useWashMode = m_d->props.useWashMode(); const qreal flow = m_d->props.flow(); switch (m_d->props.mode()) { case KisLiquifyProperties::MOVE: { const qreal offsetLength = size * amount; m_d->worker->translatePoints(pi.pos(), pi.drawingDirectionVector() * offsetLength, size, useWashMode, flow); break; } case KisLiquifyProperties::SCALE: m_d->worker->scalePoints(pi.pos(), amount, size, useWashMode, flow); break; case KisLiquifyProperties::ROTATE: m_d->worker->rotatePoints(pi.pos(), 2.0 * M_PI * amount, size, useWashMode, flow); break; case KisLiquifyProperties::OFFSET: { const qreal offsetLength = size * amount; m_d->worker->translatePoints(pi.pos(), KisAlgebra2D::rightUnitNormal(pi.drawingDirectionVector()) * offsetLength, size, useWashMode, flow); break; } case KisLiquifyProperties::UNDO: m_d->worker->undoPoints(pi.pos(), amount, size); break; case KisLiquifyProperties::N_MODES: qFatal("Not supported mode"); } return KisSpacingInformation(spacing); } + +KisSpacingInformation KisLiquifyPaintop::updateSpacingImpl(const KisPaintInformation &pi) const +{ + return KisSpacingInformation(m_d->props.spacing() * computeSize(pi)); +} + +qreal KisLiquifyPaintop::computeSize(const KisPaintInformation &pi) const +{ + static const qreal sizeToSigmaCoeff = 1.0 / 3.0; + return sizeToSigmaCoeff * + (m_d->props.sizeHasPressure() ? + pi.pressure() * m_d->props.size(): + m_d->props.size()); +} diff --git a/plugins/tools/tool_transform2/kis_liquify_paintop.h b/plugins/tools/tool_transform2/kis_liquify_paintop.h index 905a2be37a..dd48af2b91 100644 --- a/plugins/tools/tool_transform2/kis_liquify_paintop.h +++ b/plugins/tools/tool_transform2/kis_liquify_paintop.h @@ -1,47 +1,60 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * 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_LIQUIFY_PAINTOP_H #define __KIS_LIQUIFY_PAINTOP_H #include class KisLiquifyTransformWorker; class KisPaintInformation; class KisSpacingInformation; +class KisDistanceInformation; class KisLiquifyProperties; class QPainterPath; class KisLiquifyPaintop { public: KisLiquifyPaintop(const KisLiquifyProperties &props, KisLiquifyTransformWorker *worker); ~KisLiquifyPaintop(); KisSpacingInformation paintAt(const KisPaintInformation &pi); + /** + * Updates the spacing in currentDistance based on the provided information. + */ + void updateSpacing(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) + const; + static QPainterPath brushOutline(const KisLiquifyProperties &props, const KisPaintInformation &info); +protected: + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &pi) const; + +private: + qreal computeSize(const KisPaintInformation &pi) const; + private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_LIQUIFY_PAINTOP_H */