• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

saitoha / libsixel / 19550705690

20 Nov 2025 08:40PM UTC coverage: 42.352% (+1.6%) from 40.773%
19550705690

push

github

saitoha
perf: boost encoder workers after dithering completes

9753 of 34088 branches covered (28.61%)

25 of 124 new or added lines in 2 files covered. (20.16%)

1896 existing lines in 12 files now uncovered.

13481 of 31831 relevant lines covered (42.35%)

933434.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.68
/src/decoder-parallel.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2025 libsixel developers. See `AUTHORS`.
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 */
24

25
#include "config.h"
26

27
#include <errno.h>
28
#include <limits.h>
29
#include <stddef.h>
30
#include <stdint.h>
31
#include <stdlib.h>
32
#include <string.h>
33

34
#include <sixel.h>
35

36
#include "allocator.h"
37
#include "decoder-image.h"
38
#include "decoder-parallel.h"
39
#include "decoder-prescan.h"
40
#include "output.h"
41
#if SIXEL_ENABLE_THREADS
42
# include "sixel_threading.h"
43
# include "threadpool.h"
44
#endif
45

46
#define SIXEL_PARALLEL_MIN_BYTES   2048
47
#define SIXEL_PARALLEL_MIN_PIXELS  (64 * 64)
48
#define SIXEL_PARALLEL_MIN_BANDS   2
49
#define SIXEL_PARALLEL_MIN_BAND_BYTES 512
50
#define SIXEL_PARALLEL_MIN_PIXELS_PER_THREAD (128 * 128)
51
#define SIXEL_PARALLEL_MIN_JOBS_PER_THREAD 4
52
#define SIXEL_PARALLEL_MAX_REPEAT  0xffff
53
#define SIXEL_PARALLEL_PALVAL(n, a, m) \
54
    (((n) * (a) + ((m) / 2)) / (m))
55
#define SIXEL_PARALLEL_RGBA(r, g, b, a) \
56
    (((uint32_t)(r) << 24) | ((uint32_t)(g) << 16) | \
57
     ((uint32_t)(b) << 8) | ((uint32_t)(a)))
58
#define SIXEL_PARALLEL_XRGB(r, g, b) \
59
    SIXEL_PARALLEL_RGBA(SIXEL_PARALLEL_PALVAL((r), 255, 100), \
60
                        SIXEL_PARALLEL_PALVAL((g), 255, 100), \
61
                        SIXEL_PARALLEL_PALVAL((b), 255, 100), \
62
                        255)
63

64
typedef struct sixel_decoder_thread_config {
65
    int env_checked;
66
    int env_valid;
67
    int env_threads;
68
    int override_active;
69
    int override_threads;
70
} sixel_decoder_thread_config_t;
71

72
static sixel_decoder_thread_config_t g_decoder_threads = {
73
    0,
74
    0,
75
    1,
76
    0,
77
    1
78
};
79

80
static int
81
sixel_decoder_threads_token_is_auto(char const *text)
229✔
82
{
83
    if (text == NULL) {
229!
84
        return 0;
×
85
    }
86
    if ((text[0] == 'a' || text[0] == 'A') &&
229!
87
            (text[1] == 'u' || text[1] == 'U') &&
×
88
            (text[2] == 't' || text[2] == 'T') &&
×
89
            (text[3] == 'o' || text[3] == 'O') &&
×
90
            text[4] == '\0') {
×
91
        return 1;
×
92
    }
93
    return 0;
229✔
94
}
77✔
95

96
static int
97
sixel_decoder_threads_normalize(int requested)
226✔
98
{
99
    int normalized;
100

101
#if SIXEL_ENABLE_THREADS
102
    int hw_threads;
103

104
    if (requested <= 0) {
226!
105
        hw_threads = sixel_get_hw_threads();
×
106
        if (hw_threads < 1) {
×
107
            hw_threads = 1;
×
108
        }
109
        normalized = hw_threads;
×
110
    } else {
111
        normalized = requested;
226✔
112
    }
113
#else
114
    (void)requested;
115
    normalized = 1;
116
#endif
117
    if (normalized < 1) {
226!
118
        normalized = 1;
×
119
    }
120
    return normalized;
226✔
121
}
122

123
static int
124
sixel_decoder_threads_parse_value(char const *text, int *value)
229✔
125
{
126
    long parsed;
127
    char *endptr;
128
    int normalized;
129

130
    if (text == NULL || value == NULL) {
229!
131
        return 0;
×
132
    }
133
    if (sixel_decoder_threads_token_is_auto(text)) {
229!
134
        normalized = sixel_decoder_threads_normalize(0);
×
135
        *value = normalized;
×
136
        return 1;
×
137
    }
138
    errno = 0;
229✔
139
    parsed = strtol(text, &endptr, 10);
229✔
140
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
229!
141
        return 0;
3✔
142
    }
143
    if (parsed < 1) {
226!
144
        normalized = sixel_decoder_threads_normalize(1);
×
145
    } else if (parsed > INT_MAX) {
226!
146
        normalized = sixel_decoder_threads_normalize(INT_MAX);
×
147
    } else {
148
        normalized = sixel_decoder_threads_normalize((int)parsed);
226✔
149
    }
150
    *value = normalized;
226✔
151
    return 1;
226✔
152
}
77✔
153

