Krita Source Code Documentation
Loading...
Searching...
No Matches
KisScreentoneScreentoneFunctions.cpp
Go to the documentation of this file.
1/*
2 * KDE. Krita Project.
3 *
4 * SPDX-FileCopyrightText: 2020 Deif Lou <ginoba@gmail.com>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
10
11#include <cmath>
12
14
15qreal sin(qreal x)
16{
17 x = std::cos(x * M_PI);
18 return x * x;
19}
20
21qreal triangle(qreal x)
22{
23 return 1.0 - 2.0 * std::abs(x - std::floor(x + 0.5));
24}
25
26qreal sawTooth(qreal x)
27{
28 constexpr qreal peakXOffset = 0.9;
29 constexpr qreal peakYOffset = 0.5;
30 x = x - std::floor(x);
31 return (x < peakXOffset ? 1.0 / peakXOffset * x : -1.0 / (1.0 - peakXOffset) * (x - 1.0)) * peakYOffset;
32}
33
34qreal DotsRoundLinear::operator()(qreal x, qreal y) const
35{
36 x = triangle(x);
37 y = triangle(y);
38 return std::sqrt(x * x + y * y) / M_SQRT2;
39}
40
41qreal DotsRoundLinearEqualized::operator()(qreal x, qreal y) const
42{
43 // In theory, the cumulative function for this spot function is:
44 // "coverage = area of the intersection between the disk formed by the value,
45 // and the screen cell".
46 // This uses a piecewise cumulative function obtained analytically. If
47 // the value is less than or equal to "sqrt(2) / 2" (value at which the
48 // disk touches the screen cell borders) then the coverage for that value
49 // is obtained simply by getting the area of the disk. If the value is
50 // greater than "sqrt(2) / 2" then the coverage is obtained by computing
51 // the area of the intersection between the disk and the screen cell (area
52 // of the disk minus area of the chords of the disk outside the screen cell)
53 const qreal z = DotsRoundLinear::operator()(x, y);
54 const qreal zOverSqrt2 = z / M_SQRT2;
55 const qreal zOverSqrt2Squared = zOverSqrt2 * zOverSqrt2;
56 if (z <= M_SQRT2 / 2.0) {
57 return M_PI * zOverSqrt2Squared;
58 } else {
59 return M_PI * zOverSqrt2Squared -
60 4.0 * (zOverSqrt2Squared * std::acos(M_SQRT2 / (2.0 * z)) -
61 0.5 * std::sqrt(zOverSqrt2Squared - 0.25));
62 }
63}
64
65qreal DotsRoundSinusoidal::operator()(qreal x, qreal y) const
66{
67 return (sin(x) + sin(y)) / 2.;
68}
69
70qreal DotsRoundSinusoidalEqualized::operator()(qreal x, qreal y) const
71{
72 // Here the cumulative function is a piecewise function obtained empirically
73 // by fitting some simple curves to a list of points
74 const qreal z = DotsRoundSinusoidal::operator()(x, y);
75 if (z <= 0.5) {
76 return M_SQRT2 / 2.0 - std::sqrt(-(z - 0.5469) / 1.0938);
77 } else {
78 return (1.0 - M_SQRT2 / 2.0) + std::sqrt((z - (1.0 - 0.5469)) / 1.0938);
79 }
80}
81
82qreal DotsEllipseLinear::operator()(qreal x, qreal y) const
83{
84 constexpr qreal aspectRatio = 1.25;
85 // The following magic number makes the function go to 1.0 in
86 // the corners of the cell (normalizes it)
87 constexpr qreal factor = 0.625;
88 x = triangle(x);
89 y = triangle(y) * aspectRatio;
90 return std::sqrt(x * x + y * y) * factor;
91}
92
93qreal DotsEllipseLinearEqualized::operator()(qreal x, qreal y) const
94{
95 // In theory, the cumulative function for this spot function is:
96 // "coverage = area of the intersection between the elliptical disk formed
97 // by the value, and the screen cell".
98 // This uses a piecewise cumulative function obtained analytically. First,
99 // the area of the elliptical disk is obtained. If the value is greater than
100 // "0.625" (value at which the elliptical disk touches the left and right
101 // screen cell borders) then the area of the left and right elliptical
102 // chords is subtracted; and if the value is greater than "0.78125" then the
103 // area of the top and bottom elliptical chords is also subtracted
104 const qreal z = DotsEllipseLinear::operator()(x, y);
105 constexpr qreal factor = 0.625;
106 constexpr qreal factorTimes2 = factor * 2.0;
107 const qreal zOverFactorTimes2 = z / factorTimes2;
108 const qreal zTimesPoint8OverFactorTimes2 = 0.8 * zOverFactorTimes2;
109 qreal result = M_PI * zOverFactorTimes2 * zTimesPoint8OverFactorTimes2;
110 if (z > 0.625) {
111 const qreal zOverFactorTimes2Squared = zOverFactorTimes2 * zOverFactorTimes2;
112 result -= 2.0 * (zOverFactorTimes2Squared * std::acos(factor / z) -
113 0.5 * std::sqrt(zOverFactorTimes2Squared - 0.25)) * 0.8;
114 }
115 if (z > 0.78125) {
116 const qreal zTimesPoint8OverFactorTimes2Squared =
117 zTimesPoint8OverFactorTimes2 * zTimesPoint8OverFactorTimes2;
118 result -= 2.0 * (zTimesPoint8OverFactorTimes2Squared * std::acos(factor / (0.8 * z)) -
119 0.5 * std::sqrt(zTimesPoint8OverFactorTimes2Squared - 0.25)) / 0.8;
120 }
121 return result;
122}
123
124qreal DotsEllipseSinusoidal::operator()(qreal x, qreal y) const
125{
126 // The "0.4" and "0.6" values make a function such that if one thresholds it
127 // at 0.4 the resulting shape touches the borders of the cell horizontally
128 // and if one thresholds it at "0.6" it touches the cell vertically. That is
129 // the standard convention
130 x = sin(x) * 0.4;
131 y = sin(y) * 0.6;
132 // We would need to divide the following by ("0.4" + "0.6"), but since that is
133 // equal to 1, we skip it. The division is required to normalize the values
134 // of the function. If those magic numbers change, and they don't sum to 1,
135 // then we must divide
136 return (x + y);
137}
138
140{
141 // Here the cumulative function is a piecewise function obtained empirically
142 // by fitting some simple cubic curves to a list of points
143 const qreal z = DotsEllipseSinusoidal::operator()(x, y);
144 const qreal z2 = z * z;
145 const qreal z3 = z * z2;
146 if (z <= 0.3) {
147 return 0.8795 * z3 + 0.1825 * z2 + 0.6649 * z + 0.0008;
148 } else if (z <= 0.4) {
149 return 32.0507 * z3 - 30.3781 * z2 + 10.6756 * z - 1.0937;
150 } else if (z <= 0.5) {
151 return 27.8089 * z3 - 39.4726 * z2 + 19.8992 * z - 3.0553;
152 } else if (z <= 0.6) {
153 return 35.1490 * z3 - 55.6810 * z2 + 30.6244 * z - 5.2839;
154 } else if (z <= 0.7) {
155 return 24.3210 * z3 - 50.1381 * z2 + 35.6452 * z - 7.9322;
156 } else {
157 return 0.7457 * z3 - 2.4792 * z2 + 3.3748 * z - 0.6402;
158 }
159}
160
161qreal DotsEllipseLinear_Legacy::operator()(qreal x, qreal y) const
162{
163 // This is the function used for the elliptical spots in Krita 4.*
164 // It is wrong because it produces too dark values. The function should
165 // produce a value of 1 at the corners of the screen cell
166 constexpr qreal ellipseRatioX = 0.4 / M_SQRT2;
167 constexpr qreal ellipseRatioY = 0.6 / M_SQRT2;
168 x = triangle(x) * ellipseRatioX;
169 y = triangle(y) * ellipseRatioY;
170 return std::sqrt(x * x + y * y) * M_SQRT2;
171}
172
173qreal DotsDiamond::operator()(qreal x, qreal y) const
174{
175 return (triangle(x) + triangle(y)) / 2.;
176}
177
178qreal DotsDiamondEqualized::operator()(qreal x, qreal y) const
179{
180 // Here the cumulative function is a piecewise function obtained
181 // analytically. If the value is less than or equal to "0.5" then the
182 // coverage is simply the area of the diamond and if the value is greater
183 // than "0.5" then the coverage is the area of the intersection between the
184 // diamond and the screen cell
185 const qreal z = DotsDiamond::operator()(x, y);
186 if (z <= 0.5) {
187 return 2.0 * z * z;
188 } else {
189 return -2.0 * z * z + 4.0 * z - 1.0;
190 }
191}
192
193qreal DotsSquare::operator()(qreal x, qreal y) const
194{
195 return std::max(triangle(x), triangle(y));
196}
197
198qreal DotsSquareEqualized::operator()(qreal x, qreal y) const
199{
200 // Here the cumulative function was obtained analytically and it is just the
201 // area of the square
202 const qreal z = DotsSquare::operator()(x, y);
203 return z * z;
204}
205
206qreal LinesStraightLinear::operator()(qreal x, qreal y) const
207{
208 Q_UNUSED(x);
209 return triangle(y);
210}
211
212qreal LinesStraightSinusoidal::operator()(qreal x, qreal y) const
213{
214 Q_UNUSED(x);
215 return sin(y);
216}
217
218qreal LinesSineWaveLinear::operator()(qreal x, qreal y) const
219{
220 return triangle(y + sin(x));
221}
222
223qreal LinesSineWaveSinusoidal::operator()(qreal x, qreal y) const
224{
225 return sin(y + sin(x));
226}
227
228qreal LinesTriangularWaveLinear::operator()(qreal x, qreal y) const
229{
230 return triangle(y + triangle(x));
231}
232
233qreal LinesTriangularWaveSinusoidal::operator()(qreal x, qreal y) const
234{
235 return sin(y + triangle(x));
236}
237
238qreal LinesSawToothWaveLinear::operator()(qreal x, qreal y) const
239{
240 return triangle(y + sawTooth(x));
241}
242
243qreal LinesSawToothWaveSinusoidal::operator()(qreal x, qreal y) const
244{
245 return sin(y + sawTooth(x));
246}
247
248qreal LinesCurtainsLinear::operator()(qreal x, qreal y) const
249{
250 x = triangle(x);
251 return triangle(y + x * x);
252}
253
254qreal LinesCurtainsSinusoidal::operator()(qreal x, qreal y) const
255{
256 x = triangle(x);
257 return sin(y + x * x);
258}
259
260}
#define M_PI
Definition kis_global.h:111