File indexing completed on 2024-05-12 15:57:02

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