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