File indexing completed on 2024-05-26 04:33:19

0001 /*
0002  * KDE. Krita Project.
0003  *
0004  * SPDX-FileCopyrightText: 2021 Deif Lou <ginoba@gmail.com>
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #ifndef KISSCREENTONEGENERATORFUNCTIONSAMPLER_H
0010 #define KISSCREENTONEGENERATORFUNCTIONSAMPLER_H
0011 
0012 #include <QtGlobal>
0013 #include <QTransform>
0014 
0015 #include "KisScreentoneGeneratorConfiguration.h"
0016 
0017 template <typename Function>
0018 class KisScreentoneGeneratorFunctionSampler
0019 {
0020 public:
0021     KisScreentoneGeneratorFunctionSampler(const KisScreentoneGeneratorConfigurationSP config)
0022         : KisScreentoneGeneratorFunctionSampler(config, Function())
0023     {}
0024 
0025     KisScreentoneGeneratorFunctionSampler(const KisScreentoneGeneratorConfigurationSP config,
0026                                           const Function &the_function)
0027         : m_function(the_function)
0028     {
0029         // Get transformation parameters
0030         qreal sizeX, sizeY;
0031         if (config->sizeMode() == KisScreentoneSizeMode_PixelBased) {
0032             const bool constrainSize = config->constrainSize();
0033             sizeX = config->sizeX();
0034             // Ensure that the size y component is equal to the x component if keepSizeSquare is true
0035             sizeY = constrainSize ? sizeX : config->sizeY();
0036         } else {
0037             const qreal resolution = config->resolution();
0038             const bool constrainFrequency = config->constrainFrequency();
0039             const qreal frequencyX = config->frequencyX();
0040             // Ensure that the frequency y component is equal to the x component if constrainFrequency is true
0041             const qreal frequencyY = constrainFrequency ? frequencyX : config->frequencyY();
0042             sizeX = qMax(1.0, resolution / frequencyX);
0043             sizeY = qMax(1.0, resolution / frequencyY);
0044         }
0045         const qreal positionX = config->positionX();
0046         const qreal positionY = config->positionY();
0047         const qreal shearX = config->shearX();
0048         const qreal shearY = config->shearY();
0049         const qreal rotation = config->rotation();
0050         
0051         // Get final transformation
0052         QTransform t;
0053         if (config->alignToPixelGrid()) {
0054             t.rotate(-rotation);
0055             t.scale(sizeX, sizeY);
0056             t.shear(-shearX, -shearY);
0057             const QSizeF macrocellSize(
0058                 static_cast<qreal>(config->alignToPixelGridX()),
0059                 static_cast<qreal>(config->alignToPixelGridY())
0060             );
0061             // u1 is the unaligned vector that goes from the origin to the top-right
0062             // corner of the macrocell. v1 is the aligned version
0063             // u2 is the unaligned vector that goes from the origin to the bottom-left
0064             // corner of the macrocell. v2 is the aligned version
0065             const QPointF u1 = t.map(QPointF(macrocellSize.width(), 0.0));
0066             const QPointF u2 = t.map(QPointF(0.0, macrocellSize.height()));
0067             QPointF v1(qRound(u1.x()), qRound(u1.y()));
0068             QPointF v2(qRound(u2.x()), qRound(u2.y()));
0069             // If the following condition is met, that means that the screen is
0070             // transformed in such a way that the cell corners are colinear so we move
0071             // v1 or v2 to a neighbor position
0072             if (qFuzzyCompare(v1.y() * v2.x(), v2.y() * v1.x()) &&
0073                 !qFuzzyIsNull(v1.x() * v2.x() + v1.y() * v2.y())) {
0074                 // Choose point to move based on distance from non aligned point to
0075                 // aligned point
0076                 const qreal dist1 = kisSquareDistance(u1, v1);
0077                 const qreal dist2 = kisSquareDistance(u2, v2);
0078                 const QPointF *p_u = dist1 > dist2 ? &u1 : &u2;
0079                 QPointF *p_v = dist1 > dist2 ? &v1 : &v2;
0080                 // Then we get the closest pixel aligned point to the current,
0081                 // colinear, point
0082                 QPair<int, qreal> dists[4]{
0083                     {1, kisSquareDistance(*p_u, *p_v + QPointF(0.0, -1.0))},
0084                     {2, kisSquareDistance(*p_u, *p_v + QPointF(1.0, 0.0))},
0085                     {3, kisSquareDistance(*p_u, *p_v + QPointF(0.0, 1.0))},
0086                     {4, kisSquareDistance(*p_u, *p_v + QPointF(-1.0, 0.0))}
0087                 };
0088                 std::sort(
0089                     std::begin(dists), std::end(dists),
0090                     [](const QPair<int, qreal> &a, const QPair<int, qreal> &b)
0091                     {
0092                         return a.second < b.second;
0093                     }
0094                 );
0095                 // Move the point
0096                 if (dists[0].first == 1) {
0097                     p_v->setY(p_v->y() - 1.0);
0098                 } else if (dists[0].first == 2) {
0099                     p_v->setX(p_v->x() + 1.0);
0100                 } else if (dists[0].first == 3) {
0101                     p_v->setY(p_v->y() + 1.0);
0102                 } else {
0103                     p_v->setX(p_v->x() - 1.0);
0104                 }
0105             }
0106             QPolygonF quad;
0107             quad.append(QPointF(0, 0));
0108             quad.append(v1 / macrocellSize.width());
0109             quad.append(v1 / macrocellSize.width() + v2 / macrocellSize.height());
0110             quad.append(v2 / macrocellSize.height());
0111             QTransform::quadToSquare(quad, t);
0112             t.translate(qRound(positionX), qRound(positionY));
0113         } else {
0114             t.shear(shearX, shearY);
0115             t.scale(qFuzzyIsNull(sizeX) ? 0.0 : 1.0 / sizeX, qFuzzyIsNull(sizeY) ? 0.0 : 1.0 / sizeY);
0116             t.rotate(rotation);
0117             t.translate(positionX, positionY);
0118         }
0119 
0120         m_imageToScreenTransform = t;
0121     }
0122     
0123     qreal operator()(qreal x, qreal y) const
0124     {
0125         // Get the coordinates in screen
0126         qreal xx, yy;
0127         m_imageToScreenTransform.map(x, y, &xx, &yy);
0128         // Get the value
0129         return m_function(xx, yy);
0130     }
0131 
0132 private:
0133     Function m_function;
0134     QTransform m_imageToScreenTransform;
0135 };
0136 
0137 #endif