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