• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

saitoha / libsixel / 19512329812

19 Nov 2025 06:32PM UTC coverage: 41.21% (+1.6%) from 39.633%
19512329812

push

github

saitoha
feat: initial implementation of parallel decoder

9693 of 33280 branches covered (29.13%)

739 of 1117 new or added lines in 5 files covered. (66.16%)

1 existing line in 1 file now uncovered.

12686 of 30784 relevant lines covered (41.21%)

664929.31 hits per line

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

66.43
/src/decoder-parallel.c
1
#include "config.h"
2

3
#include <errno.h>
4
#include <limits.h>
5
#include <stddef.h>
6
#include <stdint.h>
7
#include <stdlib.h>
8
#include <string.h>
9

10
#include <sixel.h>
11

12
#include "allocator.h"
13
#include "decoder-image.h"
14
#include "decoder-parallel.h"
15
#include "decoder-prescan.h"
16
#include "output.h"
17
#if SIXEL_ENABLE_THREADS
18
# include "sixel_threading.h"
19
# include "threadpool.h"
20
#endif
21

22
#define SIXEL_PARALLEL_MIN_BYTES   2048
23
#define SIXEL_PARALLEL_MIN_PIXELS  (64 * 64)
24
#define SIXEL_PARALLEL_MIN_BANDS   2
25
#define SIXEL_PARALLEL_MIN_BAND_BYTES 512
26
#define SIXEL_PARALLEL_MIN_PIXELS_PER_THREAD (128 * 128)
27
#define SIXEL_PARALLEL_MIN_JOBS_PER_THREAD 4
28
#define SIXEL_PARALLEL_MAX_REPEAT  0xffff
29
#define SIXEL_PARALLEL_PALVAL(n, a, m) \
30
    (((n) * (a) + ((m) / 2)) / (m))
31
#define SIXEL_PARALLEL_RGBA(r, g, b, a) \
32
    (((uint32_t)(r) << 24) | ((uint32_t)(g) << 16) | \
33
     ((uint32_t)(b) << 8) | ((uint32_t)(a)))
34
#define SIXEL_PARALLEL_XRGB(r, g, b) \
35
    SIXEL_PARALLEL_RGBA(SIXEL_PARALLEL_PALVAL((r), 255, 100), \
36
                        SIXEL_PARALLEL_PALVAL((g), 255, 100), \
37
                        SIXEL_PARALLEL_PALVAL((b), 255, 100), \
38
                        255)
39

40
typedef struct sixel_decoder_thread_config {
41
    int env_checked;
42
    int env_valid;
43
    int env_threads;
44
    int override_active;
45
    int override_threads;
46
} sixel_decoder_thread_config_t;
47

48
static sixel_decoder_thread_config_t g_decoder_threads = {
49
    0,
50
    0,
51
    1,
52
    0,
53
    1
54
};
55

56
static int
57
sixel_decoder_threads_token_is_auto(char const *text)
22✔
58
{
59
    if (text == NULL) {
22!
NEW
60
        return 0;
×
61
    }
62
    if ((text[0] == 'a' || text[0] == 'A') &&
22!
NEW
63
            (text[1] == 'u' || text[1] == 'U') &&
×
NEW
64
            (text[2] == 't' || text[2] == 'T') &&
×
NEW
65
            (text[3] == 'o' || text[3] == 'O') &&
×
NEW
66
            text[4] == '\0') {
×
NEW
67
        return 1;
×
68
    }
69
    return 0;
22✔
70
}
71

72
static int
73
sixel_decoder_threads_normalize(int requested)
20✔
74
{
75
    int normalized;
76

77
#if SIXEL_ENABLE_THREADS
78
    int hw_threads;
79

80
    if (requested <= 0) {
20!
NEW
81
        hw_threads = sixel_get_hw_threads();
×
NEW
82
        if (hw_threads < 1) {
×
NEW
83
            hw_threads = 1;
×
84
        }
NEW
85
        normalized = hw_threads;
×
86
    } else {
87
        normalized = requested;
20✔
88
    }
89
#else
90
    (void)requested;
91
    normalized = 1;
92
#endif
93
    if (normalized < 1) {
20!
NEW
94
        normalized = 1;
×
95
    }
96
    return normalized;
20✔
97
}
98

99
static int
100
sixel_decoder_threads_parse_value(char const *text, int *value)
22✔
101
{
102
    long parsed;
103
    char *endptr;
104
    int normalized;
105

106
    if (text == NULL || value == NULL) {
22!
NEW
107
        return 0;
×
108
    }
109
    if (sixel_decoder_threads_token_is_auto(text)) {
22!
NEW
110
        normalized = sixel_decoder_threads_normalize(0);
×
NEW
111
        *value = normalized;
×
NEW
112
        return 1;
×
113
    }
114
    errno = 0;
22✔
115
    parsed = strtol(text, &endptr, 10);
22✔
116
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
22!
117
        return 0;
2✔
118
    }
119
    if (parsed < 1) {
20!
NEW
120
        normalized = sixel_decoder_threads_normalize(1);
×
121
    } else if (parsed > INT_MAX) {
20!
NEW
122
        normalized = sixel_decoder_threads_normalize(INT_MAX);
×
123
    } else {
124
        normalized = sixel_decoder_threads_normalize((int)parsed);
20✔
125
    }
126
    *value = normalized;
20✔
127
    return 1;
20✔
128
}
129