154
static void
155
sixel_decoder_threads_load_env(void)
223✔
156
{
157
    char const *text;
158
    int parsed;
159

160
    if (g_decoder_threads.env_checked) {
223✔
161
        return;
3✔
162
    }
163
    g_decoder_threads.env_checked = 1;
220✔
164
    g_decoder_threads.env_valid = 0;
220✔
165
    text = getenv("SIXEL_THREADS");
220✔
166
    if (text == NULL || text[0] == '\0') {
220!
UNCOV
167
        return;
×
168
    }
169
    if (sixel_decoder_threads_parse_value(text, &parsed)) {
220!
170
        g_decoder_threads.env_threads = parsed;
220✔
171
        g_decoder_threads.env_valid = 1;
220✔
172
    }
74✔
173
}
75✔
174

175
SIXELSTATUS
176
sixel_decoder_parallel_override_threads(char const *text)
9✔
177
{
178
    SIXELSTATUS status;
179
    int parsed;
180

181
    status = SIXEL_BAD_ARGUMENT;
9✔
182
    if (text == NULL || text[0] == '\0') {
9!
183
        sixel_helper_set_additional_message(
×
184
            "decoder: missing thread count after -=/--threads.");
185
        goto end;
×
186
    }
187
    if (!sixel_decoder_threads_parse_value(text, &parsed)) {
9✔
188
        sixel_helper_set_additional_message(
3✔
189
            "decoder: threads must be a positive integer or 'auto'.");
190
        goto end;
3✔
191
    }
192
    g_decoder_threads.override_active = 1;
6✔
193
    g_decoder_threads.override_threads = parsed;
6✔
194
    status = SIXEL_OK;
6✔
195
end:
6✔
196
    return status;
9✔
197
}
198

199
#if SIXEL_ENABLE_THREADS
200

201
typedef struct sixel_parallel_decode_plan {
202
    unsigned char *data;
203
    int len;
204
    image_buffer_t *image;
205
    sixel_allocator_t *allocator;
206
    sixel_prescan_t *prescan;
207
    int depth;
208
    int threads;
209
    int direct_mode;
210
    int width;
211
    int height;
212
    int *band_color_max;
213
    int band_count;
214
} sixel_parallel_decode_plan_t;
215

216
static SIXELSTATUS sixel_parallel_safe_addition(
300,085✔
217
    sixel_prescan_band_state_t *state,
218
    unsigned char value)
219
{
220
    SIXELSTATUS status;
221
    int digit;
222

223
    status = SIXEL_FALSE;
300,085✔
224
    digit = (int)value - '0';
300,085✔
225
    if ((state->param > INT_MAX / 10) ||
300,085!
226
            (digit > INT_MAX - state->param * 10)) {
299,649!
227
        status = SIXEL_BAD_INTEGER_OVERFLOW;
872✔
228
        sixel_helper_set_additional_message(
872✔
229
            "decoder-parallel: integer overflow in parameter.");
230
        goto end;
872✔
231
    }
232
    state->param = state->param * 10 + digit;
300,085✔
233
    status = SIXEL_OK;
300,085✔
234
end:
204,640✔
235
    return status;
300,085✔
236
}
237

238
static uint32_t sixel_parallel_hls_to_rgba(int hue, int lum, int sat)
×
239
{
240
    double min;
241
    double max;
242
    int r;
243
    int g;
244
    int b;
245

246
    if (sat == 0) {
×
247
        r = g = b = lum;
×
248
    }
249
    max = lum + sat *
×
250
        (1.0 - (lum > 50 ? (2 * (lum / 100.0) - 1.0) :
×
251
                - (2 * (lum / 100.0) - 1.0))) / 2.0;
×
252
    min = lum - sat *
×
253
        (1.0 - (lum > 50 ? (2 * (lum / 100.0) - 1.0) :
×
254
                - (2 * (lum / 100.0) - 1.0))) / 2.0;
×
255
    hue = (hue + 240) % 360;
×
256
    switch (hue / 60) {
×
257
    case 0:
258
        r = max;
×
259
        g = (min + (max - min) * (hue / 60.0));
×
260
        b = min;
×
261
        break;
×
262
    case 1:
263
        r = min + (max - min) * ((120 - hue) / 60.0);
×
264
        g = max;
×
265
        b = min;
×
266
        break;
×
267
    case 2:
268
        r = min;
×
269
        g = max;
×
270
        b = (min + (max - min) * ((hue - 120) / 60.0));
×
271
        break;
×
272
    case 3:
273
        r = min;
×
274
        g = (min + (max - min) * ((240 - hue) / 60.0));
×
275
        b = max;
×
276
        break;
×
277
    case 4:
278
        r = (min + (max - min) * ((hue - 240) / 60.0));
×
279
        g = min;
×
280
        b = max;
×
281
        break;
×
282
    case 5:
283
        r = max;
×
284
        g = min;
×
285
        b = (min + (max - min) * ((360 - hue) / 60.0));
×
286
        break;
×
287
    default:
288
#if HAVE___BUILTIN_UNREACHABLE
289
        __builtin_unreachable();
×
290
#endif
291
        r = g = b = 0;
292
        break;
293
    }
294
    return SIXEL_PARALLEL_RGBA(r, g, b, 255);
×
295
}
296

297
static void sixel_parallel_store_indexed(image_buffer_t *image,
687,448✔
298
                                         int x,
299
                                         int y,
300
                                         int repeat,
301
                                         int color_index)
