Krita Source Code Documentation
Loading...
Searching...
No Matches
CurvilinearPerspectiveAssistant.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 *
7 * SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9
11
12#include "kis_debug.h"
13#include <klocalizedstring.h>
14
15#include <QPainter>
16#include <QPainterPath>
17#include <QLinearGradient>
18#include <QTransform>
19
20#include <kis_canvas2.h>
22#include <kis_algebra_2d.h>
24
25#include <math.h>
26#include <limits>
27
29 : KisPaintingAssistant("curvilinear-perspective", i18n("Curvilinear Perspective assistant"))
30{
31}
32
33CurvilinearPerspectiveAssistant::CurvilinearPerspectiveAssistant(const CurvilinearPerspectiveAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
34 : KisPaintingAssistant(rhs, handleMap)
35{
36}
37
38KisPaintingAssistantSP CurvilinearPerspectiveAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
39{
40 return KisPaintingAssistantSP(new CurvilinearPerspectiveAssistant(*this, handleMap));
41}
42
43void CurvilinearPerspectiveAssistant::adjustLine(QPointF &point, QPointF &strokeBegin)
44{
45 point = QPointF();
46 strokeBegin = QPointF();
47}
48
49void CurvilinearPerspectiveAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, const KoColorDisplayRendererInterface *displayRenderInterface, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
50{
51 Q_UNUSED(cached);
52 Q_UNUSED(updateRect);
53
54 gc.save();
55 gc.resetTransform();
56
57 const QColor canvasAssistantColor = displayRenderInterface->convertColorToDisplayColorSpace(KoColor(effectiveAssistantColor(), KoColorSpaceRegistry::instance()->rgb8()));
58
59 if (isSnappingActive()) {
60
61 QTransform initialTransform = converter->documentToWidgetTransform();
62 QPainterPath baseGuidePath;
63 QPainterPath mouseGuidePath;
64
65 gc.setTransform(initialTransform);
66
67 /*
68 * Curvilinear perspective is created by circular arcs that intersect 2 vanishing points.
69 * As such, the center of the circle and the radius of the circle need to be determined.
70 *
71 * Create guidelines by selecting incremental multipliers for the assistant size between [-1, 1),
72 * and calculating the center location and radius of the circle to include the point
73 * at the location specified by the multiplier (the "arbitrary point").
74 *
75 * Formulas:
76 * Radius^2 = HalfHandleDist^2 + CenterDist^2 (b.c. The circle must include both vanishing points.)
77 * Radius^2 = (CenterDist + Multiplier * HalfHandleDist)^2 (b.c. The circle must include the arbitrary point)
78 *
79 * Solve for CenterDist and Radius:
80 * CenterDist = HalfHandleDist * (1 - Multiplier * Multiplier) / ( 2 * Multiplier)
81 * Radius = HalfHandleDist * (1 + Multiplier * Multiplier) / ( 2 * Multiplier)
82 *
83 */
84
85 QPointF p1 = *handles()[0];
86 QPointF p2 = *handles()[1];
87
88 double deltaX = p2.x() - p1.x();
89 double deltaY = p2.y() - p1.y();
90
91 // Copied from Two-Point Perspective's fading effect for approaching vanishing points.
92 // Set up the fading effect for the grid lines
93 // Needed so the grid density doesn't look distracting
94 QColor color = canvasAssistantColor;
95 QGradient fade = QLinearGradient(
96 QPointF(p1.x() - deltaY, p1.y() + deltaX),
97 QPointF(p1.x() + deltaY, p1.y() - deltaX));
98
99 color.setAlphaF(0.0);
100 fade.setColorAt(0.42, canvasAssistantColor);
101 fade.setColorAt(0.5, color);
102 fade.setColorAt(0.58, canvasAssistantColor);
103 const QPen pen = gc.pen();
104 const QBrush new_brush = QBrush(fade);
105 int width = 0;
106 const QPen new_pen = QPen(new_brush, width, pen.style());
107 gc.setPen(new_pen);
108
109 double handleDistance = KisAlgebra2D::norm(QPointF(deltaX, deltaY));
110 double halfHandleDist = handleDistance / 2.0;
111
112 double avgX = deltaX / 2.0 + p1.x();
113 double avgY = deltaY / 2.0 + p1.y();
114
115 // Rotate 90 degrees by formula: (-y, x)
116 // Then normalize vector.
117 double dirX = -deltaY / handleDistance;
118 double dirY = deltaX / handleDistance;
119
120 int resolution = halfHandleDist / 3;
121
122 if(assistantVisible) {
123
124 for(int i = -resolution; i < resolution; i++) {
125 // If i = 0, the circle would be infinitely far away with an infinite radius (aka a line)
126 if(i == 0) {
127 baseGuidePath.moveTo(QPointF(p1.x() - deltaX*2, p1.y() - deltaY*2));
128 baseGuidePath.lineTo(QPointF(p2.x() + deltaX*2, p2.y() + deltaY*2));
129 continue;
130 }
131 // Map loop iterator to multiplier. This line gives the depth-like effect.
132 double mult = 1.0 / i;
133 // Use formula to calculate CenterDist
134 double centerDist = halfHandleDist * (1 - pow2(mult)) / (2*mult);
135
136 // Use the distance to the center (from the average point) to calculate the center location.
137 double circleCenterX = centerDist * dirX + avgX;
138 double circleCenterY = centerDist * dirY + avgY;
139 // Use formula to calculate Radius
140 double radius = halfHandleDist * (1 + pow2(mult)) / (2*mult);
141
142 baseGuidePath.addEllipse(QPointF(circleCenterX, circleCenterY), radius, radius);
143
144 }
145 gc.drawPath(baseGuidePath);//drawPath(gc, baseGuidePath);
146 }
147
148 if(previewVisible) {
149 // Draw guideline for the mouse, based on mouse position.
150 QPointF mousePos = effectiveBrushPosition(converter, canvas);
151 // Get location on the screen of handles.
152 QPointF screenP1 = initialTransform.map(*handles()[0]);
153 QPointF screenP2 = initialTransform.map(*handles()[1]);
154 // Don't draw if mouse is too close to vanishing points (will flicker if not)
155 // Use distance squared to avoid expensive sqrt.
156 if(
157 kisSquareDistance(mousePos, screenP2) > 9 &&
158 kisSquareDistance(mousePos, screenP1) > 9
159 ) {
160 QLineF circle = identifyCircle(initialTransform.inverted().map(mousePos));
161 double radius = circle.length();
162 mouseGuidePath.addEllipse(circle.p1(), radius, radius);
163 }
164
165 gc.drawPath(mouseGuidePath);//drawPath(gc, mouseGuidePath);
166 }
167
168 }
169 gc.restore();
170
171 //KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible);
172
173}
174
175void CurvilinearPerspectiveAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, const KoColorDisplayRendererInterface *displayRenderInterface, bool assistantVisible)
176{
177 Q_UNUSED(gc);
178 Q_UNUSED(converter);
179 Q_UNUSED(assistantVisible);
180 Q_UNUSED(displayRenderInterface);
181}
182
183QLineF CurvilinearPerspectiveAssistant::identifyCircle(const QPointF thirdPoint) {
184 /*
185 * Calculate center location and radius for an arbitrary point (usually the mouse location).
186 * Given Formulas:
187 * Radius^2 = HalfHandleDist^2 + CenterDist^2
188 * avgX + CenterDist * dirX = CenterX
189 * avgY + CenterDist * dirY = CenterY
190 *
191 * For ease of use, let BetaX = MouseX - AvgX, BetaY = MouseY - AvgY
192 * Calculated Formula for CenterDist:
193 * CenterDist = (BetaX^2 + BetaY^2 - HalfHandleDist^2) / (2 * DirY * BetaX + 2 * DirY * BetaY)
194 *
195 * Returns line from center to the arbitrary point.
196 *
197 */
198 QPointF p1 = *handles()[0];
199 QPointF p2 = *handles()[1];
200
201 double deltaX = p2.x() - p1.x();
202 double deltaY = p2.y() - p1.y();
203
204 double handleDistance = KisAlgebra2D::norm(QPointF(deltaX, deltaY));
205 double halfHandleDist = handleDistance / 2.0;
206
207 double avgX = deltaX / 2.0 + p1.x();
208 double avgY = deltaY / 2.0 + p1.y();
209
210 double dirX = -deltaY / handleDistance;
211 double dirY = deltaX / handleDistance;
212
213 double betaX = thirdPoint.x() - avgX;
214 double betaY = thirdPoint.y() - avgY;
215
216 double centerDist =
217 (pow2(betaX) + pow2(betaY) - pow2(halfHandleDist))
218 /
219 (2 * dirX * betaX + 2 * dirY * betaY);
220
221 double circleCenterX = centerDist*dirX + avgX;
222 double circleCenterY = centerDist*dirY + avgY;
223 return QLineF(QPointF(circleCenterX, circleCenterY), thirdPoint);
224}
225
226QPointF CurvilinearPerspectiveAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin, const bool /*snapToAny*/, qreal /*moveThresholdPt*/)
227{
228 // Get the center and radius for the given point
229 QLineF initialCircle = identifyCircle(strokeBegin);
230
231 // Set the new point onto the circle.
232 QLineF magnetizedCircle(initialCircle.p1(), pt);
233 magnetizedCircle.setLength(initialCircle.length());
234
235 return magnetizedCircle.p2();
236
237}
238
240{
241 return (*handles()[0] + *handles()[1]) * 0.5;
242}
243
245{
246 return handles().size() >= 2;
247}
248
249
253
257
259{
260 return "curvilinear-perspective";
261}
262
264{
265 return i18n("Curvilinear Perspective");
266}
267
QPointF p2
QPointF p1
KisPaintingAssistant * createPaintingAssistant() const override
void drawCache(QPainter &gc, const KisCoordinatesConverter *converter, const KoColorDisplayRendererInterface *displayRenderInterface, bool assistantVisible=true) override
performance layer where the graphics can be drawn from a cache instead of generated every render upda...
KisPaintingAssistantSP clone(QMap< KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP > &handleMap) const override
QLineF identifyCircle(const QPointF thirdPoint)
QPointF adjustPosition(const QPointF &point, const QPointF &strokeBegin, const bool snapToAny, qreal moveThresholdPt) override
void adjustLine(QPointF &point, QPointF &strokeBegin) override
void drawAssistant(QPainter &gc, const QRectF &updateRect, const KisCoordinatesConverter *converter, const KoColorDisplayRendererInterface *displayRenderInterface, bool cached=true, KisCanvas2 *canvas=0, bool assistantVisible=true, bool previewVisible=true) override
QPointF effectiveBrushPosition(const KisCoordinatesConverter *converter, KisCanvas2 *canvas) const
Query the effective brush position to be used for preview lines. This is intended to be used for pain...
const QList< KisPaintingAssistantHandleSP > & handles() const
virtual QColor convertColorToDisplayColorSpace(const KoColor color) const =0
convertColorToDisplayColorSpace
T pow2(const T &x)
Definition kis_global.h:166
qreal kisSquareDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:194
QSharedPointer< KisPaintingAssistant > KisPaintingAssistantSP
Definition kis_types.h:189
qreal norm(const T &a)
static KoColorSpaceRegistry * instance()