diff --git a/libs/image/brushengine/kis_paint_information.cc b/libs/image/brushengine/kis_paint_information.cc index 80edf3bcd7..07a3f85bc7 100644 --- a/libs/image/brushengine/kis_paint_information.cc +++ b/libs/image/brushengine/kis_paint_information.cc @@ -1,643 +1,655 @@ /* * Copyright (c) 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 #include #include #include "kis_paintop.h" #include "kis_algebra_2d.h" #include "kis_lod_transform.h" #include "kis_spacing_information.h" #include struct KisPaintInformation::Private { Private(const QPointF & pos_, qreal pressure_, qreal xTilt_, qreal yTilt_, qreal rotation_, qreal tangentialPressure_, qreal perspective_, qreal time_, qreal speed_, bool isHoveringMode_) : pos(pos_), pressure(pressure_), xTilt(xTilt_), yTilt(yTilt_), rotation(rotation_), tangentialPressure(tangentialPressure_), perspective(perspective_), time(time_), speed(speed_), isHoveringMode(isHoveringMode_), randomSource(0), perStrokeRandomSource(0), levelOfDetail(0) { } ~Private() { KIS_ASSERT_RECOVER_NOOP(!sanityIsRegistered); } Private(const Private &rhs) { copy(rhs); } Private& operator=(const Private &rhs) { copy(rhs); return *this; } void copy(const Private &rhs) { pos = rhs.pos; pressure = rhs.pressure; xTilt = rhs.xTilt; yTilt = rhs.yTilt; rotation = rhs.rotation; tangentialPressure = rhs.tangentialPressure; perspective = rhs.perspective; time = rhs.time; speed = rhs.speed; isHoveringMode = rhs.isHoveringMode; randomSource = rhs.randomSource; perStrokeRandomSource = rhs.perStrokeRandomSource; sanityIsRegistered = false; // HINT: we do not copy registration mark! directionHistoryInfo = rhs.directionHistoryInfo; canvasRotation = rhs.canvasRotation; canvasMirroredH = rhs.canvasMirroredH; + canvasMirroredV = rhs.canvasMirroredV; + if (rhs.drawingAngleOverride) { drawingAngleOverride = *rhs.drawingAngleOverride; } levelOfDetail = rhs.levelOfDetail; } QPointF pos; qreal pressure; qreal xTilt; qreal yTilt; qreal rotation; qreal tangentialPressure; qreal perspective; qreal time; qreal speed; bool isHoveringMode; KisRandomSourceSP randomSource; KisPerStrokeRandomSourceSP perStrokeRandomSource; - int canvasRotation {0}; + qreal canvasRotation {0}; bool canvasMirroredH {false}; + bool canvasMirroredV {false}; boost::optional drawingAngleOverride; bool sanityIsRegistered = false; struct DirectionHistoryInfo { DirectionHistoryInfo() {} DirectionHistoryInfo(qreal _totalDistance, int _currentDabSeqNo, qreal _lastAngle, QPointF _lastPosition, boost::optional _lockedDrawingAngle) : totalStrokeLength(_totalDistance), currentDabSeqNo(_currentDabSeqNo), lastAngle(_lastAngle), lastPosition(_lastPosition), lockedDrawingAngle(_lockedDrawingAngle) { } qreal totalStrokeLength = 0.0; int currentDabSeqNo = 0; qreal lastAngle = 0.0; QPointF lastPosition; boost::optional lockedDrawingAngle; }; boost::optional directionHistoryInfo; int levelOfDetail; void registerDistanceInfo(KisDistanceInformation *di) { directionHistoryInfo = DirectionHistoryInfo(di->scalarDistanceApprox(), di->currentDabSeqNo(), di->lastDrawingAngle(), di->lastPosition(), di->lockedDrawingAngleOptional()); KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityIsRegistered); sanityIsRegistered = true; } void unregisterDistanceInfo() { sanityIsRegistered = false; } }; KisPaintInformation::DistanceInformationRegistrar:: DistanceInformationRegistrar(KisPaintInformation *_p, KisDistanceInformation *distanceInfo) : p(_p) { p->d->registerDistanceInfo(distanceInfo); } KisPaintInformation::DistanceInformationRegistrar::DistanceInformationRegistrar(KisPaintInformation::DistanceInformationRegistrar &&rhs) : p(0) { std::swap(p, rhs.p); } KisPaintInformation::DistanceInformationRegistrar:: ~DistanceInformationRegistrar() { if (p) { p->d->unregisterDistanceInfo(); } } KisPaintInformation::KisPaintInformation(const QPointF & pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation, qreal tangentialPressure, qreal perspective, qreal time, qreal speed) : d(new Private(pos, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed, false)) { } KisPaintInformation::KisPaintInformation(const QPointF & pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation) : d(new Private(pos, pressure, xTilt, yTilt, rotation, 0.0, 1.0, 0.0, 0.0, false)) { } KisPaintInformation::KisPaintInformation(const QPointF &pos, qreal pressure) : d(new Private(pos, pressure, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, false)) { } KisPaintInformation::KisPaintInformation(const KisPaintInformation& rhs) : d(new Private(*rhs.d)) { } void KisPaintInformation::operator=(const KisPaintInformation & rhs) { *d = *rhs.d; } KisPaintInformation::~KisPaintInformation() { delete d; } bool KisPaintInformation::isHoveringMode() const { return d->isHoveringMode; } KisPaintInformation KisPaintInformation::createHoveringModeInfo(const QPointF &pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation, qreal tangentialPressure, qreal perspective, qreal speed, - int canvasrotation, - bool canvasMirroredH) + qreal canvasrotation, + bool canvasMirroredH, + bool canvasMirroredV) { KisPaintInformation info(pos, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, 0, speed); info.d->isHoveringMode = true; info.d->canvasRotation = canvasrotation; info.d->canvasMirroredH = canvasMirroredH; + info.d->canvasMirroredV = canvasMirroredV; return info; } -int KisPaintInformation::canvasRotation() const +qreal KisPaintInformation::canvasRotation() const { return d->canvasRotation; } -void KisPaintInformation::setCanvasRotation(int rotation) +void KisPaintInformation::setCanvasRotation(qreal rotation) { - if (rotation < 0) { - d->canvasRotation= 360- abs(rotation % 360); - } else { - d->canvasRotation= rotation % 360; - } - + d->canvasRotation = normalizeAngleDegrees(rotation); } bool KisPaintInformation::canvasMirroredH() const { return d->canvasMirroredH; } -void KisPaintInformation::setCanvasHorizontalMirrorState(bool mir) +void KisPaintInformation::setCanvasMirroredH(bool value) { - d->canvasMirroredH = mir; + d->canvasMirroredH = value; +} +bool KisPaintInformation::canvasMirroredV() const +{ + return d->canvasMirroredV; +} + +void KisPaintInformation::setCanvasMirroredV(bool value) +{ + d->canvasMirroredV = value; } void KisPaintInformation::toXML(QDomDocument&, QDomElement& e) const { // hovering mode infos are not supposed to be saved KIS_ASSERT_RECOVER_NOOP(!d->isHoveringMode); e.setAttribute("pointX", QString::number(pos().x(), 'g', 15)); e.setAttribute("pointY", QString::number(pos().y(), 'g', 15)); e.setAttribute("pressure", QString::number(pressure(), 'g', 15)); e.setAttribute("xTilt", QString::number(xTilt(), 'g', 15)); e.setAttribute("yTilt", QString::number(yTilt(), 'g', 15)); e.setAttribute("rotation", QString::number(rotation(), 'g', 15)); e.setAttribute("tangentialPressure", QString::number(tangentialPressure(), 'g', 15)); e.setAttribute("perspective", QString::number(perspective(), 'g', 15)); e.setAttribute("time", QString::number(d->time, 'g', 15)); e.setAttribute("speed", QString::number(d->speed, 'g', 15)); } KisPaintInformation KisPaintInformation::fromXML(const QDomElement& e) { qreal pointX = qreal(KisDomUtils::toDouble(e.attribute("pointX", "0.0"))); qreal pointY = qreal(KisDomUtils::toDouble(e.attribute("pointY", "0.0"))); qreal pressure = qreal(KisDomUtils::toDouble(e.attribute("pressure", "0.0"))); qreal rotation = qreal(KisDomUtils::toDouble(e.attribute("rotation", "0.0"))); qreal tangentialPressure = qreal(KisDomUtils::toDouble(e.attribute("tangentialPressure", "0.0"))); qreal perspective = qreal(KisDomUtils::toDouble(e.attribute("perspective", "0.0"))); qreal xTilt = qreal(KisDomUtils::toDouble(e.attribute("xTilt", "0.0"))); qreal yTilt = qreal(KisDomUtils::toDouble(e.attribute("yTilt", "0.0"))); qreal time = KisDomUtils::toDouble(e.attribute("time", "0")); qreal speed = KisDomUtils::toDouble(e.attribute("speed", "0")); return KisPaintInformation(QPointF(pointX, pointY), pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed); } const QPointF& KisPaintInformation::pos() const { return d->pos; } void KisPaintInformation::setPos(const QPointF& p) { d->pos = p; } qreal KisPaintInformation::pressure() const { return d->pressure; } void KisPaintInformation::setPressure(qreal p) { d->pressure = p; } qreal KisPaintInformation::xTilt() const { return d->xTilt; } qreal KisPaintInformation::yTilt() const { return d->yTilt; } void KisPaintInformation::overrideDrawingAngle(qreal angle) { d->drawingAngleOverride = angle; } qreal KisPaintInformation::drawingAngleSafe(const KisDistanceInformation &distance) const { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!d->directionHistoryInfo, 0.0); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(distance.hasLastDabInformation(), 0.0); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!d->drawingAngleOverride, 0.0); return KisAlgebra2D::directionBetweenPoints(distance.lastPosition(), pos(), distance.lastDrawingAngle()); } KisPaintInformation::DistanceInformationRegistrar KisPaintInformation::registerDistanceInformation(KisDistanceInformation *distance) { return DistanceInformationRegistrar(this, distance); } qreal KisPaintInformation::drawingAngle(bool considerLockedAngle) const { if (d->drawingAngleOverride) return *d->drawingAngleOverride; if (!d->directionHistoryInfo) { warnKrita << "KisPaintInformation::drawingAngleSafe()" << "DirectionHistoryInfo object is not available"; return 0.0; } if (considerLockedAngle && d->directionHistoryInfo->lockedDrawingAngle) { return *d->directionHistoryInfo->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(d->directionHistoryInfo->lastPosition, pos(), d->directionHistoryInfo->lastAngle); } QPointF KisPaintInformation::drawingDirectionVector() const { const qreal angle = drawingAngle(false); return QPointF(cos(angle), sin(angle)); } qreal KisPaintInformation::drawingDistance() const { if (!d->directionHistoryInfo) { warnKrita << "KisPaintInformation::drawingDistance()" << "DirectionHistoryInfo object is not available"; return 1.0; } QVector2D diff(pos() - d->directionHistoryInfo->lastPosition); qreal length = diff.length(); if (d->levelOfDetail) { length *= KisLodTransform::lodToInvScale(d->levelOfDetail); } return length; } qreal KisPaintInformation::drawingSpeed() const { return d->speed; } qreal KisPaintInformation::rotation() const { return d->rotation; } qreal KisPaintInformation::tangentialPressure() const { return d->tangentialPressure; } qreal KisPaintInformation::perspective() const { return d->perspective; } qreal KisPaintInformation::currentTime() const { return d->time; } int KisPaintInformation::currentDabSeqNo() const { if (!d->directionHistoryInfo) { warnKrita << "KisPaintInformation::currentDabSeqNo()" << "DirectionHistoryInfo object is not available"; return 0; } return d->directionHistoryInfo->currentDabSeqNo; } qreal KisPaintInformation::totalStrokeLength() const { if (!d->directionHistoryInfo) { warnKrita << "KisPaintInformation::totalStrokeLength()" << "DirectionHistoryInfo object is not available"; return 0; } return d->directionHistoryInfo->totalStrokeLength; } KisRandomSourceSP KisPaintInformation::randomSource() const { if (!d->randomSource) { qWarning() << "Accessing uninitialized random source!"; d->randomSource = new KisRandomSource(); } return d->randomSource; } void KisPaintInformation::setRandomSource(KisRandomSourceSP value) { d->randomSource = value; } KisPerStrokeRandomSourceSP KisPaintInformation::perStrokeRandomSource() const { if (!d->perStrokeRandomSource) { qWarning() << "Accessing uninitialized per stroke random source!"; d->perStrokeRandomSource = new KisPerStrokeRandomSource(); } return d->perStrokeRandomSource; } void KisPaintInformation::setPerStrokeRandomSource(KisPerStrokeRandomSourceSP value) { d->perStrokeRandomSource = value; } void KisPaintInformation::setLevelOfDetail(int levelOfDetail) { d->levelOfDetail = levelOfDetail; } QDebug operator<<(QDebug dbg, const KisPaintInformation &info) { #ifdef NDEBUG Q_UNUSED(info); #else dbg.nospace() << "Position: " << info.pos(); dbg.nospace() << ", Pressure: " << info.pressure(); dbg.nospace() << ", X Tilt: " << info.xTilt(); dbg.nospace() << ", Y Tilt: " << info.yTilt(); dbg.nospace() << ", Rotation: " << info.rotation(); dbg.nospace() << ", Tangential Pressure: " << info.tangentialPressure(); dbg.nospace() << ", Perspective: " << info.perspective(); dbg.nospace() << ", Drawing Angle: " << info.drawingAngle(); dbg.nospace() << ", Drawing Speed: " << info.drawingSpeed(); dbg.nospace() << ", Drawing Distance: " << info.drawingDistance(); dbg.nospace() << ", Time: " << info.currentTime(); #endif return dbg.space(); } KisPaintInformation KisPaintInformation::mixOnlyPosition(qreal t, const KisPaintInformation& mixedPi, const KisPaintInformation& basePi) { QPointF pt = (1 - t) * mixedPi.pos() + t * basePi.pos(); return mixImpl(pt, t, mixedPi, basePi, true, false); } KisPaintInformation KisPaintInformation::mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2) { QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos(); return mix(pt, t, pi1, pi2); } KisPaintInformation KisPaintInformation::mix(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2) { return mixImpl(p, t, pi1, pi2, false, true); } KisPaintInformation KisPaintInformation::mixWithoutTime(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2) { QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos(); return mixWithoutTime(pt, t, pi1, pi2); } KisPaintInformation KisPaintInformation::mixWithoutTime(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2) { return mixImpl(p, t, pi1, pi2, false, false); } void KisPaintInformation::mixOtherOnlyPosition(qreal t, const KisPaintInformation& other) { QPointF pt = (1 - t) * other.pos() + t * this->pos(); this->mixOtherImpl(pt, t, other, true, false); } void KisPaintInformation::mixOtherWithoutTime(qreal t, const KisPaintInformation& other) { QPointF pt = (1 - t) * other.pos() + t * this->pos(); this->mixOtherImpl(pt, t, other, false, false); } KisPaintInformation KisPaintInformation::mixImpl(const QPointF &p, qreal t, const KisPaintInformation &pi1, const KisPaintInformation &pi2, bool posOnly, bool mixTime) { KisPaintInformation result(pi2); result.mixOtherImpl(p, t, pi1, posOnly, mixTime); return result; } void KisPaintInformation::mixOtherImpl(const QPointF &p, qreal t, const KisPaintInformation &other, bool posOnly, bool mixTime) { if (posOnly) { this->d->pos = p; this->d->isHoveringMode = false; this->d->levelOfDetail = 0; return; } else { qreal pressure = (1 - t) * other.pressure() + t * this->pressure(); qreal xTilt = (1 - t) * other.xTilt() + t * this->xTilt(); qreal yTilt = (1 - t) * other.yTilt() + t * this->yTilt(); qreal rotation = other.rotation(); if (other.rotation() != this->rotation()) { qreal a1 = kisDegreesToRadians(other.rotation()); qreal a2 = kisDegreesToRadians(this->rotation()); qreal distance = shortestAngularDistance(a2, a1); rotation = kisRadiansToDegrees(incrementInDirection(a1, t * distance, a2)); } qreal tangentialPressure = (1 - t) * other.tangentialPressure() + t * this->tangentialPressure(); qreal perspective = (1 - t) * other.perspective() + t * this->perspective(); qreal time = mixTime ? ((1 - t) * other.currentTime() + t * this->currentTime()) : this->currentTime(); qreal speed = (1 - t) * other.drawingSpeed() + t * this->drawingSpeed(); KIS_ASSERT_RECOVER_NOOP(other.isHoveringMode() == this->isHoveringMode()); *(this->d) = Private(p, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed, other.isHoveringMode()); + this->d->canvasRotation = other.d->canvasRotation; + this->d->canvasMirroredH = other.d->canvasMirroredH; + this->d->canvasMirroredV = other.d->canvasMirroredV; this->d->randomSource = other.d->randomSource; this->d->perStrokeRandomSource = other.d->perStrokeRandomSource; // this->d->isHoveringMode = other.isHoveringMode(); this->d->levelOfDetail = other.d->levelOfDetail; } } qreal KisPaintInformation::tiltDirection(const KisPaintInformation& info, bool normalize) { qreal xTilt = info.xTilt(); qreal yTilt = info.yTilt(); // radians -PI, PI qreal tiltDirection = atan2(-xTilt, yTilt); // if normalize is true map to 0.0..1.0 return normalize ? (tiltDirection / (2 * M_PI) + 0.5) : tiltDirection; } qreal KisPaintInformation::tiltElevation(const KisPaintInformation& info, qreal maxTiltX, qreal maxTiltY, bool normalize) { qreal xTilt = qBound(qreal(-1.0), info.xTilt() / maxTiltX , qreal(1.0)); qreal yTilt = qBound(qreal(-1.0), info.yTilt() / maxTiltY , qreal(1.0)); qreal e; if (fabs(xTilt) > fabs(yTilt)) { e = sqrt(qreal(1.0) + yTilt * yTilt); } else { e = sqrt(qreal(1.0) + xTilt * xTilt); } qreal cosAlpha = sqrt(xTilt * xTilt + yTilt * yTilt) / e; qreal tiltElevation = acos(cosAlpha); // in radians in [0, 0.5 * PI] // mapping to 0.0..1.0 if normalize is true return normalize ? (tiltElevation / (M_PI * qreal(0.5))) : tiltElevation; } diff --git a/libs/image/brushengine/kis_paint_information.h b/libs/image/brushengine/kis_paint_information.h index bc1067d38d..baa1bc0f0b 100644 --- a/libs/image/brushengine/kis_paint_information.h +++ b/libs/image/brushengine/kis_paint_information.h @@ -1,311 +1,324 @@ /* * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2006 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. */ #ifndef _KIS_PAINT_INFORMATION_ #define _KIS_PAINT_INFORMATION_ #include #include #include "kis_global.h" #include "kritaimage_export.h" #include #include "kis_random_source.h" #include "KisPerStrokeRandomSource.h" #include "kis_spacing_information.h" #include "kis_timing_information.h" class QDomDocument; class QDomElement; class KisDistanceInformation; /** * KisPaintInformation contains information about the input event that * causes the brush action to happen to the brush engine's paint * methods. * * XXX: we directly pass the KoPointerEvent x and y tilt to * KisPaintInformation, and their range is -60 to +60! * * @param pos: the position of the paint event in subpixel accuracy * @param pressure: the pressure of the stylus * @param xTilt: the angle between the device (a pen, for example) and * the perpendicular in the direction of the x axis. Positive values * are towards the bottom of the tablet. The angle is within the range * 0 to 1 * @param yTilt: the angle between the device (a pen, for example) and * the perpendicular in the direction of the y axis. Positive values * are towards the bottom of the tablet. The angle is within the range * 0 to . * @param movement: current position minus the last position of the call to paintAt * @param rotation * @param tangentialPressure * @param perspective **/ class KRITAIMAGE_EXPORT KisPaintInformation { public: /** * Note, that this class is relied on the compiler optimization * of the return value. So if it doesn't work for some reason, * please implement a proper copy c-tor */ class KRITAIMAGE_EXPORT DistanceInformationRegistrar { public: DistanceInformationRegistrar(KisPaintInformation *_p, KisDistanceInformation *distanceInfo); DistanceInformationRegistrar(const DistanceInformationRegistrar &rhs) = delete; DistanceInformationRegistrar(DistanceInformationRegistrar &&rhs); ~DistanceInformationRegistrar(); private: KisPaintInformation *p; }; public: /** * Create a new KisPaintInformation object. */ KisPaintInformation(const QPointF & pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation, qreal tangentialPressure, qreal perspective, qreal time, qreal speed); KisPaintInformation(const QPointF & pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation); KisPaintInformation(const QPointF & pos = QPointF(), qreal pressure = PRESSURE_DEFAULT); KisPaintInformation(const KisPaintInformation& rhs); void operator=(const KisPaintInformation& rhs); ~KisPaintInformation(); template void paintAt(PaintOp &op, KisDistanceInformation *distanceInfo) { KisSpacingInformation spacingInfo; KisTimingInformation timingInfo; { DistanceInformationRegistrar r = registerDistanceInformation(distanceInfo); spacingInfo = op.paintAt(*this); timingInfo = op.updateTimingImpl(*this); // Initiate the process of locking the drawing angle. The locked value will // always be present in the internals, but it will be requested but the users // with a special parameter of drawingAngle() only. if (!this->isHoveringMode()) { distanceInfo->lockCurrentDrawingAngle(*this); } } distanceInfo->registerPaintedDab(*this, spacingInfo, timingInfo); } const QPointF& pos() const; void setPos(const QPointF& p); /// The pressure of the value (from 0.0 to 1.0) qreal pressure() const; /// Set the pressure void setPressure(qreal p); /// The tilt of the pen on the horizontal axis (from 0.0 to 1.0) qreal xTilt() const; /// The tilt of the pen on the vertical axis (from 0.0 to 1.0) qreal yTilt() const; /// XXX !!! :-| Please add dox! void overrideDrawingAngle(qreal angle); /// XXX !!! :-| Please add dox! qreal drawingAngleSafe(const KisDistanceInformation &distance) const; /** * Causes the specified distance information to be temporarily registered with this * KisPaintInformation object, so that the KisPaintInformation can compute certain values that * may be needed at painting time, such as the drawing direction. When the returned object is * destroyed, the KisDistanceInformation will be unregistered. At most one * KisDistanceInformation can be registered with a given KisPaintInformation at a time. */ DistanceInformationRegistrar registerDistanceInformation(KisDistanceInformation *distance); /** * Current brush direction computed from the cursor movement * * WARNING: this method is available *only* inside paintAt() call, * that is when the distance information is registered. */ qreal drawingAngle(bool considerLockedAngle = false) const; /** * Current brush direction vector computed from the cursor movement * * WARNING: this method is available *only* inside paintAt() call, * that is when the distance information is registered. */ QPointF drawingDirectionVector() const; /** * Current brush speed computed from the cursor movement * * WARNING: this method is available *only* inside paintAt() call, * that is when the distance information is registered. */ qreal drawingSpeed() const; /** * Current distance from the previous dab * * WARNING: this method is available *only* inside paintAt() call, * that is when the distance information is registered. */ qreal drawingDistance() const; /// rotation as given by the tablet event qreal rotation() const; /// tangential pressure (i.e., rate for an airbrush device) qreal tangentialPressure() const; /// reciprocal of distance on the perspective grid qreal perspective() const; /// Number of ms since the beginning of the stroke qreal currentTime() const; /// Number of dabs painted since the beginning of the stroke int currentDabSeqNo() const; /// The length of the stroke **before** painting the current dab qreal totalStrokeLength() const; // random source for generating in-stroke effects KisRandomSourceSP randomSource() const; // the stroke should initialize random source of all the used // paint info objects, otherwise it shows a warning void setRandomSource(KisRandomSourceSP value); // random source for generating in-stroke effects, generates one(!) value per stroke KisPerStrokeRandomSourceSP perStrokeRandomSource() const; // the stroke should initialize per stroke random source of all the used // paint info objects, otherwise it shows a warning void setPerStrokeRandomSource(KisPerStrokeRandomSourceSP value); // set level of detail which info object has been generated for void setLevelOfDetail(int levelOfDetail); /** * The paint information may be generated not only during real * stroke when the actual painting is happening, but also when the * cursor is hovering the canvas. In this mode some of the sensors * work a bit differently. The most outstanding example is Fuzzy * sensor, which returns unit value in this mode, otherwise it is * too irritating for a user. * * This value is true only for paint information objects created with * createHoveringModeInfo() constructor. * * \see createHoveringModeInfo() */ bool isHoveringMode() const; /** * Create a fake info object with isHoveringMode() property set to * true. * * \see isHoveringMode() */ static KisPaintInformation createHoveringModeInfo(const QPointF &pos, qreal pressure = PRESSURE_DEFAULT, qreal xTilt = 0.0, qreal yTilt = 0.0, qreal rotation = 0.0, qreal tangentialPressure = 0.0, qreal perspective = 1.0, qreal speed = 0.0, - int canvasrotation = 0, - bool canvasMirroredH = false); + qreal canvasrotation = 0, + bool canvasMirroredH = false, + bool canvasMirroredV = false); + /** - *Returns the canvas rotation if that has been given to the kispaintinformation. + * Returns the canvas rotation if that has been given to the kispaintinformation. */ - int canvasRotation() const; + qreal canvasRotation() const; + /** - *set the canvas rotation. + * Set the canvas rotation. */ - void setCanvasRotation(int rotation); + void setCanvasRotation(qreal rotation); - /* - *Whether the canvas is mirrored for the paint-operation. + /** + * Whether the canvas is horizontally mirrored for the paint-operation. */ bool canvasMirroredH() const; - /* - *Set whether the canvas is mirrored for the paint-operation. + /** + * Set whether the canvas is horizontally mirrored for the paint-operation. + */ + void setCanvasMirroredH(bool value); + + /** + * Whether the canvas is vertically mirrored for the paint-operation. + */ + bool canvasMirroredV() const; + + /** + * Set whether the canvas is vertically mirrored for the paint-operation. */ - void setCanvasHorizontalMirrorState(bool mir); + void setCanvasMirroredV(bool value); void toXML(QDomDocument&, QDomElement&) const; static KisPaintInformation fromXML(const QDomElement&); // TODO: Refactor the static mix functions to non-static in-place mutation // versions like mixOtherOnlyPosition and mixOtherWithoutTime. // Heap allocation on Windows is awfully slow and will fragment the memory // badly. Since KisPaintInformation allocates on the heap, we should re-use // existing instance whenever possible, especially in loops. // Ref: https://phabricator.kde.org/D6578 /// (1-t) * p1 + t * p2 static KisPaintInformation mixOnlyPosition(qreal t, const KisPaintInformation& mixedPi, const KisPaintInformation& basePi); static KisPaintInformation mix(const QPointF& p, qreal t, const KisPaintInformation& p1, const KisPaintInformation& p2); static KisPaintInformation mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2); static KisPaintInformation mixWithoutTime(const QPointF &p, qreal t, const KisPaintInformation &p1, const KisPaintInformation &p2); static KisPaintInformation mixWithoutTime(qreal t, const KisPaintInformation &pi1, const KisPaintInformation &pi2); void mixOtherOnlyPosition(qreal t, const KisPaintInformation& other); void mixOtherWithoutTime(qreal t, const KisPaintInformation& other); static qreal tiltDirection(const KisPaintInformation& info, bool normalize = true); static qreal tiltElevation(const KisPaintInformation& info, qreal maxTiltX = 60.0, qreal maxTiltY = 60.0, bool normalize = true); private: static KisPaintInformation mixImpl(const QPointF &p, qreal t, const KisPaintInformation &p1, const KisPaintInformation &p2, bool posOnly, bool mixTime); void mixOtherImpl(const QPointF &p, qreal t, const KisPaintInformation &other, bool posOnly, bool mixTime); private: struct Private; Private* const d; }; KRITAIMAGE_EXPORT QDebug operator<<(QDebug debug, const KisPaintInformation& info); #endif diff --git a/libs/image/brushengine/kis_paintop_settings.cpp b/libs/image/brushengine/kis_paintop_settings.cpp index 2c7aba0e1a..ce3b06a39d 100644 --- a/libs/image/brushengine/kis_paintop_settings.cpp +++ b/libs/image/brushengine/kis_paintop_settings.cpp @@ -1,529 +1,510 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2008 Lukáš Tvrdý * Copyright (c) 2014 Mohit Goyal * 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 #include #include #include #include #include #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_paint_device.h" #include "kis_paintop_registry.h" #include "kis_timing_information.h" #include #include "kis_paintop_config_widget.h" #include #include "kis_paintop_settings_update_proxy.h" #include #include #include #include #include #include "KisPaintopSettingsIds.h" struct Q_DECL_HIDDEN KisPaintOpSettings::Private { Private() : disableDirtyNotifications(false) {} QPointer settingsWidget; QString modelName; KisPaintOpPresetWSP preset; QList uniformProperties; bool disableDirtyNotifications; class DirtyNotificationsLocker { public: DirtyNotificationsLocker(KisPaintOpSettings::Private *d) : m_d(d), m_oldNotificationsState(d->disableDirtyNotifications) { m_d->disableDirtyNotifications = true; } ~DirtyNotificationsLocker() { m_d->disableDirtyNotifications = m_oldNotificationsState; } private: KisPaintOpSettings::Private *m_d; bool m_oldNotificationsState; Q_DISABLE_COPY(DirtyNotificationsLocker) }; KisPaintopSettingsUpdateProxy* updateProxyNoCreate() const { auto presetSP = preset.toStrongRef(); return presetSP ? presetSP->updateProxyNoCreate() : 0; } KisPaintopSettingsUpdateProxy* updateProxyCreate() const { auto presetSP = preset.toStrongRef(); return presetSP ? presetSP->updateProxy() : 0; } }; KisPaintOpSettings::KisPaintOpSettings() : d(new Private) { d->preset = 0; } KisPaintOpSettings::~KisPaintOpSettings() { } KisPaintOpSettings::KisPaintOpSettings(const KisPaintOpSettings &rhs) : KisPropertiesConfiguration(rhs) , d(new Private) { d->settingsWidget = 0; d->preset = rhs.preset(); d->modelName = rhs.modelName(); } void KisPaintOpSettings::setOptionsWidget(KisPaintOpConfigWidget* widget) { d->settingsWidget = widget; } void KisPaintOpSettings::setPreset(KisPaintOpPresetWSP preset) { d->preset = preset; } KisPaintOpPresetWSP KisPaintOpSettings::preset() const { return d->preset; } bool KisPaintOpSettings::mousePressEvent(const KisPaintInformation &paintInformation, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode) { Q_UNUSED(modifiers); Q_UNUSED(currentNode); setRandomOffset(paintInformation); return true; // ignore the event by default } bool KisPaintOpSettings::mouseReleaseEvent() { return true; // ignore the event by default } void KisPaintOpSettings::setRandomOffset(const KisPaintInformation &paintInformation) { if (getBool("Texture/Pattern/Enabled")) { if (getBool("Texture/Pattern/isRandomOffsetX")) { setProperty("Texture/Pattern/OffsetX", paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetX"))); } if (getBool("Texture/Pattern/isRandomOffsetY")) { setProperty("Texture/Pattern/OffsetY", paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetY"))); } } } bool KisPaintOpSettings::hasMaskingSettings() const { return getBool(KisPaintOpUtils::MaskingBrushEnabledTag, false); } KisPaintOpSettingsSP KisPaintOpSettings::createMaskingSettings() const { if (!hasMaskingSettings()) return KisPaintOpSettingsSP(); const KoID pixelBrushId(KisPaintOpUtils::MaskingBrushPaintOpId, QString()); KisPaintOpSettingsSP maskingSettings = KisPaintOpRegistry::instance()->settings(pixelBrushId); this->getPrefixedProperties(KisPaintOpUtils::MaskingBrushPresetPrefix, maskingSettings); const bool useMasterSize = this->getBool(KisPaintOpUtils::MaskingBrushUseMasterSizeTag, true); if (useMasterSize) { const qreal masterSizeCoeff = getDouble(KisPaintOpUtils::MaskingBrushMasterSizeCoeffTag, 1.0); maskingSettings->setPaintOpSize(masterSizeCoeff * paintOpSize()); } return maskingSettings; } QString KisPaintOpSettings::maskingBrushCompositeOp() const { return getString(KisPaintOpUtils::MaskingBrushCompositeOpTag, COMPOSITE_MULT); } KisPaintOpSettingsSP KisPaintOpSettings::clone() const { QString paintopID = getString("paintop"); if (paintopID.isEmpty()) return 0; KisPaintOpSettingsSP settings = KisPaintOpRegistry::instance()->settings(KoID(paintopID)); QMapIterator i(getProperties()); while (i.hasNext()) { i.next(); settings->setProperty(i.key(), QVariant(i.value())); } settings->setPreset(this->preset()); return settings; } void KisPaintOpSettings::resetSettings(const QStringList &preserveProperties) { QStringList allKeys = preserveProperties; allKeys << "paintop"; QHash preserved; Q_FOREACH (const QString &key, allKeys) { if (hasProperty(key)) { preserved[key] = getProperty(key); } } clearProperties(); for (auto it = preserved.constBegin(); it != preserved.constEnd(); ++it) { setProperty(it.key(), it.value()); } } void KisPaintOpSettings::activate() { } void KisPaintOpSettings::setPaintOpOpacity(qreal value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("OpacityValue", value); } void KisPaintOpSettings::setPaintOpFlow(qreal value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("FlowValue", value); } void KisPaintOpSettings::setPaintOpCompositeOp(const QString &value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("CompositeOp", value); } qreal KisPaintOpSettings::paintOpOpacity() { KisLockedPropertiesProxySP proxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this); return proxy->getDouble("OpacityValue", 1.0); } qreal KisPaintOpSettings::paintOpFlow() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getDouble("FlowValue", 1.0); } QString KisPaintOpSettings::paintOpCompositeOp() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getString("CompositeOp", COMPOSITE_OVER); } void KisPaintOpSettings::setEraserMode(bool value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("EraserMode", value); } bool KisPaintOpSettings::eraserMode() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getBool("EraserMode", false); } QString KisPaintOpSettings::effectivePaintOpCompositeOp() { return !eraserMode() ? paintOpCompositeOp() : COMPOSITE_ERASE; } qreal KisPaintOpSettings::savedEraserSize() const { return getDouble("SavedEraserSize", 0.0); } void KisPaintOpSettings::setSavedEraserSize(qreal value) { setProperty("SavedEraserSize", value); setPropertyNotSaved("SavedEraserSize"); } qreal KisPaintOpSettings::savedBrushSize() const { return getDouble("SavedBrushSize", 0.0); } void KisPaintOpSettings::setSavedBrushSize(qreal value) { setProperty("SavedBrushSize", value); setPropertyNotSaved("SavedBrushSize"); } qreal KisPaintOpSettings::savedEraserOpacity() const { return getDouble("SavedEraserOpacity", 0.0); } void KisPaintOpSettings::setSavedEraserOpacity(qreal value) { setProperty("SavedEraserOpacity", value); setPropertyNotSaved("SavedEraserOpacity"); } qreal KisPaintOpSettings::savedBrushOpacity() const { return getDouble("SavedBrushOpacity", 0.0); } void KisPaintOpSettings::setSavedBrushOpacity(qreal value) { setProperty("SavedBrushOpacity", value); setPropertyNotSaved("SavedBrushOpacity"); } QString KisPaintOpSettings::modelName() const { return d->modelName; } void KisPaintOpSettings::setModelName(const QString & modelName) { d->modelName = modelName; } KisPaintOpConfigWidget* KisPaintOpSettings::optionsWidget() const { if (d->settingsWidget.isNull()) return 0; return d->settingsWidget.data(); } bool KisPaintOpSettings::isValid() const { return true; } bool KisPaintOpSettings::isLoadable() { return isValid(); } QString KisPaintOpSettings::indirectPaintingCompositeOp() const { return COMPOSITE_ALPHA_DARKEN; } bool KisPaintOpSettings::isAirbrushing() const { return getBool(AIRBRUSH_ENABLED, false); } qreal KisPaintOpSettings::airbrushInterval() const { qreal rate = getDouble(AIRBRUSH_RATE, 1.0); if (rate == 0.0) { return LONG_TIME; } else { return 1000.0 / rate; } } bool KisPaintOpSettings::useSpacingUpdates() const { return getBool(SPACING_USE_UPDATES, false); } bool KisPaintOpSettings::needsAsynchronousUpdates() const { return false; } QPainterPath KisPaintOpSettings::brushOutline(const KisPaintInformation &info, const OutlineMode &mode) { QPainterPath path; if (mode.isVisible) { path = ellipseOutline(10, 10, 1.0, 0); if (mode.showTiltDecoration) { path.addPath(makeTiltIndicator(info, QPointF(0.0, 0.0), 0.0, 2.0)); } path.translate(info.pos()); } return path; } QPainterPath KisPaintOpSettings::ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation) { QPainterPath path; QRectF ellipse(0, 0, width * scale, height * scale); ellipse.translate(-ellipse.center()); path.addEllipse(ellipse); QTransform m; m.reset(); m.rotate(rotation); path = m.map(path); return path; } QPainterPath KisPaintOpSettings::makeTiltIndicator(KisPaintInformation const& info, QPointF const& start, qreal maxLength, qreal angle) { if (maxLength == 0.0) maxLength = 50.0; maxLength = qMax(maxLength, 50.0); qreal const length = maxLength * (1 - info.tiltElevation(info, 60.0, 60.0, true)); qreal const baseAngle = 360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0); QLineF guideLine = QLineF::fromPolar(length, baseAngle + angle); guideLine.translate(start); QPainterPath ret; ret.moveTo(guideLine.p1()); ret.lineTo(guideLine.p2()); guideLine.setAngle(baseAngle - angle); ret.lineTo(guideLine.p2()); ret.lineTo(guideLine.p1()); return ret; } -void KisPaintOpSettings::setCanvasRotation(qreal angle) -{ - Private::DirtyNotificationsLocker locker(d.data()); - - setProperty("runtimeCanvasRotation", angle); - setPropertyNotSaved("runtimeCanvasRotation"); -} - -void KisPaintOpSettings::setCanvasMirroring(bool xAxisMirrored, bool yAxisMirrored) -{ - Private::DirtyNotificationsLocker locker(d.data()); - - setProperty("runtimeCanvasMirroredX", xAxisMirrored); - setPropertyNotSaved("runtimeCanvasMirroredX"); - - setProperty("runtimeCanvasMirroredY", yAxisMirrored); - setPropertyNotSaved("runtimeCanvasMirroredY"); -} - void KisPaintOpSettings::setProperty(const QString & name, const QVariant & value) { if (value != KisPropertiesConfiguration::getProperty(name) && !d->disableDirtyNotifications) { KisPaintOpPresetSP presetSP = preset().toStrongRef(); if (presetSP) { presetSP->setDirty(true); } } KisPropertiesConfiguration::setProperty(name, value); onPropertyChanged(); } void KisPaintOpSettings::onPropertyChanged() { KisPaintopSettingsUpdateProxy *proxy = d->updateProxyNoCreate(); if (proxy) { proxy->notifySettingsChanged(); } } bool KisPaintOpSettings::isLodUserAllowed(const KisPropertiesConfigurationSP config) { return config->getBool("lodUserAllowed", true); } void KisPaintOpSettings::setLodUserAllowed(KisPropertiesConfigurationSP config, bool value) { config->setProperty("lodUserAllowed", value); } bool KisPaintOpSettings::lodSizeThresholdSupported() const { return true; } qreal KisPaintOpSettings::lodSizeThreshold() const { return getDouble("lodSizeThreshold", 100.0); } void KisPaintOpSettings::setLodSizeThreshold(qreal value) { setProperty("lodSizeThreshold", value); } #include "kis_standard_uniform_properties_factory.h" QList KisPaintOpSettings::uniformProperties(KisPaintOpSettingsSP settings) { QList props = listWeakToStrong(d->uniformProperties); if (props.isEmpty()) { using namespace KisStandardUniformPropertiesFactory; props.append(createProperty(opacity, settings, d->updateProxyCreate())); props.append(createProperty(size, settings, d->updateProxyCreate())); props.append(createProperty(flow, settings, d->updateProxyCreate())); d->uniformProperties = listStrongToWeak(props); } return props; } diff --git a/libs/image/brushengine/kis_paintop_settings.h b/libs/image/brushengine/kis_paintop_settings.h index e884d7df74..7773fb8d2f 100644 --- a/libs/image/brushengine/kis_paintop_settings.h +++ b/libs/image/brushengine/kis_paintop_settings.h @@ -1,358 +1,349 @@ /* * Copyright (c) 2007 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. */ #ifndef KIS_PAINTOP_SETTINGS_H_ #define KIS_PAINTOP_SETTINGS_H_ #include "kis_types.h" #include "kritaimage_export.h" #include #include #include "kis_properties_configuration.h" #include #include class KisPaintOpConfigWidget; class KisPaintopSettingsUpdateProxy; /** * Configuration property used to control whether airbrushing is enabled. */ const QString AIRBRUSH_ENABLED = "PaintOpSettings/isAirbrushing"; /** * Configuration property used to control airbrushing rate. The value should be in dabs per second. */ const QString AIRBRUSH_RATE = "PaintOpSettings/rate"; /** * Configuration property used to control whether airbrushing is configured to ignore distance-based * spacing. */ const QString AIRBRUSH_IGNORE_SPACING = "PaintOpSettings/ignoreSpacing"; /** * Configuration property used to control whether the spacing settings can be updated between * painted dabs. */ const QString SPACING_USE_UPDATES = "PaintOpSettings/updateSpacingBetweenDabs"; /** * This class is used to cache the settings for a paintop * between two creations. There is one KisPaintOpSettings per input device (mouse, tablet, * etc...). * * The settings may be stored in a preset. Note that if your * paintop's settings subclass has data that is not stored as a property, that data is not * saved and restored. * * The object also contains a pointer to its parent KisPaintOpPreset object.This is to control the DirtyPreset * property of KisPaintOpPreset. Whenever the settings are changed/modified from the original -- the preset is * set to dirty. */ class KRITAIMAGE_EXPORT KisPaintOpSettings : public KisPropertiesConfiguration { public: KisPaintOpSettings(); ~KisPaintOpSettings() override; KisPaintOpSettings(const KisPaintOpSettings &rhs); /** * */ void setOptionsWidget(KisPaintOpConfigWidget* widget); /** * This function is called by a tool when the mouse is pressed. It's useful if * the paintop needs mouse interaction for instance in the case of the clone op. * If the tool is supposed to ignore the event, the paint op should return true * and if the tool is supposed to use the event, return false. * See kis_tool_freehand:tryPickByPaintOp() */ virtual bool mousePressEvent(const KisPaintInformation &paintInformation, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode); /** * This function is called by a tool when the mouse is released. It's useful if * the paintop needs mouse interaction for instance in the case of the clone op. * If the tool is supposed to ignore the event, the paint op should return true * and if the tool is supposed to use the event, return false. */ virtual bool mouseReleaseEvent(); /** * Clone the current settings object. Override this if your settings instance doesn't * store everything as properties. */ virtual KisPaintOpSettingsSP clone() const; /** * Removes all the settings from the object while keeping the paintop id, * which is loaded to the object by the factory */ void resetSettings(const QStringList &preserveProperties = QStringList()); /** * @return the node the paintop is working on. */ KisNodeSP node() const; /** * Call this function when the paint op is selected or the tool is activated */ virtual void activate(); /** * XXX: Remove this after 2.0, when the paint operation (incremental/non incremental) will * be completely handled in the paintop, not in the tool. This is a filthy hack to move * the option to the right place, at least. * @return true if we paint incrementally, false if we paint like Photoshop. By default, paintops * do not support non-incremental. */ virtual bool paintIncremental() { return true; } /** * @return the composite op it to which the indirect painting device * should be initialized to. This is used by clone op to reset * the composite op to COMPOSITE_COPY */ virtual QString indirectPaintingCompositeOp() const; /** * Whether this paintop wants to deposit paint even when not moving, i.e. the tool needs to * activate its timer. If this is true, painting updates need to be generated at regular * intervals even in the absence of input device events, e.g. when the cursor is not moving. * * The default implementation checks the property AIRBRUSH_ENABLED, defaulting to false if the * property is not found. This should be suitable for most paintops. */ virtual bool isAirbrushing() const; /** * Indicates the minimum time interval that might be needed between airbrush dabs, in * milliseconds. A lower value means painting updates need to happen more frequently. This value * should be ignored if isAirbrushing() is false. * * The default implementation uses the property AIRBRUSH_RATE, defaulting to an interval of * one second if the property is not found. This should be suitable for most paintops. */ virtual qreal airbrushInterval() const; /** * Indicates whether this configuration allows spacing information to be updated between painted * dabs during a stroke. */ virtual bool useSpacingUpdates() const; /** * Indicates if the tool should call paintOp->doAsynchronousUpdate() inbetween * paintAt() calls to do the asynchronous rendering */ virtual bool needsAsynchronousUpdates() const; /** * This structure defines the current mode for painting an outline. */ struct OutlineMode { bool isVisible = false; bool forceCircle = false; bool showTiltDecoration = false; bool forceFullSize = false; }; /** * Returns the brush outline in pixel coordinates. Tool is responsible for conversion into view coordinates. * Outline mode has to be passed to the paintop which builds the outline as some paintops have to paint outline * always like clone paintop indicating the duplicate position */ virtual QPainterPath brushOutline(const KisPaintInformation &info, const OutlineMode &mode); /** * Helpers for drawing the brush outline */ static QPainterPath ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation); /** * Helper for drawing a triangle representing the tilt of the stylus. * * @param start is the offset from the brush's outline's bounding box * @param lengthScale is used for deciding the size of the triangle. * Brush diameter or width are common choices for this. * @param angle is the angle between the two sides of the triangle. */ static QPainterPath makeTiltIndicator(KisPaintInformation const& info, QPointF const& start, qreal lengthScale, qreal angle); /** * Set paintop opacity directly in the properties */ void setPaintOpOpacity(qreal value); /** * Set paintop flow directly in the properties */ void setPaintOpFlow(qreal value); /** * Set paintop composite mode directly in the properties */ void setPaintOpCompositeOp(const QString &value); /** * @return opacity saved in the properties */ qreal paintOpOpacity(); /** * @return flow saved in the properties */ qreal paintOpFlow(); /** * @return composite mode saved in the properties */ QString paintOpCompositeOp(); /** * Set paintop size directly in the properties */ virtual void setPaintOpSize(qreal value) = 0; /** * @return size saved in the properties */ virtual qreal paintOpSize() const = 0; void setEraserMode(bool value); bool eraserMode(); qreal savedEraserSize() const; void setSavedEraserSize(qreal value); qreal savedBrushSize() const; void setSavedBrushSize(qreal value); qreal savedEraserOpacity() const; void setSavedEraserOpacity(qreal value); qreal savedBrushOpacity() const; void setSavedBrushOpacity(qreal value); QString effectivePaintOpCompositeOp(); void setPreset(KisPaintOpPresetWSP preset); KisPaintOpPresetWSP preset() const; /** * @return filename of the 3D brush model, empty if no brush is set */ virtual QString modelName() const; /** * Set filename of 3D brush model. By default no brush is set */ void setModelName(const QString & modelName); /// Check if the settings are valid, setting might be invalid through missing brushes etc /// Overwrite if the settings of a paintop can be invalid /// @return state of the settings, default implementation is true virtual bool isValid() const; /// Check if the settings are loadable, that might the case if we can fallback to something /// Overwrite if the settings can do some kind of fallback /// @return loadable state of the settings, by default implementation return the same as isValid() virtual bool isLoadable(); - /** - * These methods are populating properties with runtime - * information about canvas rotation/mirroring. This information - * is set directly by KisToolFreehand. Later the data is accessed - * by the pressure options to make a final decision. - */ - void setCanvasRotation(qreal angle); - void setCanvasMirroring(bool xAxisMirrored, bool yAxisMirrored); - /** * Overrides the method in KisPropertiesCofiguration to allow * onPropertyChanged() callback */ void setProperty(const QString & name, const QVariant & value) override; virtual QList uniformProperties(KisPaintOpSettingsSP settings); static bool isLodUserAllowed(const KisPropertiesConfigurationSP config); static void setLodUserAllowed(KisPropertiesConfigurationSP config, bool value); virtual bool lodSizeThresholdSupported() const; qreal lodSizeThreshold() const; void setLodSizeThreshold(qreal value); /** * @return the option widget of the paintop (can be 0 is no option widgets is set) */ KisPaintOpConfigWidget* optionsWidget() const; /** * This function is called to set random offsets to the brush whenever the mouse is clicked. It is * specific to when the pattern option is set. * */ virtual void setRandomOffset(const KisPaintInformation &paintInformation); /** * @return true if this preset demands a secondary masked brush running * alongside it */ bool hasMaskingSettings() const; /** * @return a newly created settings object representing a preset of the masking * brush that should be run alongside the current brush */ KisPaintOpSettingsSP createMaskingSettings() const; /** * @return a composite op id of the masked brush rendering algorithm. * * Please take into account that the brush itself always paints in alpha- * darken mode, but the final result is combined with this composite op. */ QString maskingBrushCompositeOp() const; protected: /** * The callback is called every time when a property changes */ virtual void onPropertyChanged(); private: struct Private; const QScopedPointer d; }; #endif diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 4625189838..a8f60ce179 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,616 +1,615 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ) add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) find_library(APPKIT_LIBRARY AppKit) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp - canvas/kis_paintop_transformation_connector.cpp canvas/kis_display_color_converter.cpp canvas/kis_display_filter.cpp canvas/kis_exposure_gamma_correction_interface.cpp canvas/kis_tool_proxy.cpp canvas/kis_canvas_decoration.cc canvas/kis_coordinates_converter.cpp canvas/kis_grid_manager.cpp canvas/kis_grid_decoration.cpp canvas/kis_grid_config.cpp canvas/kis_prescaled_projection.cpp canvas/kis_qpainter_canvas.cpp canvas/kis_projection_backend.cpp canvas/kis_update_info.cpp canvas/kis_image_patch.cpp canvas/kis_image_pyramid.cpp canvas/kis_infinity_manager.cpp canvas/kis_change_guides_command.cpp canvas/kis_guides_decoration.cpp canvas/kis_guides_manager.cpp canvas/kis_guides_config.cpp canvas/kis_snap_config.cpp canvas/kis_snap_line_strategy.cpp canvas/KisSnapPointStrategy.cpp canvas/KisSnapPixelStrategy.cpp canvas/KisMirrorAxisConfig.cpp dialogs/kis_about_application.cpp dialogs/kis_dlg_adj_layer_props.cc dialogs/kis_dlg_adjustment_layer.cc dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_generator_layer.cpp dialogs/kis_dlg_file_layer.cpp dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_stroke_selection_properties.cpp dialogs/kis_dlg_image_properties.cc dialogs/kis_dlg_layer_properties.cc dialogs/kis_dlg_preferences.cc dialogs/slider_and_spin_box_sync.cpp dialogs/kis_dlg_blacklist_cleanup.cpp dialogs/kis_dlg_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp dialogs/KisSessionManagerDialog.cpp dialogs/KisNewWindowLayoutDialog.cpp dialogs/KisDlgChangeCloneSource.cpp flake/kis_node_dummies_graph.cpp flake/kis_dummies_facade_base.cpp flake/kis_dummies_facade.cpp flake/kis_node_shapes_graph.cpp flake/kis_node_shape.cpp flake/kis_shape_controller.cpp flake/kis_shape_layer.cc flake/kis_shape_layer_canvas.cpp flake/kis_shape_selection.cpp flake/kis_shape_selection_canvas.cpp flake/kis_shape_selection_model.cpp flake/kis_take_all_shapes_command.cpp brushhud/kis_uniform_paintop_property_widget.cpp brushhud/kis_brush_hud.cpp brushhud/kis_round_hud_button.cpp brushhud/kis_dlg_brush_hud_config.cpp brushhud/kis_brush_hud_properties_list.cpp brushhud/kis_brush_hud_properties_config.cpp kis_aspect_ratio_locker.cpp kis_autogradient.cc kis_bookmarked_configurations_editor.cc kis_bookmarked_configurations_model.cc kis_bookmarked_filter_configurations_model.cc KisPaintopPropertiesBase.cpp kis_canvas_resource_provider.cpp kis_derived_resources.cpp kis_categories_mapper.cpp kis_categorized_list_model.cpp kis_categorized_item_delegate.cpp kis_clipboard.cc kis_config.cc KisOcioConfiguration.cpp kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_cursor.cc kis_cursor_cache.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_change_file_layer_command.h kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc KisImageBarrierLockerWithFeedback.cpp kis_image_manager.cc kis_image_view_converter.cpp kis_import_catcher.cc kis_layer_manager.cc kis_mask_manager.cc kis_mimedata.cpp kis_node_commands_adapter.cpp kis_node_manager.cpp kis_node_juggler_compressed.cpp kis_node_selection_adapter.cpp kis_node_insertion_adapter.cpp KisNodeDisplayModeAdapter.cpp kis_node_model.cpp kis_node_filter_proxy_model.cpp kis_model_index_converter_base.cpp kis_model_index_converter.cpp kis_model_index_converter_show_all.cpp kis_painting_assistant.cc kis_painting_assistants_decoration.cpp KisDecorationsManager.cpp kis_paintop_box.cc kis_paintop_option.cpp kis_paintop_options_model.cpp kis_paintop_settings_widget.cpp kis_popup_palette.cpp kis_png_converter.cpp kis_preference_set_registry.cpp KisResourceServerProvider.cpp KisResourceBundleServerProvider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc KisSelectionActionsAdapter.cpp kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp KisActionPlugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp KisWelcomePageWidget.cpp KisChangeCloneLayersCommand.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp opengl/kis_opengl_shader_loader.cpp opengl/kis_texture_tile_info_pool.cpp opengl/KisOpenGLUpdateInfoBuilder.cpp opengl/KisOpenGLModeProber.cpp opengl/KisScreenInformationAdapter.cpp kis_fps_decoration.cpp tool/KisToolChangesTracker.cpp tool/KisToolChangesTrackerData.cpp tool/kis_selection_tool_helper.cpp tool/kis_selection_tool_config_widget_helper.cpp tool/kis_rectangle_constraint_widget.cpp tool/kis_shape_tool_helper.cpp tool/kis_tool.cc tool/kis_delegated_tool_policies.cpp tool/kis_tool_freehand.cc tool/kis_speed_smoother.cpp tool/kis_painting_information_builder.cpp tool/kis_stabilized_events_sampler.cpp tool/kis_tool_freehand_helper.cpp tool/kis_tool_multihand_helper.cpp tool/kis_figure_painting_tool_helper.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/KisStabilizerDelayedPaintHelper.cpp tool/KisStrokeSpeedMonitor.cpp tool/strokes/freehand_stroke.cpp tool/strokes/KisStrokeEfficiencyMeasurer.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp tool/strokes/KisFreehandStrokeInfo.cpp tool/strokes/KisMaskedFreehandStrokePainter.cpp tool/strokes/KisMaskingBrushRenderer.cpp tool/strokes/KisMaskingBrushCompositeOpFactory.cpp tool/strokes/move_stroke_strategy.cpp tool/KisSelectionToolFactoryBase.cpp tool/KisToolPaintFactoryBase.cpp widgets/kis_cmb_composite.cc widgets/kis_cmb_contour.cpp widgets/kis_cmb_gradient.cpp widgets/kis_paintop_list_widget.cpp widgets/kis_cmb_idlist.cc widgets/kis_color_space_selector.cc widgets/kis_advanced_color_space_selector.cc widgets/kis_cie_tongue_widget.cpp widgets/kis_tone_curve_widget.cpp widgets/kis_curve_widget.cpp widgets/kis_custom_image_widget.cc widgets/kis_image_from_clipboard_widget.cpp widgets/kis_double_widget.cc widgets/kis_filter_selector_widget.cc widgets/kis_gradient_chooser.cc widgets/kis_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_paintop_presets_save.cpp widgets/kis_paintop_preset_icon_library.cpp widgets/kis_pattern_chooser.cc widgets/kis_preset_chooser.cpp widgets/kis_progress_widget.cpp widgets/kis_selection_options.cc widgets/kis_scratch_pad.cpp widgets/kis_scratch_pad_event_filter.cpp widgets/kis_preset_selector_strip.cpp widgets/kis_slider_spin_box.cpp widgets/KisSelectionPropertySlider.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/kis_categorized_list_view.cpp widgets/kis_widget_chooser.cpp widgets/kis_tool_button.cpp widgets/kis_floating_message.cpp widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp widgets/kis_elided_label.cpp widgets/kis_stopgradient_slider_widget.cpp widgets/kis_preset_live_preview_view.cpp widgets/KisScreenColorPicker.cpp widgets/KoDualColorButton.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp widgets/KisLayerStyleAngleSelector.cpp widgets/KisMemoryReportButton.cpp widgets/KisDitherWidget.cpp KisPaletteEditor.cpp dialogs/KisDlgPaletteEditor.cpp widgets/KisNewsWidget.cpp widgets/KisGamutMaskToolbar.cpp utils/kis_document_aware_spin_box_unit_manager.cpp utils/KisSpinBoxSplineUnitConverter.cpp utils/KisClipboardUtil.cpp utils/KisDitherUtil.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_native_gesture_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp input/KisQtWidgetsTweaker.cpp input/KisInputActionGroup.cpp input/kis_zoom_and_rotate_action.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactories.cpp actions/KisTransformToolActivationCommand.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.cpp kis_gui_context_command.cpp kis_gui_context_command_p.cpp input/kis_tablet_debugger.cpp input/kis_input_profile_manager.cpp input/kis_input_profile.cpp input/kis_shortcut_configuration.cpp input/config/kis_input_configuration_page.cpp input/config/kis_edit_profiles_dialog.cpp input/config/kis_input_profile_model.cpp input/config/kis_input_configuration_page_item.cpp input/config/kis_action_shortcuts_model.cpp input/config/kis_input_type_delegate.cpp input/config/kis_input_mode_delegate.cpp input/config/kis_input_button.cpp input/config/kis_input_editor_delegate.cpp input/config/kis_mouse_input_editor.cpp input/config/kis_wheel_input_editor.cpp input/config/kis_key_input_editor.cpp processing/fill_processing_visitor.cpp kis_asl_layer_style_serializer.cpp kis_psd_layer_style_resource.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisCloneDocumentStroke.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisImportExportManager.cpp KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoActionsUpdateManager.cpp KisView.cpp KisImportExportErrorCode.cpp KisImportExportAdditionalChecks.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisResourceBundle.cpp KisResourceBundleManifest.cpp kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisSaveGroupVisitor.cpp KisWindowLayoutResource.cpp KisWindowLayoutManager.cpp KisSessionResource.cpp KisReferenceImagesDecoration.cpp KisReferenceImage.cpp flake/KisReferenceImagesLayer.cpp flake/KisReferenceImagesLayer.h KisMouseClickEater.cpp ) if(WIN32) # Private headers are needed for: # * KisDlgCustomTabletResolution # * KisScreenInformationAdapter include_directories(SYSTEM ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_win.cpp ) if (NOT USE_QT_TABLET_WINDOWS) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp input/wintab/kis_tablet_support_win8.cpp ) else() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} dialogs/KisDlgCustomTabletResolution.cpp ) endif() endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAsyncAnimationRendererBase.cpp KisAsyncAnimationCacheRenderer.cpp KisAsyncAnimationFramesSavingRenderer.cpp dialogs/KisAsyncAnimationRenderDialogBase.cpp dialogs/KisAsyncAnimationCacheRenderDialog.cpp dialogs/KisAsyncAnimationFramesSaveDialog.cpp canvas/kis_animation_player.cpp kis_animation_importer.cpp KisSyncedAudioPlayback.cpp KisFrameDataSerializer.cpp KisFrameCacheStore.cpp KisFrameCacheSwapper.cpp KisAbstractFrameCacheSwapper.cpp KisInMemoryFrameCacheSwapper.cpp input/wintab/drawpile_tablettester/tablettester.cpp input/wintab/drawpile_tablettester/tablettest.cpp ) if (UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_unix.cpp ) endif() if(APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} osx.mm ) endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui widgets/KisDitherWidget.ui forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgbookmarkedconfigurationseditor.ui forms/wdgapplyprofile.ui forms/wdgcustompattern.ui forms/wdglayerproperties.ui forms/wdgcolorsettings.ui forms/wdgtabletsettings.ui forms/wdgcolorspaceselector.ui forms/wdgcolorspaceselectoradvanced.ui forms/wdgdisplaysettings.ui forms/kis_previewwidgetbase.ui forms/kis_matrix_widget.ui forms/wdgselectionoptions.ui forms/wdggeometryoptions.ui forms/wdgnewimage.ui forms/wdgimageproperties.ui forms/wdgmaskfromselection.ui forms/wdgmasksource.ui forms/wdgfilterdialog.ui forms/wdgmetadatamergestrategychooser.ui forms/wdgpaintoppresets.ui forms/wdgpaintopsettings.ui forms/wdgdlggeneratorlayer.ui forms/wdgdlgfilelayer.ui forms/wdgfilterselector.ui forms/wdgfilternodecreation.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgsavebrushpreset.ui forms/wdgpreseticonlibrary.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui forms/wdgsessionmanager.ui forms/wdgnewwindowlayout.ui forms/KisWelcomePage.ui forms/WdgDlgPaletteEditor.ui forms/KisNewsPage.ui forms/wdgGamutMaskToolbar.ui forms/wdgchangeclonesource.ui brushhud/kis_dlg_brush_hud_config.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui layerstyles/wdgKisLayerStyleAngleSelector.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui input/wintab/drawpile_tablettester/tablettest.ui ) if(WIN32) if(USE_QT_TABLET_WINDOWS) ki18n_wrap_ui(kritaui_LIB_SRCS dialogs/KisDlgCustomTabletResolution.ui ) else() ki18n_wrap_ui(kritaui_LIB_SRCS input/wintab/kis_screen_size_choice_dialog.ui ) endif() endif() add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} LibExiv2::LibExiv2 ) if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB} ${XCB_LIBRARIES}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) target_link_libraries(kritaui ${APPKIT_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras) endif() if(X11_FOUND) target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES}) endif() target_include_directories(kritaui PUBLIC $ $ $ $ $ $ $ ) set_target_properties(kritaui PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS}) if (APPLE) install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita) endif () diff --git a/libs/ui/canvas/kis_canvas_controller.cpp b/libs/ui/canvas/kis_canvas_controller.cpp index 2ab25dec3b..7af9689b70 100644 --- a/libs/ui/canvas/kis_canvas_controller.cpp +++ b/libs/ui/canvas/kis_canvas_controller.cpp @@ -1,398 +1,386 @@ /* * Copyright (c) 2010 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_canvas_controller.h" #include #include #include #include #include #include "kis_canvas_decoration.h" -#include "kis_paintop_transformation_connector.h" #include "kis_coordinates_converter.h" #include "kis_canvas2.h" #include "opengl/kis_opengl_canvas2.h" #include "KisDocument.h" #include "kis_image.h" #include "KisViewManager.h" #include "KisView.h" #include "krita_utils.h" #include "kis_config.h" #include "kis_signal_compressor_with_param.h" #include "kis_config_notifier.h" static const int gRulersUpdateDelay = 80 /* ms */; struct KisCanvasController::Private { Private(KisCanvasController *qq) - : q(qq), - paintOpTransformationConnector(0) + : q(qq) { using namespace std::placeholders; std::function callback( std::bind(&KisCanvasController::Private::emitPointerPositionChangedSignals, this, _1)); mousePositionCompressor.reset( new KisSignalCompressorWithParam( gRulersUpdateDelay, callback, KisSignalCompressor::FIRST_ACTIVE)); } QPointer view; KisCoordinatesConverter *coordinatesConverter; KisCanvasController *q; - KisPaintopTransformationConnector *paintOpTransformationConnector; QScopedPointer > mousePositionCompressor; void emitPointerPositionChangedSignals(QPoint pointerPos); void updateDocumentSizeAfterTransform(); void showRotationValueOnCanvas(); void showMirrorStateOnCanvas(); }; void KisCanvasController::Private::emitPointerPositionChangedSignals(QPoint pointerPos) { if (!coordinatesConverter) return; QPointF documentPos = coordinatesConverter->widgetToDocument(pointerPos); q->proxyObject->emitDocumentMousePositionChanged(documentPos); q->proxyObject->emitCanvasMousePositionChanged(pointerPos); } void KisCanvasController::Private::updateDocumentSizeAfterTransform() { // round the size of the area to the nearest integer instead of getting aligned rect QSize widgetSize = coordinatesConverter->imageRectInWidgetPixels().toRect().size(); q->updateDocumentSize(widgetSize, true); KisCanvas2 *kritaCanvas = dynamic_cast(q->canvas()); Q_ASSERT(kritaCanvas); kritaCanvas->notifyZoomChanged(); } KisCanvasController::KisCanvasController(QPointerparent, KActionCollection * actionCollection) : KoCanvasControllerWidget(actionCollection, parent), m_d(new Private(this)) { m_d->view = parent; } KisCanvasController::~KisCanvasController() { delete m_d; } void KisCanvasController::setCanvas(KoCanvasBase *canvas) { if (canvas) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas); KIS_SAFE_ASSERT_RECOVER_RETURN(kritaCanvas); m_d->coordinatesConverter = const_cast(kritaCanvas->coordinatesConverter()); - - m_d->paintOpTransformationConnector = - new KisPaintopTransformationConnector(kritaCanvas, this); } else { m_d->coordinatesConverter = 0; - - delete m_d->paintOpTransformationConnector; - m_d->paintOpTransformationConnector = 0; } KoCanvasControllerWidget::setCanvas(canvas); } void KisCanvasController::activate() { KoCanvasControllerWidget::activate(); } QPointF KisCanvasController::currentCursorPosition() const { KoCanvasBase *canvas = m_d->view->canvasBase(); QWidget *canvasWidget = canvas->canvasWidget(); const QPointF cursorPosWidget = canvasWidget->mapFromGlobal(QCursor::pos()); return m_d->coordinatesConverter->widgetToDocument(cursorPosWidget); } void KisCanvasController::keyPressEvent(QKeyEvent *event) { /** * Dirty Hack Alert: * Do not call the KoCanvasControllerWidget::keyPressEvent() * to avoid activation of Pan and Default tool activation shortcuts */ Q_UNUSED(event); } void KisCanvasController::wheelEvent(QWheelEvent *event) { /** * Dirty Hack Alert: * Do not call the KoCanvasControllerWidget::wheelEvent() * to disable the default behavior of KoCanvasControllerWidget and QAbstractScrollArea */ Q_UNUSED(event); } bool KisCanvasController::eventFilter(QObject *watched, QEvent *event) { KoCanvasBase *canvas = this->canvas(); if (!canvas || !canvas->canvasWidget() || canvas->canvasWidget() != watched) return false; if (event->type() == QEvent::MouseMove) { QMouseEvent *mevent = static_cast(event); m_d->mousePositionCompressor->start(mevent->pos()); } else if (event->type() == QEvent::TabletMove) { QTabletEvent *tevent = static_cast(event); m_d->mousePositionCompressor->start(tevent->pos()); } else if (event->type() == QEvent::FocusIn) { m_d->view->syncLastActiveNodeToDocument(); } return false; } void KisCanvasController::updateDocumentSize(const QSizeF &sz, bool recalculateCenter) { KoCanvasControllerWidget::updateDocumentSize(sz, recalculateCenter); emit documentSizeChanged(); } void KisCanvasController::Private::showMirrorStateOnCanvas() { bool isXMirrored = coordinatesConverter->xAxisMirrored(); view->viewManager()-> showFloatingMessage( i18nc("floating message about mirroring", "Horizontal mirroring: %1 ", isXMirrored ? i18n("ON") : i18n("OFF")), QIcon(), 500, KisFloatingMessage::Low); } void KisCanvasController::mirrorCanvas(bool enable) { QPoint newOffset = m_d->coordinatesConverter->mirror(m_d->coordinatesConverter->widgetCenterPoint(), enable, false); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); - m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showMirrorStateOnCanvas(); } void KisCanvasController::Private::showRotationValueOnCanvas() { qreal rotationAngle = coordinatesConverter->rotationAngle(); view->viewManager()-> showFloatingMessage( i18nc("floating message about rotation", "Rotation: %1° ", KritaUtils::prettyFormatReal(rotationAngle)), QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter); } void KisCanvasController::rotateCanvas(qreal angle, const QPointF ¢er) { QPoint newOffset = m_d->coordinatesConverter->rotate(center, angle); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); - m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showRotationValueOnCanvas(); } void KisCanvasController::rotateCanvas(qreal angle) { rotateCanvas(angle, m_d->coordinatesConverter->widgetCenterPoint()); } void KisCanvasController::rotateCanvasRight15() { rotateCanvas(15.0); } void KisCanvasController::rotateCanvasLeft15() { rotateCanvas(-15.0); } qreal KisCanvasController::rotation() const { return m_d->coordinatesConverter->rotationAngle(); } void KisCanvasController::resetCanvasRotation() { QPoint newOffset = m_d->coordinatesConverter->resetRotation(m_d->coordinatesConverter->widgetCenterPoint()); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); - m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showRotationValueOnCanvas(); } void KisCanvasController::slotToggleWrapAroundMode(bool value) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); if (!canvas()->canvasIsOpenGL() && value) { m_d->view->viewManager()->showFloatingMessage(i18n("You are activating wrap-around mode, but have not enabled OpenGL.\n" "To visualize wrap-around mode, enable OpenGL."), QIcon()); } kritaCanvas->setWrapAroundViewingMode(value); kritaCanvas->image()->setWrapAroundModePermitted(value); } bool KisCanvasController::wrapAroundMode() const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->wrapAroundViewingMode(); } void KisCanvasController::slotTogglePixelGrid(bool value) { KisConfig cfg(false); cfg.enablePixelGrid(value); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); } void KisCanvasController::slotToggleLevelOfDetailMode(bool value) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); kritaCanvas->setLodAllowedInCanvas(value); bool result = levelOfDetailMode(); if (!value || result) { m_d->view->viewManager()->showFloatingMessage( i18n("Instant Preview Mode: %1", result ? i18n("ON") : i18n("OFF")), QIcon(), 500, KisFloatingMessage::Low); } else { QString reason; if (!kritaCanvas->canvasIsOpenGL()) { reason = i18n("Instant Preview is only supported with OpenGL activated"); } else if (kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode || kritaCanvas->openGLFilterMode() == KisOpenGL::NearestFilterMode) { QString filteringMode = kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ? i18n("Bilinear") : i18n("Nearest Neighbour"); reason = i18n("Instant Preview is supported\n in Trilinear or High Quality filtering modes.\nCurrent mode is %1", filteringMode); } m_d->view->viewManager()->showFloatingMessage( i18n("Failed activating Instant Preview mode!\n\n%1", reason), QIcon(), 5000, KisFloatingMessage::Low); } } bool KisCanvasController::levelOfDetailMode() const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->lodAllowedInCanvas(); } void KisCanvasController::saveCanvasState(KisPropertiesConfiguration &config) const { const QPointF ¢er = preferredCenter(); config.setProperty("panX", center.x()); config.setProperty("panY", center.y()); config.setProperty("rotation", rotation()); config.setProperty("mirror", m_d->coordinatesConverter->xAxisMirrored()); config.setProperty("wrapAround", wrapAroundMode()); config.setProperty("enableInstantPreview", levelOfDetailMode()); } void KisCanvasController::restoreCanvasState(const KisPropertiesConfiguration &config) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); mirrorCanvas(config.getBool("mirror", false)); rotateCanvas(config.getFloat("rotation", 0.0f)); const QPointF ¢er = preferredCenter(); float panX = config.getFloat("panX", center.x()); float panY = config.getFloat("panY", center.y()); setPreferredCenter(QPointF(panX, panY)); slotToggleWrapAroundMode(config.getBool("wrapAround", false)); kritaCanvas->setLodAllowedInCanvas(config.getBool("enableInstantPreview", false)); } void KisCanvasController::resetScrollBars() { // The scrollbar value always points at the top-left corner of the // bit of image we paint. KisDocument *doc = m_d->view->document(); if (!doc) return; QRectF documentBounds = doc->documentBounds(); QRectF viewRect = m_d->coordinatesConverter->imageToWidget(documentBounds); // Cancel out any existing pan const QRectF imageBounds = m_d->view->image()->bounds(); const QRectF imageBB = m_d->coordinatesConverter->imageToWidget(imageBounds); QPointF pan = imageBB.topLeft(); viewRect.translate(-pan); int drawH = viewport()->height(); int drawW = viewport()->width(); qreal horizontalReserve = vastScrollingFactor() * drawW; qreal verticalReserve = vastScrollingFactor() * drawH; qreal xMin = viewRect.left() - horizontalReserve; qreal yMin = viewRect.top() - verticalReserve; qreal xMax = viewRect.right() - drawW + horizontalReserve; qreal yMax = viewRect.bottom() - drawH + verticalReserve; QScrollBar *hScroll = horizontalScrollBar(); QScrollBar *vScroll = verticalScrollBar(); hScroll->setRange(static_cast(xMin), static_cast(xMax)); vScroll->setRange(static_cast(yMin), static_cast(yMax)); int fontHeight = QFontMetrics(font()).height(); vScroll->setPageStep(drawH); vScroll->setSingleStep(fontHeight); hScroll->setPageStep(drawW); hScroll->setSingleStep(fontHeight); } diff --git a/libs/ui/canvas/kis_paintop_transformation_connector.cpp b/libs/ui/canvas/kis_paintop_transformation_connector.cpp deleted file mode 100644 index bf93986495..0000000000 --- a/libs/ui/canvas/kis_paintop_transformation_connector.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 "kis_paintop_transformation_connector.h" - -#include "kis_canvas_resource_provider.h" -#include "kis_canvas2.h" -#include "kis_coordinates_converter.h" -#include "brushengine/kis_paintop_preset.h" -#include "brushengine/kis_paintop_settings.h" - - -KisPaintopTransformationConnector::KisPaintopTransformationConnector(KisCanvas2 *canvas, QObject *parent) - : QObject(parent), - m_canvas(canvas) -{ - connect(m_canvas->resourceManager(), - SIGNAL(canvasResourceChanged(int,QVariant)), - SLOT(slotCanvasResourceChanged(int,QVariant))); -} - -void KisPaintopTransformationConnector::notifyTransformationChanged() -{ - KisPaintOpPresetSP preset = - m_canvas->resourceManager()-> - resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); - - if (preset) { - const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter(); - preset->settings()->setCanvasRotation(converter->rotationAngle()); - preset->settings()->setCanvasMirroring(converter->xAxisMirrored(), - converter->yAxisMirrored()); - } -} - -void KisPaintopTransformationConnector::slotCanvasResourceChanged(int key, const QVariant &resource) -{ - Q_UNUSED(resource); - - if (key == KisCanvasResourceProvider::CurrentPaintOpPreset) { - notifyTransformationChanged(); - } -} diff --git a/libs/ui/canvas/kis_paintop_transformation_connector.h b/libs/ui/canvas/kis_paintop_transformation_connector.h deleted file mode 100644 index 68c751ee0a..0000000000 --- a/libs/ui/canvas/kis_paintop_transformation_connector.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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_PAINTOP_TRANSFORMATION_CONNECTOR_H -#define __KIS_PAINTOP_TRANSFORMATION_CONNECTOR_H - -#include -#include - -#include - -class KisPaintopTransformationConnector : public QObject -{ - Q_OBJECT -public: - KisPaintopTransformationConnector(KisCanvas2 *canvas, QObject *parent); - -public: - void notifyTransformationChanged(); - -public Q_SLOTS: - void slotCanvasResourceChanged(int key, const QVariant &resource); - -private: - QPointer m_canvas; -}; - -#endif /* __KIS_PAINTOP_TRANSFORMATION_CONNECTOR_H */ diff --git a/libs/ui/tool/kis_painting_information_builder.cpp b/libs/ui/tool/kis_painting_information_builder.cpp index e52259105f..b005a4a67b 100644 --- a/libs/ui/tool/kis_painting_information_builder.cpp +++ b/libs/ui/tool/kis_painting_information_builder.cpp @@ -1,198 +1,259 @@ /* * 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_painting_information_builder.h" #include #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_cubic_curve.h" #include "kis_speed_smoother.h" #include #include "kis_canvas_resource_provider.h" /***********************************************************************/ /* KisPaintingInformationBuilder */ /***********************************************************************/ const int KisPaintingInformationBuilder::LEVEL_OF_PRESSURE_RESOLUTION = 1024; KisPaintingInformationBuilder::KisPaintingInformationBuilder() : m_speedSmoother(new KisSpeedSmoother()), m_pressureDisabled(false) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(updateSettings())); updateSettings(); } KisPaintingInformationBuilder::~KisPaintingInformationBuilder() { } void KisPaintingInformationBuilder::updateSettings() { KisConfig cfg(true); KisCubicCurve curve; curve.fromString(cfg.pressureTabletCurve()); m_pressureSamples = curve.floatTransfer(LEVEL_OF_PRESSURE_RESOLUTION + 1); } KisPaintInformation KisPaintingInformationBuilder::startStroke(KoPointerEvent *event, int timeElapsed, const KoCanvasResourceProvider *manager) { if (manager) { m_pressureDisabled = manager->resource(KisCanvasResourceProvider::DisablePressure).toBool(); } m_startPoint = event->point; return createPaintingInformation(event, timeElapsed); } KisPaintInformation KisPaintingInformationBuilder::continueStroke(KoPointerEvent *event, int timeElapsed) { return createPaintingInformation(event, timeElapsed); } QPointF KisPaintingInformationBuilder::adjustDocumentPoint(const QPointF &point, const QPointF &/*startPoint*/) { return point; } QPointF KisPaintingInformationBuilder::documentToImage(const QPointF &point) { return point; } QPointF KisPaintingInformationBuilder::imageToView(const QPointF &point) { return point; } qreal KisPaintingInformationBuilder::calculatePerspective(const QPointF &documentPoint) { Q_UNUSED(documentPoint); return 1.0; } +qreal KisPaintingInformationBuilder::canvasRotation() const +{ + return 0; +} + +bool KisPaintingInformationBuilder::canvasMirroredX() const +{ + return false; +} + +bool KisPaintingInformationBuilder::canvasMirroredY() const +{ + return false; +} KisPaintInformation KisPaintingInformationBuilder::createPaintingInformation(KoPointerEvent *event, int timeElapsed) { QPointF adjusted = adjustDocumentPoint(event->point, m_startPoint); QPointF imagePoint = documentToImage(adjusted); qreal perspective = calculatePerspective(adjusted); qreal speed = m_speedSmoother->getNextSpeed(imageToView(imagePoint)); - return KisPaintInformation(imagePoint, + KisPaintInformation pi(imagePoint, !m_pressureDisabled ? 1.0 : pressureToCurve(event->pressure()), event->xTilt(), event->yTilt(), event->rotation(), event->tangentialPressure(), perspective, timeElapsed, speed); + + pi.setCanvasRotation(canvasRotation()); + pi.setCanvasMirroredH(canvasMirroredX()); + pi.setCanvasMirroredV(canvasMirroredY()); + + return pi; } KisPaintInformation KisPaintingInformationBuilder::hover(const QPointF &imagePoint, const KoPointerEvent *event) { qreal perspective = calculatePerspective(imagePoint); qreal speed = m_speedSmoother->getNextSpeed(imageToView(imagePoint)); if (event) { return KisPaintInformation::createHoveringModeInfo(imagePoint, PRESSURE_DEFAULT, event->xTilt(), event->yTilt(), event->rotation(), event->tangentialPressure(), perspective, - speed); + speed, + canvasRotation(), + canvasMirroredX(), + canvasMirroredY()); } else { - return KisPaintInformation::createHoveringModeInfo(imagePoint); + KisPaintInformation pi = KisPaintInformation::createHoveringModeInfo(imagePoint); + pi.setCanvasRotation(canvasRotation()); + pi.setCanvasMirroredH(canvasMirroredX()); + pi.setCanvasMirroredV(canvasMirroredY()); + return pi; } } qreal KisPaintingInformationBuilder::pressureToCurve(qreal pressure) { return KisCubicCurve::interpolateLinear(pressure, m_pressureSamples); } /***********************************************************************/ /* KisConverterPaintingInformationBuilder */ /***********************************************************************/ #include "kis_coordinates_converter.h" KisConverterPaintingInformationBuilder::KisConverterPaintingInformationBuilder(const KisCoordinatesConverter *converter) : m_converter(converter) { } QPointF KisConverterPaintingInformationBuilder::documentToImage(const QPointF &point) { return m_converter->documentToImage(point); } QPointF KisConverterPaintingInformationBuilder::imageToView(const QPointF &point) { return m_converter->documentToWidget(point); } +qreal KisConverterPaintingInformationBuilder::canvasRotation() const +{ + return m_converter->rotationAngle(); +} + +bool KisConverterPaintingInformationBuilder::canvasMirroredX() const +{ + return m_converter->xAxisMirrored(); +} + +bool KisConverterPaintingInformationBuilder::canvasMirroredY() const +{ + return m_converter->yAxisMirrored(); +} + /***********************************************************************/ -/* KisToolFreehandPaintingInformationBuilder */ +/* KisToolFreehandPaintingInformationBuilder */ /***********************************************************************/ #include "kis_tool_freehand.h" +#include "kis_canvas2.h" KisToolFreehandPaintingInformationBuilder::KisToolFreehandPaintingInformationBuilder(KisToolFreehand *tool) : m_tool(tool) { } QPointF KisToolFreehandPaintingInformationBuilder::documentToImage(const QPointF &point) { return m_tool->convertToPixelCoord(point); } QPointF KisToolFreehandPaintingInformationBuilder::imageToView(const QPointF &point) { return m_tool->pixelToView(point); } QPointF KisToolFreehandPaintingInformationBuilder::adjustDocumentPoint(const QPointF &point, const QPointF &startPoint) { return m_tool->adjustPosition(point, startPoint); } qreal KisToolFreehandPaintingInformationBuilder::calculatePerspective(const QPointF &documentPoint) { return m_tool->calculatePerspective(documentPoint); } + +qreal KisToolFreehandPaintingInformationBuilder::canvasRotation() const +{ + KisCanvas2 *canvas = dynamic_cast(m_tool->canvas()); + return canvas->coordinatesConverter()->rotationAngle(); +} + +bool KisToolFreehandPaintingInformationBuilder::canvasMirroredX() const +{ + KisCanvas2 *canvas = dynamic_cast(m_tool->canvas()); + return canvas->coordinatesConverter()->xAxisMirrored(); +} + +bool KisToolFreehandPaintingInformationBuilder::canvasMirroredY() const +{ + KisCanvas2 *canvas = dynamic_cast(m_tool->canvas()); + return canvas->coordinatesConverter()->yAxisMirrored(); +} diff --git a/libs/ui/tool/kis_painting_information_builder.h b/libs/ui/tool/kis_painting_information_builder.h index 42e9820a9d..c5c2e77651 100644 --- a/libs/ui/tool/kis_painting_information_builder.h +++ b/libs/ui/tool/kis_painting_information_builder.h @@ -1,112 +1,124 @@ /* * 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_PAINTING_INFORMATION_BUILDER_H #define __KIS_PAINTING_INFORMATION_BUILDER_H #include #include #include "kis_types.h" #include "kritaui_export.h" #include class KoPointerEvent; class KisToolFreehand; class KisCoordinatesConverter; class KisSpeedSmoother; class KoCanvasResourceProvider; class KRITAUI_EXPORT KisPaintingInformationBuilder : public QObject { Q_OBJECT public: KisPaintingInformationBuilder(); ~KisPaintingInformationBuilder() override; KisPaintInformation startStroke(KoPointerEvent *event, int timeElapsed, const KoCanvasResourceProvider *manager); KisPaintInformation continueStroke(KoPointerEvent *event, int timeElapsed); KisPaintInformation hover(const QPointF &imagePoint, const KoPointerEvent *event); qreal pressureToCurve(qreal pressure); protected Q_SLOTS: void updateSettings(); protected: virtual QPointF adjustDocumentPoint(const QPointF &point, const QPointF &startPoint); virtual QPointF documentToImage(const QPointF &point); virtual QPointF imageToView(const QPointF &point); virtual qreal calculatePerspective(const QPointF &documentPoint); + virtual qreal canvasRotation() const; + virtual bool canvasMirroredX() const; + virtual bool canvasMirroredY() const; + private: KisPaintInformation createPaintingInformation(KoPointerEvent *event, int timeElapsed); /** * Defines how many discrete samples are stored in a precomputed array * of different pressures. */ static const int LEVEL_OF_PRESSURE_RESOLUTION; private: QVector m_pressureSamples; QPointF m_startPoint; QScopedPointer m_speedSmoother; bool m_pressureDisabled; }; class KRITAUI_EXPORT KisConverterPaintingInformationBuilder : public KisPaintingInformationBuilder { Q_OBJECT public: KisConverterPaintingInformationBuilder(const KisCoordinatesConverter *converter); protected: QPointF documentToImage(const QPointF &point) override; QPointF imageToView(const QPointF &point) override; + qreal canvasRotation() const override; + bool canvasMirroredX() const override; + bool canvasMirroredY() const override; + private: const KisCoordinatesConverter *m_converter; }; class KRITAUI_EXPORT KisToolFreehandPaintingInformationBuilder : public KisPaintingInformationBuilder { Q_OBJECT public: KisToolFreehandPaintingInformationBuilder(KisToolFreehand *tool); protected: QPointF documentToImage(const QPointF &point) override; QPointF imageToView(const QPointF &point) override; QPointF adjustDocumentPoint(const QPointF &point, const QPointF &startPoint) override; qreal calculatePerspective(const QPointF &documentPoint) override; + qreal canvasRotation() const override; + bool canvasMirroredX() const override; + bool canvasMirroredY() const override; + private: KisToolFreehand *m_tool; }; #endif /* __KIS_PAINTING_INFORMATION_BUILDER_H */ diff --git a/libs/ui/tool/kis_tool_freehand.cc b/libs/ui/tool/kis_tool_freehand.cc index 44bfc63df7..284869d934 100644 --- a/libs/ui/tool/kis_tool_freehand.cc +++ b/libs/ui/tool/kis_tool_freehand.cc @@ -1,461 +1,455 @@ /* * kis_tool_freehand.cc - part of Krita * * Copyright (c) 2003-2007 Boudewijn Rempt * Copyright (c) 2004 Bart Coppens * Copyright (c) 2007,2008,2010 Cyrille Berger * Copyright (c) 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. */ #include "kis_tool_freehand.h" #include #include #include #include #include #include #include #include #include #include //pop up palette #include // Krita/image #include #include #include #include #include #include // Krita/ui #include "kis_abstract_perspective_grid.h" #include "kis_config.h" #include "canvas/kis_canvas2.h" #include "kis_cursor.h" #include #include #include "kis_painting_information_builder.h" #include "kis_tool_freehand_helper.h" #include "strokes/freehand_stroke.h" using namespace std::placeholders; // For _1 placeholder KisToolFreehand::KisToolFreehand(KoCanvasBase * canvas, const QCursor & cursor, const KUndo2MagicString &transactionText) : KisToolPaint(canvas, cursor), m_paintopBasedPickingInAction(false), m_brushResizeCompressor(200, std::bind(&KisToolFreehand::slotDoResizeBrush, this, _1)) { m_assistant = false; m_magnetism = 1.0; m_only_one_assistant = true; setSupportOutline(true); setMaskSyntheticEvents(KisConfig(true).disableTouchOnCanvas()); // Disallow mouse events from finger presses unless enabled m_infoBuilder = new KisToolFreehandPaintingInformationBuilder(this); m_helper = new KisToolFreehandHelper(m_infoBuilder, transactionText); connect(m_helper, SIGNAL(requestExplicitUpdateOutline()), SLOT(explicitUpdateOutline())); } KisToolFreehand::~KisToolFreehand() { delete m_helper; delete m_infoBuilder; } void KisToolFreehand::mouseMoveEvent(KoPointerEvent *event) { KisToolPaint::mouseMoveEvent(event); m_helper->cursorMoved(convertToPixelCoord(event)); } KisSmoothingOptionsSP KisToolFreehand::smoothingOptions() const { return m_helper->smoothingOptions(); } void KisToolFreehand::resetCursorStyle() { KisConfig cfg(true); switch (cfg.newCursorStyle()) { case CURSOR_STYLE_NO_CURSOR: useCursor(KisCursor::blankCursor()); break; case CURSOR_STYLE_POINTER: useCursor(KisCursor::arrowCursor()); break; case CURSOR_STYLE_SMALL_ROUND: useCursor(KisCursor::roundCursor()); break; case CURSOR_STYLE_CROSSHAIR: useCursor(KisCursor::crossCursor()); break; case CURSOR_STYLE_TRIANGLE_RIGHTHANDED: useCursor(KisCursor::triangleRightHandedCursor()); break; case CURSOR_STYLE_TRIANGLE_LEFTHANDED: useCursor(KisCursor::triangleLeftHandedCursor()); break; case CURSOR_STYLE_BLACK_PIXEL: useCursor(KisCursor::pixelBlackCursor()); break; case CURSOR_STYLE_WHITE_PIXEL: useCursor(KisCursor::pixelWhiteCursor()); break; case CURSOR_STYLE_TOOLICON: default: KisToolPaint::resetCursorStyle(); break; } } KisPaintingInformationBuilder* KisToolFreehand::paintingInformationBuilder() const { return m_infoBuilder; } void KisToolFreehand::resetHelper(KisToolFreehandHelper *helper) { delete m_helper; m_helper = helper; } int KisToolFreehand::flags() const { return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET |KisTool::FLAG_USES_CUSTOM_SIZE; } void KisToolFreehand::activate(ToolActivation activation, const QSet &shapes) { KisToolPaint::activate(activation, shapes); } void KisToolFreehand::deactivate() { if (mode() == PAINT_MODE) { endStroke(); setMode(KisTool::HOVER_MODE); } KisToolPaint::deactivate(); } void KisToolFreehand::initStroke(KoPointerEvent *event) { m_helper->initPaint(event, convertToPixelCoord(event), canvas()->resourceManager(), image(), currentNode(), image().data()); } void KisToolFreehand::doStroke(KoPointerEvent *event) { - //set canvas information here?// - KisCanvas2 *canvas2 = dynamic_cast(canvas()); - if (canvas2) { - m_helper->setCanvasHorizontalMirrorState(canvas2->xAxisMirrored()); - m_helper->setCanvasRotation(canvas2->rotationAngle()); - } m_helper->paintEvent(event); } void KisToolFreehand::endStroke() { m_helper->endPaint(); bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()->mouseReleaseEvent(); Q_UNUSED(paintOpIgnoredEvent); } bool KisToolFreehand::primaryActionSupportsHiResEvents() const { return true; } void KisToolFreehand::beginPrimaryAction(KoPointerEvent *event) { // FIXME: workaround for the Duplicate Op tryPickByPaintOp(event, PickFgImage); requestUpdateOutline(event->point, event); NodePaintAbility paintability = nodePaintAbility(); // XXX: move this to KisTool and make it work properly for clone layers: for clone layers, the shape paint tools don't work either if (!nodeEditable() || paintability != PAINT) { if (paintability == KisToolPaint::VECTOR || paintability == KisToolPaint::CLONE){ KisCanvas2 * kiscanvas = static_cast(canvas()); QString message = i18n("The brush tool cannot paint on this layer. Please select a paint layer or mask."); kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked")); } event->ignore(); return; } setMode(KisTool::PAINT_MODE); KisCanvas2 *canvas2 = dynamic_cast(canvas()); if (canvas2) { canvas2->viewManager()->disableControls(); } initStroke(event); } void KisToolFreehand::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); requestUpdateOutline(event->point, event); /** * Actual painting */ doStroke(event); } void KisToolFreehand::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); endStroke(); if (m_assistant && static_cast(canvas())->paintingAssistantsDecoration()) { static_cast(canvas())->paintingAssistantsDecoration()->endStroke(); } KisCanvas2 *canvas2 = dynamic_cast(canvas()); if (canvas2) { canvas2->viewManager()->enableControls(); } setMode(KisTool::HOVER_MODE); } bool KisToolFreehand::tryPickByPaintOp(KoPointerEvent *event, AlternateAction action) { if (action != PickFgNode && action != PickFgImage) return false; /** * FIXME: we need some better way to implement modifiers * for a paintop level. This method is used in DuplicateOp only! */ QPointF pos = adjustPosition(event->point, event->point); qreal perspective = 1.0; Q_FOREACH (const QPointer grid, static_cast(canvas())->viewManager()->canvasResourceProvider()->perspectiveGrids()) { if (grid && grid->contains(pos)) { perspective = grid->distance(pos); break; } } if (!currentPaintOpPreset()) { return false; } bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()-> mousePressEvent(KisPaintInformation(convertToPixelCoord(event->point), m_infoBuilder->pressureToCurve(event->pressure()), event->xTilt(), event->yTilt(), event->rotation(), event->tangentialPressure(), perspective, 0, 0), event->modifiers(), currentNode()); // DuplicateOP during the picking of new source point (origin) // is the only paintop that returns "false" here return !paintOpIgnoredEvent; } void KisToolFreehand::activateAlternateAction(AlternateAction action) { if (action != ChangeSize) { KisToolPaint::activateAlternateAction(action); return; } useCursor(KisCursor::blankCursor()); setOutlineEnabled(true); } void KisToolFreehand::deactivateAlternateAction(AlternateAction action) { if (action != ChangeSize) { KisToolPaint::deactivateAlternateAction(action); return; } resetCursorStyle(); setOutlineEnabled(false); } void KisToolFreehand::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action)) { m_paintopBasedPickingInAction = true; return; } if (action != ChangeSize) { KisToolPaint::beginAlternateAction(event, action); return; } setMode(GESTURE_MODE); m_initialGestureDocPoint = event->point; m_initialGestureGlobalPoint = QCursor::pos(); m_lastDocumentPoint = event->point; m_lastPaintOpSize = currentPaintOpPreset()->settings()->paintOpSize(); } void KisToolFreehand::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action) || m_paintopBasedPickingInAction) return; if (action != ChangeSize) { KisToolPaint::continueAlternateAction(event, action); return; } QPointF lastWidgetPosition = convertDocumentToWidget(m_lastDocumentPoint); QPointF actualWidgetPosition = convertDocumentToWidget(event->point); QPointF offset = actualWidgetPosition - lastWidgetPosition; KisCanvas2 *canvas2 = dynamic_cast(canvas()); QRect screenRect = QApplication::desktop()->screenGeometry(); qreal scaleX = 0; qreal scaleY = 0; canvas2->coordinatesConverter()->imageScale(&scaleX, &scaleY); const qreal maxBrushSize = KisConfig(true).readEntry("maximumBrushSize", 1000); const qreal effectiveMaxDragSize = 0.5 * screenRect.width(); const qreal effectiveMaxBrushSize = qMin(maxBrushSize, effectiveMaxDragSize / scaleX); const qreal scaleCoeff = effectiveMaxBrushSize / effectiveMaxDragSize; const qreal sizeDiff = scaleCoeff * offset.x() ; if (qAbs(sizeDiff) > 0.01) { KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings(); const qreal newSize = qBound(0.01, m_lastPaintOpSize + sizeDiff, maxBrushSize); settings->setPaintOpSize(newSize); requestUpdateOutline(m_initialGestureDocPoint, 0); //m_brushResizeCompressor.start(newSize); m_lastDocumentPoint = event->point; m_lastPaintOpSize = newSize; } } void KisToolFreehand::endAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action) || m_paintopBasedPickingInAction) { m_paintopBasedPickingInAction = false; return; } if (action != ChangeSize) { KisToolPaint::endAlternateAction(event, action); return; } QCursor::setPos(m_initialGestureGlobalPoint); requestUpdateOutline(m_initialGestureDocPoint, 0); setMode(HOVER_MODE); } bool KisToolFreehand::wantsAutoScroll() const { return false; } void KisToolFreehand::setAssistant(bool assistant) { m_assistant = assistant; } void KisToolFreehand::setOnlyOneAssistantSnap(bool assistant) { m_only_one_assistant = assistant; } void KisToolFreehand::slotDoResizeBrush(qreal newSize) { KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings(); settings->setPaintOpSize(newSize); requestUpdateOutline(m_initialGestureDocPoint, 0); } QPointF KisToolFreehand::adjustPosition(const QPointF& point, const QPointF& strokeBegin) { if (m_assistant && static_cast(canvas())->paintingAssistantsDecoration()) { static_cast(canvas())->paintingAssistantsDecoration()->setOnlyOneAssistantSnap(m_only_one_assistant); QPointF ap = static_cast(canvas())->paintingAssistantsDecoration()->adjustPosition(point, strokeBegin); return (1.0 - m_magnetism) * point + m_magnetism * ap; } return point; } qreal KisToolFreehand::calculatePerspective(const QPointF &documentPoint) { qreal perspective = 1.0; Q_FOREACH (const QPointer grid, static_cast(canvas())->viewManager()->canvasResourceProvider()->perspectiveGrids()) { if (grid && grid->contains(documentPoint)) { perspective = grid->distance(documentPoint); break; } } return perspective; } void KisToolFreehand::explicitUpdateOutline() { requestUpdateOutline(m_outlineDocPoint, 0); } QPainterPath KisToolFreehand::getOutlinePath(const QPointF &documentPos, const KoPointerEvent *event, KisPaintOpSettings::OutlineMode outlineMode) { QPointF imagePos = convertToPixelCoord(documentPos); if (currentPaintOpPreset()) return m_helper->paintOpOutline(imagePos, event, currentPaintOpPreset()->settings(), outlineMode); else return QPainterPath(); } diff --git a/libs/ui/tool/kis_tool_freehand_helper.cpp b/libs/ui/tool/kis_tool_freehand_helper.cpp index 7bcae1f1b8..42e12ffda4 100644 --- a/libs/ui/tool/kis_tool_freehand_helper.cpp +++ b/libs/ui/tool/kis_tool_freehand_helper.cpp @@ -1,1003 +1,975 @@ /* * 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_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 "kis_random_source.h" #include "KisPerStrokeRandomSource.h" #include "strokes/freehand_stroke.h" #include "strokes/KisFreehandStrokeInfo.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 spacing updates between dabs are enabled. const qreal SPACING_UPDATE_INTERVAL = 50.0; // The amount of time, in milliseconds, to allow between updates of the timing information. Only // used when airbrushing. const qreal TIMING_UPDATE_INTERVAL = 50.0; struct KisToolFreehandHelper::Private { KisPaintingInformationBuilder *infoBuilder; KisStrokesFacade *strokesFacade; KUndo2MagicString transactionText; bool haveTangent; QPointF previousTangent; bool hasPaintAtLeastOnce; QTime strokeTime; QTimer strokeTimeoutTimer; QVector strokeInfos; KisResourcesSnapshotSP resources; KisStrokeId strokeId; KisPaintInformation previousPaintInformation; KisPaintInformation olderPaintInformation; KisSmoothingOptionsSP smoothingOptions; // fake random sources for hovering outline *only* KisRandomSourceSP fakeDabRandomSource; KisPerStrokeRandomSourceSP fakeStrokeRandomSource; // 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 bool usingStabilizer; QQueue stabilizerDeque; QTimer stabilizerPollTimer; KisStabilizedEventsSampler stabilizedSampler; KisStabilizerDelayedPaintHelper stabilizerDelayedPaintHelper; QTimer asynchronousUpdatesThresholdTimer; - int canvasRotation; - bool canvasMirroredH; - qreal effectiveSmoothnessDistance() const; }; KisToolFreehandHelper::KisToolFreehandHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText, KisSmoothingOptions *smoothingOptions) : m_d(new Private()) { m_d->infoBuilder = infoBuilder; m_d->transactionText = transactionText; m_d->smoothingOptions = KisSmoothingOptionsSP( smoothingOptions ? smoothingOptions : new KisSmoothingOptions()); - m_d->canvasRotation = 0; m_d->fakeDabRandomSource = new KisRandomSource(); m_d->fakeStrokeRandomSource = new KisPerStrokeRandomSource(); m_d->strokeTimeoutTimer.setSingleShot(true); connect(&m_d->strokeTimeoutTimer, SIGNAL(timeout()), SLOT(finishStroke())); connect(&m_d->airbrushingTimer, SIGNAL(timeout()), SLOT(doAirbrushing())); connect(&m_d->asynchronousUpdatesThresholdTimer, SIGNAL(timeout()), SLOT(doAsynchronousUpdate())); connect(&m_d->stabilizerPollTimer, SIGNAL(timeout()), SLOT(stabilizerPollAndPaint())); connect(m_d->smoothingOptions.data(), SIGNAL(sigSmoothingTypeChanged()), SLOT(slotSmoothingTypeChanged())); 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, startAngle); if (!m_d->strokeInfos.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->strokeInfos.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, startAngle); } else if (m_d->strokeInfos.first()->dragDistance->isStarted()) { distanceInfo = *m_d->strokeInfos.first()->dragDistance; } } KisPaintInformation::DistanceInformationRegistrar registrar = info.registerDistanceInformation(&distanceInfo); info.setRandomSource(m_d->fakeDabRandomSource); info.setPerStrokeRandomSource(m_d->fakeStrokeRandomSource); 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, KoCanvasResourceProvider *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, KoCanvasResourceProvider *resourceManager, KisImageWSP image, KisNodeSP currentNode, KisStrokesFacade *strokesFacade, KisNodeSP overrideNode, KisDefaultBoundsBaseSP bounds) { m_d->strokesFacade = strokesFacade; m_d->haveTangent = false; m_d->previousTangent = QPointF(); m_d->hasPaintAtLeastOnce = false; m_d->previousPaintInformation = pi; m_d->resources = new KisResourcesSnapshot(image, currentNode, resourceManager, bounds); if(overrideNode) { m_d->resources->setCurrentNode(overrideNode); } const bool airbrushing = m_d->resources->needsAirbrushing(); const bool useSpacingUpdates = m_d->resources->needsSpacingUpdates(); KisDistanceInitInfo startDistInfo(m_d->previousPaintInformation.pos(), startAngle, useSpacingUpdates ? SPACING_UPDATE_INTERVAL : LONG_TIME, airbrushing ? TIMING_UPDATE_INTERVAL : LONG_TIME, 0); KisDistanceInformation startDist = startDistInfo.makeDistInfo(); createPainters(m_d->strokeInfos, startDist); KisStrokeStrategy *stroke = new FreehandStrokeStrategy(m_d->resources, m_d->strokeInfos, m_d->transactionText); m_d->strokeId = m_d->strokesFacade->startStroke(stroke); m_d->history.clear(); m_d->distanceHistory.clear(); if (airbrushing) { m_d->airbrushingTimer.setInterval(computeAirbrushTimerInterval()); m_d->airbrushingTimer.start(); } else if (m_d->resources->presetNeedsAsynchronousUpdates()) { m_d->asynchronousUpdatesThresholdTimer.setInterval(80 /* msec */); m_d->asynchronousUpdatesThresholdTimer.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/timing // information until paintAt is called. if (airbrushing) { 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 << "WARNING: there is no intersection point " // << "in the basic smoothing algorithms"; } 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 controlling 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; // Enable stroke timeout only when not airbrushing. if (!m_d->airbrushingTimer.isActive()) { 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); if (m_d->stabilizerDelayedPaintHelper.running()) { // Paint here so we don't have to rely on the timer // This is just a tricky source for a relatively stable 7ms "timer" m_d->stabilizerDelayedPaintHelper.paintSome(); } } 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->asynchronousUpdatesThresholdTimer.isActive()) { m_d->asynchronousUpdatesThresholdTimer.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->strokeInfos.clear(); // last update to complete rendering if there is still something pending doAsynchronousUpdate(true); m_d->strokesFacade->endStroke(m_d->strokeId); m_d->strokeId.clear(); } void KisToolFreehandHelper::cancelPaint() { if (!m_d->strokeId) return; m_d->strokeTimeoutTimer.stop(); if (m_d->airbrushingTimer.isActive()) { m_d->airbrushingTimer.stop(); } if (m_d->asynchronousUpdatesThresholdTimer.isActive()) { m_d->asynchronousUpdatesThresholdTimer.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->strokeInfos.clear(); m_d->strokesFacade->cancelStroke(m_d->strokeId); m_d->strokeId.clear(); } int KisToolFreehandHelper::elapsedStrokeTime() const { return m_d->strokeTime.elapsed(); } void KisToolFreehandHelper::stabilizerStart(KisPaintInformation firstPaintInfo) { m_d->usingStabilizer = true; // 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(true); int stabilizerSampleSize = cfg.stabilizerSampleSize(); m_d->stabilizerPollTimer.setInterval(stabilizerSampleSize); m_d->stabilizerPollTimer.start(); bool delayedPaintEnabled = cfg.stabilizerDelayedPaint(); if (delayedPaintEnabled) { m_d->stabilizerDelayedPaintHelper.start(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::mixOtherWithoutTime(k, *it); it++; i++; } } else{ while (it != end) { qreal k = qreal(i - 1) / i; // coeff for uniform averaging result.KisPaintInformation::mixOtherOnlyPosition(k, *it); 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(); } m_d->usingStabilizer = false; } void KisToolFreehandHelper::slotSmoothingTypeChanged() { if (!isRunning()) { return; } KisSmoothingOptions::SmoothingType currentSmoothingType = m_d->smoothingOptions->smoothingType(); if (m_d->usingStabilizer && (currentSmoothingType != KisSmoothingOptions::STABILIZER)) { stabilizerEnd(); } else if (!m_d->usingStabilizer && (currentSmoothingType == KisSmoothingOptions::STABILIZER)) { stabilizerStart(m_d->previousPaintInformation); } } 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->strokeInfos.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); } } void KisToolFreehandHelper::doAsynchronousUpdate(bool forceUpdate) { m_d->strokesFacade->addJob(m_d->strokeId, new FreehandStrokeStrategy::UpdateData(forceUpdate)); } int KisToolFreehandHelper::computeAirbrushTimerInterval() const { qreal realInterval = m_d->resources->airbrushingInterval() * AIRBRUSH_INTERVAL_FACTOR; return qMax(1, qFloor(realInterval)); } void KisToolFreehandHelper::paintAt(int strokeInfoId, const KisPaintInformation &pi) { m_d->hasPaintAtLeastOnce = true; m_d->strokesFacade->addJob(m_d->strokeId, new FreehandStrokeStrategy::Data(strokeInfoId, pi)); } void KisToolFreehandHelper::paintLine(int strokeInfoId, const KisPaintInformation &pi1, const KisPaintInformation &pi2) { m_d->hasPaintAtLeastOnce = true; m_d->strokesFacade->addJob(m_d->strokeId, new FreehandStrokeStrategy::Data(strokeInfoId, pi1, pi2)); } void KisToolFreehandHelper::paintBezierCurve(int strokeInfoId, 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(strokeInfoId, pi1, control1, control2, pi2)); } void KisToolFreehandHelper::createPainters(QVector &strokeInfos, const KisDistanceInformation &startDist) { strokeInfos << new KisFreehandStrokeInfo(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 39c3c56b9d..6323952380 100644 --- a/libs/ui/tool/kis_tool_freehand_helper.h +++ b/libs/ui/tool/kis_tool_freehand_helper.h @@ -1,166 +1,163 @@ /* * 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 #include "kis_types.h" #include "kritaui_export.h" #include #include "kis_default_bounds.h" #include #include "kis_smoothing_options.h" #include "kundo2magicstring.h" class KoPointerEvent; class KoCanvasResourceProvider; class KisPaintingInformationBuilder; class KisStrokesFacade; class KisPostExecutionUndoAdapter; class KisPaintOp; class KisFreehandStrokeInfo; class KRITAUI_EXPORT KisToolFreehandHelper : public QObject { Q_OBJECT public: KisToolFreehandHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText = KUndo2MagicString(), KisSmoothingOptions *smoothingOptions = 0); ~KisToolFreehandHelper() override; void setSmoothness(KisSmoothingOptionsSP smoothingOptions); KisSmoothingOptionsSP smoothingOptions() const; bool isRunning() const; void cursorMoved(const QPointF &cursorPos); /** * @param event The event * @param pixelCoords The position of the KoPointerEvent, in pixel coordinates. * @param resourceManager The canvas resource manager * @param image The image * @param currentNode The current node * @param strokesFacade The strokes facade * @param overrideNode The override node * @param bounds The bounds */ void initPaint(KoPointerEvent *event, const QPointF &pixelCoords, KoCanvasResourceProvider *resourceManager, KisImageWSP image, KisNodeSP currentNode, KisStrokesFacade *strokesFacade, KisNodeSP overrideNode = 0, KisDefaultBoundsBaseSP bounds = 0); void paintEvent(KoPointerEvent *event); void endPaint(); 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, KoCanvasResourceProvider *resourceManager, KisImageWSP image, KisNodeSP node, KisStrokesFacade *strokesFacade, KisNodeSP overrideNode = 0, KisDefaultBoundsBaseSP bounds = 0); protected: virtual void createPainters(QVector &strokeInfos, const KisDistanceInformation &startDist); // lo-level methods for painting primitives void paintAt(int strokeInfoId, const KisPaintInformation &pi); void paintLine(int strokeInfoId, const KisPaintInformation &pi1, const KisPaintInformation &pi2); void paintBezierCurve(int strokeInfoId, 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 doAsynchronousUpdate(bool forceUpdate = false); void stabilizerPollAndPaint(); void slotSmoothingTypeChanged(); 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 f34368f99f..70fa1f1827 100644 --- a/libs/ui/tool/kis_tool_multihand_helper.cpp +++ b/libs/ui/tool/kis_tool_multihand_helper.cpp @@ -1,154 +1,140 @@ /* * 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" #include +#include "kis_algebra_2d.h" struct KisToolMultihandHelper::Private { QVector transformations; }; KisToolMultihandHelper::KisToolMultihandHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText) : KisToolFreehandHelper(infoBuilder, transactionText) , d(new Private) { } KisToolMultihandHelper::~KisToolMultihandHelper() { delete d; } void KisToolMultihandHelper::setupTransformations(const QVector &transformations) { d->transformations = transformations; } void KisToolMultihandHelper::createPainters(QVector &strokeInfos, const KisDistanceInformation &startDist) { for (int i = 0; i < d->transformations.size(); i++) { const QTransform &transform = d->transformations[i]; KisDistanceInitInfo __startDistInfo(transform.map(startDist.lastPosition()), startDist.lastDrawingAngle(), startDist.getSpacingInterval(), startDist.getTimingUpdateInterval(), 0); KisDistanceInformation __startDist = __startDistInfo.makeDistInfo(); strokeInfos << new KisFreehandStrokeInfo(__startDist); } } +void adjustPointInformationRotation(KisPaintInformation &pi, const QTransform &t) +{ + KisAlgebra2D::DecomposedMatix d(t); + + qreal rotation = d.angle; + const bool mirrorX = KisAlgebra2D::signPZ(d.scaleX) < 0; + const bool mirrorY = KisAlgebra2D::signPZ(d.scaleY) < 0; + + pi.setCanvasMirroredH(pi.canvasMirroredH() ^ mirrorX); + pi.setCanvasMirroredV(pi.canvasMirroredV() ^ mirrorY); + + if (pi.canvasMirroredH()!= pi.canvasMirroredV()) { + rotation = normalizeAngleDegrees(360.0 - rotation); + } + + pi.setCanvasRotation(normalizeAngleDegrees(pi.canvasRotation() - rotation)); +} + + 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); - } + adjustPointInformationRotation(__pi, transform); 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); - } - + adjustPointInformationRotation(__pi1, transform); + adjustPointInformationRotation(__pi2, transform); 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); - } - + adjustPointInformationRotation(__pi1, transform); + adjustPointInformationRotation(__pi2, transform); QPointF __control1 = transform.map(control1); QPointF __control2 = transform.map(control2); paintBezierCurve(i, __pi1, __control1, __control2, __pi2); } } diff --git a/plugins/paintops/defaultpaintops/brush/tests/kis_brushop_test.cpp b/plugins/paintops/defaultpaintops/brush/tests/kis_brushop_test.cpp index f6bbb6fe56..30b0f85fdf 100644 --- a/plugins/paintops/defaultpaintops/brush/tests/kis_brushop_test.cpp +++ b/plugins/paintops/defaultpaintops/brush/tests/kis_brushop_test.cpp @@ -1,242 +1,254 @@ /* * 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 "kis_brushop_test.h" #include #include #include #include #include #include #include #include #include class TestBrushOp : public TestUtil::QImageBasedTest { public: TestBrushOp(const QString &presetFileName, const QString &prefix = "simple") : QImageBasedTest("brushop") { m_presetFileName = presetFileName; m_prefix = prefix; } virtual ~TestBrushOp() {} void test() { test(false, false, 0.0); test(false, false, 10.0); test(false, false, 20.0); test(true, false, 0.0); test(true, false, 10.0); test(true, false, 20.0); test(false, true, 0.0); test(false, true, 10.0); test(false, true, 20.0); test(true, true, 0.0); test(true, true, 10.0); test(true, true, 20.0); } void test(bool mirrorX, bool mirrorY, qreal rotation) { test(mirrorX, mirrorY, rotation, false, false); test(mirrorX, mirrorY, rotation, true, false); test(mirrorX, mirrorY, rotation, false, true); test(mirrorX, mirrorY, rotation, true, true); } void test(bool mirrorX, bool mirrorY, qreal rotation, bool mirrorDabX, bool mirrorDabY) { test(mirrorX, mirrorY, rotation, mirrorDabX, mirrorDabY, 0.0); test(mirrorX, mirrorY, rotation, mirrorDabX, mirrorDabY, 360.0 - 10.0); test(mirrorX, mirrorY, rotation, mirrorDabX, mirrorDabY, 360.0 - 20.0); } void test(bool mirrorX, bool mirrorY, qreal rotation, bool mirrorDabX, bool mirrorDabY, qreal dabRotation) { KisSurrogateUndoStore *undoStore = new KisSurrogateUndoStore(); KisImageSP image = createTrivialImage(undoStore); image->initialRefreshGraph(); KisNodeSP paint1 = findNode(image->root(), "paint1"); QVERIFY(paint1->extent().isEmpty()); KisPainter gc(paint1->paintDevice()); QScopedPointer manager( utils::createResourceManager(image, 0, m_presetFileName)); KisPaintOpPresetSP preset = manager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); - preset->settings()->setCanvasRotation(rotation); - preset->settings()->setCanvasMirroring(mirrorY, mirrorX); - - if (mirrorDabX || mirrorDabY) { KisPaintOpSettingsSP settings = preset->settings()->clone(); KisPressureMirrorOption mirrorOption; mirrorOption.readOptionSetting(settings); mirrorOption.setChecked(true); mirrorOption.setCurveUsed(false); mirrorOption.enableHorizontalMirror(mirrorDabX); mirrorOption.enableVerticalMirror(mirrorDabY); mirrorOption.writeOptionSetting(settings.data()); preset->setSettings(settings); } if (dabRotation != 0.0) { KisPaintOpSettingsSP settings = preset->settings()->clone(); KisPressureRotationOption rotationOption; rotationOption.readOptionSetting(settings); rotationOption.setChecked(true); rotationOption.setCurveUsed(false); rotationOption.setValue(dabRotation / 360.0); rotationOption.writeOptionSetting(settings.data()); preset->setSettings(settings); } QString testName = QString("%7_cmY_%1_cmX_%2_cR_%3_dmX_%4_dmY_%5_dR_%6") .arg(mirrorY) .arg(mirrorX) .arg(rotation) .arg(mirrorDabX) .arg(mirrorDabY) .arg(std::fmod(360.0 - dabRotation, 360.0)) .arg(m_prefix); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, paint1, manager.data()); resources->setupPainter(&gc); - doPaint(gc); + doPaint(gc, rotation, mirrorX, mirrorY); checkOneLayer(image, paint1, testName); } - virtual void doPaint(KisPainter &gc) { + virtual void doPaint(KisPainter &gc, qreal rotation, bool mirrorX, bool mirrorY) { KisPaintInformation pi(QPointF(100, 100), 1.0); + pi.setCanvasRotation(rotation); + pi.setCanvasMirroredH(mirrorX); + pi.setCanvasMirroredV(mirrorY); KisDistanceInformation dist; gc.paintAt(pi, &dist); } QString m_presetFileName; QString m_prefix; }; class TestBrushOpLines : public TestBrushOp { public: TestBrushOpLines(const QString &presetFileName) : TestBrushOp(presetFileName) { } - void doPaint(KisPainter &gc) override { + void doPaint(KisPainter &gc, qreal rotation, bool mirrorX, bool mirrorY) override { QVector vector; vector << KisPaintInformation(QPointF(100, 100)); vector << KisPaintInformation(QPointF(200, 150)); vector << KisPaintInformation(QPointF(100, 350)); + + for (auto pi : vector) { + pi.setCanvasRotation(rotation); + pi.setCanvasMirroredH(mirrorX); + pi.setCanvasMirroredV(mirrorY); + } + KisDistanceInformation dist; for (int i = 1; i < vector.size(); i++) { gc.paintLine(vector[i - 1], vector[i], &dist); } } }; class TestBrushOpPressureLines : public TestBrushOp { public: TestBrushOpPressureLines(const QString &presetFileName, const QString &prefix) : TestBrushOp(presetFileName, prefix) { } - void doPaint(KisPainter &gc) override { + void doPaint(KisPainter &gc, qreal rotation, bool mirrorX, bool mirrorY) override { QVector vector; vector << KisPaintInformation(QPointF(0, 0), 0.2); vector << KisPaintInformation(QPointF(200, 50), 1.0); vector << KisPaintInformation(QPointF(100, 250), 0.0); vector << KisPaintInformation(QPointF(200, 150), 1.0); vector << KisPaintInformation(QPointF(100, 350), 1.0); + for (auto pi : vector) { + pi.setCanvasRotation(rotation); + pi.setCanvasMirroredH(mirrorX); + pi.setCanvasMirroredV(mirrorY); + } + KisDistanceInformation dist; for (int i = 1; i < vector.size(); i++) { gc.paintLine(vector[i - 1], vector[i], &dist); } } }; #include void KisBrushOpTest::initTestCase() { KoResourcePaths::addResourceDir("kis_brushes", QString(SYSTEM_RESOURCES_DATA_DIR) + "/brushes"); } void KisBrushOpTest::testRotationMirroring() { TestBrushOp t("LR_simple.kpp"); t.test(); } void KisBrushOpTest::testRotationMirroringDrawingAngle() { TestBrushOpLines t("LR_drawing_angle.kpp"); t.test(); } void KisBrushOpTest::testMagicSeven() { /** * A special preset that forces Qt to bug: * mask size: 56 * brush size: 7 * therefore scale is: 0.125 * which causes QTransform work as a pure Translate in the mipmap */ TestBrushOpPressureLines t("magic_seven.kpp", "magicseven"); t.test(); } QTEST_MAIN(KisBrushOpTest) diff --git a/plugins/paintops/hairy/kis_hairy_paintop.cpp b/plugins/paintops/hairy/kis_hairy_paintop.cpp index 2927af5fae..bd53194983 100644 --- a/plugins/paintops/hairy/kis_hairy_paintop.cpp +++ b/plugins/paintops/hairy/kis_hairy_paintop.cpp @@ -1,172 +1,170 @@ /* * 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 #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; KisBrushOptionProperties brushOption; brushOption.readOptionSetting(settings); KisBrushSP brush = brushOption.brush(); KisFixedPaintDeviceSP dab = cachedDab(painter->device()->compositionSourceColorSpace()); // properly initialize fake paint information to avoid warnings KisPaintInformation fakePaintInformation; fakePaintInformation.setRandomSource(new KisRandomSource()); fakePaintInformation.setPerStrokeRandomSource(new KisPerStrokeRandomSource()); if (brush->brushType() == IMAGE || brush->brushType() == PIPE_IMAGE) { dab = brush->paintDevice(source()->colorSpace(), KisDabShape(), fakePaintInformation); } else { brush->mask(dab, painter->paintColor(), KisDabShape(), fakePaintInformation); } 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(); - - bool mirrorXAxis = settings->getBool("runtimeCanvasMirroredX", false); - bool mirrorYAxis = settings->getBool("runtimeCanvasMirroredY", false); - m_mirrorFlip = mirrorXAxis != mirrorYAxis; } 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(); } /** * Even though we don't use spacing in hairy brush, we should still * initialize its distance information to ensure drawing angle and * other history-based sensors work fine. */ KisPaintInformation pi(pi2); KisPaintInformation::DistanceInformationRegistrar r = pi.registerDistanceInformation(currentDistance); // Hairy Brush is capable of working with zero scale, // so no additional checks for 'zero'ness are needed qreal scale = m_sizeOption.apply(pi); scale *= KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(pi); quint8 origOpacity = m_opacityOption.apply(painter(), pi); + const bool mirrorFlip = pi1.canvasMirroredH() != pi1.canvasMirroredV(); + // we don't use spacing here (the brush itself is used only once // during initialization), so we should just skip the distance info // update - m_brush.paintLine(m_dab, m_dev, pi1, pi, scale * m_properties.scaleFactor, m_mirrorFlip? -rotation : rotation); + m_brush.paintLine(m_dab, m_dev, pi1, pi, scale * m_properties.scaleFactor, mirrorFlip ? -rotation : 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); // we don't use spacing in hairy brush, but history is // still important for us currentDistance->registerPaintedDab(pi, KisSpacingInformation(), KisTimingInformation()); } diff --git a/plugins/paintops/hairy/kis_hairy_paintop.h b/plugins/paintops/hairy/kis_hairy_paintop.h index 9bda32cbe2..e9de44d51d 100644 --- a/plugins/paintops/hairy/kis_hairy_paintop.h +++ b/plugins/paintops/hairy/kis_hairy_paintop.h @@ -1,65 +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); 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; - bool m_mirrorFlip; - void loadSettings(const KisBrushBasedPaintOpSettings* settings); }; #endif // KIS_HAIRYPAINTOP_H_ diff --git a/plugins/paintops/libpaintop/kis_current_outline_fetcher.cpp b/plugins/paintops/libpaintop/kis_current_outline_fetcher.cpp index 599a46d649..fee3928f66 100644 --- a/plugins/paintops/libpaintop/kis_current_outline_fetcher.cpp +++ b/plugins/paintops/libpaintop/kis_current_outline_fetcher.cpp @@ -1,162 +1,162 @@ /* * 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 "kis_current_outline_fetcher.h" #include "kis_pressure_size_option.h" #include "kis_pressure_rotation_option.h" #include "kis_pressure_mirror_option.h" #include #include #include "kis_paintop_settings.h" #include #define NOISY_UPDATE_SPEED 50 // Time in ms for outline updates to noisy brushes struct KisCurrentOutlineFetcher::Private { Private(Options optionsAvailable) : isDirty(true) { if (optionsAvailable & SIZE_OPTION) { sizeOption.reset(new KisPressureSizeOption()); } if (optionsAvailable & ROTATION_OPTION) { rotationOption.reset(new KisPressureRotationOption()); } if (optionsAvailable & MIRROR_OPTION) { mirrorOption.reset(new KisPressureMirrorOption()); } } QScopedPointer sizeOption; QScopedPointer rotationOption; QScopedPointer mirrorOption; bool isDirty; QElapsedTimer lastUpdateTime; qreal lastRotationApplied; qreal lastSizeApplied; MirrorProperties lastMirrorApplied; }; KisCurrentOutlineFetcher::KisCurrentOutlineFetcher(Options optionsAvailable) : d(new Private(optionsAvailable)) { d->lastUpdateTime.start(); } KisCurrentOutlineFetcher::~KisCurrentOutlineFetcher() { } void KisCurrentOutlineFetcher::setDirty() { d->isDirty = true; } QPainterPath KisCurrentOutlineFetcher::fetchOutline(const KisPaintInformation &info, const KisPaintOpSettingsSP settings, const QPainterPath &originalOutline, const KisPaintOpSettings::OutlineMode &mode, qreal additionalScale, qreal additionalRotation, bool tilt, qreal tiltcenterx, qreal tiltcentery) const { if (d->isDirty) { if (d->sizeOption) { d->sizeOption->readOptionSetting(settings); d->sizeOption->resetAllSensors(); } if (d->rotationOption) { d->rotationOption->readOptionSetting(settings); d->rotationOption->resetAllSensors(); } if (d->mirrorOption) { d->mirrorOption->readOptionSetting(settings); d->mirrorOption->resetAllSensors(); } d->isDirty = false; } qreal scale = additionalScale; qreal rotation = additionalRotation; bool needsUpdate = false; // Randomized rotation at full speed looks noisy, so slow it down if (d->lastUpdateTime.elapsed() > NOISY_UPDATE_SPEED) { needsUpdate = true; d->lastUpdateTime.restart(); } if (d->sizeOption && !tilt && !mode.forceFullSize) { if (!d->sizeOption->isRandom() || needsUpdate) { d->lastSizeApplied = d->sizeOption->apply(info); } scale *= d->lastSizeApplied; } if (d->rotationOption && !tilt) { if (!d->rotationOption->isRandom() || needsUpdate) { d->lastRotationApplied = d->rotationOption->apply(info); } rotation += d->lastRotationApplied; } else if (d->rotationOption && tilt) { - rotation += settings->getDouble("runtimeCanvasRotation", 0.0) * M_PI / 180.0; + rotation += info.canvasRotation() * M_PI / 180.0; } qreal xFlip = 1.0; qreal yFlip = 1.0; if (d->mirrorOption) { if (!d->mirrorOption->isRandom() || needsUpdate) { d->lastMirrorApplied = d->mirrorOption->apply(info); } if (d->lastMirrorApplied.coordinateSystemFlipped) { rotation = 2 * M_PI - rotation; } if (d->lastMirrorApplied.horizontalMirror) { xFlip = -1.0; } if (d->lastMirrorApplied.verticalMirror) { yFlip = -1.0; } } QTransform rot; rot.rotateRadians(-rotation); QPointF hotSpot = originalOutline.boundingRect().center(); if (tilt) { hotSpot.setX(tiltcenterx); hotSpot.setY(tiltcentery); } QTransform T1 = QTransform::fromTranslate(-hotSpot.x(), -hotSpot.y()); QTransform T2 = QTransform::fromTranslate(info.pos().x(), info.pos().y()); QTransform S = QTransform::fromScale(xFlip * scale, yFlip * scale); return (T1 * rot * S * T2).map(originalOutline); } diff --git a/plugins/paintops/libpaintop/kis_pressure_mirror_option.cpp b/plugins/paintops/libpaintop/kis_pressure_mirror_option.cpp index f32d6158b4..c026ba12bf 100644 --- a/plugins/paintops/libpaintop/kis_pressure_mirror_option.cpp +++ b/plugins/paintops/libpaintop/kis_pressure_mirror_option.cpp @@ -1,101 +1,98 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_pressure_mirror_option.h" #include #include #include #include KisPressureMirrorOption::KisPressureMirrorOption() : KisCurveOption("Mirror", KisPaintOpOption::GENERAL, false) { m_enableHorizontalMirror = false; m_enableVerticalMirror = false; } void KisPressureMirrorOption::enableHorizontalMirror(bool mirror) { m_enableHorizontalMirror = mirror; } void KisPressureMirrorOption::enableVerticalMirror(bool mirror) { m_enableVerticalMirror = mirror; } bool KisPressureMirrorOption::isHorizontalMirrorEnabled() { return m_enableHorizontalMirror; } bool KisPressureMirrorOption::isVerticalMirrorEnabled() { return m_enableVerticalMirror; } void KisPressureMirrorOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const { KisCurveOption::writeOptionSetting(setting); setting->setProperty(MIRROR_HORIZONTAL_ENABLED, m_enableHorizontalMirror); setting->setProperty(MIRROR_VERTICAL_ENABLED, m_enableVerticalMirror); } void KisPressureMirrorOption::readOptionSetting(const KisPropertiesConfigurationSP setting) { KisCurveOption::readOptionSetting(setting); m_enableHorizontalMirror = setting->getBool(MIRROR_HORIZONTAL_ENABLED, false); m_enableVerticalMirror = setting->getBool(MIRROR_VERTICAL_ENABLED, false); - - m_canvasAxisXMirrored = setting->getBool("runtimeCanvasMirroredX", false); - m_canvasAxisYMirrored = setting->getBool("runtimeCanvasMirroredY", false); } MirrorProperties KisPressureMirrorOption::apply(const KisPaintInformation& info) const { - int mirrorXIncrement = m_canvasAxisXMirrored; - int mirrorYIncrement = m_canvasAxisYMirrored; + int mirrorXIncrement = info.canvasMirroredH(); + int mirrorYIncrement = info.canvasMirroredV(); bool coordinateSystemFlipped = false; if (isChecked() && (m_enableHorizontalMirror || m_enableVerticalMirror)) { qreal sensorResult = computeSizeLikeValue(info); bool result = (sensorResult >= 0.5); mirrorXIncrement += result && m_enableHorizontalMirror; mirrorYIncrement += result && m_enableVerticalMirror; coordinateSystemFlipped = result && (m_enableHorizontalMirror != m_enableVerticalMirror); } MirrorProperties mirrors; mirrors.verticalMirror = mirrorYIncrement % 2; mirrors.horizontalMirror = mirrorXIncrement % 2; mirrors.coordinateSystemFlipped = coordinateSystemFlipped; return mirrors; } diff --git a/plugins/paintops/libpaintop/kis_pressure_mirror_option.h b/plugins/paintops/libpaintop/kis_pressure_mirror_option.h index 5bcf75e837..14edf66872 100644 --- a/plugins/paintops/libpaintop/kis_pressure_mirror_option.h +++ b/plugins/paintops/libpaintop/kis_pressure_mirror_option.h @@ -1,76 +1,74 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_PRESSURE_MIRROR_OPTION_H #define KIS_PRESSURE_MIRROR_OPTION_H #include "kis_curve_option.h" #include #include #include struct MirrorProperties { MirrorProperties() : horizontalMirror(false), verticalMirror(false), coordinateSystemFlipped(false) {} bool horizontalMirror; bool verticalMirror; bool coordinateSystemFlipped; bool isEmpty() const { return !horizontalMirror && !verticalMirror; } }; const QString MIRROR_HORIZONTAL_ENABLED = "HorizontalMirrorEnabled"; const QString MIRROR_VERTICAL_ENABLED = "VerticalMirrorEnabled"; /** * If the sensor value is higher then 0.5, then the related mirror option is true, false otherwise */ class PAINTOP_EXPORT KisPressureMirrorOption : public KisCurveOption { public: KisPressureMirrorOption(); /** * Set the */ MirrorProperties apply(const KisPaintInformation& info) const; void writeOptionSetting(KisPropertiesConfigurationSP setting) const override; void readOptionSetting(const KisPropertiesConfigurationSP setting) override; void enableVerticalMirror(bool mirror); void enableHorizontalMirror(bool mirror); bool isVerticalMirrorEnabled(); bool isHorizontalMirrorEnabled(); private: bool m_enableVerticalMirror; bool m_enableHorizontalMirror; - bool m_canvasAxisXMirrored; - bool m_canvasAxisYMirrored; }; #endif diff --git a/plugins/paintops/libpaintop/kis_pressure_rotation_option.cpp b/plugins/paintops/libpaintop/kis_pressure_rotation_option.cpp index 9421a53d9d..ea6f60a72c 100644 --- a/plugins/paintops/libpaintop/kis_pressure_rotation_option.cpp +++ b/plugins/paintops/libpaintop/kis_pressure_rotation_option.cpp @@ -1,86 +1,80 @@ /* This file is part of the KDE project * Copyright (c) 2009 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_pressure_rotation_option.h" #include "kis_pressure_opacity_option.h" #include "sensors/kis_dynamic_sensor_drawing_angle.h" #include "sensors/kis_dynamic_sensor_fuzzy.h" #include #include #include #include KisPressureRotationOption::KisPressureRotationOption() - : KisCurveOption("Rotation", KisPaintOpOption::GENERAL, false), - m_defaultAngle(0.0), - m_canvasAxisXMirrored(false), - m_canvasAxisYMirrored(false) + : KisCurveOption("Rotation", KisPaintOpOption::GENERAL, false) { } double KisPressureRotationOption::apply(const KisPaintInformation & info) const { - if (!isChecked()) return m_defaultAngle; + if (!isChecked()) return kisDegreesToRadians(info.canvasRotation()); - const bool absoluteAxesFlipped = m_canvasAxisXMirrored != m_canvasAxisYMirrored; - const qreal normalizedBaseAngle = -m_defaultAngle / (2 * M_PI); + const bool absoluteAxesFlipped = info.canvasMirroredH() != info.canvasMirroredV(); + + const qreal normalizedBaseAngle = info.canvasRotation() / 360.0; qreal value = computeRotationLikeValue(info, normalizedBaseAngle, absoluteAxesFlipped); // flip to conform global legacy code value = 1.0 - value; return normalizeAngle(value * M_PI); } void KisPressureRotationOption::readOptionSetting(const KisPropertiesConfigurationSP setting) { - m_defaultAngle = setting->getDouble("runtimeCanvasRotation", 0.0) * M_PI / 180.0; KisCurveOption::readOptionSetting(setting); - - m_canvasAxisXMirrored = setting->getBool("runtimeCanvasMirroredX", false); - m_canvasAxisYMirrored = setting->getBool("runtimeCanvasMirroredY", false); } void KisPressureRotationOption::applyFanCornersInfo(KisPaintOp *op) { KisDynamicSensorDrawingAngle *sensor = dynamic_cast(this->sensor(ANGLE, true).data()); /** * A special case for the Drawing Angle sensor, because it * changes the behavior of KisPaintOp::paintLine() */ if (sensor) { op->setFanCornersInfo(sensor->fanCornersEnabled(), qreal(sensor->fanCornersStep()) * M_PI / 180.0); } } int KisPressureRotationOption::intMinValue() const { return -180; } int KisPressureRotationOption::intMaxValue() const { return 180; } QString KisPressureRotationOption::valueSuffix() const { return i18n("°"); } diff --git a/plugins/paintops/libpaintop/kis_pressure_rotation_option.h b/plugins/paintops/libpaintop/kis_pressure_rotation_option.h index f52e6e8c0a..52396d501c 100644 --- a/plugins/paintops/libpaintop/kis_pressure_rotation_option.h +++ b/plugins/paintops/libpaintop/kis_pressure_rotation_option.h @@ -1,56 +1,49 @@ /* This file is part of the KDE project * Copyright (c) 2009 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_PRESSURE_ROTATION_OPTION_H #define KIS_PRESSURE_ROTATION_OPTION_H #include "kis_curve_option.h" #include #include /** * The pressure opacity option defines a curve that is used to * calculate the effect of pressure on the size of the dab */ class PAINTOP_EXPORT KisPressureRotationOption : public KisCurveOption { public: KisPressureRotationOption(); double apply(const KisPaintInformation & info) const; void readOptionSetting(const KisPropertiesConfigurationSP setting) override; void applyFanCornersInfo(KisPaintOp *op); // KisCurveOption interface public: int intMinValue() const override; int intMaxValue() const override; QString valueSuffix() const override; private: - qreal m_defaultAngle; - bool m_canvasAxisXMirrored; - bool m_canvasAxisYMirrored; - - - - }; #endif diff --git a/plugins/paintops/tangentnormal/kis_tangent_tilt_option.cpp b/plugins/paintops/tangentnormal/kis_tangent_tilt_option.cpp index b6edb053b0..b0888bf6bf 100644 --- a/plugins/paintops/tangentnormal/kis_tangent_tilt_option.cpp +++ b/plugins/paintops/tangentnormal/kis_tangent_tilt_option.cpp @@ -1,269 +1,260 @@ /* This file is part of the KDE project * * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_tangent_tilt_option.h" #include #include #include #include "ui_wdgtangenttiltoption.h" #include "kis_global.h" class KisTangentTiltOptionWidget: public QWidget, public Ui::WdgTangentTiltOptions { public: KisTangentTiltOptionWidget(QWidget *parent = 0) : QWidget(parent) { setupUi(this); } }; KisTangentTiltOption::KisTangentTiltOption() -: KisPaintOpOption(KisPaintOpOption::GENERAL, false), - m_canvasAngle(0.0), - m_canvasAxisXMirrored(false), - m_canvasAxisYMirrored(false) +: KisPaintOpOption(KisPaintOpOption::GENERAL, false) { m_checkable = false; m_options = new KisTangentTiltOptionWidget(); //Setup tangent tilt. m_options->comboRed->setCurrentIndex(0); m_options->comboGreen->setCurrentIndex(2); m_options->comboBlue->setCurrentIndex(4); m_options->sliderElevationSensitivity->setRange(0, 100, 0); m_options->sliderElevationSensitivity->setValue(100); m_options->sliderElevationSensitivity->setSuffix("%"); m_options->sliderMixValue->setRange(0, 100, 0); m_options->sliderMixValue->setValue(50); m_options->sliderMixValue->setSuffix("%"); connect(m_options->comboRed, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged())); connect(m_options->comboGreen, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged())); connect(m_options->comboBlue, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged())); connect(m_options->comboRed, SIGNAL(currentIndexChanged(int)), m_options->TangentTiltPreview, SLOT(setRedChannel(int))); connect(m_options->comboGreen, SIGNAL(currentIndexChanged(int)), m_options->TangentTiltPreview, SLOT(setGreenChannel(int))); connect(m_options->comboBlue, SIGNAL(currentIndexChanged(int)), m_options->TangentTiltPreview, SLOT(setBlueChannel(int))); connect(m_options->optionTilt, SIGNAL(toggled(bool)), SLOT(emitSettingChanged())); connect(m_options->optionDirection, SIGNAL(toggled(bool)), SLOT(emitSettingChanged())); connect(m_options->optionRotation, SIGNAL(toggled(bool)), SLOT(emitSettingChanged())); connect(m_options->optionMix, SIGNAL(toggled(bool)), SLOT(emitSettingChanged())); connect(m_options->sliderElevationSensitivity, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged())); connect(m_options->sliderMixValue, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged())); m_options->sliderMixValue->setVisible(false); setConfigurationPage(m_options); } KisTangentTiltOption::~KisTangentTiltOption() { delete m_options; } //options int KisTangentTiltOption::redChannel() const { return m_options->comboRed->currentIndex(); } int KisTangentTiltOption::greenChannel() const { return m_options->comboGreen->currentIndex(); } int KisTangentTiltOption::blueChannel() const { return m_options->comboBlue->currentIndex(); } int KisTangentTiltOption::directionType() const { int type=0; if (m_options->optionTilt->isChecked()==true) { type=0; } else if (m_options->optionDirection->isChecked()==true) { type=1; } else if (m_options->optionRotation->isChecked()==true) { type=2; } else if (m_options->optionMix->isChecked()==true) { type=3; } else { warnKrita<<"There's something odd with the radio buttons. We'll use Tilt"; } return type; } double KisTangentTiltOption::elevationSensitivity() const { return m_options->sliderElevationSensitivity->value(); } double KisTangentTiltOption::mixValue() const { return m_options->sliderMixValue->value(); } void KisTangentTiltOption::swizzleAssign(qreal const horizontal, qreal const vertical, qreal const depth, qreal *component, int index, qreal maxvalue) { switch(index) { case 0: *component = horizontal; break; case 1: *component = maxvalue-horizontal; break; case 2: *component = vertical; break; case 3: *component = maxvalue-vertical; break; case 4: *component = depth; break; case 5: *component = maxvalue-depth; break; } } void KisTangentTiltOption::apply(const KisPaintInformation& info,qreal *r,qreal *g,qreal *b) { //formula based on http://www.cerebralmeltdown.com/programming_projects/Altitude%20and%20Azimuth%20to%20Vector/index.html /* It doesn't make sense of have higher than 8bit color depth. * Instead we make sure in the paintAt function of kis_tangent_normal_paintop to pick an 8bit space of the current * color space if the space is an RGB space. If not, it'll pick sRGB 8bit. */ qreal halfvalue = 0.5; qreal maxvalue = 1.0; //have the azimuth and altitude in degrees. qreal direction = KisPaintInformation::tiltDirection(info, true)*360.0; qreal elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0); - if (directionType()==0) { + + if (directionType() == 0) { direction = KisPaintInformation::tiltDirection(info, true)*360.0; - elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0); - } else if (directionType()==1) { + elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0); + } else if (directionType() == 1) { direction = (0.75 + info.drawingAngle() / (2.0 * M_PI))*360.0; - elevation= 0;//turns out that tablets that don't support tilt just return 90 degrees for elevation. - } else if (directionType()==2) { + elevation= 0;//turns out that tablets that don't support tilt just return 90 degrees for elevation. + } else if (directionType() == 2) { direction = info.rotation(); - elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0);//artpens have tilt-recognition, so this should work. - } else if (directionType()==3) {//mix of tilt+direction - qreal mixamount = mixValue()/100.0; + elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0);//artpens have tilt-recognition, so this should work. + } else if (directionType() == 3) {//mix of tilt+direction + qreal mixamount = mixValue()/100.0; direction = (KisPaintInformation::tiltDirection(info, true)*360.0*(1.0-mixamount))+((0.75 + info.drawingAngle() / (2.0 * M_PI))*360.0*(mixamount)); - elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0); + elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0); } - //subtract/add the rotation of the canvas. + const qreal oldDirection = direction; - if (info.canvasRotation()!=m_canvasAngle && info.canvasMirroredH()==m_canvasAxisXMirrored) { - m_canvasAngle=info.canvasRotation(); - } - if (directionType()!=1) { - direction = direction-m_canvasAngle; + //subtract/add the rotation of the canvas. + if (directionType() != 1) { + direction = normalizeAngleDegrees(direction - info.canvasRotation()); } //limit the direction/elevation //qreal elevationMax = (elevationSensitivity()*90.0)/100.0; qreal elevationT = elevation*(elevationSensitivity()/100.0)+(90-(elevationSensitivity()*90.0)/100.0); elevation = static_cast(elevationT); //convert to radians. // Convert this to kis_global's radian function. direction = kisDegreesToRadians(direction); elevation = kisDegreesToRadians(elevation); //make variables for axes for easy switching later on. qreal horizontal, vertical, depth; //spherical coordinates always center themselves around the origin, leading to values. We need to work around those... horizontal = cos(elevation)*sin(direction); if (horizontal>0.0) { horizontal= halfvalue+(fabs(horizontal)*halfvalue); } else { horizontal= halfvalue-(fabs(horizontal)*halfvalue); } vertical = cos(elevation)*cos(direction); if (vertical>0.0) { vertical = halfvalue+(fabs(vertical)*halfvalue); } else { vertical = halfvalue-(fabs(vertical)*halfvalue); } - if (m_canvasAxisXMirrored && info.canvasMirroredH()) {horizontal = maxvalue-horizontal;} - if (m_canvasAxisYMirrored && info.canvasMirroredH()) {vertical = maxvalue-vertical;} + if (info.canvasMirroredH()) {horizontal = maxvalue - horizontal;} + if (info.canvasMirroredV()) {vertical = maxvalue - vertical;} depth = sin(elevation)*maxvalue; //assign right components to correct axes. swizzleAssign(horizontal, vertical, depth, r, redChannel(), maxvalue); swizzleAssign(horizontal, vertical, depth, g, greenChannel(), maxvalue); swizzleAssign(horizontal, vertical, depth, b, blueChannel(), maxvalue); } /*settings*/ void KisTangentTiltOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const { setting->setProperty(TANGENT_RED, redChannel()); setting->setProperty(TANGENT_GREEN, greenChannel()); setting->setProperty(TANGENT_BLUE, blueChannel()); setting->setProperty(TANGENT_TYPE, directionType()); setting->setProperty(TANGENT_EV_SEN, elevationSensitivity()); setting->setProperty(TANGENT_MIX_VAL, mixValue()); } void KisTangentTiltOption::readOptionSetting(const KisPropertiesConfigurationSP setting) { m_options->comboRed->setCurrentIndex(setting->getInt(TANGENT_RED, 0)); m_options->comboGreen->setCurrentIndex(setting->getInt(TANGENT_GREEN, 2)); m_options->comboBlue->setCurrentIndex(setting->getInt(TANGENT_BLUE, 4)); //The comboboxes are connected to the TangentTiltPreview, so that gets automatically updated by them. if (setting->getInt(TANGENT_TYPE)== 0){ m_options->optionTilt->setChecked(true); - m_options->sliderMixValue->setVisible(false); + m_options->sliderMixValue->setVisible(false); } else if (setting->getInt(TANGENT_TYPE)== 1) { m_options->optionDirection->setChecked(true); - m_options->sliderMixValue->setVisible(false); + m_options->sliderMixValue->setVisible(false); } else if (setting->getInt(TANGENT_TYPE)== 2) { m_options->optionRotation->setChecked(true); - m_options->sliderMixValue->setVisible(false); + m_options->sliderMixValue->setVisible(false); } else if (setting->getInt(TANGENT_TYPE)== 3) { m_options->optionMix->setChecked(true); - m_options->sliderMixValue->setVisible(true); + m_options->sliderMixValue->setVisible(true); } - m_canvasAngle = setting->getDouble("runtimeCanvasRotation", 0.0);//in degrees please. - m_canvasAxisXMirrored = setting->getBool("runtimeCanvasMirroredX", false); - m_canvasAxisYMirrored = setting->getBool("runtimeCanvasMirroredY", false); - m_options->sliderElevationSensitivity->setValue(setting->getDouble(TANGENT_EV_SEN, 100)); m_options->sliderMixValue->setValue(setting->getDouble(TANGENT_MIX_VAL, 50)); - } diff --git a/plugins/paintops/tangentnormal/kis_tangent_tilt_option.h b/plugins/paintops/tangentnormal/kis_tangent_tilt_option.h index 53f84cb31b..0a7fbad56e 100644 --- a/plugins/paintops/tangentnormal/kis_tangent_tilt_option.h +++ b/plugins/paintops/tangentnormal/kis_tangent_tilt_option.h @@ -1,68 +1,65 @@ /* This file is part of the KDE project * * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_TANGENT_TILT_OPTION_H #define KIS_TANGENT_TILT_OPTION_H #include #include #include const QString TANGENT_RED = "Tangent/swizzleRed"; const QString TANGENT_GREEN = "Tangent/swizzleGreen"; const QString TANGENT_BLUE = "Tangent/swizzleBlue"; const QString TANGENT_TYPE = "Tangent/directionType"; const QString TANGENT_EV_SEN = "Tangent/elevationSensitivity"; const QString TANGENT_MIX_VAL = "Tangent/mixValue"; //const QString TANGENT_DIR_MIN = "Tangent/directionMinimum"; //const QString TANGENT_DIR_MAX = "Tangent/directionMaximum"; class KisPropertiesConfiguration; class KisTangentTiltOptionWidget; class KisTangentTiltOption: public KisPaintOpOption //not really// { public: KisTangentTiltOption(); ~KisTangentTiltOption() override; /*These three give away which the index of the combobox for a given channel*/ int redChannel() const; int greenChannel() const; int blueChannel() const; int directionType() const; double elevationSensitivity() const; double mixValue() const; - qreal m_canvasAngle; - bool m_canvasAxisXMirrored; - bool m_canvasAxisYMirrored; /*This assigns the right axis to the component, based on index and maximum value*/ void swizzleAssign(qreal const horizontal, qreal const vertical, qreal const depth, qreal *component, int index, qreal maxvalue); //takes the RGB values and will deform them depending on tilt. void apply(const KisPaintInformation& info, qreal *r, qreal *g, qreal *b); void writeOptionSetting(KisPropertiesConfigurationSP setting) const override; void readOptionSetting(const KisPropertiesConfigurationSP setting) override; private: KisTangentTiltOptionWidget * m_options; }; #endif // KIS_TANGENT_TILT_OPTION_H