302
{
303
    size_t offset;
304

305
    if (repeat <= 0 || image == NULL) {
687,448!
306
        return;
842✔
307
    }
308
    offset = (size_t)image->width * (size_t)y + (size_t)x;
687,448✔
309
    memset(image->pixels.in_bytes + offset,
687,448✔
310
           color_index,
311
           (size_t)repeat);
312
}
163,686✔
313

314
static void sixel_parallel_store_rgba(image_buffer_t *image,
684,031✔
315
                                      int x,
316
                                      int y,
317
                                      int repeat,
318
                                      uint32_t rgba)
319
{
320
    unsigned char r;
321
    unsigned char g;
322
    unsigned char b;
323
    unsigned char a;
324
    unsigned char *dst;
325
    size_t offset;
326
    int i;
327

328
    if (repeat <= 0 || image == NULL) {
684,031!
329
        return;
996✔
330
    }
331
    r = (unsigned char)(rgba >> 24);
684,031✔
332
    g = (unsigned char)((rgba >> 16) & 0xff);
684,031✔
333
    b = (unsigned char)((rgba >> 8) & 0xff);
684,031✔
334
    a = (unsigned char)(rgba & 0xff);
684,031✔
335
    offset = ((size_t)image->width * (size_t)y + (size_t)x) * 4u;
684,031✔
336
    dst = image->pixels.in_bytes + offset;
684,031✔
337
    for (i = 0; i < repeat; ++i) {
1,387,527✔
338
        dst[0] = r;
703,496✔
339
        dst[1] = g;
703,496✔
340
        dst[2] = b;
703,496✔
341
        dst[3] = a;
703,496✔
342
        dst += 4;
703,496✔
343
    }
163,496✔
344
}
160,269✔
345

346
static void sixel_parallel_fill_span(sixel_parallel_decode_plan_t *plan,
1,367,696✔
347
                                     int y,
348
                                     int x,
349
                                     int repeat,
350
                                     int color_index,
351
                                     uint32_t *palette)
352
{
353
    if (y < 0 || y >= plan->height) {
1,367,696!
354
        return;
240✔
355
    }
356
    if (color_index < 0) {
1,367,696!
357
        color_index = 0;
×
358
    }
359
    if (color_index >= SIXEL_PRESCAN_PALETTE_MAX) {
1,367,696!
360
        color_index = SIXEL_PRESCAN_PALETTE_MAX - 1;
×
361
    }
362
    if (plan->direct_mode) {
1,367,696✔
363
        sixel_parallel_store_rgba(plan->image,
840,608✔
364
                                  x,
158,423✔
365
                                  y,
158,423✔
366
                                  repeat,
158,423✔
367
                                  palette[color_index]);
682,185✔
368
    } else {
158,423✔
369
        sixel_parallel_store_indexed(plan->image,
847,260✔
370
                                     x,
161,749✔
371
                                     y,
161,749✔
372
                                     repeat,
161,749✔
373
                                     color_index);
161,749✔
374
    }
375
}
320,172✔
376

377
static void sixel_parallel_track_color(int color_index,
1,368,070✔
378
                                       int *local_max_color)
379
{
380
    if (color_index > *local_max_color) {
1,368,070✔
381
        *local_max_color = color_index;
2,620✔
382
    }
872✔
383
}
1,368,070✔
384

385
static SIXELSTATUS sixel_parallel_decode_band(
52,764✔
386
    sixel_parallel_decode_plan_t *plan,
387
    int band_index)
