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 }