Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_grid_decoration.cpp
Go to the documentation of this file.
1/*
2 * This file is part of Krita
3 *
4 * SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger@cberger.net>
5 * SPDX-FileCopyrightText: 2014 Sven Langkamp <sven.langkamp@gmail.com>
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10#include "kis_grid_decoration.h"
11
12#include <QPainter>
13#include <QPen>
14#include <QtMath>
15#include <klocalizedstring.h>
16
17#include <KoUnit.h>
18
19#include "kis_grid_config.h"
21
26
28 : KisCanvasDecoration("grid", parent),
29 m_d(new Private)
30{
31 setPriority(0);
32}
33
38
40{
41 m_d->config = config;
42}
43
44void KisGridDecoration::drawDecoration(QPainter& gc, const QRectF& updateArea, const KisCoordinatesConverter* converter, KisCanvas2* canvas)
45{
46 if (!m_d->config.showGrid()) return;
47
48 Q_UNUSED(canvas);
49
50 QTransform transform = converter->imageToWidgetTransform();
51
52 const qreal scale = KoUnit::approxTransformScale(transform);
53 const int minWidgetSize = 3;
54 const int effectiveSize = qMin(m_d->config.spacing().x(), m_d->config.spacing().y());
55
56 int scaleCoeff = 1;
57 quint32 subdivision = m_d->config.subdivision();
58
59 while (qFloor(scale * scaleCoeff * effectiveSize) <= minWidgetSize) {
60 if (subdivision > 1) {
61 scaleCoeff = subdivision;
62 subdivision = 1;
63 } else {
64 scaleCoeff *= 2;
65 }
66
67 if (scaleCoeff > 32768) {
68 qWarning() << "WARNING: Grid Scale Coeff is too high! That is surely a bug!";
69 return;
70 }
71 }
72
73 QPen mainPen = m_d->config.penMain();
74 QPen subdivisionPen = m_d->config.penSubdivision();
75
76 gc.save();
77 gc.setTransform(transform);
78
79 const QRect imageRectInImagePixels = converter->imageRectInImagePixels();
80 const QRectF updateRectInImagePixels =
81 converter->documentToImage(updateArea) &
82 imageRectInImagePixels;
83
84 // for angles. This will later be a combobox to select different types of options
85 // also add options to hide specific lines (vertical, horizontal, angle 1, etc
86 KisGridConfig::GridType gridType = m_d->config.gridType();
87
88 if (gridType == KisGridConfig::GRID_RECTANGULAR) {
89 gc.setRenderHints(QPainter::Antialiasing, false);
90 qreal x1, y1, x2, y2;
91 updateRectInImagePixels.getCoords(&x1, &y1, &x2, &y2);
92
93 // compensate the fact the getCoordt returns off-by-one pixel
94 // at the bottom right of the rect.
95 x2++;
96 y2++;
97
98 if (m_d->config.xSpacingActive()) {
99 // vertical lines
100 int offset = 0;
101 if (m_d->config.offsetActive()) {
102 offset = m_d->config.offset().x();
103 }
104 const int step = scaleCoeff * m_d->config.spacing().x();
105 const int lineIndexFirst = qCeil((x1 - offset) / step);
106 const int lineIndexLast = qFloor((x2 - offset) / step);
107
108 if (mainPen.style() != Qt::SolidLine) {
109 mainPen.setDashOffset(y1 * scale);
110 }
111 if (subdivisionPen.style() != Qt::SolidLine) {
112 subdivisionPen.setDashOffset(y1 * scale);
113 }
114
115 for (int i = lineIndexFirst; i <= lineIndexLast; i++) {
116 int w = offset + i * step;
117
118 gc.setPen(i % subdivision == 0 ? mainPen : subdivisionPen);
119 // we adjusted y2 to draw the grid correctly, clip it now...
120 gc.drawLine(QPointF(w, y1),QPointF(w, qMin(y2, qreal(imageRectInImagePixels.bottom() + 1))));
121 }
122 }
123
124 if (m_d->config.ySpacingActive()) {
125 // horizontal lines
126 int offset = 0;
127 if (m_d->config.offsetActive()) {
128 offset = m_d->config.offset().y();
129 }
130 const int step = scaleCoeff * m_d->config.spacing().y();
131 const int lineIndexFirst = qCeil((y1 - offset) / step);
132 const int lineIndexLast = qFloor((y2 - offset) / step);
133
134 if (mainPen.style() != Qt::SolidLine) {
135 mainPen.setDashOffset(x1 * scale);
136 }
137 if (subdivisionPen.style() != Qt::SolidLine) {
138 subdivisionPen.setDashOffset(x1 * scale);
139 }
140
141 for (int i = lineIndexFirst; i <= lineIndexLast; i++) {
142 int w = offset + i * step;
143
144 gc.setPen(i % subdivision == 0 ? mainPen : subdivisionPen);
145 // we adjusted x2 to draw the grid correctly, clip it now...
146 gc.drawLine(QPointF(x1, w),QPointF(qMin(x2, qreal(imageRectInImagePixels.right() + 1)), w));
147 }
148 }
149 } else if (gridType == KisGridConfig::GRID_ISOMETRIC_LEGACY) {
150 qreal x1, y1, x2, y2;
151
152 // get true coordinates, not just the updateArea
153 QRectF trueImageRect = converter->imageRectInImagePixels();
154 trueImageRect.getCoords(&x1, &y1, &x2, &y2);
155
156 // compensate the fact the getCoordt returns off-by-one pixel
157 // at the bottom right of the rect.
158 x2++;
159 y2++;
160
161 int offset = 0;
162 int offsetY = 0;
163 if (m_d->config.offsetActive()) {
164 offset = m_d->config.offset().x();
165 offsetY = m_d->config.offset().y();
166 }
167 const int cellSpacing = m_d->config.cellSpacing();
168
169 gc.setClipping(true);
170 gc.setClipRect(updateRectInImagePixels, Qt::IntersectClip);
171
172 // left angle
173 if (m_d->config.angleLeftActive()) {
174 const qreal gridXAngle = m_d->config.angleLeft();
175 const qreal bottomRightOfImageY = y2; // this should be the height of the image
176 qreal finalY = 0.0;
177
178 // figure out the spacing based off the angle. The spacing needs to be perpendicular to the angle,
179 // so we need to do a bit of trig to get the correct spacing.
180 qreal correctedAngleSpacing = cellSpacing;
181 if (gridXAngle > 0.0) {
182 correctedAngleSpacing = cellSpacing / qCos(qDegreesToRadians(gridXAngle));
183 gc.setRenderHints(QPainter::Antialiasing, true);
184 } else {
185 // horizontal line: don't want to be antialiased
186 gc.setRenderHints(QPainter::Antialiasing, false);
187 }
188
189 qreal counter = qFloor((-(offset + offsetY)) / correctedAngleSpacing);
190
191 while (finalY < bottomRightOfImageY) {
192
193 const qreal w = (counter * correctedAngleSpacing) + offsetY + offset;
194 gc.setPen(mainPen);
195
196 // calculate where the ending point will be based off the angle
197 const qreal startingY = w;
198 const qreal horizontalDistance = x2;
199
200 // qTan takes radians, so convert first before sending it
201 const qreal length2 = qTan(qDegreesToRadians(gridXAngle)) * x2;
202
203 finalY = startingY - length2;
204
205 gc.drawLine(QPointF(x1, w), QPointF(horizontalDistance, finalY));
206
207 counter = counter + 1.0;
208 }
209 }
210
211 // right angle (almost the same thing, except starting the lines on the right side)
212 if (m_d->config.angleRightActive()) {
213 const qreal gridXAngle = m_d->config.angleRight(); // TODO: add another angle property
214 const qreal bottomLeftOfImageY = y2;
215
216 // figure out the spacing based off the angle
217 qreal correctedAngleSpacing = cellSpacing;
218 if (gridXAngle > 0.0) {
219 correctedAngleSpacing = cellSpacing / qCos(qDegreesToRadians(gridXAngle));
220 gc.setRenderHints(QPainter::Antialiasing, true);
221 } else {
222 // horizontal line: don't want to be antialiased
223 gc.setRenderHints(QPainter::Antialiasing, false);
224 }
225
226 // distance is the same (width of the image)
227 const qreal horizontalDistance = x2;
228 // qTan takes radians, so convert first before sending it
229 const qreal length2 = qTan(qDegreesToRadians(gridXAngle)) * horizontalDistance;
230
231 // let's get x, y of the line that starts in the top right corder
232 const qreal yLower = 0.0;
233 const qreal yHigher = yLower - length2;
234
235 const qreal yLeftFirst = qCeil(yHigher / correctedAngleSpacing) * correctedAngleSpacing;
236 const qreal additionalOffset = yLeftFirst - yHigher;
237 qreal finalY = 0.0;
238 qreal counter = qFloor((-(offsetY - offset)) / correctedAngleSpacing);
239
240 while (finalY < bottomLeftOfImageY) {
241
242 const qreal w = (counter * correctedAngleSpacing) + offsetY - offset + additionalOffset;
243 gc.setPen(mainPen);
244
245 // calculate where the ending point will be based off the angle
246 const qreal startingY = w;
247
248 finalY = startingY - length2;
249
250 gc.drawLine(QPointF(x2, w), QPointF(0.0, finalY));
251
252 counter = counter + 1.0;
253 }
254 }
255 } else if (gridType == KisGridConfig::GRID_ISOMETRIC) {
256 qreal x1, y1, xRight, yBottom;
257
258 // get true coordinates, not just the updateArea
259 QRectF trueImageRect = converter->imageRectInImagePixels();
260 trueImageRect.getCoords(&x1, &y1, &xRight, &yBottom);
261
262 // compensate the fact the getCoordt returns off-by-one pixel
263 // at the bottom right of the rect.
264 xRight++;
265 yBottom++;
266
267 const KisGridConfig::TrigoCache trigoCache = m_d->config.trigoCache();
268
269 int offsetX = 0;
270 int offsetY = 0;
271 if (m_d->config.offsetActive()) {
272 offsetX = trigoCache.correctedAngleRightOffsetX;
273 offsetY = m_d->config.offset().y();
274 }
275
276 gc.setClipping(true);
277 gc.setClipRect(updateRectInImagePixels, Qt::IntersectClip);
278
279 // left angle
280 if (trigoCache.correctedAngleLeftCellSize > 0 && m_d->config.angleLeftActive()) {
281 if (m_d->config.angleLeft() > 0.0) {
282 gc.setRenderHints(QPainter::Antialiasing, true);
283 } else {
284 // horizontal line: don't want to be antialiased
285 gc.setRenderHints(QPainter::Antialiasing, false);
286 }
287
288 const qreal length2 = trigoCache.tanAngleLeft * xRight;
289
290 // let's get x, y of the line that starts in the top right corner
291 const qreal yLower = 0.0;
292 const qreal yHigher = yLower - length2;
293
294 const qreal yLeftFirst = qCeil(yHigher / trigoCache.correctedAngleLeftCellSize) * trigoCache.correctedAngleLeftCellSize;
295 const qreal additionalOffset = yLeftFirst - yHigher;
296
297 qreal finalY = 0.0;
298
299 // define line number used to determinate if is a subdivision or not
300 int subdivisionIndex = qCeil(yHigher / trigoCache.correctedAngleLeftCellSize);
301
302 qreal startingY = yLeftFirst + offsetY + offsetX + additionalOffset;
303
304 while (finalY < yBottom) {
305 // calculate where the ending point will be based off the angle
306 finalY = startingY - length2;
307
308 gc.setPen(qAbs(subdivisionIndex) % subdivision == 0 ? mainPen : subdivisionPen);
309 gc.drawLine(QPointF(0.0, startingY), QPointF(xRight, finalY));
310
311 subdivisionIndex++;
312 startingY += trigoCache.correctedAngleLeftCellSize;
313 }
314 }
315
316 // right angle (almost the same thing, except starting the lines on the right side)
317 if (trigoCache.correctedAngleRightCellSize > 0 && m_d->config.angleRightActive()) {
318 if (m_d->config.angleRight() > 0.0) {
319 gc.setRenderHints(QPainter::Antialiasing, true);
320 } else {
321 // horizontal line: don't want to be antialiased
322 gc.setRenderHints(QPainter::Antialiasing, false);
323 }
324
325 const qreal length2 = trigoCache.tanAngleRight * xRight;
326
327 // let's get x, y of the line that starts in the top right corner
328 const qreal yLower = 0.0;
329 const qreal yHigher = yLower - length2;
330
331 const qreal yLeftFirst = qCeil(yHigher / trigoCache.correctedAngleRightCellSize) * trigoCache.correctedAngleRightCellSize;
332 const qreal additionalOffset = yLeftFirst - yHigher;
333
334 qreal finalY = 0.0;
335
336 // define line number used to determinate if is a subdivision or not
337 int subdivisionIndex = qCeil(yHigher / trigoCache.correctedAngleRightCellSize);
338
339 qreal startingY = yLeftFirst + offsetY - offsetX + additionalOffset;
340
341 while (finalY < yBottom) {
342 // calculate where the ending point will be based off the angle
343 finalY = startingY - length2;
344
345 gc.setPen(qAbs(subdivisionIndex) % subdivision == 0 ? mainPen : subdivisionPen);
346 gc.drawLine(QPointF(xRight, startingY), QPointF(0.0, finalY));
347
348 subdivisionIndex++;
349 startingY += trigoCache.correctedAngleRightCellSize;
350 }
351 }
352
353 // vertical
354 if (trigoCache.verticalSpace > 0) {
355 QPen verticalPen = m_d->config.penVertical();
356 if (verticalPen.style() != Qt::SolidLine) {
357 verticalPen.setDashOffset(updateRectInImagePixels.top() * scale);
358 }
359 gc.setPen(verticalPen);
360 gc.setRenderHints(QPainter::Antialiasing, false);
361
362 int offset = 0;
363 if (m_d->config.offsetActive()) {
364 offset = m_d->config.offset().x();
365 }
366
367 qreal pX = (xRight - x1)/(2*trigoCache.verticalSpace);
368 pX = offset + (pX - qFloor(pX)) * trigoCache.verticalSpace;
369 pX = pX - trigoCache.verticalSpace * qFloor(pX/trigoCache.verticalSpace);
370
371 while(pX <= updateRectInImagePixels.right()) {
372 if (pX > updateRectInImagePixels.left()) {
373 gc.drawLine(QPointF(pX, updateRectInImagePixels.top()),QPointF(pX, qMin(yBottom, qreal(updateRectInImagePixels.bottom() + 1))));
374 }
375 pX += trigoCache.verticalSpace;
376 }
377 }
378 }
379
380 gc.restore();
381}
_Private::Traits< T >::Result documentToImage(const T &obj) const
KisGridDecoration(KisView *parent)
const QScopedPointer< Private > m_d
void drawDecoration(QPainter &gc, const QRectF &updateArea, const KisCoordinatesConverter *converter, KisCanvas2 *canvas) override
void setGridConfig(const KisGridConfig &config)
static qreal approxTransformScale(const QTransform &t)
Definition KoUnit.cpp:387