388
{
389
    SIXELSTATUS status;
390
    sixel_prescan_band_state_t state;
391
    unsigned char *cursor;
392
    unsigned char *end;
393
    int bits;
394
    int mask;
395
    int i;
396
    int local_max_color;
397

398
    status = SIXEL_FALSE;
52,764✔
399
    state = plan->prescan->band_states[band_index];
52,764✔
400
    if ((size_t)plan->len < plan->prescan->band_start_offsets[band_index] ||
52,914!
401
            (size_t)plan->len < plan->prescan->band_end_offsets[band_index] ||
450!
402
            plan->prescan->band_end_offsets[band_index] <
600✔
403
                plan->prescan->band_start_offsets[band_index]) {
450!
404
        sixel_helper_set_additional_message(
104,628✔
405
            "decoder-parallel: invalid band boundaries detected.");
406
        status = SIXEL_BAD_INPUT;
104,628✔
407
        goto end;
104,628✔
408
    }
409
    cursor = plan->data + plan->prescan->band_start_offsets[band_index];
52,764✔
410
    end = plan->data + plan->prescan->band_end_offsets[band_index];
52,764✔
411
    local_max_color = -1;
52,764✔
412

413
    while (cursor < end) {
1,667,053✔
414
        switch (state.state) {
1,666,603!
415
        case SIXEL_PRESCAN_PS_GROUND:
4✔
416
            switch (*cursor) {
6!
417
            case 0x1b:
4✔
418
                state.state = SIXEL_PRESCAN_PS_ESC;
6✔
419
                break;
6✔
420
            case 0x90:
421
                state.state = SIXEL_PRESCAN_PS_DCS;
×
422
                break;
×
423
            case 0x9c:
424
                cursor = end;
×
425
                continue;
×
426
            default:
427
                break;
×
428
            }
429
            break;
6✔
430
        case SIXEL_PRESCAN_PS_ESC:
8✔
431
            switch (*cursor) {
12!
432
            case '\\':
4✔
433
            case 0x9c:
434
                cursor = end;
6✔
435
                continue;
6✔
436
            case 'P':
4✔
437
                state.param = -1;
6✔
438
                state.state = SIXEL_PRESCAN_PS_DCS;
6✔
439
                break;
6✔
440
            default:
441
                break;
×
442
            }
443
            break;
6✔
444
        case SIXEL_PRESCAN_PS_DCS:
4✔
445
            switch (*cursor) {
6!
446
            case 0x1b:
447
                state.state = SIXEL_PRESCAN_PS_ESC;
×
448
                break;
×
449
            case '0':
450
            case '1':
451
            case '2':
452
            case '3':
453
            case '4':
454
            case '5':
455
            case '6':
456
            case '7':
457
            case '8':
458
            case '9':
459
                if (state.param < 0) {
×
460
                    state.param = 0;
×
461
                }
462
                status = sixel_parallel_safe_addition(&state, *cursor);
×
463
                if (SIXEL_FAILED(status)) {
×
464
                    goto end;
×
465
                }
466
                break;
×
467
            case ';':
468
                if (state.param < 0) {
×
469
                    state.param = 0;
×
470
                }
471
                if (state.nparams < DECSIXEL_PARAMS_MAX) {
×
472
                    state.params[state.nparams++] = state.param;
×
473
                }
474
                state.param = 0;
×
475
                break;
×
476
            case 'q':
4✔
477
                if (state.param >= 0 &&
6!
478
                        state.nparams < DECSIXEL_PARAMS_MAX) {
×
479
                    state.params[state.nparams++] = state.param;
×
480
                }
481
                state.param = 0;
6✔
482
                state.state = SIXEL_PRESCAN_PS_DECSIXEL;
6✔
483
                break;
6✔
484
            default:
485
                break;
×
486
            }
487
            break;
6✔
488
        case SIXEL_PRESCAN_PS_DECSIXEL:
844,020✔
489
            switch (*cursor) {
1,198,595!
490
            case 0x1b:
4✔
491
                state.state = SIXEL_PRESCAN_PS_ESC;
6✔
492
                break;
6✔
493
            case '"':
4✔
494
                state.param = 0;
6✔
495
                state.nparams = 0;
6✔
496
                state.state = SIXEL_PRESCAN_PS_DECGRA;
6✔
497
                break;
6✔
498
            case '!':
46,592✔
499
                state.param = 0;
69,132✔
500
                state.nparams = 0;
69,132✔
501
                state.state = SIXEL_PRESCAN_PS_DECGRI;
69,132✔
502
                break;
69,132✔
503
            case '#':
61,636✔
504
                state.param = 0;
90,971✔
505
                state.nparams = 0;
90,971✔
506
                state.state = SIXEL_PRESCAN_PS_DECGCI;
90,971✔
507
                break;
90,971✔
508
            case '$':
3,168✔
509
                state.pos_x = 0;
4,750✔
510
                break;
4,750✔
511
            case '-':
512
                state.pos_x = 0;
×
513
                state.pos_y += 6;
×
514
                break;
×
515
            default:
732,616✔
516
                if (*cursor >= '?' && *cursor <= '~') {
1,033,730!
517
                    bits = *cursor - '?';
1,007,659✔
518
                    if (state.pos_x < 0 || state.pos_y < 0) {
1,007,659!
519
                        status = SIXEL_BAD_INPUT;
45,308✔
520
                        sixel_helper_set_additional_message(
45,308✔
521
                            "decoder-parallel: negative draw position.");
522
                        goto end;
45,308✔
523
                    }
524
                    if (bits == 0) {
1,007,659✔
525
                        state.pos_x += state.repeat_count;
280,799✔
526
                    } else {
87,171✔
527
                        mask = 0x01;
726,860✔
528
                        if (state.repeat_count <= 1) {
726,860✔
529
                            for (i = 0; i < 6; ++i) {
4,824,561✔
530
                                if ((bits & mask) != 0) {
4,100,443✔
531
                                    sixel_parallel_fill_span(plan,
1,696,500✔
532
                                                            state.pos_y + i,
1,369,142✔
533
                                                            state.pos_x,
327,358✔
534
                                                            1,
535
                                                            state.color_index,
327,358✔
536
                                                            state.palette);
327,358✔
537
                                    sixel_parallel_track_color(
1,369,142✔
538
                                        state.color_index,
327,358✔
539
                                        &local_max_color);
540
                                }
327,358✔
541
                                mask <<= 1;
4,100,443✔
542
                            }
877,483✔
543
                            state.pos_x += 1;
724,118✔
544
                        } else {
186,958✔
545
                            for (i = 0; i < 6; ++i) {
13,362✔
546
                                if ((bits & mask) != 0) {
10,620✔
547
                                    int c;
548
                                    int run_span;
549
                                    int row;
550

551
                                    c = mask << 1;
2,778✔
552
                                    run_span = 1;
2,778✔
553
                                    while ((i + run_span) < 6 &&
9,531✔
554
                                            (bits & c) != 0) {
6,996✔
555
                                        c <<= 1;
5,827✔
556
                                        run_span += 1;
5,827✔
557
                                    }
558
                                    for (row = 0; row < run_span; ++row) {
11,370✔
559
                                        sixel_parallel_fill_span(
8,592✔
560
                                            plan,
2,852✔
561
                                            state.pos_y + i + row,
8,592✔
562
                                            state.pos_x,
2,852✔
563
                                            state.repeat_count,
2,852✔
564
                                            state.color_index,
2,852✔
565
                                            state.palette);
2,852✔
566
                                    }
2,852✔
567
                                    sixel_parallel_track_color(
2,778✔
568
                                        state.color_index,
926✔
569
                                        &local_max_color);
570
                                    i += run_span - 1;
2,778✔
571
                                    mask <<= run_span - 1;
2,778✔
572
                                }
926✔
573
                                mask <<= 1;
10,620✔
574
                            }
3,540✔
575
                            state.pos_x += state.repeat_count;
2,742✔
576
                        }
577
                    }
578
                    state.repeat_count = 1;
1,007,659✔
579
                }
275,043✔
580
                break;
1,080,040✔
581
            }
582
            break;
1,146,453✔
583
        case SIXEL_PRESCAN_PS_DECGRA:
48✔
584
            switch (*cursor) {
72!
585
            case 0x1b:
586
                state.state = SIXEL_PRESCAN_PS_ESC;
×
587
                break;
×
588
            case '0':
32✔
589
            case '1':
590
            case '2':
591
            case '3':
592
            case '4':
593
            case '5':
594
            case '6':
595
            case '7':
596
            case '8':
597
            case '9':
598
                status = sixel_parallel_safe_addition(&state, *cursor);
48✔
599
                if (SIXEL_FAILED(status)) {
48!
600
                    goto end;
×
601
                }
602
                break;
48✔
603
            case ';':
12✔
604
                if (state.nparams < DECSIXEL_PARAMS_MAX) {
18!
605
                    state.params[state.nparams++] = state.param;
18✔
606
                }
6✔
607
                state.param = 0;
18✔
608
                break;
18✔
609
            default:
4✔
610
                if (state.nparams < DECSIXEL_PARAMS_MAX) {
6!
611
                    state.params[state.nparams++] = state.param;
6✔
612
                }
2✔
613
                if (state.nparams > 0) {
6!
614
                    state.attributed_pad = state.params[0];
6✔
615
                }
2✔
616
                if (state.nparams > 1) {
6!
617
                    state.attributed_pan = state.params[1];
6✔
618
                }
2✔
619
                if (state.nparams > 2 && state.params[2] > 0) {
6!
620
                    state.attributed_ph = state.params[2];
6✔
621
                }
2✔
622
                if (state.nparams > 3 && state.params[3] > 0) {
6!
623
                    state.attributed_pv = state.params[3];
6✔
624
                }
2✔
625
                if (state.attributed_pan <= 0) {
6!
626
                    state.attributed_pan = 1;
×
627
                }
628
                if (state.attributed_pad <= 0) {
6!
629
                    state.attributed_pad = 1;
×
630
                }
631
                state.param = 0;
6✔
632
                state.nparams = 0;
6✔
633
                state.state = SIXEL_PRESCAN_PS_DECSIXEL;
6✔
634
                continue;
6✔
635
            }
636
            break;
66✔
637
        case SIXEL_PRESCAN_PS_DECGRI:
111,620✔
638
            switch (*cursor) {
165,958!
639
            case 0x1b:
640
                state.state = SIXEL_PRESCAN_PS_ESC;
×
641
                break;
×
642
            case '0':
65,028✔
643
            case '1':
644
            case '2':
645
            case '3':
646
            case '4':
647
            case '5':
648
            case '6':
649
            case '7':
650
            case '8':
651
            case '9':
652
                status = sixel_parallel_safe_addition(&state, *cursor);
96,600✔
653
                if (SIXEL_FAILED(status)) {
96,600!
654
                    goto end;
×
655
                }
656
                break;
96,600✔
657
            case ';':
658
                break;
×
659
            default:
46,592✔
660
                state.repeat_count = state.param;
69,358✔
661
                if (state.repeat_count == 0) {
69,358!
662
                    state.repeat_count = 1;
×
663
                }
664
                if (state.repeat_count > SIXEL_PARALLEL_MAX_REPEAT) {
69,358!
665
                    status = SIXEL_BAD_INPUT;
×
666
                    sixel_helper_set_additional_message(
×
667
                        "decoder-parallel: repeat parameter too large.");
668
                    goto end;
×
669
                }
670
                state.param = 0;
69,358✔
671
                state.nparams = 0;
69,358✔
672
                state.state = SIXEL_PRESCAN_PS_DECSIXEL;
69,358✔
673
                continue;
69,358✔
674
            }
675
            break;
96,600✔
676
        case SIXEL_PRESCAN_PS_DECGCI:
203,888✔
677
            switch (*cursor) {
301,954!
678
            case 0x1b:
679
                state.state = SIXEL_PRESCAN_PS_ESC;
×
680
                break;
×
681
            case '0':
139,580✔
682
            case '1':
683
            case '2':
684
            case '3':
685
            case '4':
686
            case '5':
687
            case '6':
688
            case '7':
689
            case '8':
690
            case '9':
691
                status = sixel_parallel_safe_addition(&state, *cursor);
206,080✔
692
                if (SIXEL_FAILED(status)) {
206,080!
693
                    goto end;
×
694
                }
695
                break;
206,080✔
696
            case ';':
2,672✔
697
                if (state.nparams < DECSIXEL_PARAMS_MAX) {
4,008!
698
                    state.params[state.nparams++] = state.param;
4,008✔
699
                }
1,336✔
700
                state.param = 0;
4,008✔
701
                break;
4,008✔
702
            default:
61,636✔
703
                if (state.nparams < DECSIXEL_PARAMS_MAX) {
91,866!
704
                    state.params[state.nparams++] = state.param;
91,483✔
705
                }
29,847✔
706
                state.param = 0;
91,866✔
707
                if (state.nparams > 0) {
91,866!
708
                    state.color_index = state.params[0];
91,780✔
709
                    if (state.color_index < 0) {
91,780!
710
                        state.color_index = 0;
×
711
                    } else if (state.color_index >=
91,780!
712
                            SIXEL_PRESCAN_PALETTE_MAX) {
713
                        state.color_index = SIXEL_PRESCAN_PALETTE_MAX - 1;
×
714
                    }
715
                }
30,144✔
716
                if (state.nparams > 4) {
91,866✔
717
                    if (state.params[1] == 1) {
1,002!
718
                        if (state.params[2] > 360) {
×
719
                            state.params[2] = 360;
×
720
                        }
721
                        if (state.params[3] > 100) {
×
722
                            state.params[3] = 100;
×
723
                        }
724
                        if (state.params[4] > 100) {
×
725
                            state.params[4] = 100;
×
726
                        }
727
                        state.palette[state.color_index] =
×
728
                            sixel_parallel_hls_to_rgba(state.params[2],
×
729
                                                       state.params[3],
730
                                                       state.params[4]);
731
                    } else if (state.params[1] == 2) {
1,002!
732
                        if (state.params[2] > 100) {
1,002!
733
                            state.params[2] = 100;
×
734
                        }
735
                        if (state.params[3] > 100) {
1,002!
736
                            state.params[3] = 100;
×
737
                        }
738
                        if (state.params[4] > 100) {
1,002!
739
                            state.params[4] = 100;
×
740
                        }
741
                        state.palette[state.color_index] =
1,002✔
742
                            SIXEL_PARALLEL_XRGB(state.params[2],
1,002✔
743
                                                state.params[3],
744
                                                state.params[4]);
745
                    }
334✔
746
                }
334✔
747
                state.nparams = 0;
91,694✔
748
                state.state = SIXEL_PRESCAN_PS_DECSIXEL;
91,694✔
749
                continue;
91,694✔
750
            }
751
            break;
210,088✔
752
        default:
753
            break;
×
754
        }
755
        cursor++;
1,453,225✔
756
    }
757
    plan->band_color_max[band_index] = local_max_color;
450✔
758
    status = SIXEL_OK;
450✔
759
end:
300✔
760
    return status;
450✔
761
}
762

