File indexing completed on 2024-06-23 04:27:08

0001 /*
0002  * KDE. Krita Project.
0003  *
0004  * SPDX-FileCopyrightText: 2020 Deif Lou <ginoba@gmail.com>
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "KisScreentoneScreentoneFunctions.h"
0010 
0011 #include <cmath>
0012 
0013 namespace KisScreentoneScreentoneFunctions {
0014 
0015 qreal sin(qreal x)
0016 {
0017     x = std::cos(x * M_PI);
0018     return  x * x;
0019 }
0020 
0021 qreal triangle(qreal x)
0022 {
0023     return 1.0 - 2.0 * std::abs(x - std::floor(x + 0.5));
0024 }
0025 
0026 qreal sawTooth(qreal x)
0027 {
0028     constexpr qreal peakXOffset = 0.9;
0029     constexpr qreal peakYOffset = 0.5;
0030     x = x - std::floor(x);
0031     return (x < peakXOffset ? 1.0 / peakXOffset * x : -1.0 / (1.0 - peakXOffset) * (x - 1.0)) * peakYOffset;
0032 }
0033 
0034 qreal DotsRoundLinear::operator()(qreal x, qreal y) const
0035 {
0036     x = triangle(x);
0037     y = triangle(y);
0038     return std::sqrt(x * x + y * y) / M_SQRT2;
0039 }
0040 
0041 qreal DotsRoundLinearEqualized::operator()(qreal x, qreal y) const
0042 {
0043     // In theory, the cumulative function for this spot function is:
0044     // "coverage = area of the intersection between the disk formed by the value,
0045     // and the screen cell".
0046     // This uses a piecewise cumulative function obtained analytically. If
0047     // the value is less than or equal to "sqrt(2) / 2" (value at which the
0048     // disk touches the screen cell borders) then the coverage for that value
0049     // is obtained simply by getting the area of the disk. If the value is
0050     // greater than "sqrt(2) / 2" then the coverage is obtained by computing
0051     // the area of the intersection between the disk and the screen cell (area
0052     // of the disk minus area of the chords of the disk outside the screen cell)
0053     const qreal z = DotsRoundLinear::operator()(x, y);
0054     const qreal zOverSqrt2 = z / M_SQRT2;
0055     const qreal zOverSqrt2Squared = zOverSqrt2 * zOverSqrt2;
0056     if (z <= M_SQRT2 / 2.0) {
0057         return M_PI * zOverSqrt2Squared;
0058     } else {
0059         return M_PI * zOverSqrt2Squared -
0060                4.0 * (zOverSqrt2Squared * std::acos(M_SQRT2 / (2.0 * z)) -
0061                       0.5 * std::sqrt(zOverSqrt2Squared - 0.25));
0062     }
0063 }
0064 
0065 qreal DotsRoundSinusoidal::operator()(qreal x, qreal y) const
0066 {
0067     return (sin(x) + sin(y)) / 2.;
0068 }
0069 
0070 qreal DotsRoundSinusoidalEqualized::operator()(qreal x, qreal y) const
0071 {
0072     // Here the cumulative function is a piecewise function obtained empirically
0073     // by fitting some simple curves to a list of points
0074     const qreal z = DotsRoundSinusoidal::operator()(x, y);
0075     if (z <= 0.5) {
0076         return M_SQRT2 / 2.0 - std::sqrt(-(z - 0.5469) / 1.0938);
0077     } else {
0078         return (1.0 - M_SQRT2 / 2.0) + std::sqrt((z - (1.0 - 0.5469)) / 1.0938);
0079     }
0080 }
0081 
0082 qreal DotsEllipseLinear::operator()(qreal x, qreal y) const
0083 {
0084     constexpr qreal aspectRatio = 1.25;
0085     // The following magic number makes the function go to 1.0 in
0086     // the corners of the cell (normalizes it)
0087     constexpr qreal factor = 0.625;
0088     x = triangle(x);
0089     y = triangle(y) * aspectRatio;
0090     return std::sqrt(x * x + y * y) * factor;
0091 }
0092 
0093 qreal DotsEllipseLinearEqualized::operator()(qreal x, qreal y) const
0094 {
0095     // In theory, the cumulative function for this spot function is:
0096     // "coverage = area of the intersection between the elliptical disk formed
0097     // by the value, and the screen cell".
0098     // This uses a piecewise cumulative function obtained analytically. First,
0099     // the area of the elliptical disk is obtained. If the value is greater than
0100     // "0.625" (value at which the elliptical disk touches the left and right
0101     // screen cell borders) then the area of the left and right elliptical
0102     // chords is subtracted; and if the value is greater than "0.78125" then the
0103     // area of the top and bottom elliptical chords is also subtracted
0104     const qreal z = DotsEllipseLinear::operator()(x, y);
0105     constexpr qreal factor = 0.625;
0106     constexpr qreal factorTimes2 = factor * 2.0;
0107     const qreal zOverFactorTimes2 = z / factorTimes2;
0108     const qreal zTimesPoint8OverFactorTimes2 = 0.8 * zOverFactorTimes2;
0109     qreal result = M_PI * zOverFactorTimes2 * zTimesPoint8OverFactorTimes2;
0110     if (z > 0.625) {
0111         const qreal zOverFactorTimes2Squared = zOverFactorTimes2 * zOverFactorTimes2;
0112         result -= 2.0 * (zOverFactorTimes2Squared * std::acos(factor / z) -
0113                          0.5 * std::sqrt(zOverFactorTimes2Squared - 0.25)) * 0.8;
0114     }
0115     if (z > 0.78125) {
0116         const qreal zTimesPoint8OverFactorTimes2Squared =
0117             zTimesPoint8OverFactorTimes2 * zTimesPoint8OverFactorTimes2;
0118         result -= 2.0 * (zTimesPoint8OverFactorTimes2Squared * std::acos(factor / (0.8 * z)) -
0119                          0.5 * std::sqrt(zTimesPoint8OverFactorTimes2Squared - 0.25)) / 0.8;
0120     }
0121     return result;
0122 }
0123 
0124 qreal DotsEllipseSinusoidal::operator()(qreal x, qreal y) const
0125 {
0126     // The "0.4" and "0.6" values make a function such that if one thresholds it
0127     // at 0.4 the resulting shape touches the borders of the cell horizontally
0128     // and if one thresholds it at "0.6" it touches the cell vertically. That is
0129     // the standard convention
0130     x = sin(x) * 0.4;
0131     y = sin(y) * 0.6;
0132     // We would need to divide the following by ("0.4" + "0.6"), but since that is
0133     // equal to 1, we skip it. The division is required to normalize the values
0134     // of the function. If those magic numbers change, and they don't sum to 1,
0135     // then we must divide
0136     return (x + y);
0137 }
0138 
0139 qreal DotsEllipseSinusoidalEqualized::operator()(qreal x, qreal y) const
0140 {
0141     // Here the cumulative function is a piecewise function obtained empirically
0142     // by fitting some simple cubic curves to a list of points
0143     const qreal z = DotsEllipseSinusoidal::operator()(x, y);
0144     const qreal z2 = z * z;
0145     const qreal z3 = z * z2;
0146     if (z <= 0.3) {
0147         return 0.8795 * z3 + 0.1825 * z2 + 0.6649 * z + 0.0008;
0148     } else if (z <= 0.4) {
0149         return 32.0507 * z3 - 30.3781 * z2 + 10.6756 * z - 1.0937;
0150     } else if (z <= 0.5) {
0151         return 27.8089 * z3 - 39.4726 * z2 + 19.8992 * z - 3.0553;
0152     } else if (z <= 0.6) {
0153         return 35.1490 * z3 - 55.6810 * z2 + 30.6244 * z - 5.2839;
0154     } else if (z <= 0.7) {
0155         return 24.3210 * z3 - 50.1381 * z2 + 35.6452 * z - 7.9322;
0156     } else {
0157         return 0.7457 * z3 - 2.4792 * z2 + 3.3748 * z - 0.6402;
0158     }
0159 }
0160 
0161 qreal DotsEllipseLinear_Legacy::operator()(qreal x, qreal y) const
0162 {
0163     // This is the function used for the elliptical spots in Krita 4.*
0164     // It is wrong because it produces too dark values. The function should
0165     // produce a value of 1 at the corners of the screen cell
0166     constexpr qreal ellipseRatioX = 0.4 / M_SQRT2;
0167     constexpr qreal ellipseRatioY = 0.6 / M_SQRT2;
0168     x = triangle(x) * ellipseRatioX;
0169     y = triangle(y) * ellipseRatioY;
0170     return std::sqrt(x * x + y * y) * M_SQRT2;
0171 }
0172 
0173 qreal DotsDiamond::operator()(qreal x, qreal y) const
0174 {
0175     return (triangle(x) + triangle(y)) / 2.;
0176 }
0177 
0178 qreal DotsDiamondEqualized::operator()(qreal x, qreal y) const
0179 {
0180     // Here the cumulative function is a piecewise function obtained
0181     // analytically. If the value is less than or equal to "0.5" then the
0182     // coverage is simply the area of the diamond and if the value is greater
0183     // than "0.5" then the coverage is the area of the intersection between the
0184     // diamond and the screen cell
0185     const qreal z = DotsDiamond::operator()(x, y);
0186     if (z <= 0.5) {
0187         return 2.0 * z * z;
0188     } else {
0189         return -2.0 * z * z + 4.0 * z - 1.0;
0190     }
0191 }
0192 
0193 qreal DotsSquare::operator()(qreal x, qreal y) const
0194 {
0195     return std::max(triangle(x), triangle(y));
0196 }
0197 
0198 qreal DotsSquareEqualized::operator()(qreal x, qreal y) const
0199 {
0200     // Here the cumulative function was obtained analytically and it is just the
0201     // area of the square
0202     const qreal z = DotsSquare::operator()(x, y);
0203     return z * z;
0204 }
0205 
0206 qreal LinesStraightLinear::operator()(qreal x, qreal y) const
0207 {
0208     Q_UNUSED(x);
0209     return triangle(y);
0210 }
0211 
0212 qreal LinesStraightSinusoidal::operator()(qreal x, qreal y) const
0213 {
0214     Q_UNUSED(x);
0215     return sin(y);
0216 }
0217 
0218 qreal LinesSineWaveLinear::operator()(qreal x, qreal y) const
0219 {
0220     return triangle(y + sin(x));
0221 }
0222 
0223 qreal LinesSineWaveSinusoidal::operator()(qreal x, qreal y) const
0224 {
0225     return sin(y + sin(x));
0226 }
0227 
0228 qreal LinesTriangularWaveLinear::operator()(qreal x, qreal y) const
0229 {
0230     return triangle(y + triangle(x));
0231 }
0232 
0233 qreal LinesTriangularWaveSinusoidal::operator()(qreal x, qreal y) const
0234 {
0235     return sin(y + triangle(x));
0236 }
0237 
0238 qreal LinesSawToothWaveLinear::operator()(qreal x, qreal y) const
0239 {
0240     return triangle(y + sawTooth(x));
0241 }
0242 
0243 qreal LinesSawToothWaveSinusoidal::operator()(qreal x, qreal y) const
0244 {
0245     return sin(y + sawTooth(x));
0246 }
0247 
0248 qreal LinesCurtainsLinear::operator()(qreal x, qreal y) const
0249 {
0250     x = triangle(x);
0251     return triangle(y + x * x);
0252 }
0253 
0254 qreal LinesCurtainsSinusoidal::operator()(qreal x, qreal y) const
0255 {
0256     x = triangle(x);
0257     return sin(y + x * x);
0258 }
0259 
0260 }