/src/poppler/splash/SplashScreen.cc
Line | Count | Source (jump to first uncovered line) |
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 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 "SplashMath.h" |
31 | | #include "SplashScreen.h" |
32 | | |
33 | | static const SplashScreenParams defaultParams = { |
34 | | splashScreenDispersed, // type |
35 | | 2, // size |
36 | | 2, // dotRadius |
37 | | 1.0, // gamma |
38 | | 0.0, // blackThreshold |
39 | | 1.0 // whiteThreshold |
40 | | }; |
41 | | |
42 | | //------------------------------------------------------------------------ |
43 | | |
44 | | struct SplashScreenPoint |
45 | | { |
46 | | int x, y; |
47 | | int dist; |
48 | | }; |
49 | | |
50 | | struct cmpDistancesFunctor |
51 | | { |
52 | 0 | bool operator()(const SplashScreenPoint p0, const SplashScreenPoint p1) { return p0.dist < p1.dist; } |
53 | | }; |
54 | | |
55 | | //------------------------------------------------------------------------ |
56 | | // SplashScreen |
57 | | //------------------------------------------------------------------------ |
58 | | |
59 | | // If <clustered> is true, this generates a 45 degree screen using a |
60 | | // circular dot spot function. DPI = resolution / ((size / 2) * |
61 | | // sqrt(2)). If <clustered> is false, this generates an optimal |
62 | | // threshold matrix using recursive tesselation. Gamma correction |
63 | | // (gamma = 1 / 1.33) is also computed here. |
64 | | SplashScreen::SplashScreen(const SplashScreenParams *params) |
65 | 352k | { |
66 | | |
67 | 352k | if (!params) { |
68 | 59.7k | params = &defaultParams; |
69 | 59.7k | } |
70 | | |
71 | 352k | screenParams = params; |
72 | 352k | mat = nullptr; |
73 | 352k | size = 0; |
74 | 352k | maxVal = 0; |
75 | 352k | minVal = 0; |
76 | 352k | } |
77 | | |
78 | | void SplashScreen::createMatrix() |
79 | 144 | { |
80 | 144 | unsigned char u; |
81 | 144 | int black, white, i; |
82 | | |
83 | 144 | const SplashScreenParams *params = screenParams; |
84 | | |
85 | | // size must be a power of 2, and at least 2 |
86 | 144 | for (size = 2, log2Size = 1; size < params->size; size <<= 1, ++log2Size) { |
87 | 0 | ; |
88 | 0 | } |
89 | | |
90 | 144 | switch (params->type) { |
91 | | |
92 | 144 | case splashScreenDispersed: |
93 | 144 | mat = (unsigned char *)gmallocn(size * size, sizeof(unsigned char)); |
94 | 144 | buildDispersedMatrix(size / 2, size / 2, 1, size / 2, 1); |
95 | 144 | break; |
96 | | |
97 | 0 | case splashScreenClustered: |
98 | 0 | mat = (unsigned char *)gmallocn(size * size, sizeof(unsigned char)); |
99 | 0 | buildClusteredMatrix(); |
100 | 0 | break; |
101 | | |
102 | 0 | case splashScreenStochasticClustered: |
103 | | // size must be at least 2*r |
104 | 0 | while (size < (params->dotRadius << 1)) { |
105 | 0 | size <<= 1; |
106 | 0 | ++log2Size; |
107 | 0 | } |
108 | 0 | mat = (unsigned char *)gmallocn(size * size, sizeof(unsigned char)); |
109 | 0 | buildSCDMatrix(params->dotRadius); |
110 | 0 | break; |
111 | 144 | } |
112 | | |
113 | 144 | sizeM1 = size - 1; |
114 | | |
115 | | // do gamma correction and compute minVal/maxVal |
116 | 144 | minVal = 255; |
117 | 144 | maxVal = 0; |
118 | 144 | black = splashRound((SplashCoord)255.0 * params->blackThreshold); |
119 | 144 | if (black < 1) { |
120 | 144 | black = 1; |
121 | 144 | } |
122 | 144 | int whiteAux = splashRound((SplashCoord)255.0 * params->whiteThreshold); |
123 | 144 | if (whiteAux > 255) { |
124 | 0 | white = 255; |
125 | 144 | } else { |
126 | 144 | white = whiteAux; |
127 | 144 | } |
128 | 720 | for (i = 0; i < size * size; ++i) { |
129 | 576 | u = splashRound((SplashCoord)255.0 * splashPow((SplashCoord)mat[i] / 255.0, params->gamma)); |
130 | 576 | if (u < black) { |
131 | 0 | u = (unsigned char)black; |
132 | 576 | } else if (u >= white) { |
133 | 144 | u = (unsigned char)white; |
134 | 144 | } |
135 | 576 | mat[i] = u; |
136 | 576 | if (u < minVal) { |
137 | 288 | minVal = u; |
138 | 288 | } else if (u > maxVal) { |
139 | 288 | maxVal = u; |
140 | 288 | } |
141 | 576 | } |
142 | 144 | } |
143 | | |
144 | | void SplashScreen::buildDispersedMatrix(int i, int j, int val, int delta, int offset) |
145 | 720 | { |
146 | 720 | if (delta == 0) { |
147 | | // map values in [1, size^2] --> [1, 255] |
148 | 576 | mat[(i << log2Size) + j] = 1 + (254 * (val - 1)) / (size * size - 1); |
149 | 576 | } else { |
150 | 144 | buildDispersedMatrix(i, j, val, delta / 2, 4 * offset); |
151 | 144 | buildDispersedMatrix((i + delta) % size, (j + delta) % size, val + offset, delta / 2, 4 * offset); |
152 | 144 | buildDispersedMatrix((i + delta) % size, j, val + 2 * offset, delta / 2, 4 * offset); |
153 | 144 | buildDispersedMatrix((i + 2 * delta) % size, (j + delta) % size, val + 3 * offset, delta / 2, 4 * offset); |
154 | 144 | } |
155 | 720 | } |
156 | | |
157 | | void SplashScreen::buildClusteredMatrix() |
158 | 0 | { |
159 | 0 | SplashCoord *dist; |
160 | 0 | SplashCoord u, v, d; |
161 | 0 | unsigned char val; |
162 | 0 | int size2, x, y, x1, y1, i; |
163 | |
|
164 | 0 | size2 = size >> 1; |
165 | | |
166 | | // initialize the threshold matrix |
167 | 0 | for (y = 0; y < size; ++y) { |
168 | 0 | for (x = 0; x < size; ++x) { |
169 | 0 | mat[(y << log2Size) + x] = 0; |
170 | 0 | } |
171 | 0 | } |
172 | | |
173 | | // build the distance matrix |
174 | 0 | dist = (SplashCoord *)gmallocn(size * size2, sizeof(SplashCoord)); |
175 | 0 | for (y = 0; y < size2; ++y) { |
176 | 0 | for (x = 0; x < size2; ++x) { |
177 | 0 | if (x + y < size2 - 1) { |
178 | 0 | u = (SplashCoord)x + 0.5 - 0; |
179 | 0 | v = (SplashCoord)y + 0.5 - 0; |
180 | 0 | } else { |
181 | 0 | u = (SplashCoord)x + 0.5 - (SplashCoord)size2; |
182 | 0 | v = (SplashCoord)y + 0.5 - (SplashCoord)size2; |
183 | 0 | } |
184 | 0 | dist[y * size2 + x] = u * u + v * v; |
185 | 0 | } |
186 | 0 | } |
187 | 0 | for (y = 0; y < size2; ++y) { |
188 | 0 | for (x = 0; x < size2; ++x) { |
189 | 0 | if (x < y) { |
190 | 0 | u = (SplashCoord)x + 0.5 - 0; |
191 | 0 | v = (SplashCoord)y + 0.5 - (SplashCoord)size2; |
192 | 0 | } else { |
193 | 0 | u = (SplashCoord)x + 0.5 - (SplashCoord)size2; |
194 | 0 | v = (SplashCoord)y + 0.5 - 0; |
195 | 0 | } |
196 | 0 | dist[(size2 + y) * size2 + x] = u * u + v * v; |
197 | 0 | } |
198 | 0 | } |
199 | | |
200 | | // build the threshold matrix |
201 | 0 | x1 = y1 = 0; // make gcc happy |
202 | 0 | for (i = 0; i < size * size2; ++i) { |
203 | 0 | d = -1; |
204 | 0 | for (y = 0; y < size; ++y) { |
205 | 0 | for (x = 0; x < size2; ++x) { |
206 | 0 | if (mat[(y << log2Size) + x] == 0 && dist[y * size2 + x] > d) { |
207 | 0 | x1 = x; |
208 | 0 | y1 = y; |
209 | 0 | d = dist[y1 * size2 + x1]; |
210 | 0 | } |
211 | 0 | } |
212 | 0 | } |
213 | | // map values in [0, 2*size*size2-1] --> [1, 255] |
214 | 0 | val = 1 + (254 * (2 * i)) / (2 * size * size2 - 1); |
215 | 0 | mat[(y1 << log2Size) + x1] = val; |
216 | 0 | val = 1 + (254 * (2 * i + 1)) / (2 * size * size2 - 1); |
217 | 0 | if (y1 < size2) { |
218 | 0 | mat[((y1 + size2) << log2Size) + x1 + size2] = val; |
219 | 0 | } else { |
220 | 0 | mat[((y1 - size2) << log2Size) + x1 + size2] = val; |
221 | 0 | } |
222 | 0 | } |
223 | |
|
224 | 0 | gfree(dist); |
225 | 0 | } |
226 | | |
227 | | // Compute the distance between two points on a toroid. |
228 | | int SplashScreen::distance(int x0, int y0, int x1, int y1) |
229 | 0 | { |
230 | 0 | int dx0, dx1, dx, dy0, dy1, dy; |
231 | |
|
232 | 0 | dx0 = abs(x0 - x1); |
233 | 0 | dx1 = size - dx0; |
234 | 0 | dx = dx0 < dx1 ? dx0 : dx1; |
235 | 0 | dy0 = abs(y0 - y1); |
236 | 0 | dy1 = size - dy0; |
237 | 0 | dy = dy0 < dy1 ? dy0 : dy1; |
238 | 0 | return dx * dx + dy * dy; |
239 | 0 | } |
240 | | |
241 | | // Algorithm taken from: |
242 | | // Victor Ostromoukhov and Roger D. Hersch, "Stochastic Clustered-Dot |
243 | | // Dithering" in Color Imaging: Device-Independent Color, Color |
244 | | // Hardcopy, and Graphic Arts IV, SPIE Vol. 3648, pp. 496-505, 1999. |
245 | | void SplashScreen::buildSCDMatrix(int r) |
246 | 0 | { |
247 | 0 | SplashScreenPoint *dots, *pts; |
248 | 0 | int dotsLen, dotsSize; |
249 | 0 | char *tmpl; |
250 | 0 | char *grid; |
251 | 0 | int *region, *dist; |
252 | 0 | int x, y, xx, yy, x0, x1, y0, y1, i, j, d, iMin, dMin, n; |
253 | | |
254 | | // generate the random space-filling curve |
255 | 0 | pts = (SplashScreenPoint *)gmallocn(size * size, sizeof(SplashScreenPoint)); |
256 | 0 | i = 0; |
257 | 0 | for (y = 0; y < size; ++y) { |
258 | 0 | for (x = 0; x < size; ++x) { |
259 | 0 | pts[i].x = x; |
260 | 0 | pts[i].y = y; |
261 | 0 | ++i; |
262 | 0 | } |
263 | 0 | } |
264 | 0 | for (i = 0; i < size * size; ++i) { |
265 | 0 | j = i + (int)((double)(size * size - i) * grandom_double()); |
266 | 0 | x = pts[i].x; |
267 | 0 | y = pts[i].y; |
268 | 0 | pts[i].x = pts[j].x; |
269 | 0 | pts[i].y = pts[j].y; |
270 | 0 | pts[j].x = x; |
271 | 0 | pts[j].y = y; |
272 | 0 | } |
273 | | |
274 | | // construct the circle template |
275 | 0 | tmpl = (char *)gmallocn((r + 1) * (r + 1), sizeof(char)); |
276 | 0 | for (y = 0; y <= r; ++y) { |
277 | 0 | for (x = 0; x <= r; ++x) { |
278 | 0 | tmpl[y * (r + 1) + x] = (x * y <= r * r) ? 1 : 0; |
279 | 0 | } |
280 | 0 | } |
281 | | |
282 | | // mark all grid cells as free |
283 | 0 | grid = (char *)gmallocn(size * size, sizeof(char)); |
284 | 0 | for (y = 0; y < size; ++y) { |
285 | 0 | for (x = 0; x < size; ++x) { |
286 | 0 | grid[(y << log2Size) + x] = 0; |
287 | 0 | } |
288 | 0 | } |
289 | | |
290 | | // walk the space-filling curve, adding dots |
291 | 0 | dotsLen = 0; |
292 | 0 | dotsSize = 32; |
293 | 0 | dots = (SplashScreenPoint *)gmallocn(dotsSize, sizeof(SplashScreenPoint)); |
294 | 0 | for (i = 0; i < size * size; ++i) { |
295 | 0 | x = pts[i].x; |
296 | 0 | y = pts[i].y; |
297 | 0 | if (!grid[(y << log2Size) + x]) { |
298 | 0 | if (dotsLen == dotsSize) { |
299 | 0 | dotsSize *= 2; |
300 | 0 | dots = (SplashScreenPoint *)greallocn(dots, dotsSize, sizeof(SplashScreenPoint)); |
301 | 0 | } |
302 | 0 | dots[dotsLen++] = pts[i]; |
303 | 0 | for (yy = 0; yy <= r; ++yy) { |
304 | 0 | y0 = (y + yy) % size; |
305 | 0 | y1 = (y - yy + size) % size; |
306 | 0 | for (xx = 0; xx <= r; ++xx) { |
307 | 0 | if (tmpl[yy * (r + 1) + xx]) { |
308 | 0 | x0 = (x + xx) % size; |
309 | 0 | x1 = (x - xx + size) % size; |
310 | 0 | grid[(y0 << log2Size) + x0] = 1; |
311 | 0 | grid[(y0 << log2Size) + x1] = 1; |
312 | 0 | grid[(y1 << log2Size) + x0] = 1; |
313 | 0 | grid[(y1 << log2Size) + x1] = 1; |
314 | 0 | } |
315 | 0 | } |
316 | 0 | } |
317 | 0 | } |
318 | 0 | } |
319 | |
|
320 | 0 | gfree(tmpl); |
321 | 0 | gfree(grid); |
322 | | |
323 | | // assign each cell to a dot, compute distance to center of dot |
324 | 0 | region = (int *)gmallocn(size * size, sizeof(int)); |
325 | 0 | dist = (int *)gmallocn(size * size, sizeof(int)); |
326 | 0 | for (y = 0; y < size; ++y) { |
327 | 0 | for (x = 0; x < size; ++x) { |
328 | 0 | iMin = 0; |
329 | 0 | dMin = distance(dots[0].x, dots[0].y, x, y); |
330 | 0 | for (i = 1; i < dotsLen; ++i) { |
331 | 0 | d = distance(dots[i].x, dots[i].y, x, y); |
332 | 0 | if (d < dMin) { |
333 | 0 | iMin = i; |
334 | 0 | dMin = d; |
335 | 0 | } |
336 | 0 | } |
337 | 0 | region[(y << log2Size) + x] = iMin; |
338 | 0 | dist[(y << log2Size) + x] = dMin; |
339 | 0 | } |
340 | 0 | } |
341 | | |
342 | | // compute threshold values |
343 | 0 | for (i = 0; i < dotsLen; ++i) { |
344 | 0 | n = 0; |
345 | 0 | for (y = 0; y < size; ++y) { |
346 | 0 | for (x = 0; x < size; ++x) { |
347 | 0 | if (region[(y << log2Size) + x] == i) { |
348 | 0 | pts[n].x = x; |
349 | 0 | pts[n].y = y; |
350 | 0 | pts[n].dist = distance(dots[i].x, dots[i].y, x, y); |
351 | 0 | ++n; |
352 | 0 | } |
353 | 0 | } |
354 | 0 | } |
355 | 0 | std::sort(pts, pts + n, cmpDistancesFunctor()); |
356 | 0 | for (j = 0; j < n; ++j) { |
357 | | // map values in [0 .. n-1] --> [255 .. 1] |
358 | 0 | mat[(pts[j].y << log2Size) + pts[j].x] = 255 - (254 * j) / (n - 1); |
359 | 0 | } |
360 | 0 | } |
361 | |
|
362 | 0 | gfree(pts); |
363 | 0 | gfree(region); |
364 | 0 | gfree(dist); |
365 | |
|
366 | 0 | gfree(dots); |
367 | 0 | } |
368 | | |
369 | | SplashScreen::SplashScreen(const SplashScreen *screen) |
370 | 4.15M | { |
371 | 4.15M | screenParams = screen->screenParams; |
372 | 4.15M | size = screen->size; |
373 | 4.15M | sizeM1 = screen->sizeM1; |
374 | 4.15M | log2Size = screen->log2Size; |
375 | 4.15M | mat = (unsigned char *)gmallocn(size * size, sizeof(unsigned char)); |
376 | 4.15M | if (likely(mat != nullptr)) { |
377 | 0 | memcpy(mat, screen->mat, size * size * sizeof(unsigned char)); |
378 | 0 | } |
379 | 4.15M | minVal = screen->minVal; |
380 | 4.15M | maxVal = screen->maxVal; |
381 | 4.15M | } |
382 | | |
383 | | SplashScreen::~SplashScreen() |
384 | 4.51M | { |
385 | 4.51M | gfree(mat); |
386 | 4.51M | } |