763
static int sixel_parallel_worker(tp_job_t job,
450✔
764
                                 void *userdata,
765
                                 void *workspace)
766
{
767
    sixel_parallel_decode_plan_t *plan;
768
    SIXELSTATUS status;
769

770
    (void)workspace;
150✔
771
    plan = (sixel_parallel_decode_plan_t *)userdata;
450✔
772
    status = sixel_parallel_decode_band(plan, job.band_index);
450✔
773
    if (SIXEL_FAILED(status)) {
450!
774
        return -1;
×
775
    }
776
    return 0;
450✔
777
}
150✔
778

779
static SIXELSTATUS sixel_parallel_run_workers(
6✔
780
    sixel_parallel_decode_plan_t *plan)
781
{
782
    SIXELSTATUS status;
783
    threadpool_t *pool;
784
    int worker_threads;
785
    int job_index;
786

787
    status = SIXEL_FALSE;
6✔
788
    pool = NULL;
6✔
789
    worker_threads = plan->threads;
6✔
790
    if (worker_threads > plan->band_count) {
6!
791
        worker_threads = plan->band_count;
×
792
    }
793
    if (worker_threads < 1) {
6!
794
        worker_threads = 1;
×
795
    }
796
    if (worker_threads == 1) {
6!
797
        for (job_index = 0; job_index < plan->band_count;
×
798
                ++job_index) {
×
799
            status = sixel_parallel_decode_band(plan, job_index);
×
800
            if (SIXEL_FAILED(status)) {
×
801
                goto end;
×
802
            }
803
        }
804
        status = SIXEL_OK;
×
805
        goto end;
×
806
    }
807
    pool = threadpool_create(worker_threads,
8✔
808
                             plan->band_count,
2✔
809
                             0,
810
                             sixel_parallel_worker,
811
                             plan);
2✔
812
    if (pool == NULL) {
6!
813
        sixel_helper_set_additional_message(
×
814
            "decoder-parallel: failed to create threadpool.");
815
        status = SIXEL_BAD_ALLOCATION;
×
816
        goto end;
×
817
    }
818
    for (job_index = 0; job_index < plan->band_count;
456✔
819
            ++job_index) {
450✔
820
        threadpool_push(pool, (tp_job_t){ job_index });
450✔
821
    }
150✔
822
    threadpool_finish(pool);
6✔
823
    if (threadpool_get_error(pool) != 0) {
6!
824
        sixel_helper_set_additional_message(
×
825
            "decoder-parallel: worker reported an error.");
826
        status = SIXEL_BAD_INPUT;
×
827
        goto end;
×
828
    }
829
    status = SIXEL_OK;
6✔
830
end:
4✔
831
    if (pool != NULL) {
6!
832
        threadpool_destroy(pool);
6✔
833
    }
2✔
834
    return status;
6✔
835
}
836

