Krita Source Code Documentation
Loading...
Searching...
No Matches
hatching_brush.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2008, 2009, 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
3 * SPDX-FileCopyrightText: 2010 José Luis Vergara <pentalis@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include "hatching_brush.h"
9
10#include <KoColor.h>
12
13#include <QVariant>
14
16#include <cmath>
17#include <time.h>
18
19
20void inline myround(double *x)
21{
22 *x = ((*x - floor(*x)) >= 0.5) ? ceil(*x) : floor(*x);
23}
24
26 : m_settings(settings)
27 , separation(m_settings->separation)
28 , origin_x(m_settings->origin_x)
29 , origin_y(m_settings->origin_y)
30{
31}
32
33
37
39{
40}
41
42void HatchingBrush::hatch(KisPaintDeviceSP dev, qreal x, qreal y, double width, double height, double givenAngle, const KoColor &color, qreal additionalScale)
43{
44 m_painter.begin(dev);
48
49 angle = givenAngle;
50 double tempthickness = m_settings->thickness * m_settings->thicknesssensorvalue;
51 thickness = qMax(1, qRound(additionalScale * tempthickness));
52 separation = additionalScale *
56
57 height_ = height;
58 width_ = width;
59
61
62 /* dx and dy are the separation between lines in the x and y axis
63 dx = separation / sin(angle*M_PI/180); csc = 1/sin(angle) */
64 dy = fabs(separation / cos(angle * M_PI / 180)); // sec = 1/cos(angle)
65 // I took the absolute value to avoid confusions with negative numbers
66
68 modf(dy, &dy);
69
70 // Exception for vertical lines, for which a tangent does not exist
71 if ((angle == 90) || (angle == -90)) {
72 verticalHotX = fmod((origin_x - x), separation);
73
74 iterateVerticalLines(true, 1, false); // Forward
75 iterateVerticalLines(true, 0, true); // In Between both
76 iterateVerticalLines(false, 1, false); // Backward
77 }
78 else {
79 // Turn Angle + Point into Slope + Intercept
80 slope = tan(angle * M_PI / 180); // Angle into slope
81 baseLineIntercept = origin_y - slope * origin_x; // Slope and Point of the Base Line into Intercept
82 cursorLineIntercept = y - slope * x;
83 hotIntercept = fmod((baseLineIntercept - cursorLineIntercept), dy); // This hotIntercept belongs to a line that intersects with the hatching area
84
85 iterateLines(true, 1, false); // Forward
86 iterateLines(true, 0, true); // In Between both
87 iterateLines(false, 1, false); // Backward
88 // I tried to make this cleaner but there's too many possibilities to be
89 // worth the micromanagement to optimize
90 }
91}
92
93void HatchingBrush::iterateLines(bool forward, int lineindex, bool oneline)
94{
95 //---Preparations before the loop---
96
97 double xdraw[2] = {0, 0};
98 double ydraw[2] = {0, 0};
99 //points A and B of the segments to trace
100 QPointF A, B;
101 int append_index = 0;
102 bool remaininginnerlines = true;
103
104 while (remaininginnerlines) {
105
106 //---------START INTERSECTION POINT VERIFICATION--------
107
108 append_index = 0;
109 remaininginnerlines = false; // We assume there's no more lines unless proven contrary
110 if (forward)
111 scanIntercept = hotIntercept + dy * lineindex; // scanIntercept will represent the Intercept of the current line
112 else
113 scanIntercept = hotIntercept - dy * lineindex; // scanIntercept will represent the Intercept of the current line
114
115 lineindex++; // We are descending vertically out of convenience, see blog entry at pentalis.org/kritablog
116
117 /*
118 Explanation: only 2 out of the 4 segments can include limit values
119 to verify intersections, otherwise we could encounter a situation where
120 our new lines intersect with all 4 segments and is still considered an
121 inner line (for example, a line that goes from corner to corner), thus
122 triggering an error. The idea is of the algorithm is that only 2 intersections
123 at most are considered at a time. Graphically this is indistinguishable, it's
124 just there to avoid making unnecessary control structures (like additional "ifs").
125 */
126
127 if ((scanIntercept >= 0) && (scanIntercept <= height_)) {
128 xdraw[append_index] = 0;
129 ydraw[append_index] = scanIntercept; //intersection at left
130 remaininginnerlines = true;
131 append_index++;
132 }
133
134 if ((slope * width_ + scanIntercept <= height_) && (slope * width_ + scanIntercept >= 0)) {
135 xdraw[append_index] = width_;
136 ydraw[append_index] = scanIntercept + slope * width_; //intersection at right
137 remaininginnerlines = true;
138 append_index++;
139 }
140
141 if ((-scanIntercept / slope > 0) && (-scanIntercept / slope < width_)) {
142 xdraw[append_index] = -scanIntercept / slope;
143 ydraw[append_index] = 0; //intersection at top
144 remaininginnerlines = true;
145 append_index++;
146 }
147
148 if (((height_ - scanIntercept) / slope > 0) && ((height_ - scanIntercept) / slope < width_)) {
149 xdraw[append_index] = (height_ - scanIntercept) / slope;
150 ydraw[append_index] = height_; //intersection at bottom
151 remaininginnerlines = true;
152 append_index++;
153 }
154 //--------END INTERSECTION POINT VERIFICATION---------
155
156 if (!remaininginnerlines)
157 break;
158
160 myround(&xdraw[0]);
161 myround(&xdraw[1]);
162 myround(&ydraw[0]);
163 myround(&ydraw[1]);
164 }
165
166 A.setX(xdraw[0]);
167 A.setY(ydraw[0]);
168
169 // If 2 lines intersect with the dab square
170 if (append_index == 2) {
171 B.setX(xdraw[1]);
172 B.setY(ydraw[1]);
173
176 else
177 m_painter.drawLine(A, B, thickness, false); //testing no subpixel;
178
179 if (oneline)
180 break;
181 }
182 else {
183 continue;
184 /*Drawing points at the vertices causes inconsistent results due to
185 floating point calculations not being quite in sync with algebra,
186 therefore if I have only 1 intersection (= corner = this case),
187 don't draw*/
188 }
189 }
190}
191
192void HatchingBrush::iterateVerticalLines(bool forward, int lineindex, bool oneline)
193{
194 //---Preparations before the loop---
195
196 double xdraw = 0;
197 double ydraw[2] = {0, height_};
198 //points A and B of the segments to trace
199 QPointF A, B;
200 bool remaininginnerlines = true;
201
202 while (remaininginnerlines) {
203
204 //---------START INTERSECTION POINT VERIFICATION--------
205 remaininginnerlines = false; // We assume there's no more lines unless proven contrary
206 if (forward)
207 verticalScanX = verticalHotX + separation * lineindex;
208 else
209 verticalScanX = verticalHotX - separation * lineindex;
210
211 lineindex++;
212
213 /*Read the explanation in HatchingBrush::iterateLines for more information*/
214
215 if ((verticalScanX >= 0) && (verticalScanX <= width_)) {
216 xdraw = verticalScanX;
217 remaininginnerlines = true;
218 }
219 //--------END INTERSECTION POINT VERIFICATION---------
220
221 if (!remaininginnerlines)
222 break;
223
225 myround(&xdraw);
226 myround(&ydraw[1]);
227 }
228
229 A.setX(xdraw);
230 A.setY(ydraw[0]);
231 B.setX(xdraw);
232 B.setY(ydraw[1]);
233
236 else
237 m_painter.drawLine(A, B, thickness, false); //testing no subpixel;
238
239 if (oneline)
240 break;
241 else
242 continue;
243 }
244}
245
246double HatchingBrush::separationAsFunctionOfParameter(double parameter, double separation, int numintervals)
247{
248 if ((numintervals < 2) || (numintervals > 7)) {
249 dbgKrita << "Fix your function" << numintervals << "<> 2-7" ;
250 return separation;
251 }
252
253 double sizeinterval = 1 / double(numintervals);
254 double lowerlimit = 0;
255 double upperlimit = 0;
256 double factor = 0;
257
258 int basefactor = numintervals / 2;
259 // Make the base separation factor tend to greater instead of lesser numbers when numintervals is even
260 if ((numintervals % 2) == 0)
261 basefactor--;
262
263 for (quint8 currentinterval = 0; currentinterval < numintervals; currentinterval++) {
264 lowerlimit = upperlimit;
265 upperlimit += sizeinterval;
266 if (currentinterval == (numintervals - 1))
267 upperlimit = 1;
268 if ((parameter >= lowerlimit) && (parameter <= upperlimit)) {
269 factor = pow(2.0, (basefactor - currentinterval));
270 //dbgKrita << factor;
271 return (separation * factor);
272 }
273 }
274
275 dbgKrita << "Fix your function" << parameter << ">" << upperlimit ;
276 return separation;
277}
double baseLineIntercept
HatchingBrush(KisHatchingPaintOpSettingsSP settings)
void iterateLines(bool forward, int lineindex, bool oneline)
KisHatchingPaintOpSettingsSP m_settings
void iterateVerticalLines(bool forward, int lineindex, bool oneline)
double cursorLineIntercept
KisPainter m_painter
void hatch(KisPaintDeviceSP dev, qreal x, qreal y, double width, double height, double givenAngle, const KoColor &color, qreal additionalScale)
double separationAsFunctionOfParameter(double parameter, double separation, int numintervals)
@ FillStyleForegroundColor
void setMaskImageSize(qint32 width, qint32 height)
void drawThickLine(const QPointF &start, const QPointF &end, int startWidth, int endWidth)
void setBackgroundColor(const KoColor &color)
void setFillStyle(FillStyle fillStyle)
Set the current style with which to fill.
void begin(KisPaintDeviceSP device)
void drawLine(const QPointF &start, const QPointF &end)
void setPaintColor(const KoColor &color)
void myround(double *x)
#define dbgKrita
Definition kis_debug.h:45
#define M_PI
Definition kis_global.h:111