Coverage Report

Created: 2025-12-31 07:23

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