837
static int sixel_parallel_should_attempt(sixel_prescan_t *prescan,
6✔
838
                                         int len,
839
                                         int threads)
840
{
841
    int pixels;
842
    int pixels_per_thread;
843
    int avg_band_bytes;
844
    int jobs_per_thread;
845

846
    if (prescan == NULL) {
6!
847
        return 0;
×
848
    }
849
    if (threads < 2) {
6!
850
        return 0;
×
851
    }
852
    if (len < SIXEL_PARALLEL_MIN_BYTES) {
6!
853
        return 0;
×
854
    }
855
    if (prescan->band_count < SIXEL_PARALLEL_MIN_BANDS) {
6!
856
        return 0;
×
857
    }
858
    if (prescan->flags != 0u) {
6!
859
        return 0;
×
860
    }
861
    pixels = prescan->width * prescan->height;
6✔
862
    if (pixels < SIXEL_PARALLEL_MIN_PIXELS) {
6!
863
        return 0;
×
864
    }
865
    pixels_per_thread = pixels / threads;
6✔
866
    if (pixels_per_thread < SIXEL_PARALLEL_MIN_PIXELS_PER_THREAD) {
6!
867
        return 0;
×
868
    }
869
    jobs_per_thread = prescan->band_count / threads;
6✔
870
    if (jobs_per_thread < SIXEL_PARALLEL_MIN_JOBS_PER_THREAD) {
6!
871
        return 0;
×
872
    }
873
    avg_band_bytes = len / prescan->band_count;
6✔
874
    if (avg_band_bytes < SIXEL_PARALLEL_MIN_BAND_BYTES) {
6!
875
        return 0;
×
876
    }
877
    return 1;
6✔
878
}
2✔
879

