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