Krita Source Code Documentation
Loading...
Searching...
No Matches
InfiniteRulerAssistant.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net>
3 * SPDX-FileCopyrightText: 2010 Geoffry Song <goffrie@gmail.com>
4 * SPDX-FileCopyrightText: 2014 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
5 * SPDX-FileCopyrightText: 2017 Scott Petrovic <scottpetrovic@gmail.com>
6 * SPDX-FileCopyrightText: 2022 Julian Schmidt <julisch1107@web.de>
7 *
8 * SPDX-License-Identifier: LGPL-2.0-or-later
9 */
10
12
13#include "kis_debug.h"
14#include <klocalizedstring.h>
15
16#include <QPainter>
17#include <QPainterPath>
18#include <QTransform>
19
20#include <kis_canvas2.h>
22#include <kis_algebra_2d.h>
23
24#include <math.h>
25
27 : RulerAssistant("infinite ruler", i18n("Infinite Ruler assistant"))
28{
29}
30
31InfiniteRulerAssistant::InfiniteRulerAssistant(const InfiniteRulerAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
32 : RulerAssistant(rhs, handleMap)
33{
34}
35
36KisPaintingAssistantSP InfiniteRulerAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
37{
38 return KisPaintingAssistantSP(new InfiniteRulerAssistant(*this, handleMap));
39}
40
41QPointF InfiniteRulerAssistant::project(const QPointF& pt, const QPointF& strokeBegin, const bool checkForInitialMovement, qreal moveThresholdPt)
42{
43 Q_ASSERT(isAssistantComplete());
44 //code nicked from the perspective ruler.
45 qreal dx = pt.x() - strokeBegin.x();
46 qreal dy = pt.y() - strokeBegin.y();
47 if (checkForInitialMovement && KisAlgebra2D::norm(QPointF(dx, dy)) < moveThresholdPt) {
48 // allow some movement before snapping
49 return strokeBegin;
50 }
51
52 QLineF snapLine = QLineF(*handles()[0], *handles()[1]);
53
54 dx = snapLine.dx();
55 dy = snapLine.dy();
56 const qreal
57 dx2 = dx * dx,
58 dy2 = dy * dy,
59 invsqrlen = 1.0 / (dx2 + dy2);
60 QPointF r(dx2 * pt.x() + dy2 * snapLine.x1() + dx * dy * (pt.y() - snapLine.y1()),
61 dx2 * snapLine.y1() + dy2 * pt.y() + dx * dy * (pt.x() - snapLine.x1()));
62 r *= invsqrlen;
63 return r;
64 //return pt;
65}
66
67QPointF InfiniteRulerAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin, const bool /*snapToAny*/, qreal moveThresholdPt)
68{
69 return project(pt, strokeBegin, true, moveThresholdPt);
70}
71
72void InfiniteRulerAssistant::adjustLine(QPointF &point, QPointF &strokeBegin)
73{
74
75 point = project(point, strokeBegin, false, 0.0);
76 strokeBegin = project(strokeBegin, strokeBegin, false, 0.0);
77}
78
80 if (subdivisions() == 0) {
81 return;
82 }
83
84 // Get handle positions
85 QTransform document2widget = converter->documentToWidgetTransform();
86
87 QPointF p1 = document2widget.map(*handles()[0]);
88 QPointF p2 = document2widget.map(*handles()[1]);
89
90 const qreal scale = 16.0 / 2;
91 const qreal minorScale = scale / 2;
92 const QRectF clipping = QRectF(gc.viewport()).adjusted(-scale, -scale, scale, scale);
93 // If the lines would end up closer to each other than this threshold (in
94 // screen coordinates), they are not rendered, as they wouldn't be
95 // distinguishable anymore.
96 const qreal threshold = 3.0;
97
98 // Calculate line direction and normal vector
99 QPointF delta = p2 - p1;
100 qreal length = sqrt(KisPaintingAssistant::norm2(delta));
101 qreal stepsize = length / subdivisions();
102
103 // Only draw if lines are far enough apart
104 if (stepsize >= threshold) {
105 QPointF normal = QPointF(delta.y(), -delta.x());
106 normal /= length;
107
108 // Clip the line to the viewport and find the t parameters for these
109 // points
110 ClippingResult res = clipLineParametric(QLineF(p1, p2), clipping);
111 // Abort if line is outside clipping area
112 if (!res.intersects) {
113 return;
114 }
115 // Calculate indices to start and end the subdivisions on screen by
116 // rounding further "away" from the visible area, ensuring that all
117 // divisions that could be visible are actually drawn
118 int istart = (int) floor(res.tmin * subdivisions());
119 int iend = (int) ceil(res.tmax * subdivisions());
120
121 QPainterPath path;
122 QPainterPath highlight;
123
124 // Draw the major subdivisions
125 for (int ii = istart; ii < iend; ++ii) {
126 QPointF pos = p1 + delta * ((qreal)ii / subdivisions());
127 // No additional clipping check needed, since we're already
128 // constrained inside it by the ii values.
129 // However, don't draw over the thicker lines where the actual
130 // ruler is located and already drawn!
131 if (0 <= ii && ii < subdivisions()) {
132 continue;
133 }
134 // Special case at ii == subdivs: minor subdivisions are needed
135 // here, but not the major one, as this is the last line drawn of
136 // the main ruler.
137 if (ii != subdivisions()) {
138 // Highlight the integer multiples of the ruler length
139 if (ii % subdivisions() == 0) {
140 highlight.moveTo(pos - normal * scale);
141 highlight.lineTo(pos + normal * scale);
142 } else {
143 path.moveTo(pos - normal * scale);
144 path.lineTo(pos + normal * scale);
145 }
146 }
147
148 // Draw minor subdivisions, if they exist (implicit check due to
149 // the loop bounds)
150 // Skip if major subdivisions are too close already
151 if (stepsize / minorSubdivisions() < threshold)
152 continue;
153 // Draw minor marks in between the major ones
154 for (int jj = 1; jj < minorSubdivisions(); ++jj) {
155 QPointF mpos = pos + delta * ((qreal)jj / (subdivisions() * minorSubdivisions()));
156
157 path.moveTo(mpos - normal * minorScale);
158 path.lineTo(mpos + normal * minorScale);
159 }
160 }
161
162 // Draw highlight as regular path (2 px wide)
163 drawPath(gc, highlight);
164 // Draw normal lines as preview (1 px wide)
165 drawPreview(gc, path);
166 }
167}
168
169void InfiniteRulerAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
170{
171 gc.save();
172 gc.resetTransform();
173
174 if (isAssistantComplete() && isSnappingActive() && previewVisible) {
175
176 // Extend the line to the full viewport
177 QTransform initialTransform = converter->documentToWidgetTransform();
178 QLineF snapLine = QLineF(initialTransform.map(*handles()[0]), initialTransform.map(*handles()[1]));
179 QRect viewport = gc.viewport();
180 KisAlgebra2D::intersectLineRect(snapLine, viewport, true);
181
182 // Draw as preview (thin lines)
183 QPainterPath path;
184 path.moveTo(snapLine.p1());
185 path.lineTo(snapLine.p2());
186 drawPreview(gc, path);
187
188 // Add the extended subdivisions, if active
189 // When the number of subdivisions (or minor subdivisions) is set to
190 // 0, the respective feature is turned off and won't be rendered.
191 if (subdivisions() > 0) {
192 drawSubdivisions(gc, converter);
193 }
194 }
195
196 gc.restore();
197
198 RulerAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible);
199}
200
201InfiniteRulerAssistant::ClippingResult InfiniteRulerAssistant::clipLineParametric(QLineF line, QRectF rect, bool extendFirst, bool extendSecond) {
202 double dx = line.x2() - line.x1();
203 double dy = line.y2() - line.y1();
204
205 double q1 = line.x1() - rect.x();
206 double q2 = rect.x() + rect.width() - line.x1();
207 double q3 = line.y1() - rect.y();
208 double q4 = rect.y() + rect.height() - line.y1();
209
210 QVector<double> p = QVector<double>({-dx, dx, -dy, dy});
212
213 double tmin = extendFirst ? -std::numeric_limits<double>::infinity() : 0.0;
214 double tmax = extendSecond ? +std::numeric_limits<double>::infinity() : 1.0;
215
216 for (int i = 0; i < p.length(); i++) {
217
218 if (p[i] == 0 && q[i] < 0) {
219 // Line is parallel to this boundary and outside of it
220 return ClippingResult{false, 0, 0};
221
222 } else if (p[i] < 0) {
223 // Line moves into this boundary with increasing t
224 // Set minimum t where it just comes in
225 double t = q[i] / p[i];
226 if (t > tmin) {
227 tmin = t;
228 }
229
230 } else if (p[i] > 0) {
231 // Line moves out of this boundary with increasing t
232 // Set maximum t where it is still inside
233 double t = q[i] / p[i];
234 if (t < tmax) {
235 tmax = t;
236 }
237 }
238 }
239
240 // The line intersects the rectangle if tmin < tmax.
241 return ClippingResult{tmin < tmax, tmin, tmax};
242}
243
245{
246 return (*handles()[0]);
247}
248
250{
251 return handles().size() >= 2;
252}
253
255
257
259{
260 return "infinite ruler";
261}
262
264{
265 return i18n("Infinite Ruler");
266}
267
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
QPointF q1
const Params2D p
QPointF q3
QPointF p2
QPointF q2
QPointF p1
KisPaintingAssistant * createPaintingAssistant() const override
~InfiniteRulerAssistantFactory() override
QPointF getDefaultEditorPosition() const override
void drawAssistant(QPainter &gc, const QRectF &updateRect, const KisCoordinatesConverter *converter, bool cached=true, KisCanvas2 *canvas=0, bool assistantVisible=true, bool previewVisible=true) override
void drawSubdivisions(QPainter &gc, const KisCoordinatesConverter *converter)
bool isAssistantComplete() const override
void adjustLine(QPointF &point, QPointF &strokeBegin) override
KisPaintingAssistantSP clone(QMap< KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP > &handleMap) const override
static ClippingResult clipLineParametric(QLineF line, QRectF rect, bool extendFirst=true, bool extendSecond=true)
QPointF project(const QPointF &pt, const QPointF &strokeBegin, const bool checkForInitialMovement, qreal moveThresholdPt)
QPointF adjustPosition(const QPointF &point, const QPointF &strokeBegin, const bool snapToAny, qreal moveThresholdPt) override
void drawPath(QPainter &painter, const QPainterPath &path, bool drawActive=true)
void drawPreview(QPainter &painter, const QPainterPath &path)
static double norm2(const QPointF &p)
const QList< KisPaintingAssistantHandleSP > & handles() const
void drawAssistant(QPainter &gc, const QRectF &updateRect, const KisCoordinatesConverter *converter, bool cached, KisCanvas2 *canvas, bool assistantVisible=true, bool previewVisible=true) override
int subdivisions() const
int minorSubdivisions() const
QSharedPointer< KisPaintingAssistant > KisPaintingAssistantSP
Definition kis_types.h:189
qreal norm(const T &a)
bool intersectLineRect(QLineF &line, const QRect rect, bool extend)