880
static SIXELSTATUS sixel_parallel_finalize_palette(image_buffer_t *image,
3✔
881
                                                   int *band_color_max,
882
                                                   int band_count)
883
{
884
    int max_color;
885
    int i;
886

887
    if (image == NULL || band_color_max == NULL) {
3!
888
        return SIXEL_BAD_ARGUMENT;
×
889
    }
890
    max_color = 0;
3✔
891
    for (i = 0; i < band_count; ++i) {
228✔
892
        if (band_color_max[i] > max_color) {
225✔
893
            max_color = band_color_max[i];
72✔
894
        }
24✔
895
    }
75✔
896
    if (max_color < 0) {
3!
897
        max_color = 0;
×
898
    }
899
    image->ncolors = max_color;
3✔
900
    return SIXEL_OK;
3✔
901
}
1✔
902

903
static void sixel_parallel_apply_final_palette(image_buffer_t *image,
3✔
904
                                               sixel_prescan_t *prescan)
905
{
906
    uint32_t rgba;
907
    int entries;
908
    int r;
909
    int g;
910
    int b;
911
    int i;
912

913
    if (image == NULL || prescan == NULL) {
3!
914
        return;
×
915
    }
916
    entries = SIXEL_PRESCAN_PALETTE_MAX;
3✔
917
    if (entries > SIXEL_PALETTE_MAX_DECODER) {
3!
918
        entries = SIXEL_PALETTE_MAX_DECODER;
×
919
    }
920
    for (i = 0; i < entries; ++i) {
771✔
921
        rgba = prescan->final_state.palette[i];
768✔
922
        r = (int)((rgba >> 24) & 0xff);
768✔
923
        g = (int)((rgba >> 16) & 0xff);
768✔
924
        b = (int)((rgba >> 8) & 0xff);
768✔
925
        image->palette[i] = (r << 16) | (g << 8) | b;
768✔
926
    }
256✔
927
}
1✔
928

929
int
930
sixel_decoder_parallel_resolve_threads(void)
223✔
931
{
932
    sixel_decoder_threads_load_env();
223✔
933
    if (g_decoder_threads.override_active) {
223✔
934
        return g_decoder_threads.override_threads;
6✔
935
    }
936
    if (g_decoder_threads.env_valid) {
217!
937
        return g_decoder_threads.env_threads;
217✔
938
    }
UNCOV
939
    return sixel_decoder_threads_normalize(0);
×
940
}
75✔
941

942
static SIXELSTATUS sixel_parallel_decode_internal(
223✔
943
    unsigned char *p,
944
    int len,
945
    image_buffer_t *image,
946
    sixel_allocator_t *allocator,
947
    int depth,
948
    int *used_parallel)