130
static void
131
sixel_decoder_threads_load_env(void)
148✔
132
{
133
    char const *text;
134
    int parsed;
135

136
    if (g_decoder_threads.env_checked) {
148✔
137
        return;
132✔
138
    }
139
    g_decoder_threads.env_checked = 1;
146✔
140
    g_decoder_threads.env_valid = 0;
146✔
141
    text = getenv("SIXEL_THREADS");
146✔
142
    if (text == NULL || text[0] == '\0') {
146!
143
        return;
130✔
144
    }
145
    if (sixel_decoder_threads_parse_value(text, &parsed)) {
16!
146
        g_decoder_threads.env_threads = parsed;
16✔
147
        g_decoder_threads.env_valid = 1;
16✔
148
    }
149
}
150

151
SIXELSTATUS
152
sixel_decoder_parallel_override_threads(char const *text)
6✔
153
{
154
    SIXELSTATUS status;
155
    int parsed;
156

157
    status = SIXEL_BAD_ARGUMENT;
6✔
158
    if (text == NULL || text[0] == '\0') {
6!
NEW
159
        sixel_helper_set_additional_message(
×
160
            "decoder: missing thread count after -=/--threads.");
NEW
161
        goto end;
×
162
    }
163
    if (!sixel_decoder_threads_parse_value(text, &parsed)) {
6✔
164
        sixel_helper_set_additional_message(
2✔
165
            "decoder: threads must be a positive integer or 'auto'.");
166
        goto end;
2✔
167
    }
168
    g_decoder_threads.override_active = 1;
4✔
169
    g_decoder_threads.override_threads = parsed;
4✔
170
    status = SIXEL_OK;
4✔
171
end:
6✔
172
    return status;
6✔
173
}
174

175
#if SIXEL_ENABLE_THREADS
176

177
typedef struct sixel_parallel_decode_plan {
178
    unsigned char *data;
179
    int len;
180
    image_buffer_t *image;
181
    sixel_allocator_t *allocator;
182
    sixel_prescan_t *prescan;
183
    int depth;
184
    int threads;
185
    int direct_mode;
186
    int width;
187
    int height;
188
    int *band_color_max;
189
    int band_count;
190
} sixel_parallel_decode_plan_t;
191

192
static SIXELSTATUS sixel_parallel_safe_addition(
204,640✔
193
    sixel_prescan_band_state_t *state,
194
    unsigned char value)
195
{
196
    SIXELSTATUS status;
197
    int digit;
198

199
    status = SIXEL_FALSE;
204,640✔
200
    digit = (int)value - '0';
204,640✔
201
    if ((state->param > INT_MAX / 10) ||
204,640!
202
            (digit > INT_MAX - state->param * 10)) {
204,640!
NEW
203
        status = SIXEL_BAD_INTEGER_OVERFLOW;
×
NEW
204
        sixel_helper_set_additional_message(
×
205
            "decoder-parallel: integer overflow in parameter.");
NEW
206
        goto end;
×
207
    }
208
    state->param = state->param * 10 + digit;
204,640✔
209
    status = SIXEL_OK;
204,640✔
210
end:
204,640✔
211
    return status;
204,640✔
212
}
213

NEW
214
static uint32_t sixel_parallel_hls_to_rgba(int hue, int lum, int sat)
×
215
{
216
    double min;
217
    double max;
218
    int r;
219
    int g;
220
    int b;
221

NEW
222
    if (sat == 0) {
×
NEW
223
        r = g = b = lum;
×
224
    }
NEW
225
    max = lum + sat *
×
NEW
226
        (1.0 - (lum > 50 ? (2 * (lum / 100.0) - 1.0) :
×
NEW
227
                - (2 * (lum / 100.0) - 1.0))) / 2.0;
×
NEW
228
    min = lum - sat *
×
NEW
229
        (1.0 - (lum > 50 ? (2 * (lum / 100.0) - 1.0) :
×
NEW
230
                - (2 * (lum / 100.0) - 1.0))) / 2.0;
×
NEW
231
    hue = (hue + 240) % 360;
×
NEW
232
    switch (hue / 60) {
×
NEW
233
    case 0:
×
NEW
234
        r = max;
×
NEW
235
        g = (min + (max - min) * (hue / 60.0));
×
NEW
236
        b = min;
×
NEW
237
        break;
×
NEW
238
    case 1:
×
NEW
239
        r = min + (max - min) * ((120 - hue) / 60.0);
×
NEW
240
        g = max;
×
NEW
241
        b = min;
×
NEW
242
        break;
×
NEW
243
    case 2:
×
NEW
244
        r = min;
×
NEW
245
        g = max;
×
NEW
246
        b = (min + (max - min) * ((hue - 120) / 60.0));
×
NEW
247
        break;
×
NEW
248
    case 3:
×
NEW
249
        r = min;
×
NEW
250
        g = (min + (max - min) * ((240 - hue) / 60.0));
×
NEW
251
        b = max;
×
NEW
252
        break;
×
NEW
253
    case 4:
×
NEW
254
        r = (min + (max - min) * ((hue - 240) / 60.0));
×
NEW
255
        g = min;
×
NEW
256
        b = max;
×
NEW
257
        break;
×
NEW
258
    case 5:
×
NEW
259
        r = max;
×
NEW
260
        g = min;
×
NEW
261
        b = (min + (max - min) * ((360 - hue) / 60.0));
×
NEW
262
        break;
×
NEW
263
    default:
×
264
#if HAVE___BUILTIN_UNREACHABLE
NEW
265
        __builtin_unreachable();
×
266
#endif
267
        r = g = b = 0;
268
        break;
269
    }
NEW
270
    return SIXEL_PARALLEL_RGBA(r, g, b, 255);
×
271
}
272

