File indexing completed on 2024-05-19 04:25:08

0001 /*
0002  *  SPDX-FileCopyrightText: 2016 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KisHandlePainterHelper.h"
0008 
0009 #include <QPainter>
0010 #include <QPainterPath>
0011 #include "kis_algebra_2d.h"
0012 #include "kis_painting_tweaks.h"
0013 
0014 using KisPaintingTweaks::PenBrushSaver;
0015 
0016 KisHandlePainterHelper::KisHandlePainterHelper(QPainter *_painter, qreal handleRadius, int decorationThickness)
0017     : m_painter(_painter),
0018       m_originalPainterTransform(m_painter->transform()),
0019       m_painterTransform(m_painter->transform()),
0020       m_handleRadius(handleRadius),
0021       m_decorationThickness(decorationThickness),
0022       m_decomposedMatrix(m_painterTransform)
0023 {
0024     init();
0025 }
0026 
0027 KisHandlePainterHelper::KisHandlePainterHelper(QPainter *_painter, const QTransform &originalPainterTransform, qreal handleRadius, int decorationThickness)
0028     : m_painter(_painter),
0029       m_originalPainterTransform(originalPainterTransform),
0030       m_painterTransform(m_painter->transform()),
0031       m_handleRadius(handleRadius),
0032       m_decorationThickness(decorationThickness),
0033       m_decomposedMatrix(m_painterTransform)
0034 {
0035     init();
0036 }
0037 
0038 KisHandlePainterHelper::KisHandlePainterHelper(KisHandlePainterHelper &&rhs)
0039     : m_painter(rhs.m_painter),
0040       m_originalPainterTransform(rhs.m_originalPainterTransform),
0041       m_painterTransform(rhs.m_painterTransform),
0042       m_handleRadius(rhs.m_handleRadius),
0043       m_decorationThickness(rhs.m_decorationThickness),
0044       m_decomposedMatrix(rhs.m_decomposedMatrix),
0045       m_handleTransform(rhs.m_handleTransform),
0046       m_handlePolygon(rhs.m_handlePolygon),
0047       m_handleStyle(rhs.m_handleStyle)
0048 {
0049     // disable the source helper
0050     rhs.m_painter = 0;
0051 }
0052 
0053 void KisHandlePainterHelper::init()
0054 {
0055     m_handleStyle = KisHandleStyle::inheritStyle();
0056 
0057     m_painter->setTransform(QTransform());
0058     m_handleTransform = m_decomposedMatrix.shearTransform() * m_decomposedMatrix.rotateTransform();
0059 
0060     if (m_handleRadius > 0.0) {
0061         const QRectF handleRect(-m_handleRadius, -m_handleRadius, 2 * m_handleRadius, 2 * m_handleRadius);
0062         m_handlePolygon = m_handleTransform.map(QPolygonF(handleRect));
0063     }
0064 }
0065 
0066 KisHandlePainterHelper::~KisHandlePainterHelper() {
0067     if (m_painter) {
0068         m_painter->setTransform(m_originalPainterTransform);
0069     }
0070 }
0071 
0072 void KisHandlePainterHelper::setHandleStyle(const KisHandleStyle &style)
0073 {
0074     m_handleStyle = style;
0075 }
0076 
0077 void KisHandlePainterHelper::drawHandleRect(const QPointF &center, qreal radius, QPoint offset = QPoint(0,0))
0078 {
0079     KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
0080 
0081     QRectF handleRect(-radius, -radius, 2 * radius, 2 * radius);
0082     QPolygonF handlePolygon = m_handleTransform.map(QPolygonF(handleRect));
0083     handlePolygon.translate(m_painterTransform.map(center));
0084 
0085     handlePolygon.translate(offset);
0086 
0087     const QPen originalPen = m_painter->pen();
0088 
0089     // temporarily set the pen width to 2 to avoid pixel shifting dropping pixels the border
0090     QPen *tempPen = new QPen(m_painter->pen());
0091     tempPen->setCosmetic(true);
0092     tempPen->setWidth(4  * m_decorationThickness);
0093     const QPen customPen = *tempPen;
0094     m_painter->setPen(customPen);
0095 
0096 
0097     Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
0098         it.stylePair.first.setWidthF(it.stylePair.first.widthF() * m_decorationThickness);
0099         PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
0100         m_painter->drawPolygon(handlePolygon);
0101     }
0102 
0103     m_painter->setPen(originalPen);
0104 }
0105 
0106 void KisHandlePainterHelper::drawHandleCircle(const QPointF &center, qreal radius) {
0107     KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
0108 
0109     QRectF handleRect(-radius, -radius, 2 * radius, 2 * radius);
0110     handleRect.translate(m_painterTransform.map(center));
0111 
0112     Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
0113         it.stylePair.first.setWidthF(it.stylePair.first.widthF() * m_decorationThickness);
0114         PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
0115         m_painter->drawEllipse(handleRect);
0116     }
0117 }
0118 
0119 void KisHandlePainterHelper::drawHandleCircle(const QPointF &center)
0120 {
0121     drawHandleCircle(center, m_handleRadius);
0122 }
0123 
0124 void KisHandlePainterHelper::drawHandleSmallCircle(const QPointF &center)
0125 {
0126     drawHandleCircle(center, 0.7 * m_handleRadius);
0127 }
0128 
0129 void KisHandlePainterHelper::drawHandleLine(const QLineF &line, qreal width, QVector<qreal> dashPattern, qreal dashOffset)
0130 {
0131     KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
0132 
0133     QPainterPath p;
0134     p.moveTo(m_painterTransform.map(line.p1()));
0135     p.lineTo(m_painterTransform.map(line.p2()));
0136     QPainterPathStroker s;
0137     s.setWidth(width);
0138     if (!dashPattern.isEmpty()) {
0139         s.setDashPattern(dashPattern);
0140         s.setDashOffset(dashOffset);
0141     }
0142     s.setCapStyle(Qt::RoundCap);
0143     s.setJoinStyle(Qt::RoundJoin);
0144     p = s.createStroke(p);
0145 
0146     Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
0147         it.stylePair.first.setWidthF(it.stylePair.first.widthF() * m_decorationThickness);
0148         PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
0149         m_painter->strokePath(p, m_painter->pen());
0150         m_painter->fillPath(p, m_painter->brush());
0151     }
0152 }
0153 
0154 void KisHandlePainterHelper::drawHandleRect(const QPointF &center) {
0155     KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
0156     QPolygonF paintingPolygon = m_handlePolygon.translated(m_painterTransform.map(center));
0157 
0158     Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
0159         it.stylePair.first.setWidthF(it.stylePair.first.widthF() * m_decorationThickness);
0160         PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
0161         m_painter->drawPolygon(paintingPolygon);
0162     }
0163 }
0164 
0165 void KisHandlePainterHelper::drawGradientHandle(const QPointF &center, qreal radius) {
0166     KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
0167 
0168     QPolygonF handlePolygon;
0169 
0170     handlePolygon << QPointF(-radius, 0);
0171     handlePolygon << QPointF(0, radius);
0172     handlePolygon << QPointF(radius, 0);
0173     handlePolygon << QPointF(0, -radius);
0174 
0175     handlePolygon = m_handleTransform.map(handlePolygon);
0176     handlePolygon.translate(m_painterTransform.map(center));
0177 
0178     Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
0179         it.stylePair.first.setWidthF(it.stylePair.first.widthF() * m_decorationThickness);
0180         PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
0181         m_painter->drawPolygon(handlePolygon);
0182     }
0183 }
0184 
0185 void KisHandlePainterHelper::drawGradientHandle(const QPointF &center)
0186 {
0187     drawGradientHandle(center, 1.41 * m_handleRadius);
0188 }
0189 
0190 void KisHandlePainterHelper::drawGradientCrossHandle(const QPointF &center, qreal radius) {
0191     KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
0192 
0193     { // Draw a cross
0194         QPainterPath p;
0195         p.moveTo(-radius, -radius);
0196         p.lineTo(radius, radius);
0197         p.moveTo(radius, -radius);
0198         p.lineTo(-radius, radius);
0199 
0200         p = m_handleTransform.map(p);
0201         p.translate(m_painterTransform.map(center));
0202 
0203         Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
0204             it.stylePair.first.setWidthF(it.stylePair.first.widthF() * m_decorationThickness);
0205             PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
0206             m_painter->drawPath(p);
0207         }
0208     }
0209 
0210     { // Draw a square
0211         const qreal halfRadius = 0.5 * radius;
0212 
0213         QPolygonF handlePolygon;
0214         handlePolygon << QPointF(-halfRadius, 0);
0215         handlePolygon << QPointF(0, halfRadius);
0216         handlePolygon << QPointF(halfRadius, 0);
0217         handlePolygon << QPointF(0, -halfRadius);
0218 
0219         handlePolygon = m_handleTransform.map(handlePolygon);
0220         handlePolygon.translate(m_painterTransform.map(center));
0221 
0222         Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
0223             it.stylePair.first.setWidthF(it.stylePair.first.widthF() * m_decorationThickness);
0224             PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
0225             m_painter->drawPolygon(handlePolygon);
0226         }
0227     }
0228 }
0229 
0230 void KisHandlePainterHelper::drawArrow(const QPointF &pos, const QPointF &from, qreal radius)
0231 {
0232     KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
0233 
0234     QPainterPath p;
0235 
0236     QLineF line(pos, from);
0237     line.setLength(radius);
0238 
0239     QPointF norm = KisAlgebra2D::leftUnitNormal(pos - from);
0240     norm *= 0.34 * radius;
0241 
0242     p.moveTo(line.p2() + norm);
0243     p.lineTo(line.p1());
0244     p.lineTo(line.p2() - norm);
0245 
0246     p.translate(-pos);
0247 
0248     p = m_handleTransform.map(p).translated(m_painterTransform.map(pos));
0249 
0250     Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
0251         it.stylePair.first.setWidthF(it.stylePair.first.widthF() * m_decorationThickness);
0252         PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
0253         m_painter->drawPath(p);
0254     }
0255 }
0256 
0257 void KisHandlePainterHelper::drawGradientArrow(const QPointF &start, const QPointF &end, qreal radius)
0258 {
0259     KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
0260 
0261     QPainterPath p;
0262     p.moveTo(start);
0263     p.lineTo(end);
0264     p = m_painterTransform.map(p);
0265 
0266     Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.lineIterations) {
0267         it.stylePair.first.setWidthF(it.stylePair.first.widthF()*m_decorationThickness);
0268         PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
0269         m_painter->drawPath(p);
0270     }
0271 
0272     const qreal length = kisDistance(start, end);
0273     const QPointF diff = end - start;
0274 
0275     if (length > 5 * radius) {
0276         drawArrow(start + 0.33 * diff, start, radius);
0277         drawArrow(start + 0.66 * diff, start, radius);
0278     } else if (length > 3 * radius) {
0279         drawArrow(start + 0.5 * diff, start, radius);
0280     }
0281 }
0282 
0283 void KisHandlePainterHelper::drawRubberLine(const QPolygonF &poly) {
0284     KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
0285 
0286     QPolygonF paintingPolygon = m_painterTransform.map(poly);
0287 
0288     Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.lineIterations) {
0289         it.stylePair.first.setWidthF(it.stylePair.first.widthF() * m_decorationThickness);
0290         PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
0291         m_painter->drawPolygon(paintingPolygon);
0292     }
0293 }
0294 
0295 void KisHandlePainterHelper::drawConnectionLine(const QLineF &line)
0296 {
0297     drawConnectionLine(line.p1(), line.p2());
0298 }
0299 
0300 void KisHandlePainterHelper::drawConnectionLine(const QPointF &p1, const QPointF &p2)
0301 {
0302     KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
0303 
0304     QPointF realP1 = m_painterTransform.map(p1);
0305     QPointF realP2 = m_painterTransform.map(p2);
0306 
0307     Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.lineIterations) {
0308         it.stylePair.first.setWidthF(it.stylePair.first.widthF() * m_decorationThickness);
0309         PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
0310         m_painter->drawLine(realP1, realP2);
0311     }
0312 }
0313 
0314 void KisHandlePainterHelper::drawPath(const QPainterPath &path)
0315 {
0316     const QPainterPath realPath = m_painterTransform.map(path);
0317 
0318     Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.lineIterations) {
0319         it.stylePair.first.setWidthF(it.stylePair.first.widthF() * m_decorationThickness);
0320         PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
0321         m_painter->drawPath(realPath);
0322     }
0323 }
0324 
0325 void KisHandlePainterHelper::drawPixmap(const QPixmap &pixmap, QPointF position, int size, QRectF sourceRect)
0326 {
0327     QPointF handlePolygon = m_painterTransform.map(position);
0328 
0329     QPoint offsetPosition(0, 40);
0330     handlePolygon += offsetPosition;
0331 
0332     handlePolygon -= QPointF(size*0.5,size*0.5);
0333 
0334     m_painter->drawPixmap(QRect(handlePolygon.x(), handlePolygon.y(),
0335                                 size, size),
0336                                 pixmap,
0337                                 sourceRect);
0338 }
0339 
0340 void KisHandlePainterHelper::fillHandleRect(const QPointF &center, qreal radius, QColor fillColor, QPoint offset = QPoint(0,0))
0341 {
0342     KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
0343 
0344     QRectF handleRect(-radius, -radius, 2 * radius, 2 * radius);
0345     QPolygonF handlePolygon = m_handleTransform.map(QPolygonF(handleRect));
0346     handlePolygon.translate(m_painterTransform.map(center));
0347 
0348     QPainterPath painterPath;
0349     painterPath.addPolygon(handlePolygon);
0350 
0351     // offset that happens after zoom transform. This means the offset will be the same, no matter the zoom level
0352     // this is good for UI elements that need to be below the bounding box
0353     painterPath.translate(offset);
0354 
0355     const QPainterPath pathToSend = painterPath;
0356     const QBrush brushStyle(fillColor);
0357     m_painter->fillPath(pathToSend, brushStyle);
0358 }