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 }