273
static void sixel_parallel_store_indexed(image_buffer_t *image,
523,762✔
274
                                         int x,
275
                                         int y,
276
                                         int repeat,
277
                                         int color_index)
278
{
279
    size_t offset;
280

281
    if (repeat <= 0 || image == NULL) {
523,762!
NEW
282
        return;
×
283
    }
284
    offset = (size_t)image->width * (size_t)y + (size_t)x;
523,762✔
285
    memset(image->pixels.in_bytes + offset,
523,762✔
286
           color_index,
287
           (size_t)repeat);
288
}
289

290
static void sixel_parallel_store_rgba(image_buffer_t *image,
523,762✔
291
                                      int x,
292
                                      int y,
293
                                      int repeat,
294
                                      uint32_t rgba)
295
{
296
    unsigned char r;
297
    unsigned char g;
298
    unsigned char b;
299
    unsigned char a;
300
    unsigned char *dst;
301
    size_t offset;
302
    int i;
303

304
    if (repeat <= 0 || image == NULL) {
523,762!
NEW
305
        return;
×
306
    }
307
    r = (unsigned char)(rgba >> 24);
523,762✔
308
    g = (unsigned char)((rgba >> 16) & 0xff);
523,762✔
309
    b = (unsigned char)((rgba >> 8) & 0xff);
523,762✔
310
    a = (unsigned char)(rgba & 0xff);
523,762✔
311
    offset = ((size_t)image->width * (size_t)y + (size_t)x) * 4u;
523,762✔
312
    dst = image->pixels.in_bytes + offset;
523,762✔
313
    for (i = 0; i < repeat; ++i) {
1,063,762✔
314
        dst[0] = r;
540,000✔
315
        dst[1] = g;
540,000✔
316
        dst[2] = b;
540,000✔
317
        dst[3] = a;
540,000✔
318
        dst += 4;
540,000✔
319
    }
320
}
321

322
static void sixel_parallel_fill_span(sixel_parallel_decode_plan_t *plan,
1,047,524✔
323
                                     int y,
324
                                     int x,
325
                                     int repeat,
326
                                     int color_index,
327
                                     uint32_t *palette)
328
{
329
    if (y < 0 || y >= plan->height) {
1,047,524!
NEW
330
        return;
×
331
    }
332
    if (color_index < 0) {
1,047,524!
NEW
333
        color_index = 0;
×
334
    }
335
    if (color_index >= SIXEL_PRESCAN_PALETTE_MAX) {
1,047,524!
NEW
336
        color_index = SIXEL_PRESCAN_PALETTE_MAX - 1;
×
337
    }
338
    if (plan->direct_mode) {
1,047,524✔
339
        sixel_parallel_store_rgba(plan->image,
523,762✔
340
                                  x,
341
                                  y,
342
                                  repeat,
343
                                  palette[color_index]);
523,762✔
344
    } else {
345
        sixel_parallel_store_indexed(plan->image,
523,762✔
346
                                     x,
347
                                     y,
348
                                     repeat,
349
                                     color_index);
350
    }
351
}
352

353
static void sixel_parallel_track_color(int color_index,
1,043,636✔
354
                                       int *local_max_color)
355
{
356
    if (color_index > *local_max_color) {
1,043,636✔
357
        *local_max_color = color_index;
1,748✔
358
    }
359
}
1,043,636✔
360

361
static SIXELSTATUS sixel_parallel_decode_band(
300✔
362
    sixel_parallel_decode_plan_t *plan,
363
    int band_index)