949
{
950
    SIXELSTATUS status;
951
    sixel_prescan_t *prescan;
952
    sixel_parallel_decode_plan_t plan;
953
    int bgindex;
954
    int width;
955
    int height;
956
    int band_count;
957
    int threads;
958

959
    status = SIXEL_FALSE;
223✔
960
    prescan = NULL;
223✔
961
    memset(&plan, 0, sizeof(plan));
223✔
962
    if (used_parallel != NULL) {
223!
963
        *used_parallel = 0;
223✔
964
    }
75✔
965
    if (image == NULL || allocator == NULL) {
223!
966
        return SIXEL_BAD_ARGUMENT;
×
967
    }
968
    threads = sixel_decoder_parallel_resolve_threads();
223✔
969
    if (threads < 2 || len < SIXEL_PARALLEL_MIN_BYTES) {
223✔
970
        status = SIXEL_OK;
217✔
971
        goto end;
217✔
972
    }
973
    status = sixel_prescan_run(p, len, &prescan, allocator);
6✔
974
    if (SIXEL_FAILED(status)) {
6!
975
        goto end;
×
976
    }
977
    if (!sixel_parallel_should_attempt(prescan,
8!
978
                                       len,
2✔
979
                                       threads)) {
2✔
980
        status = SIXEL_OK;
×
981
        goto end;
×
982
    }
983
    width = prescan->width;
6✔
984
    height = prescan->height;
6✔
985
    if (width < 1) {
6!
986
        width = 1;
×
987
    }
988
    if (height < 1) {
6!
989
        height = 1;
×
990
    }
991
    bgindex = prescan->band_states[0].bgindex;
6✔
992
    status = image_buffer_init(image,
8✔
993
                               width,
2✔
994
                               height,
2✔
995
                               bgindex,
2✔
996
                               depth,
2✔
997
                               allocator);
2✔
998
    if (SIXEL_FAILED(status)) {
6!
999
        goto end;
×
1000
    }
1001
    plan.data = p;
6✔
1002
    plan.len = len;
6✔
1003
    plan.image = image;
6✔
1004
    plan.allocator = allocator;
6✔
1005
    plan.prescan = prescan;
6✔
1006
    plan.depth = depth;
6✔
1007
    plan.direct_mode = (depth == 4);
6✔
1008
    plan.width = width;
6✔
1009
    plan.height = height;
6✔
1010
    plan.threads = threads;
6✔
1011
    band_count = prescan->band_count;
6✔
1012
    if (band_count < 1) {
6!
1013
        status = SIXEL_OK;
×
1014
        goto end;
×
1015
    }
1016
    plan.band_count = band_count;
6✔
1017
    plan.band_color_max = (int *)sixel_allocator_calloc(allocator,
8✔
1018
                                                        (size_t)band_count,
2✔
1019
                                                        sizeof(int));
1020
    if (plan.band_color_max == NULL) {
6!
1021
        sixel_helper_set_additional_message(
×
1022
            "decoder-parallel: failed to allocate band metadata.");
1023
        status = SIXEL_BAD_ALLOCATION;
×
1024
        goto end;
×
1025
    }
1026
    status = sixel_parallel_run_workers(&plan);
6✔
1027
    if (SIXEL_FAILED(status)) {
6!
1028
        goto end;
×
1029
    }
1030
    if (!plan.direct_mode) {
6✔
1031
        status = sixel_parallel_finalize_palette(image,
4✔
1032
                                                 plan.band_color_max,
1✔
1033
                                                 band_count);
1✔
1034
        if (SIXEL_FAILED(status)) {
3!
1035
            goto end;
×
1036
        }
1037
        sixel_parallel_apply_final_palette(image, prescan);
3✔
1038
    } else {
1✔
1039
        image->ncolors = 0;
3✔
1040
    }
1041
    if (used_parallel != NULL) {
6!
1042
        *used_parallel = 1;
6✔
1043
    }
2✔
1044
    status = SIXEL_OK;
6✔
1045
end:
148✔
1046
    if (prescan != NULL) {
223✔
1047
        sixel_prescan_destroy(prescan, allocator);
6✔
1048
    }
2✔
1049
    if (plan.band_color_max != NULL) {
223✔
1050
        sixel_allocator_free(allocator, plan.band_color_max);
6✔
1051
    }
2✔
1052
    return status;
223✔
1053
}
75✔
1054

1055
#else /* !SIXEL_ENABLE_THREADS */
1056

1057
int sixel_decoder_parallel_resolve_threads(void)
1058
{
1059
    return 1;
1060
}
1061

1062
#endif /* SIXEL_ENABLE_THREADS */
1063

1064
SIXELSTATUS sixel_decode_raw_parallel(unsigned char *p,
205✔
1065
                                      int len,
1066
                                      image_buffer_t *image,
1067
                                      sixel_allocator_t *allocator,
1068
                                      int *used_parallel)
1069
{
1070
#if SIXEL_ENABLE_THREADS
1071
    return sixel_parallel_decode_internal(p,
274✔
1072
                                          len,
69✔
1073
                                          image,
69✔
1074
                                          allocator,
69✔
1075
                                          1,
1076
                                          used_parallel);
69✔
1077
#else
1078
    if (used_parallel != NULL) {
1079
        *used_parallel = 0;
1080
    }
1081
    (void)p;
1082
    (void)len;
1083
    (void)image;
1084
    (void)allocator;
1085
    return SIXEL_OK;
1086
#endif
1087
}
1088

1089
SIXELSTATUS sixel_decode_direct_parallel(unsigned char *p,
18✔
1090
                                         int len,
1091
                                         image_buffer_t *image,
1092
                                         sixel_allocator_t *allocator,
1093
                                         int *used_parallel)
1094
{
1095
#if SIXEL_ENABLE_THREADS
1096
    return sixel_parallel_decode_internal(p,
24✔
1097
                                          len,
6✔
1098
                                          image,
6✔
1099
                                          allocator,
6✔
1100
                                          4,
1101
                                          used_parallel);
6✔
1102
#else
1103
    if (used_parallel != NULL) {
1104
        *used_parallel = 0;
1105
    }
1106
    (void)p;
1107
    (void)len;
1108
    (void)image;
1109
    (void)allocator;
1110
    return SIXEL_OK;
1111
#endif
1112
}
1113

1114
/* emacs Local Variables:      */
1115
/* emacs mode: c               */
1116
/* emacs tab-width: 4          */
1117
/* emacs indent-tabs-mode: nil */
1118
/* emacs c-basic-offset: 4     */
1119
/* emacs End:                  */
1120
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1121
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc