Coverage Report

Created: 2025-07-23 08:13

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