364
{
365
    SIXELSTATUS status;
366
    sixel_prescan_band_state_t state;
367
    unsigned char *cursor;
368
    unsigned char *end;
369
    int bits;
370
    int mask;
371
    int i;
372
    int local_max_color;
373

374
    status = SIXEL_FALSE;
300✔
375
    state = plan->prescan->band_states[band_index];
300✔
376
    if ((size_t)plan->len < plan->prescan->band_start_offsets[band_index] ||
300!
377
            (size_t)plan->len < plan->prescan->band_end_offsets[band_index] ||
300!
378
            plan->prescan->band_end_offsets[band_index] <
300✔
379
                plan->prescan->band_start_offsets[band_index]) {
300!
NEW
380
        sixel_helper_set_additional_message(
×
381
            "decoder-parallel: invalid band boundaries detected.");
NEW
382
        status = SIXEL_BAD_INPUT;
×
NEW
383
        goto end;
×
384
    }
385
    cursor = plan->data + plan->prescan->band_start_offsets[band_index];
300✔
386
    end = plan->data + plan->prescan->band_end_offsets[band_index];
300✔
387
    local_max_color = -1;
300✔
388

389
    while (cursor < end) {
1,159,892✔
390
        switch (state.state) {
1,159,592!
391
        case SIXEL_PRESCAN_PS_GROUND:
4✔
392
            switch (*cursor) {
4!
393
            case 0x1b:
4✔
394
                state.state = SIXEL_PRESCAN_PS_ESC;
4✔
395
                break;
4✔
NEW
396
            case 0x90:
×
NEW
397
                state.state = SIXEL_PRESCAN_PS_DCS;
×
NEW
398
                break;
×
NEW
399
            case 0x9c:
×
NEW
400
                cursor = end;
×
NEW
401
                continue;
×
NEW
402
            default:
×
NEW
403
                break;
×
404
            }
405
            break;
4✔
406
        case SIXEL_PRESCAN_PS_ESC:
8✔
407
            switch (*cursor) {
8!
408
            case '\\':
4✔
409
            case 0x9c:
410
                cursor = end;
4✔
411
                continue;
4✔
412
            case 'P':
4✔
413
                state.param = -1;
4✔
414
                state.state = SIXEL_PRESCAN_PS_DCS;
4✔
415
                break;
4✔
NEW
416
            default:
×
NEW
417
                break;
×
418
            }
419
            break;
4✔
420
        case SIXEL_PRESCAN_PS_DCS:
4✔
421
            switch (*cursor) {
4!
NEW
422
            case 0x1b:
×
NEW
423
                state.state = SIXEL_PRESCAN_PS_ESC;
×
NEW
424
                break;
×
NEW
425
            case '0':
×
426
            case '1':
427
            case '2':
428
            case '3':
429
            case '4':
430
            case '5':
431
            case '6':
432
            case '7':
433
            case '8':
434
            case '9':
NEW
435
                if (state.param < 0) {
×
NEW
436
                    state.param = 0;
×
437
                }
NEW
438
                status = sixel_parallel_safe_addition(&state, *cursor);
×
NEW
439
                if (SIXEL_FAILED(status)) {
×
NEW
440
                    goto end;
×
441
                }
NEW
442
                break;
×
NEW
443
            case ';':
×
NEW
444
                if (state.param < 0) {
×
NEW
445
                    state.param = 0;
×
446
                }
NEW
447
                if (state.nparams < DECSIXEL_PARAMS_MAX) {
×
NEW
448
                    state.params[state.nparams++] = state.param;
×
449
                }
NEW
450
                state.param = 0;
×
NEW
451
                break;
×
452
            case 'q':
4✔
453
                if (state.param >= 0 &&
4!
NEW
454
                        state.nparams < DECSIXEL_PARAMS_MAX) {
×
NEW
455
                    state.params[state.nparams++] = state.param;
×
456
                }
457
                state.param = 0;
4✔
458
                state.state = SIXEL_PRESCAN_PS_DECSIXEL;
4✔
459
                break;
4✔
NEW
460
            default:
×
NEW
461
                break;
×
462
            }
463
            break;
4✔
464
        case SIXEL_PRESCAN_PS_DECSIXEL:
844,020✔
465
            switch (*cursor) {
844,020!
466
            case 0x1b:
4✔
467
                state.state = SIXEL_PRESCAN_PS_ESC;
4✔
468
                break;
4✔
469
            case '"':
4✔
470
                state.param = 0;
4✔
471
                state.nparams = 0;
4✔
472
                state.state = SIXEL_PRESCAN_PS_DECGRA;
4✔
473
                break;
4✔
474
            case '!':
46,592✔
475
                state.param = 0;
46,592✔
476
                state.nparams = 0;
46,592✔
477
                state.state = SIXEL_PRESCAN_PS_DECGRI;
46,592✔
478
                break;
46,592✔
479
            case '#':
61,636✔
480
                state.param = 0;
61,636✔
481
                state.nparams = 0;
61,636✔
482
                state.state = SIXEL_PRESCAN_PS_DECGCI;
61,636✔
483
                break;
61,636✔
484
            case '$':
3,168✔
485
                state.pos_x = 0;
3,168✔
486
                break;
3,168✔
NEW
487
            case '-':
×
NEW
488
                state.pos_x = 0;
×
NEW
489
                state.pos_y += 6;
×
NEW
490
                break;
×
491
            default:
732,616✔
492
                if (*cursor >= '?' && *cursor <= '~') {
732,616!
493
                    bits = *cursor - '?';
732,616✔
494
                    if (state.pos_x < 0 || state.pos_y < 0) {
732,616!
NEW
495
                        status = SIXEL_BAD_INPUT;
×
NEW
496
                        sixel_helper_set_additional_message(
×
497
                            "decoder-parallel: negative draw position.");
NEW
498
                        goto end;
×
499
                    }
500
                    if (bits == 0) {
732,616✔
501
                        state.pos_x += state.repeat_count;
193,628✔
502
                    } else {
503
                        mask = 0x01;
538,988✔
504
                        if (state.repeat_count <= 1) {
538,988✔
505
                            for (i = 0; i < 6; ++i) {
3,760,120✔
506
                                if ((bits & mask) != 0) {
3,222,960✔
507
                                    sixel_parallel_fill_span(plan,
1,041,784✔
508
                                                            state.pos_y + i,
1,041,784✔
509
                                                            state.pos_x,
510
                                                            1,
511
                                                            state.color_index,
512
                                                            state.palette);
513
                                    sixel_parallel_track_color(
1,041,784✔
514
                                        state.color_index,
515
                                        &local_max_color);
516
                                }
517
                                mask <<= 1;
3,222,960✔
518
                            }
519
                            state.pos_x += 1;
537,160✔
520
                        } else {
521
                            for (i = 0; i < 6; ++i) {
8,908✔
522
                                if ((bits & mask) != 0) {
7,080✔
523
                                    int c;
524
                                    int run_span;
525
                                    int row;
526

527
                                    c = mask << 1;
1,852✔
528
                                    run_span = 1;
1,852✔
529
                                    while ((i + run_span) < 6 &&
5,740✔
530
                                            (bits & c) != 0) {
4,668✔
531
                                        c <<= 1;
3,888✔
532
                                        run_span += 1;
3,888✔
533
                                    }
534
                                    for (row = 0; row < run_span; ++row) {
7,592✔
535
                                        sixel_parallel_fill_span(
5,740✔
536
                                            plan,
537
                                            state.pos_y + i + row,
5,740✔
538
                                            state.pos_x,
539
                                            state.repeat_count,
540
                                            state.color_index,
541
                                            state.palette);
542
                                    }
543
                                    sixel_parallel_track_color(
1,852✔
544
                                        state.color_index,
545
                                        &local_max_color);
546
                                    i += run_span - 1;
1,852✔
547
                                    mask <<= run_span - 1;
1,852✔
548
                                }
549
                                mask <<= 1;
7,080✔
550
                            }
551
                            state.pos_x += state.repeat_count;
1,828✔
552
                        }
553
                    }
554
                    state.repeat_count = 1;
732,616✔
555
                }
556
                break;
732,616✔
557
            }
558
            break;
844,020✔
559
        case SIXEL_PRESCAN_PS_DECGRA:
48✔
560
            switch (*cursor) {
48!
NEW
561
            case 0x1b:
×
NEW
562
                state.state = SIXEL_PRESCAN_PS_ESC;
×
NEW
563
                break;
×
564
            case '0':
32✔
565
            case '1':
566
            case '2':
567
            case '3':
568
            case '4':
569
            case '5':
570
            case '6':
571
            case '7':
572
            case '8':
573
            case '9':
574
                status = sixel_parallel_safe_addition(&state, *cursor);
32✔
575
                if (SIXEL_FAILED(status)) {
32!
NEW
576
                    goto end;
×
577
                }
578
                break;
32✔
579
            case ';':
12✔
580
                if (state.nparams < DECSIXEL_PARAMS_MAX) {
12!
581
                    state.params[state.nparams++] = state.param;
12✔
582
                }
583
                state.param = 0;
12✔
584
                break;
12✔
585
            default:
4✔
586
                if (state.nparams < DECSIXEL_PARAMS_MAX) {
4!
587
                    state.params[state.nparams++] = state.param;
4✔
588
                }
589
                if (state.nparams > 0) {
4!
590
                    state.attributed_pad = state.params[0];
4✔
591
                }
592
                if (state.nparams > 1) {
4!
593
                    state.attributed_pan = state.params[1];
4✔
594
                }
595
                if (state.nparams > 2 && state.params[2] > 0) {
4!
596
                    state.attributed_ph = state.params[2];
4✔
597
                }
598
                if (state.nparams > 3 && state.params[3] > 0) {
4!
599
                    state.attributed_pv = state.params[3];
4✔
600
                }
601
                if (state.attributed_pan <= 0) {
4!
NEW
602
                    state.attributed_pan = 1;
×
603
                }
604
                if (state.attributed_pad <= 0) {
4!
NEW
605
                    state.attributed_pad = 1;
×
606
                }
607
                state.param = 0;
4✔
608
                state.nparams = 0;
4✔
609
                state.state = SIXEL_PRESCAN_PS_DECSIXEL;
4✔
610
                continue;
4✔
611
            }
612
            break;
44✔
613
        case SIXEL_PRESCAN_PS_DECGRI:
111,620✔
614
            switch (*cursor) {
111,620!
NEW
615
            case 0x1b:
×
NEW
616
                state.state = SIXEL_PRESCAN_PS_ESC;
×
NEW
617
                break;
×
618
            case '0':
65,028✔
619
            case '1':
620
            case '2':
621
            case '3':
622
            case '4':
623
            case '5':
624
            case '6':
625
            case '7':
626
            case '8':
627
            case '9':
628
                status = sixel_parallel_safe_addition(&state, *cursor);
65,028✔
629
                if (SIXEL_FAILED(status)) {
65,028!
NEW
630
                    goto end;
×
631
                }
632
                break;
65,028✔
NEW
633
            case ';':
×
NEW
634
                break;
×
635
            default:
46,592✔
636
                state.repeat_count = state.param;
46,592✔
637
                if (state.repeat_count == 0) {
46,592!
NEW
638
                    state.repeat_count = 1;
×
639
                }
640
                if (state.repeat_count > SIXEL_PARALLEL_MAX_REPEAT) {
46,592!
NEW
641
                    status = SIXEL_BAD_INPUT;
×
NEW
642
                    sixel_helper_set_additional_message(
×
643
                        "decoder-parallel: repeat parameter too large.");
NEW
644
                    goto end;
×
645
                }
646
                state.param = 0;
46,592✔
647
                state.nparams = 0;
46,592✔
648
                state.state = SIXEL_PRESCAN_PS_DECSIXEL;
46,592✔
649
                continue;
46,592✔
650
            }
651
            break;
65,028✔
652
        case SIXEL_PRESCAN_PS_DECGCI:
203,888✔
653
            switch (*cursor) {
203,888!
NEW
654
            case 0x1b:
×
NEW
655
                state.state = SIXEL_PRESCAN_PS_ESC;
×
NEW
656
                break;
×
657
            case '0':
139,580✔
658
            case '1':
659
            case '2':
660
            case '3':
661
            case '4':
662
            case '5':
663
            case '6':
664
            case '7':
665
            case '8':
666
            case '9':
667
                status = sixel_parallel_safe_addition(&state, *cursor);
139,580✔
668
                if (SIXEL_FAILED(status)) {
139,580!
NEW
669
                    goto end;
×
670
                }
671
                break;
139,580✔
672
            case ';':
2,672✔
673
                if (state.nparams < DECSIXEL_PARAMS_MAX) {
2,672!
674
                    state.params[state.nparams++] = state.param;
2,672✔
675
                }
676
                state.param = 0;
2,672✔
677
                break;
2,672✔
678
            default:
61,636✔
679
                if (state.nparams < DECSIXEL_PARAMS_MAX) {
61,636!
680
                    state.params[state.nparams++] = state.param;
61,636✔
681
                }
682
                state.param = 0;
61,636✔
683
                if (state.nparams > 0) {
61,636!
684
                    state.color_index = state.params[0];
61,636✔
685
                    if (state.color_index < 0) {
61,636!
NEW
686
                        state.color_index = 0;
×
687
                    } else if (state.color_index >=
61,636!
688
                            SIXEL_PRESCAN_PALETTE_MAX) {
NEW
689
                        state.color_index = SIXEL_PRESCAN_PALETTE_MAX - 1;
×
690
                    }
691
                }
692
                if (state.nparams > 4) {
61,636✔
693
                    if (state.params[1] == 1) {
668!
NEW
694
                        if (state.params[2] > 360) {
×
NEW
695
                            state.params[2] = 360;
×
696
                        }
NEW
697
                        if (state.params[3] > 100) {
×
NEW
698
                            state.params[3] = 100;
×
699
                        }
NEW
700
                        if (state.params[4] > 100) {
×
NEW
701
                            state.params[4] = 100;
×
702
                        }
NEW
703
                        state.palette[state.color_index] =
×
NEW
704
                            sixel_parallel_hls_to_rgba(state.params[2],
×
705
                                                       state.params[3],
706
                                                       state.params[4]);
707
                    } else if (state.params[1] == 2) {
668!
708
                        if (state.params[2] > 100) {
668!
NEW
709
                            state.params[2] = 100;
×
710
                        }
711
                        if (state.params[3] > 100) {
668!
NEW
712
                            state.params[3] = 100;
×
713
                        }
714
                        if (state.params[4] > 100) {
668!
NEW
715
                            state.params[4] = 100;
×
716
                        }
717
                        state.palette[state.color_index] =
668✔
718
                            SIXEL_PARALLEL_XRGB(state.params[2],
668✔
719
                                                state.params[3],
720
                                                state.params[4]);
721
                    }
722
                }
723
                state.nparams = 0;
61,636✔
724
                state.state = SIXEL_PRESCAN_PS_DECSIXEL;
61,636✔
725
                continue;
61,636✔
726
            }
727
            break;
142,252✔
NEW
728
        default:
×
NEW
729
            break;
×
730
        }
731
        cursor++;
1,051,356✔
732
    }
733
    plan->band_color_max[band_index] = local_max_color;
300✔
734
    status = SIXEL_OK;
300✔
735
end:
300✔
736
    return status;
300✔
737
}
738

739
static int sixel_parallel_worker(tp_job_t job,
300✔
740
                                 void *userdata,
741
                                 void *workspace)
742
{
743
    sixel_parallel_decode_plan_t *plan;
744
    SIXELSTATUS status;
745

746
    (void)workspace;
747
    plan = (sixel_parallel_decode_plan_t *)userdata;
300✔
748
    status = sixel_parallel_decode_band(plan, job.band_index);
300✔
749
    if (SIXEL_FAILED(status)) {
300!
NEW
750
        return -1;
×
751
    }
752
    return 0;
300✔
753
}
754

755
static SIXELSTATUS sixel_parallel_run_workers(
4✔
756
    sixel_parallel_decode_plan_t *plan)
757
{
758
    SIXELSTATUS status;
759
    threadpool_t *pool;
760
    int worker_threads;
761
    int job_index;
762

763
    status = SIXEL_FALSE;
4✔
764
    pool = NULL;
4✔
765
    worker_threads = plan->threads;
4✔
766
    if (worker_threads > plan->band_count) {
4!
NEW
767
        worker_threads = plan->band_count;
×
768
    }
769
    if (worker_threads < 1) {
4!
NEW
770
        worker_threads = 1;
×
771
    }
772
    if (worker_threads == 1) {
4!
NEW
773
        for (job_index = 0; job_index < plan->band_count;
×
NEW
774
                ++job_index) {
×
NEW
775
            status = sixel_parallel_decode_band(plan, job_index);
×
NEW
776
            if (SIXEL_FAILED(status)) {
×
NEW
777
                goto end;
×
778
            }
779
        }
NEW
780
        status = SIXEL_OK;
×
NEW
781
        goto end;
×
782
    }
783
    pool = threadpool_create(worker_threads,
4✔
784
                             plan->band_count,
785
                             0,
786
                             sixel_parallel_worker,
787
                             plan);
788
    if (pool == NULL) {
4!
NEW
789
        sixel_helper_set_additional_message(
×
790
            "decoder-parallel: failed to create threadpool.");
NEW
791
        status = SIXEL_BAD_ALLOCATION;
×
NEW
792
        goto end;
×
793
    }
794
    for (job_index = 0; job_index < plan->band_count;
304✔
795
            ++job_index) {
300✔
796
        threadpool_push(pool, (tp_job_t){ job_index });
300✔
797
    }
798
    threadpool_finish(pool);
4✔
799
    if (threadpool_get_error(pool) != 0) {
4!
NEW
800
        sixel_helper_set_additional_message(
×
801
            "decoder-parallel: worker reported an error.");
NEW
802
        status = SIXEL_BAD_INPUT;
×
NEW
803
        goto end;
×
804
    }
805
    status = SIXEL_OK;
4✔
806
end:
4✔
807
    if (pool != NULL) {
4!
808
        threadpool_destroy(pool);
4✔
809
    }
810
    return status;
4✔
811
}
812

