File indexing completed on 2025-02-23 04:08:59
0001 /* 0002 * This file is part of Krita 0003 * 0004 * SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger@cberger.net> 0005 * SPDX-FileCopyrightText: 2014 Sven Langkamp <sven.langkamp@gmail.com> 0006 * 0007 * SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "kis_grid_decoration.h" 0011 0012 #include <QPainter> 0013 #include <QPen> 0014 #include <QtMath> 0015 #include <klocalizedstring.h> 0016 0017 #include <KoUnit.h> 0018 0019 #include "kis_grid_config.h" 0020 #include "kis_coordinates_converter.h" 0021 0022 struct KisGridDecoration::Private 0023 { 0024 KisGridConfig config; 0025 }; 0026 0027 KisGridDecoration::KisGridDecoration(KisView* parent) 0028 : KisCanvasDecoration("grid", parent), 0029 m_d(new Private) 0030 { 0031 setPriority(0); 0032 } 0033 0034 KisGridDecoration::~KisGridDecoration() 0035 { 0036 0037 } 0038 0039 void KisGridDecoration::setGridConfig(const KisGridConfig &config) 0040 { 0041 m_d->config = config; 0042 } 0043 0044 void KisGridDecoration::drawDecoration(QPainter& gc, const QRectF& updateArea, const KisCoordinatesConverter* converter, KisCanvas2* canvas) 0045 { 0046 if (!m_d->config.showGrid()) return; 0047 0048 Q_UNUSED(canvas); 0049 0050 QTransform transform = converter->imageToWidgetTransform(); 0051 0052 const qreal scale = KoUnit::approxTransformScale(transform); 0053 const int minWidgetSize = 3; 0054 const int effectiveSize = qMin(m_d->config.spacing().x(), m_d->config.spacing().y()); 0055 0056 int scaleCoeff = 1; 0057 quint32 subdivision = m_d->config.subdivision(); 0058 0059 while (qFloor(scale * scaleCoeff * effectiveSize) <= minWidgetSize) { 0060 if (subdivision > 1) { 0061 scaleCoeff = subdivision; 0062 subdivision = 1; 0063 } else { 0064 scaleCoeff *= 2; 0065 } 0066 0067 if (scaleCoeff > 32768) { 0068 qWarning() << "WARNING: Grid Scale Coeff is too high! That is surely a bug!"; 0069 return; 0070 } 0071 } 0072 0073 const QPen mainPen = m_d->config.penMain(); 0074 QPen subdivisionPen = m_d->config.penSubdivision(); 0075 0076 gc.save(); 0077 gc.setTransform(transform); 0078 gc.setRenderHints(QPainter::Antialiasing, false); 0079 gc.setRenderHints(QPainter::Antialiasing, false); 0080 0081 0082 const QRect imageRectInImagePixels = converter->imageRectInImagePixels(); 0083 const QRectF updateRectInImagePixels = 0084 converter->documentToImage(updateArea) & 0085 imageRectInImagePixels; 0086 0087 // for angles. This will later be a combobox to select different types of options 0088 // also add options to hide specific lines (vertical, horizontal, angle 1, etc 0089 KisGridConfig::GridType gridType = m_d->config.gridType(); 0090 0091 if (gridType == KisGridConfig::GRID_RECTANGULAR) { 0092 qreal x1, y1, x2, y2; 0093 updateRectInImagePixels.getCoords(&x1, &y1, &x2, &y2); 0094 0095 // compensate the fact the getCoordt returns off-by-one pixel 0096 // at the bottom right of the rect. 0097 x2++; 0098 y2++; 0099 0100 { 0101 // vertical lines 0102 const int offset = m_d->config.offset().x(); 0103 const int step = scaleCoeff * m_d->config.spacing().x(); 0104 const int lineIndexFirst = qCeil((x1 - offset) / step); 0105 const int lineIndexLast = qFloor((x2 - offset) / step); 0106 0107 subdivisionPen.setDashOffset(y1 * scale); 0108 0109 for (int i = lineIndexFirst; i <= lineIndexLast; i++) { 0110 int w = offset + i * step; 0111 0112 gc.setPen(i % subdivision == 0 ? mainPen : subdivisionPen); 0113 // we adjusted y2 to draw the grid correctly, clip it now... 0114 gc.drawLine(QPointF(w, y1),QPointF(w, qMin(y2, qreal(imageRectInImagePixels.bottom() + 1)))); 0115 } 0116 } 0117 0118 { 0119 // horizontal lines 0120 const int offset = m_d->config.offset().y(); 0121 const int step = scaleCoeff * m_d->config.spacing().y(); 0122 const int lineIndexFirst = qCeil((y1 - offset) / step); 0123 const int lineIndexLast = qFloor((y2 - offset) / step); 0124 0125 subdivisionPen.setDashOffset(x1 * scale); 0126 0127 for (int i = lineIndexFirst; i <= lineIndexLast; i++) { 0128 int w = offset + i * step; 0129 0130 gc.setPen(i % subdivision == 0 ? mainPen : subdivisionPen); 0131 // we adjusted x2 to draw the grid correctly, clip it now... 0132 gc.drawLine(QPointF(x1, w),QPointF(qMin(x2, qreal(imageRectInImagePixels.right() + 1)), w)); 0133 } 0134 } 0135 } 0136 0137 if (gridType == KisGridConfig::GRID_ISOMETRIC) { 0138 qreal x1, y1, x2, y2; 0139 0140 // get true coordinates, not just the updateArea 0141 QRectF trueImageRect = converter->imageRectInImagePixels(); 0142 trueImageRect.getCoords(&x1, &y1, &x2, &y2); 0143 0144 // compensate the fact the getCoordt returns off-by-one pixel 0145 // at the bottom right of the rect. 0146 x2++; 0147 y2++; 0148 0149 const int offset = m_d->config.offset().x(); 0150 const int offsetY = m_d->config.offset().y(); 0151 const int cellSpacing = m_d->config.cellSpacing(); 0152 0153 gc.setClipping(true); 0154 gc.setClipRect(updateRectInImagePixels, Qt::IntersectClip); 0155 0156 // left angle 0157 { 0158 const qreal gridXAngle = m_d->config.angleLeft(); 0159 const qreal bottomRightOfImageY = y2; // this should be the height of the image 0160 qreal finalY = 0.0; 0161 0162 // figure out the spacing based off the angle. The spacing needs to be perpendicular to the angle, 0163 // so we need to do a bit of trig to get the correct spacing. 0164 qreal correctedAngleSpacing = cellSpacing; 0165 if (gridXAngle > 0.0) { 0166 correctedAngleSpacing = cellSpacing / qCos(qDegreesToRadians(gridXAngle)); 0167 } 0168 0169 qreal counter = qFloor((-(offset + offsetY)) / correctedAngleSpacing); 0170 0171 while (finalY < bottomRightOfImageY) { 0172 0173 const qreal w = (counter * correctedAngleSpacing) + offsetY + offset; 0174 gc.setPen(mainPen); 0175 0176 // calculate where the ending point will be based off the angle 0177 const qreal startingY = w; 0178 const qreal horizontalDistance = x2; 0179 0180 // qTan takes radians, so convert first before sending it 0181 const qreal length2 = qTan(qDegreesToRadians(gridXAngle)) * x2; 0182 0183 finalY = startingY - length2; 0184 gc.drawLine(QPointF(x1, w), QPointF(horizontalDistance, finalY)); 0185 0186 counter = counter + 1.0; 0187 } 0188 } 0189 0190 // right angle (almost the same thing, except starting the lines on the right side) 0191 { 0192 const qreal gridXAngle = m_d->config.angleRight(); // TODO: add another angle property 0193 const qreal bottomLeftOfImageY = y2; 0194 0195 // figure out the spacing based off the angle 0196 qreal correctedAngleSpacing = cellSpacing; 0197 if (gridXAngle > 0.0) { 0198 correctedAngleSpacing = cellSpacing / qCos(qDegreesToRadians(gridXAngle)); 0199 } 0200 0201 // distance is the same (width of the image) 0202 const qreal horizontalDistance = x2; 0203 // qTan takes radians, so convert first before sending it 0204 const qreal length2 = qTan(qDegreesToRadians(gridXAngle)) * horizontalDistance; 0205 0206 // let's get x, y of the line that starts in the top right corder 0207 const qreal yLower = 0.0; 0208 const qreal yHigher = yLower - length2; 0209 0210 const qreal yLeftFirst = qCeil(yHigher / correctedAngleSpacing) * correctedAngleSpacing; 0211 const qreal additionalOffset = yLeftFirst - yHigher; 0212 qreal finalY = 0.0; 0213 qreal counter = qFloor((-(offsetY - offset)) / correctedAngleSpacing); 0214 0215 while (finalY < bottomLeftOfImageY) { 0216 0217 const qreal w = (counter * correctedAngleSpacing) + offsetY - offset + additionalOffset; 0218 gc.setPen(mainPen); 0219 0220 // calculate where the ending point will be based off the angle 0221 const qreal startingY = w; 0222 0223 finalY = startingY - length2; 0224 gc.drawLine(QPointF(x2, w), QPointF(0.0, finalY)); 0225 0226 counter = counter + 1.0; 0227 } 0228 } 0229 0230 0231 } 0232 0233 gc.restore(); 0234 }