File indexing completed on 2025-02-02 04:22:31

0001 /*
0002  *  SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_tool_line_helper.h"
0008 
0009 #include <QtMath>
0010 
0011 #include "kis_algebra_2d.h"
0012 #include "kis_painting_information_builder.h"
0013 #include "kis_image.h"
0014 
0015 #include "kis_canvas_resource_provider.h"
0016 #include <brushengine/kis_paintop_preset.h>
0017 
0018 struct KisToolLineHelper::Private
0019 {
0020     Private(KisPaintingInformationBuilder *_infoBuilder)
0021         : infoBuilder(_infoBuilder),
0022           useSensors(true),
0023           enabled(true)
0024     {
0025     }
0026 
0027     QVector<KisPaintInformation> linePoints;
0028     KisPaintingInformationBuilder *infoBuilder;
0029     bool useSensors;
0030     bool enabled;
0031 };
0032 
0033 KisToolLineHelper::KisToolLineHelper(KisPaintingInformationBuilder *infoBuilder,
0034                                      KoCanvasResourceProvider *resourceManager,
0035                                      const KUndo2MagicString &transactionText)
0036     : KisToolFreehandHelper(infoBuilder,
0037                             resourceManager,
0038                             transactionText,
0039                             new KisSmoothingOptions(false)),
0040       m_d(new Private(infoBuilder))
0041 {
0042 }
0043 
0044 KisToolLineHelper::~KisToolLineHelper()
0045 {
0046     delete m_d;
0047 }
0048 
0049 void KisToolLineHelper::setEnabled(bool value)
0050 {
0051     m_d->enabled = value;
0052 }
0053 
0054 void KisToolLineHelper::setUseSensors(bool value)
0055 {
0056     m_d->useSensors = value;
0057 }
0058 
0059 void KisToolLineHelper::repaintLine(KisImageWSP image, KisNodeSP node,
0060                                     KisStrokesFacade *strokesFacade)
0061 {
0062     if (!m_d->enabled) return;
0063 
0064     cancelPaint();
0065     if (m_d->linePoints.isEmpty()) return;
0066 
0067     qreal startAngle = 0.0;
0068     if (m_d->linePoints.length() > 1) {
0069         startAngle = KisAlgebra2D::directionBetweenPoints(m_d->linePoints[0].pos(),
0070                                                           m_d->linePoints[1].pos(),
0071                                                           0.0);
0072     }
0073 
0074     KisPaintOpPresetSP preset = resourceManager()->resource(KoCanvasResource::CurrentPaintOpPreset)
0075             .value<KisPaintOpPresetSP>();
0076 
0077     if (preset->settings()->paintOpSize() <= 1) {
0078         KisPaintInformation begin = m_d->linePoints.first();
0079         KisPaintInformation end = m_d->linePoints.last();
0080         m_d->linePoints.clear();
0081         m_d->linePoints.append(begin);
0082         m_d->linePoints.append(end);
0083         adjustPointsToDDA(m_d->linePoints);
0084     }
0085 
0086     QVector<KisPaintInformation>::const_iterator it = m_d->linePoints.constBegin();
0087     QVector<KisPaintInformation>::const_iterator end = m_d->linePoints.constEnd();
0088 
0089     initPaintImpl(startAngle, *it, resourceManager(), image, node, strokesFacade);
0090     ++it;
0091 
0092     while (it != end) {
0093         paintLine(*(it - 1), *it);
0094         ++it;
0095     }
0096 }
0097 
0098 void KisToolLineHelper::start(KoPointerEvent *event, KoCanvasResourceProvider *resourceManager)
0099 {
0100     if (!m_d->enabled) return;
0101 
0102     // Ignore the elapsed stroke time, so that the line tool will behave as if the whole stroke is
0103     // drawn at once. This should prevent any possible spurious dabs caused by airbrushing features.
0104     KisPaintInformation pi =
0105             m_d->infoBuilder->startStroke(event, 0, resourceManager);
0106 
0107     if (!m_d->useSensors) {
0108         pi = KisPaintInformation(pi.pos());
0109     }
0110 
0111     m_d->linePoints.append(pi);
0112 }
0113 
0114 void KisToolLineHelper::addPoint(KoPointerEvent *event, const QPointF &overridePos)
0115 {
0116     if (!m_d->enabled) return;
0117 
0118     // Ignore the elapsed stroke time, so that the line tool will behave as if the whole stroke is
0119     // drawn at once. This should prevent any possible spurious dabs caused by airbrushing features.
0120     KisPaintInformation pi =
0121             m_d->infoBuilder->continueStroke(event, 0);
0122 
0123     addPoint(pi, overridePos);
0124 }
0125 
0126 void KisToolLineHelper::addPoint(KisPaintInformation pi, const QPointF &overridePos)
0127 {
0128     if (!m_d->enabled) return;
0129     if (!m_d->useSensors) {
0130         pi = KisPaintInformation(pi.pos());
0131     }
0132 
0133     if (!overridePos.isNull()) {
0134         pi.setPos(overridePos);
0135     }
0136 
0137     if (m_d->linePoints.size() > 1) {
0138         const QPointF startPos = m_d->linePoints.first().pos();
0139         const QPointF endPos = pi.pos();
0140 
0141         if (!KisAlgebra2D::fuzzyPointCompare(startPos, endPos)) {
0142             const qreal maxDistance = kisDistance(startPos, endPos);
0143             const QPointF unit = (endPos - startPos) / maxDistance;
0144 
0145             QVector<KisPaintInformation>::iterator it = m_d->linePoints.begin();
0146             ++it;
0147             while (it != m_d->linePoints.end()) {
0148                 qreal dist = kisDistance(startPos, it->pos());
0149                 if (dist < maxDistance) {
0150                     QPointF pos = startPos + unit * dist;
0151                     it->setPos(pos);
0152                     ++it;
0153                 } else {
0154                     it = m_d->linePoints.erase(it);
0155                 }
0156             }
0157         } else {
0158             m_d->linePoints.clear();
0159         }
0160     }
0161 
0162     m_d->linePoints.append(pi);
0163 
0164 }
0165 
0166 void KisToolLineHelper::translatePoints(const QPointF &offset)
0167 {
0168     if (!m_d->enabled) return;
0169 
0170     QVector<KisPaintInformation>::iterator it = m_d->linePoints.begin();
0171     while (it != m_d->linePoints.end()) {
0172         it->setPos(it->pos() + offset);
0173         ++it;
0174     }
0175 }
0176 
0177 void KisToolLineHelper::movePointsTo(const QPointF &startPoint, const QPointF &endPoint)
0178 {
0179     if (m_d->linePoints.size() <= 1 ) {
0180         return;
0181     }
0182 
0183     if (KisAlgebra2D::fuzzyPointCompare(startPoint, endPoint)) {
0184         return;
0185     }
0186 
0187     if (m_d->linePoints.size() > 1) {
0188         const qreal maxDistance = kisDistance(startPoint, endPoint);
0189         const QPointF unit = (endPoint - startPoint) / maxDistance;
0190 
0191         QVector<KisPaintInformation>::iterator it = m_d->linePoints.begin();
0192         ++it;
0193         while (it != m_d->linePoints.end()) {
0194             qreal dist = kisDistance(startPoint, it->pos());
0195             QPointF pos = startPoint + unit * dist;
0196             it->setPos(pos);
0197             ++it;
0198         }
0199     }
0200 }
0201 
0202 void KisToolLineHelper::end()
0203 {
0204     if (!m_d->enabled) return;
0205     KIS_ASSERT_RECOVER_RETURN(isRunning());
0206 
0207     endPaint();
0208     clearPoints();
0209 }
0210 
0211 
0212 void KisToolLineHelper::cancel()
0213 {
0214     if (!m_d->enabled) return;
0215     KIS_ASSERT_RECOVER_RETURN(isRunning());
0216 
0217     cancelPaint();
0218     clearPoints();
0219 }
0220 
0221 
0222 void KisToolLineHelper::clearPoints()
0223 {
0224     m_d->linePoints.clear();
0225 }
0226 
0227 
0228 void KisToolLineHelper::clearPaint()
0229 {
0230     if (!m_d->enabled) return;
0231 
0232     cancelPaint();
0233 }
0234 
0235 void KisToolLineHelper::adjustPointsToDDA(QVector<KisPaintInformation> &points)
0236 {
0237     int x = qFloor(points.first().pos().x());
0238     int y = qFloor(points.first().pos().y());
0239 
0240     int x2 = qFloor(points.last().pos().x());
0241     int y2 = qFloor(points.last().pos().y());
0242 
0243     // Width and height of the line
0244     int xd = x2 - x;
0245     int yd = y2 - y;
0246 
0247     float m = 0;
0248     bool lockAxis = true;
0249 
0250     if (xd == 0) {
0251         m = 2.0;
0252     } else if ( yd != 0) {
0253         lockAxis = false;
0254         m = (float)yd / (float)xd;
0255     }
0256 
0257     float fx = x;
0258     float fy = y;
0259 
0260     int inc;
0261     int dist;
0262 
0263     if (fabs(m) > 1.0f) {
0264         inc = (yd > 0) ? 1 : -1;
0265         m = (lockAxis)? 0 : 1.0f / m;
0266         m *= inc;
0267 
0268         for (int i = 0; i < points.size(); i++){
0269             dist = abs(qFloor(points.at(i).pos().y()) - y);
0270             fy = y + (dist * inc);
0271             fx = qRound(x + (dist * m));
0272             points[i].setPos(QPointF(fx,fy));
0273         }
0274 
0275     } else {
0276         inc = (xd > 0) ? 1 : -1;
0277         m *= inc;
0278 
0279         for (int i = 0; i < points.size(); i++){
0280             dist = abs(qFloor(points.at(i).pos().x()) - x);
0281             fx = x + (dist * inc);
0282             fy = qRound(y + (dist * m));
0283             points[i].setPos(QPointF(fx,fy));
0284         }
0285     }
0286 }