813
static int sixel_parallel_should_attempt(sixel_prescan_t *prescan,
4✔
814
                                         int len,
815
                                         int threads)
816
{
817
    int pixels;
818
    int pixels_per_thread;
819
    int avg_band_bytes;
820
    int jobs_per_thread;
821

822
    if (prescan == NULL) {
4!
NEW
823
        return 0;
×
824
    }
825
    if (threads < 2) {
4!
NEW
826
        return 0;
×
827
    }
828
    if (len < SIXEL_PARALLEL_MIN_BYTES) {
4!
NEW
829
        return 0;
×
830
    }
831
    if (prescan->band_count < SIXEL_PARALLEL_MIN_BANDS) {
4!
NEW
832
        return 0;
×
833
    }
834
    if (prescan->flags != 0u) {
4!
NEW
835
        return 0;
×
836
    }
837
    pixels = prescan->width * prescan->height;
4✔
838
    if (pixels < SIXEL_PARALLEL_MIN_PIXELS) {
4!
NEW
839
        return 0;
×
840
    }
841
    pixels_per_thread = pixels / threads;
4✔
842
    if (pixels_per_thread < SIXEL_PARALLEL_MIN_PIXELS_PER_THREAD) {
4!
NEW
843
        return 0;
×
844
    }
845
    jobs_per_thread = prescan->band_count / threads;
4✔
846
    if (jobs_per_thread < SIXEL_PARALLEL_MIN_JOBS_PER_THREAD) {
4!
NEW
847
        return 0;
×
848
    }
849
    avg_band_bytes = len / prescan->band_count;
4✔
850
    if (avg_band_bytes < SIXEL_PARALLEL_MIN_BAND_BYTES) {
4!
NEW
851
        return 0;
×
852
    }
853
    return 1;
4✔
854
}
855

856
static SIXELSTATUS sixel_parallel_finalize_palette(image_buffer_t *image,
2✔
857
                                                   int *band_color_max,
858
                                                   int band_count)
859
{
860
    int max_color;
861
    int i;
862

863
    if (image == NULL || band_color_max == NULL) {
2!
NEW
864
        return SIXEL_BAD_ARGUMENT;
×
865
    }
866
    max_color = 0;
2✔
867
    for (i = 0; i < band_count; ++i) {
152✔
868
        if (band_color_max[i] > max_color) {
150✔
869
            max_color = band_color_max[i];
48✔
870
        }
871
    }
872
    if (max_color < 0) {
2!
NEW
873
        max_color = 0;
×
874
    }
875
    image->ncolors = max_color;
2✔
876
    return SIXEL_OK;
2✔
877
}
878

879
static void sixel_parallel_apply_final_palette(image_buffer_t *image,
2✔
880
                                               sixel_prescan_t *prescan)
881
{
882
    uint32_t rgba;
883
    int entries;
884
    int r;
885
    int g;
886
    int b;
887
    int i;
888

889
    if (image == NULL || prescan == NULL) {
2!
NEW
890
        return;
×
891
    }
892
    entries = SIXEL_PRESCAN_PALETTE_MAX;
2✔
893
    if (entries > SIXEL_PALETTE_MAX_DECODER) {
2!
NEW
894
        entries = SIXEL_PALETTE_MAX_DECODER;
×
895
    }
896
    for (i = 0; i < entries; ++i) {
514✔
897
        rgba = prescan->final_state.palette[i];
512✔
898
        r = (int)((rgba >> 24) & 0xff);
512✔
899
        g = (int)((rgba >> 16) & 0xff);
512✔
900
        b = (int)((rgba >> 8) & 0xff);
512✔
901
        image->palette[i] = (r << 16) | (g << 8) | b;
512✔
902
    }
903
}
904

905
int
906
sixel_decoder_parallel_resolve_threads(void)
148✔
907
{
908
    sixel_decoder_threads_load_env();
148✔
909
    if (g_decoder_threads.override_active) {
148✔
910
        return g_decoder_threads.override_threads;
4✔
911
    }
912
    if (g_decoder_threads.env_valid) {
144✔
913
        return g_decoder_threads.env_threads;
16✔
914
    }
915
    return 1;
128✔
916
}
917

918
static SIXELSTATUS sixel_parallel_decode_internal(
148✔
919
    unsigned char *p,
920
    int len,
921
    image_buffer_t *image,
922
    sixel_allocator_t *allocator,
923
    int depth,
924
    int *used_parallel)
925
{
926
    SIXELSTATUS status;
927
    sixel_prescan_t *prescan;
928
    sixel_parallel_decode_plan_t plan;
929
    int bgindex;
930
    int width;
931
    int height;
932
    int band_count;
933
    int threads;
934

935
    status = SIXEL_FALSE;
148✔
936
    prescan = NULL;
148✔
937
    memset(&plan, 0, sizeof(plan));
148✔
938
    if (used_parallel != NULL) {
148!
939
        *used_parallel = 0;
148✔
940
    }
941
    if (image == NULL || allocator == NULL) {
148!
NEW
942
        return SIXEL_BAD_ARGUMENT;
×
943
    }
944
    threads = sixel_decoder_parallel_resolve_threads();
148✔
945
    if (threads < 2 || len < SIXEL_PARALLEL_MIN_BYTES) {
148✔
946
        status = SIXEL_OK;
144✔
947
        goto end;
144✔
948
    }
949
    status = sixel_prescan_run(p, len, &prescan, allocator);
4✔
950
    if (SIXEL_FAILED(status)) {
4!
NEW
951
        goto end;
×
952
    }
953
    if (!sixel_parallel_should_attempt(prescan,
4!
954
                                       len,
955
                                       threads)) {
NEW
956
        status = SIXEL_OK;
×
NEW
957
        goto end;
×
958
    }
959
    width = prescan->width;
4✔
960
    height = prescan->height;
4✔
961
    if (width < 1) {
4!
NEW
962
        width = 1;
×
963
    }
964
    if (height < 1) {
4!
NEW
965
        height = 1;
×
966
    }
967
    bgindex = prescan->band_states[0].bgindex;
4✔
968
    status = image_buffer_init(image,
4✔
969
                               width,
970
                               height,
971
                               bgindex,
972
                               depth,
973
                               allocator);
974
    if (SIXEL_FAILED(status)) {
4!
NEW
975
        goto end;
×
976
    }
977
    plan.data = p;
4✔
978
    plan.len = len;
4✔
979
    plan.image = image;
4✔
980
    plan.allocator = allocator;
4✔
981
    plan.prescan = prescan;
4✔
982
    plan.depth = depth;
4✔
983
    plan.direct_mode = (depth == 4);
4✔
984
    plan.width = width;
4✔
985
    plan.height = height;
4✔
986
    plan.threads = threads;
4✔
987
    band_count = prescan->band_count;
4✔
988
    if (band_count < 1) {
4!
NEW
989
        status = SIXEL_OK;
×
NEW
990
        goto end;
×
991
    }
992
    plan.band_count = band_count;
4✔
993
    plan.band_color_max = (int *)sixel_allocator_calloc(allocator,
4✔
994
                                                        (size_t)band_count,
995
                                                        sizeof(int));
996
    if (plan.band_color_max == NULL) {
4!
NEW
997
        sixel_helper_set_additional_message(
×
998
            "decoder-parallel: failed to allocate band metadata.");
NEW
999
        status = SIXEL_BAD_ALLOCATION;
×
NEW
1000
        goto end;
×
1001
    }
1002
    status = sixel_parallel_run_workers(&plan);
4✔
1003
    if (SIXEL_FAILED(status)) {
4!
NEW
1004
        goto end;
×
1005
    }
1006
    if (!plan.direct_mode) {
4✔
1007
        status = sixel_parallel_finalize_palette(image,
2✔
1008
                                                 plan.band_color_max,
1009
                                                 band_count);
1010
        if (SIXEL_FAILED(status)) {
2!
NEW
1011
            goto end;
×
1012
        }
1013
        sixel_parallel_apply_final_palette(image, prescan);
2✔
1014
    } else {
1015
        image->ncolors = 0;
2✔
1016
    }
1017
    if (used_parallel != NULL) {
4!
1018
        *used_parallel = 1;
4✔
1019
    }
1020
    status = SIXEL_OK;
4✔
1021
end:
148✔
1022
    if (prescan != NULL) {
148✔
1023
        sixel_prescan_destroy(prescan, allocator);
4✔
1024
    }
1025
    if (plan.band_color_max != NULL) {
148✔
1026
        sixel_allocator_free(allocator, plan.band_color_max);
4✔
1027
    }
1028
    return status;
148✔
1029
}
1030

1031
#else /* !SIXEL_ENABLE_THREADS */
1032

1033
int sixel_decoder_parallel_resolve_threads(void)
1034
{
1035
    return 1;
1036
}
1037

1038
#endif /* SIXEL_ENABLE_THREADS */
1039

1040
SIXELSTATUS sixel_decode_raw_parallel(unsigned char *p,
136✔
1041
                                      int len,
1042
                                      image_buffer_t *image,
1043
                                      sixel_allocator_t *allocator,
1044
                                      int *used_parallel)
1045
{
1046
#if SIXEL_ENABLE_THREADS
1047
    return sixel_parallel_decode_internal(p,
136✔
1048
                                          len,
1049
                                          image,
1050
                                          allocator,
1051
                                          1,
1052
                                          used_parallel);
1053
#else
1054
    if (used_parallel != NULL) {
1055
        *used_parallel = 0;
1056
    }
1057
    (void)p;
1058
    (void)len;
1059
    (void)image;
1060
    (void)allocator;
1061
    return SIXEL_OK;
1062
#endif
1063
}
1064

1065
SIXELSTATUS sixel_decode_direct_parallel(unsigned char *p,
12✔
1066
                                         int len,
1067
                                         image_buffer_t *image,
1068
                                         sixel_allocator_t *allocator,
1069
                                         int *used_parallel)
1070
{
1071
#if SIXEL_ENABLE_THREADS
1072
    return sixel_parallel_decode_internal(p,
12✔
1073
                                          len,
1074
                                          image,
1075
                                          allocator,
1076
                                          4,
1077
                                          used_parallel);
1078
#else
1079
    if (used_parallel != NULL) {
1080
        *used_parallel = 0;
1081
    }
1082
    (void)p;
1083
    (void)len;
1084
    (void)image;
1085
    (void)allocator;
1086
    return SIXEL_OK;
1087
#endif
1088
}
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