/src/poppler/splash/SplashScreen.cc
Line | Count | Source |
1 | | //======================================================================== |
2 | | // |
3 | | // SplashScreen.cc |
4 | | // |
5 | | //======================================================================== |
6 | | |
7 | | //======================================================================== |
8 | | // |
9 | | // Modified under the Poppler project - http://poppler.freedesktop.org |
10 | | // |
11 | | // All changes made under the Poppler project to this file are licensed |
12 | | // under GPL version 2 or later |
13 | | // |
14 | | // Copyright (C) 2009, 2016, 2018, 2020, 2021, 2025 Albert Astals Cid <aacid@kde.org> |
15 | | // Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it> |
16 | | // |
17 | | // To see a description of the changes please see the Changelog file that |
18 | | // came with your tarball or type make ChangeLog if you are building from git |
19 | | // |
20 | | //======================================================================== |
21 | | |
22 | | #include <config.h> |
23 | | |
24 | | #include <cstdlib> |
25 | | #include <cstring> |
26 | | #include <algorithm> |
27 | | #include "goo/gmem.h" |
28 | | #include "goo/grandom.h" |
29 | | #include "goo/GooLikely.h" |
30 | | #include "SplashScreen.h" |
31 | | |
32 | | static const SplashScreenParams defaultParams = { |
33 | | splashScreenDispersed, // type |
34 | | 2, // size |
35 | | 2 // dotRadius |
36 | | }; |
37 | | |
38 | | //------------------------------------------------------------------------ |
39 | | |
40 | | struct SplashScreenPoint |
41 | | { |
42 | | int x, y; |
43 | | int dist; |
44 | | }; |
45 | | |
46 | | struct cmpDistancesFunctor |
47 | | { |
48 | 0 | bool operator()(const SplashScreenPoint p0, const SplashScreenPoint p1) { return p0.dist < p1.dist; } |
49 | | }; |
50 | | |
51 | | //------------------------------------------------------------------------ |
52 | | // SplashScreen |
53 | | //------------------------------------------------------------------------ |
54 | | |
55 | | // If <clustered> is true, this generates a 45 degree screen using a |
56 | | // circular dot spot function. DPI = resolution / ((size / 2) * |
57 | | // sqrt(2)). If <clustered> is false, this generates an optimal |
58 | | // threshold matrix using recursive tesselation. Gamma correction |
59 | | // (gamma = 1 / 1.33) is also computed here. |
60 | | SplashScreen::SplashScreen(const SplashScreenParams *params) |
61 | 380k | { |
62 | | |
63 | 380k | if (!params) { |
64 | 60.0k | params = &defaultParams; |
65 | 60.0k | } |
66 | | |
67 | 380k | screenParams = params; |
68 | 380k | mat = nullptr; |
69 | 380k | size = 0; |
70 | 380k | } |
71 | | |
72 | | void SplashScreen::createMatrix() |
73 | 202 | { |
74 | 202 | const SplashScreenParams *params = screenParams; |
75 | | |
76 | | // size must be a power of 2, and at least 2 |
77 | 202 | for (size = 2, log2Size = 1; size < params->size; size <<= 1, ++log2Size) { |
78 | 0 | ; |
79 | 0 | } |
80 | | |
81 | 202 | switch (params->type) { |
82 | | |
83 | 202 | case splashScreenDispersed: |
84 | 202 | mat = (unsigned char *)gmallocn(size * size, sizeof(unsigned char)); |
85 | 202 | buildDispersedMatrix(size / 2, size / 2, 1, size / 2, 1); |
86 | 202 | break; |
87 | | |
88 | 0 | case splashScreenClustered: |
89 | 0 | mat = (unsigned char *)gmallocn(size * size, sizeof(unsigned char)); |
90 | 0 | buildClusteredMatrix(); |
91 | 0 | break; |
92 | | |
93 | 0 | case splashScreenStochasticClustered: |
94 | | // size must be at least 2*r |
95 | 0 | while (size < (params->dotRadius << 1)) { |
96 | 0 | size <<= 1; |
97 | 0 | ++log2Size; |
98 | 0 | } |
99 | 0 | mat = (unsigned char *)gmallocn(size * size, sizeof(unsigned char)); |
100 | 0 | buildSCDMatrix(params->dotRadius); |
101 | 0 | break; |
102 | 202 | } |
103 | | |
104 | 202 | sizeM1 = size - 1; |
105 | | |
106 | 202 | static const unsigned char black = 1; |
107 | 1.01k | for (int i = 0; i < size * size; ++i) { |
108 | 808 | if (mat[i] < black) { |
109 | 0 | mat[i] = black; |
110 | 0 | } |
111 | 808 | } |
112 | 202 | } |
113 | | |
114 | | void SplashScreen::buildDispersedMatrix(int i, int j, int val, int delta, int offset) |
115 | 1.01k | { |
116 | 1.01k | if (delta == 0) { |
117 | | // map values in [1, size^2] --> [1, 255] |
118 | 808 | mat[(i << log2Size) + j] = 1 + (254 * (val - 1)) / (size * size - 1); |
119 | 808 | } else { |
120 | 202 | buildDispersedMatrix(i, j, val, delta / 2, 4 * offset); |
121 | 202 | buildDispersedMatrix((i + delta) % size, (j + delta) % size, val + offset, delta / 2, 4 * offset); |
122 | 202 | buildDispersedMatrix((i + delta) % size, j, val + 2 * offset, delta / 2, 4 * offset); |
123 | 202 | buildDispersedMatrix((i + 2 * delta) % size, (j + delta) % size, val + 3 * offset, delta / 2, 4 * offset); |
124 | 202 | } |
125 | 1.01k | } |
126 | | |
127 | | void SplashScreen::buildClusteredMatrix() |
128 | 0 | { |
129 | 0 | SplashCoord *dist; |
130 | 0 | SplashCoord u, v, d; |
131 | 0 | unsigned char val; |
132 | 0 | int size2, x, y, x1, y1, i; |
133 | |
|
134 | 0 | size2 = size >> 1; |
135 | | |
136 | | // initialize the threshold matrix |
137 | 0 | for (y = 0; y < size; ++y) { |
138 | 0 | for (x = 0; x < size; ++x) { |
139 | 0 | mat[(y << log2Size) + x] = 0; |
140 | 0 | } |
141 | 0 | } |
142 | | |
143 | | // build the distance matrix |
144 | 0 | dist = (SplashCoord *)gmallocn(size * size2, sizeof(SplashCoord)); |
145 | 0 | for (y = 0; y < size2; ++y) { |
146 | 0 | for (x = 0; x < size2; ++x) { |
147 | 0 | if (x + y < size2 - 1) { |
148 | 0 | u = (SplashCoord)x + 0.5 - 0; |
149 | 0 | v = (SplashCoord)y + 0.5 - 0; |
150 | 0 | } else { |
151 | 0 | u = (SplashCoord)x + 0.5 - (SplashCoord)size2; |
152 | 0 | v = (SplashCoord)y + 0.5 - (SplashCoord)size2; |
153 | 0 | } |
154 | 0 | dist[y * size2 + x] = u * u + v * v; |
155 | 0 | } |
156 | 0 | } |
157 | 0 | for (y = 0; y < size2; ++y) { |
158 | 0 | for (x = 0; x < size2; ++x) { |
159 | 0 | if (x < y) { |
160 | 0 | u = (SplashCoord)x + 0.5 - 0; |
161 | 0 | v = (SplashCoord)y + 0.5 - (SplashCoord)size2; |
162 | 0 | } else { |
163 | 0 | u = (SplashCoord)x + 0.5 - (SplashCoord)size2; |
164 | 0 | v = (SplashCoord)y + 0.5 - 0; |
165 | 0 | } |
166 | 0 | dist[(size2 + y) * size2 + x] = u * u + v * v; |
167 | 0 | } |
168 | 0 | } |
169 | | |
170 | | // build the threshold matrix |
171 | 0 | x1 = y1 = 0; // make gcc happy |
172 | 0 | for (i = 0; i < size * size2; ++i) { |
173 | 0 | d = -1; |
174 | 0 | for (y = 0; y < size; ++y) { |
175 | 0 | for (x = 0; x < size2; ++x) { |
176 | 0 | if (mat[(y << log2Size) + x] == 0 && dist[y * size2 + x] > d) { |
177 | 0 | x1 = x; |
178 | 0 | y1 = y; |
179 | 0 | d = dist[y1 * size2 + x1]; |
180 | 0 | } |
181 | 0 | } |
182 | 0 | } |
183 | | // map values in [0, 2*size*size2-1] --> [1, 255] |
184 | 0 | val = 1 + (254 * (2 * i)) / (2 * size * size2 - 1); |
185 | 0 | mat[(y1 << log2Size) + x1] = val; |
186 | 0 | val = 1 + (254 * (2 * i + 1)) / (2 * size * size2 - 1); |
187 | 0 | if (y1 < size2) { |
188 | 0 | mat[((y1 + size2) << log2Size) + x1 + size2] = val; |
189 | 0 | } else { |
190 | 0 | mat[((y1 - size2) << log2Size) + x1 + size2] = val; |
191 | 0 | } |
192 | 0 | } |
193 | |
|
194 | 0 | gfree(dist); |
195 | 0 | } |
196 | | |
197 | | // Compute the distance between two points on a toroid. |
198 | | int SplashScreen::distance(int x0, int y0, int x1, int y1) |
199 | 0 | { |
200 | 0 | int dx0, dx1, dx, dy0, dy1, dy; |
201 | |
|
202 | 0 | dx0 = abs(x0 - x1); |
203 | 0 | dx1 = size - dx0; |
204 | 0 | dx = dx0 < dx1 ? dx0 : dx1; |
205 | 0 | dy0 = abs(y0 - y1); |
206 | 0 | dy1 = size - dy0; |
207 | 0 | dy = dy0 < dy1 ? dy0 : dy1; |
208 | 0 | return dx * dx + dy * dy; |
209 | 0 | } |
210 | | |
211 | | // Algorithm taken from: |
212 | | // Victor Ostromoukhov and Roger D. Hersch, "Stochastic Clustered-Dot |
213 | | // Dithering" in Color Imaging: Device-Independent Color, Color |
214 | | // Hardcopy, and Graphic Arts IV, SPIE Vol. 3648, pp. 496-505, 1999. |
215 | | void SplashScreen::buildSCDMatrix(int r) |
216 | 0 | { |
217 | 0 | SplashScreenPoint *dots, *pts; |
218 | 0 | int dotsLen, dotsSize; |
219 | 0 | char *tmpl; |
220 | 0 | char *grid; |
221 | 0 | int *region, *dist; |
222 | 0 | int x, y, xx, yy, x0, x1, y0, y1, i, j, d, iMin, dMin, n; |
223 | | |
224 | | // generate the random space-filling curve |
225 | 0 | pts = (SplashScreenPoint *)gmallocn(size * size, sizeof(SplashScreenPoint)); |
226 | 0 | i = 0; |
227 | 0 | for (y = 0; y < size; ++y) { |
228 | 0 | for (x = 0; x < size; ++x) { |
229 | 0 | pts[i].x = x; |
230 | 0 | pts[i].y = y; |
231 | 0 | ++i; |
232 | 0 | } |
233 | 0 | } |
234 | 0 | for (i = 0; i < size * size; ++i) { |
235 | 0 | j = i + (int)((double)(size * size - i) * grandom_double()); |
236 | 0 | x = pts[i].x; |
237 | 0 | y = pts[i].y; |
238 | 0 | pts[i].x = pts[j].x; |
239 | 0 | pts[i].y = pts[j].y; |
240 | 0 | pts[j].x = x; |
241 | 0 | pts[j].y = y; |
242 | 0 | } |
243 | | |
244 | | // construct the circle template |
245 | 0 | tmpl = (char *)gmallocn((r + 1) * (r + 1), sizeof(char)); |
246 | 0 | for (y = 0; y <= r; ++y) { |
247 | 0 | for (x = 0; x <= r; ++x) { |
248 | 0 | tmpl[y * (r + 1) + x] = (x * y <= r * r) ? 1 : 0; |
249 | 0 | } |
250 | 0 | } |
251 | | |
252 | | // mark all grid cells as free |
253 | 0 | grid = (char *)gmallocn(size * size, sizeof(char)); |
254 | 0 | for (y = 0; y < size; ++y) { |
255 | 0 | for (x = 0; x < size; ++x) { |
256 | 0 | grid[(y << log2Size) + x] = 0; |
257 | 0 | } |
258 | 0 | } |
259 | | |
260 | | // walk the space-filling curve, adding dots |
261 | 0 | dotsLen = 0; |
262 | 0 | dotsSize = 32; |
263 | 0 | dots = (SplashScreenPoint *)gmallocn(dotsSize, sizeof(SplashScreenPoint)); |
264 | 0 | for (i = 0; i < size * size; ++i) { |
265 | 0 | x = pts[i].x; |
266 | 0 | y = pts[i].y; |
267 | 0 | if (!grid[(y << log2Size) + x]) { |
268 | 0 | if (dotsLen == dotsSize) { |
269 | 0 | dotsSize *= 2; |
270 | 0 | dots = (SplashScreenPoint *)greallocn(dots, dotsSize, sizeof(SplashScreenPoint)); |
271 | 0 | } |
272 | 0 | dots[dotsLen++] = pts[i]; |
273 | 0 | for (yy = 0; yy <= r; ++yy) { |
274 | 0 | y0 = (y + yy) % size; |
275 | 0 | y1 = (y - yy + size) % size; |
276 | 0 | for (xx = 0; xx <= r; ++xx) { |
277 | 0 | if (tmpl[yy * (r + 1) + xx]) { |
278 | 0 | x0 = (x + xx) % size; |
279 | 0 | x1 = (x - xx + size) % size; |
280 | 0 | grid[(y0 << log2Size) + x0] = 1; |
281 | 0 | grid[(y0 << log2Size) + x1] = 1; |
282 | 0 | grid[(y1 << log2Size) + x0] = 1; |
283 | 0 | grid[(y1 << log2Size) + x1] = 1; |
284 | 0 | } |
285 | 0 | } |
286 | 0 | } |
287 | 0 | } |
288 | 0 | } |
289 | |
|
290 | 0 | gfree(tmpl); |
291 | 0 | gfree(grid); |
292 | | |
293 | | // assign each cell to a dot, compute distance to center of dot |
294 | 0 | region = (int *)gmallocn(size * size, sizeof(int)); |
295 | 0 | dist = (int *)gmallocn(size * size, sizeof(int)); |
296 | 0 | for (y = 0; y < size; ++y) { |
297 | 0 | for (x = 0; x < size; ++x) { |
298 | 0 | iMin = 0; |
299 | 0 | dMin = distance(dots[0].x, dots[0].y, x, y); |
300 | 0 | for (i = 1; i < dotsLen; ++i) { |
301 | 0 | d = distance(dots[i].x, dots[i].y, x, y); |
302 | 0 | if (d < dMin) { |
303 | 0 | iMin = i; |
304 | 0 | dMin = d; |
305 | 0 | } |
306 | 0 | } |
307 | 0 | region[(y << log2Size) + x] = iMin; |
308 | 0 | dist[(y << log2Size) + x] = dMin; |
309 | 0 | } |
310 | 0 | } |
311 | | |
312 | | // compute threshold values |
313 | 0 | for (i = 0; i < dotsLen; ++i) { |
314 | 0 | n = 0; |
315 | 0 | for (y = 0; y < size; ++y) { |
316 | 0 | for (x = 0; x < size; ++x) { |
317 | 0 | if (region[(y << log2Size) + x] == i) { |
318 | 0 | pts[n].x = x; |
319 | 0 | pts[n].y = y; |
320 | 0 | pts[n].dist = distance(dots[i].x, dots[i].y, x, y); |
321 | 0 | ++n; |
322 | 0 | } |
323 | 0 | } |
324 | 0 | } |
325 | 0 | std::sort(pts, pts + n, cmpDistancesFunctor()); |
326 | 0 | for (j = 0; j < n; ++j) { |
327 | | // map values in [0 .. n-1] --> [255 .. 1] |
328 | 0 | mat[(pts[j].y << log2Size) + pts[j].x] = 255 - (254 * j) / (n - 1); |
329 | 0 | } |
330 | 0 | } |
331 | |
|
332 | 0 | gfree(pts); |
333 | 0 | gfree(region); |
334 | 0 | gfree(dist); |
335 | |
|
336 | 0 | gfree(dots); |
337 | 0 | } |
338 | | |
339 | | SplashScreen::SplashScreen(const SplashScreen *screen) |
340 | 4.83M | { |
341 | 4.83M | screenParams = screen->screenParams; |
342 | 4.83M | size = screen->size; |
343 | 4.83M | sizeM1 = screen->sizeM1; |
344 | 4.83M | log2Size = screen->log2Size; |
345 | 4.83M | mat = (unsigned char *)gmallocn(size * size, sizeof(unsigned char)); |
346 | 4.83M | if (likely(mat != nullptr)) { |
347 | 0 | memcpy(mat, screen->mat, size * size * sizeof(unsigned char)); |
348 | 0 | } |
349 | 4.83M | } |
350 | | |
351 | | SplashScreen::~SplashScreen() |
352 | 5.21M | { |
353 | 5.21M | gfree(mat); |
354 | 5.21M | } |