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 ¢er, 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 ¢er, 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 ¢er) 0120 { 0121 drawHandleCircle(center, m_handleRadius); 0122 } 0123 0124 void KisHandlePainterHelper::drawHandleSmallCircle(const QPointF ¢er) 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 ¢er) { 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 ¢er, 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 ¢er) 0186 { 0187 drawGradientHandle(center, 1.41 * m_handleRadius); 0188 } 0189 0190 void KisHandlePainterHelper::drawGradientCrossHandle(const QPointF ¢er, 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 ¢er, 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 }