Coverage Report

Created: 2023-09-25 06:41

/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
}