File indexing completed on 2024-12-22 04:14:23
0001 /* 0002 * SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net> 0003 * SPDX-FileCopyrightText: 2010 Geoffry Song <goffrie@gmail.com> 0004 * SPDX-FileCopyrightText: 2014 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com> 0005 * SPDX-FileCopyrightText: 2017 Scott Petrovic <scottpetrovic@gmail.com> 0006 * SPDX-FileCopyrightText: 2022 Julian Schmidt <julisch1107@web.de> 0007 * 0008 * SPDX-License-Identifier: LGPL-2.0-or-later 0009 */ 0010 0011 #include "InfiniteRulerAssistant.h" 0012 0013 #include "kis_debug.h" 0014 #include <klocalizedstring.h> 0015 0016 #include <QPainter> 0017 #include <QPainterPath> 0018 #include <QTransform> 0019 0020 #include <kis_canvas2.h> 0021 #include <kis_coordinates_converter.h> 0022 #include <kis_algebra_2d.h> 0023 0024 #include <math.h> 0025 0026 InfiniteRulerAssistant::InfiniteRulerAssistant() 0027 : RulerAssistant("infinite ruler", i18n("Infinite Ruler assistant")) 0028 { 0029 } 0030 0031 InfiniteRulerAssistant::InfiniteRulerAssistant(const InfiniteRulerAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) 0032 : RulerAssistant(rhs, handleMap) 0033 { 0034 } 0035 0036 KisPaintingAssistantSP InfiniteRulerAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const 0037 { 0038 return KisPaintingAssistantSP(new InfiniteRulerAssistant(*this, handleMap)); 0039 } 0040 0041 QPointF InfiniteRulerAssistant::project(const QPointF& pt, const QPointF& strokeBegin, const bool checkForInitialMovement, qreal moveThresholdPt) 0042 { 0043 Q_ASSERT(isAssistantComplete()); 0044 //code nicked from the perspective ruler. 0045 qreal dx = pt.x() - strokeBegin.x(); 0046 qreal dy = pt.y() - strokeBegin.y(); 0047 if (checkForInitialMovement && KisAlgebra2D::norm(QPointF(dx, dy)) < moveThresholdPt) { 0048 // allow some movement before snapping 0049 return strokeBegin; 0050 } 0051 0052 QLineF snapLine = QLineF(*handles()[0], *handles()[1]); 0053 0054 dx = snapLine.dx(); 0055 dy = snapLine.dy(); 0056 const qreal 0057 dx2 = dx * dx, 0058 dy2 = dy * dy, 0059 invsqrlen = 1.0 / (dx2 + dy2); 0060 QPointF r(dx2 * pt.x() + dy2 * snapLine.x1() + dx * dy * (pt.y() - snapLine.y1()), 0061 dx2 * snapLine.y1() + dy2 * pt.y() + dx * dy * (pt.x() - snapLine.x1())); 0062 r *= invsqrlen; 0063 return r; 0064 //return pt; 0065 } 0066 0067 QPointF InfiniteRulerAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin, const bool /*snapToAny*/, qreal moveThresholdPt) 0068 { 0069 return project(pt, strokeBegin, true, moveThresholdPt); 0070 } 0071 0072 void InfiniteRulerAssistant::adjustLine(QPointF &point, QPointF &strokeBegin) 0073 { 0074 0075 point = project(point, strokeBegin, false, 0.0); 0076 strokeBegin = project(strokeBegin, strokeBegin, false, 0.0); 0077 } 0078 0079 void InfiniteRulerAssistant::drawSubdivisions(QPainter& gc, const KisCoordinatesConverter *converter) { 0080 if (subdivisions() == 0) { 0081 return; 0082 } 0083 0084 // Get handle positions 0085 QTransform document2widget = converter->documentToWidgetTransform(); 0086 0087 QPointF p1 = document2widget.map(*handles()[0]); 0088 QPointF p2 = document2widget.map(*handles()[1]); 0089 0090 const qreal scale = 16.0 / 2; 0091 const qreal minorScale = scale / 2; 0092 const QRectF clipping = QRectF(gc.viewport()).adjusted(-scale, -scale, scale, scale); 0093 // If the lines would end up closer to each other than this threshold (in 0094 // screen coordinates), they are not rendered, as they wouldn't be 0095 // distinguishable anymore. 0096 const qreal threshold = 3.0; 0097 0098 // Calculate line direction and normal vector 0099 QPointF delta = p2 - p1; 0100 qreal length = sqrt(KisPaintingAssistant::norm2(delta)); 0101 qreal stepsize = length / subdivisions(); 0102 0103 // Only draw if lines are far enough apart 0104 if (stepsize >= threshold) { 0105 QPointF normal = QPointF(delta.y(), -delta.x()); 0106 normal /= length; 0107 0108 // Clip the line to the viewport and find the t parameters for these 0109 // points 0110 ClippingResult res = clipLineParametric(QLineF(p1, p2), clipping); 0111 // Abort if line is outside clipping area 0112 if (!res.intersects) { 0113 return; 0114 } 0115 // Calculate indices to start and end the subdivisions on screen by 0116 // rounding further "away" from the visible area, ensuring that all 0117 // divisions that could be visible are actually drawn 0118 int istart = (int) floor(res.tmin * subdivisions()); 0119 int iend = (int) ceil(res.tmax * subdivisions()); 0120 0121 QPainterPath path; 0122 QPainterPath highlight; 0123 0124 // Draw the major subdivisions 0125 for (int ii = istart; ii < iend; ++ii) { 0126 QPointF pos = p1 + delta * ((qreal)ii / subdivisions()); 0127 // No additional clipping check needed, since we're already 0128 // constrained inside it by the ii values. 0129 // However, don't draw over the thicker lines where the actual 0130 // ruler is located and already drawn! 0131 if (0 <= ii && ii < subdivisions()) { 0132 continue; 0133 } 0134 // Special case at ii == subdivs: minor subdivisions are needed 0135 // here, but not the major one, as this is the last line drawn of 0136 // the main ruler. 0137 if (ii != subdivisions()) { 0138 // Highlight the integer multiples of the ruler length 0139 if (ii % subdivisions() == 0) { 0140 highlight.moveTo(pos - normal * scale); 0141 highlight.lineTo(pos + normal * scale); 0142 } else { 0143 path.moveTo(pos - normal * scale); 0144 path.lineTo(pos + normal * scale); 0145 } 0146 } 0147 0148 // Draw minor subdivisions, if they exist (implicit check due to 0149 // the loop bounds) 0150 // Skip if major subdivisions are too close already 0151 if (stepsize / minorSubdivisions() < threshold) 0152 continue; 0153 // Draw minor marks in between the major ones 0154 for (int jj = 1; jj < minorSubdivisions(); ++jj) { 0155 QPointF mpos = pos + delta * ((qreal)jj / (subdivisions() * minorSubdivisions())); 0156 0157 path.moveTo(mpos - normal * minorScale); 0158 path.lineTo(mpos + normal * minorScale); 0159 } 0160 } 0161 0162 // Draw highlight as regular path (2 px wide) 0163 drawPath(gc, highlight); 0164 // Draw normal lines as preview (1 px wide) 0165 drawPreview(gc, path); 0166 } 0167 } 0168 0169 void InfiniteRulerAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) 0170 { 0171 gc.save(); 0172 gc.resetTransform(); 0173 0174 if (isAssistantComplete() && isSnappingActive() && previewVisible) { 0175 0176 // Extend the line to the full viewport 0177 QTransform initialTransform = converter->documentToWidgetTransform(); 0178 QLineF snapLine = QLineF(initialTransform.map(*handles()[0]), initialTransform.map(*handles()[1])); 0179 QRect viewport = gc.viewport(); 0180 KisAlgebra2D::intersectLineRect(snapLine, viewport, true); 0181 0182 // Draw as preview (thin lines) 0183 QPainterPath path; 0184 path.moveTo(snapLine.p1()); 0185 path.lineTo(snapLine.p2()); 0186 drawPreview(gc, path); 0187 0188 // Add the extended subdivisions, if active 0189 // When the number of subdivisions (or minor subdivisions) is set to 0190 // 0, the respective feature is turned off and won't be rendered. 0191 if (subdivisions() > 0) { 0192 drawSubdivisions(gc, converter); 0193 } 0194 } 0195 0196 gc.restore(); 0197 0198 RulerAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); 0199 } 0200 0201 InfiniteRulerAssistant::ClippingResult InfiniteRulerAssistant::clipLineParametric(QLineF line, QRectF rect, bool extendFirst, bool extendSecond) { 0202 double dx = line.x2() - line.x1(); 0203 double dy = line.y2() - line.y1(); 0204 0205 double q1 = line.x1() - rect.x(); 0206 double q2 = rect.x() + rect.width() - line.x1(); 0207 double q3 = line.y1() - rect.y(); 0208 double q4 = rect.y() + rect.height() - line.y1(); 0209 0210 QVector<double> p = QVector<double>({-dx, dx, -dy, dy}); 0211 QVector<double> q = QVector<double>({q1, q2, q3, q4}); 0212 0213 double tmin = extendFirst ? -std::numeric_limits<double>::infinity() : 0.0; 0214 double tmax = extendSecond ? +std::numeric_limits<double>::infinity() : 1.0; 0215 0216 for (int i = 0; i < p.length(); i++) { 0217 0218 if (p[i] == 0 && q[i] < 0) { 0219 // Line is parallel to this boundary and outside of it 0220 return ClippingResult{false, 0, 0}; 0221 0222 } else if (p[i] < 0) { 0223 // Line moves into this boundary with increasing t 0224 // Set minimum t where it just comes in 0225 double t = q[i] / p[i]; 0226 if (t > tmin) { 0227 tmin = t; 0228 } 0229 0230 } else if (p[i] > 0) { 0231 // Line moves out of this boundary with increasing t 0232 // Set maximum t where it is still inside 0233 double t = q[i] / p[i]; 0234 if (t < tmax) { 0235 tmax = t; 0236 } 0237 } 0238 } 0239 0240 // The line intersects the rectangle if tmin < tmax. 0241 return ClippingResult{tmin < tmax, tmin, tmax}; 0242 } 0243 0244 QPointF InfiniteRulerAssistant::getDefaultEditorPosition() const 0245 { 0246 return (*handles()[0]); 0247 } 0248 0249 bool InfiniteRulerAssistant::isAssistantComplete() const 0250 { 0251 return handles().size() >= 2; 0252 } 0253 0254 InfiniteRulerAssistantFactory::InfiniteRulerAssistantFactory() = default; 0255 0256 InfiniteRulerAssistantFactory::~InfiniteRulerAssistantFactory() = default; 0257 0258 QString InfiniteRulerAssistantFactory::id() const 0259 { 0260 return "infinite ruler"; 0261 } 0262 0263 QString InfiniteRulerAssistantFactory::name() const 0264 { 0265 return i18n("Infinite Ruler"); 0266 } 0267 0268 KisPaintingAssistant* InfiniteRulerAssistantFactory::createPaintingAssistant() const 0269 { 0270 return new InfiniteRulerAssistant; 0271 }