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

saitoha / libsixel / 20578371047

29 Dec 2025 05:06PM UTC coverage: 51.955% (-5.4%) from 57.322%
20578371047

push

github

saitoha
Revert "Merge branch 'refactor/pixelformat' into develop"

This reverts commit 4a6153922, reversing
changes made to 6f3ef3068.

14746 of 45077 branches covered (32.71%)

147 of 262 new or added lines in 15 files covered. (56.11%)

1406 existing lines in 46 files now uncovered.

21419 of 41226 relevant lines covered (51.96%)

3895522.67 hits per line

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

76.8
/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 of
7
 * this software and associated documentation files (the "Software"), to deal in
8
 * the Software without restriction, including without limitation the rights to
9
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
 * the Software, and to permit persons to whom the Software is furnished to do so,
11
 * subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in all
14
 * 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, FITNESS
18
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 */
23

24
#if defined(HAVE_CONFIG_H)
25
#include "config.h"
26
#endif
27

28
#include <stdlib.h>
29
#include <string.h>
30
#if HAVE_ERRNO_H
31
# include <errno.h>
32
#endif  /* HAVE_ERRNO_H */
33
#if HAVE_LIMITS_H
34
# include <limits.h>
35
#endif /* HAVE_LIMITS_H */
36

37
#include <sixel.h>
38

39
#include "decoder-parallel.h"
40
#if SIXEL_ENABLE_THREADS
41
# include "logger.h"
42
# include "threading.h"
43
#endif
44
#include "compat_stub.h"
45

46
/*
47
 * The previous prescan-based parallel decoder has been removed.
48
 * The current implementation keeps the public entry points so that
49
 * callers can fall back to the single threaded decoder while we
50
 * prepare the new chunked worker pipeline.
51
 */
52

53
typedef struct sixel_decoder_thread_config {
54
    int env_checked;
55
    int env_valid;
56
    int env_threads;
57
    int override_active;
58
    int override_threads;
59
} sixel_decoder_thread_config_t;
60

61
#if SIXEL_ENABLE_THREADS
62
typedef struct sixel_decoder_worker_context {
63
    struct sixel_decoder_worker_chain *chain;
64
    unsigned char *input;
65
    unsigned char *anchor;
66
    int length;
67
    int payload_len;
68
    int start_offset;
69
    int end_offset;
70
    int index;
71
    int direct_mode;
72
    int const *palette;
73
    int palette_limit;
74
    int width;
75
    int height;
76
    int pixel_size;
77
    int depth;
78
    sixel_logger_t *logger;
79
    unsigned char *local_buffer;
80
    int local_capacity;
81
    int local_written;
82
    int result;
83
} sixel_decoder_worker_context_t;
84

85
typedef struct sixel_decoder_local_chunk {
86
    unsigned char *data;
87
    int start_row;
88
    int rows;
89
    struct sixel_decoder_local_chunk *next;
90
} sixel_decoder_local_chunk_t;
91

92
typedef struct sixel_local_buffer {
93
    sixel_decoder_local_chunk_t *head;
94
    sixel_decoder_local_chunk_t *tail;
95
    sixel_decoder_local_chunk_t *cursor;
96
    int width;
97
    int pixel_size;
98
} sixel_local_buffer_t;
99

100
typedef struct sixel_decoder_worker_chain {
101
    sixel_mutex_t mutex;
102
    sixel_cond_t cond;
103
    int *copy_offsets;
104
    int *copy_ready;
105
    int thread_count;
106
    unsigned char *global_buffer;
107
    int global_capacity;
108
    int pixel_size;
109
    int abort_requested;
110
} sixel_decoder_worker_chain_t;
111
#endif
112

113
static sixel_decoder_thread_config_t g_decoder_threads = {
114
    0,
115
    0,
116
    1,
117
    0,
118
    1
119
};
120

121
#if SIXEL_ENABLE_THREADS
122
static void
123
sixel_decoder_parallel_fill_spans(int payload_len,
124
                                  int threads,
125
                                  int *spans);
126

127
static int
128
sixel_decoder_parallel_skew_percent(void)
6✔
129
{
130
    char const *text;
6✔
131
    char *endptr;
6✔
132
    long value;
6✔
133

134
    /*
135
     * SIXEL_PARALLEL_SKEW lets operators bias span lengths by +/-20% so the
136
     * trailing workers take slightly more work.  The default keeps spans
137
     * balanced.
138
     */
139
    text = sixel_compat_getenv("SIXEL_PARALLEL_SKEW");
6✔
140
    if (text == NULL || text[0] == '\0') {
6!
141
        return 0;
142
    }
143

UNCOV
144
    errno = 0;
×
UNCOV
145
    value = strtol(text, &endptr, 10);
×
UNCOV
146
    if (errno != 0 || endptr == text || *endptr != '\0') {
×
147
        return 0;
148
    }
149

UNCOV
150
    if (value < -20) {
×
151
        value = -20;
UNCOV
152
    } else if (value > 20) {
×
153
        value = 20;
154
    }
155

UNCOV
156
    return (int)value;
×
157
}
158

159
static void
160
sixel_decoder_parallel_fill_spans(int payload_len,
6✔
161
                                  int threads,
162
                                  int *spans)
163
{
164
    int base_share;
6✔
165
    int skew_percent;
6✔
166
    double skew;
6✔
167
    int i;
6✔
168
    double center;
6✔
169
    int total;
6✔
170
    int remainder;
6✔
171

172
    base_share = payload_len / threads;
6✔
173
    skew_percent = sixel_decoder_parallel_skew_percent();
6✔
174
    skew = ((double)base_share * (double)skew_percent) / 100.0;
6✔
175
    total = 0;
6✔
176
    for (i = 0; i < threads; ++i) {
30✔
177
        center = (double)i - (double)(threads - 1) / 2.0;
24✔
178
        spans[i] = base_share + (int)(skew * center);
24✔
179
        if (spans[i] < 1) {
24!
UNCOV
180
            spans[i] = 1;
×
181
        }
182
        total += spans[i];
24✔
183
    }
184

185
    remainder = payload_len - total;
6✔
186
    while (remainder > 0) {
6!
UNCOV
187
        for (i = threads - 1; i >= 0 && remainder > 0; --i) {
×
UNCOV
188
            spans[i] += 1;
×
UNCOV
189
            --remainder;
×
190
        }
191
    }
192
    while (remainder < 0) {
6!
UNCOV
193
        for (i = threads - 1; i >= 0 && remainder < 0; --i) {
×
UNCOV
194
            if (spans[i] > 1) {
×
UNCOV
195
                spans[i] -= 1;
×
UNCOV
196
                ++remainder;
×
197
            }
198
        }
199
        if (remainder < 0) {
×
200
            break;
201
        }
202
    }
203
}
6✔
204

205
static int
206
sixel_decoder_parallel_pixel_size(int depth)
216✔
207
{
208
    int pixel_size;
216✔
209

210
    pixel_size = 4;
216✔
211
    if (depth == 1) {
216✔
212
        pixel_size = 1;
213
    } else if (depth == 2) {
12!
UNCOV
214
        pixel_size = 2;
×
215
    }
216

217
    return pixel_size;
216✔
218
}
219

220
static void
221
sixel_decoder_parallel_store_pixel(unsigned char *dst,
864✔
222
                                   int depth,
223
                                   int color_index,
224
                                   int const *palette)
225
{
226
    int color;
864✔
227

228
    if (depth == 1) {
864!
UNCOV
229
        dst[0] = (unsigned char)color_index;
×
UNCOV
230
        return;
×
231
    }
232

233
    if (depth == 2) {
864!
UNCOV
234
        unsigned short packed;
×
235

UNCOV
236
        packed = (unsigned short)color_index;
×
UNCOV
237
        memcpy(dst, &packed, sizeof(packed));
×
UNCOV
238
        return;
×
239
    }
240

241
    color = 0;
864✔
242
    if (palette != NULL &&
864!
243
            color_index >= 0 &&
864!
244
            color_index < SIXEL_PALETTE_MAX_DECODER) {
245
        color = palette[color_index];
864✔
246
    }
247
    dst[0] = (unsigned char)((color >> 16) & 0xff);
864✔
248
    dst[1] = (unsigned char)((color >> 8) & 0xff);
864✔
249
    dst[2] = (unsigned char)(color & 0xff);
864✔
250
    dst[3] = 255u;
864✔
251
}
1!
252

253
static void
254
sixel_local_buffer_init(sixel_local_buffer_t *buffer,
24✔
255
                        int width,
256
                        int pixel_size)
257
{
258
    buffer->head = NULL;
24✔
259
    buffer->tail = NULL;
24✔
260
    buffer->cursor = NULL;
24✔
261
    buffer->width = width;
24✔
262
    buffer->pixel_size = pixel_size;
24✔
263
}
264

265
static void
266
sixel_local_buffer_dispose(sixel_local_buffer_t *buffer)
24✔
267
{
268
    sixel_decoder_local_chunk_t *cursor;
24✔
269

270
    cursor = buffer->head;
24✔
271
    while (cursor != NULL) {
48✔
272
        sixel_decoder_local_chunk_t *tmp;
24✔
273

274
        free(cursor->data);
24✔
275
        tmp = cursor->next;
24✔
276
        free(cursor);
24✔
277
        cursor = tmp;
24✔
278
    }
279

280
    buffer->head = NULL;
24✔
281
    buffer->tail = NULL;
24✔
282
    buffer->cursor = NULL;
24✔
283
}
24✔
284

285
static sixel_decoder_local_chunk_t *
286
sixel_local_buffer_append(sixel_local_buffer_t *buffer,
24✔
287
                          int rows,
288
                          int start_row)
289
{
290
    sixel_decoder_local_chunk_t *chunk;
24✔
291
    size_t bytes;
24✔
292

293
    if (rows % 6 != 0) {
24!
UNCOV
294
        rows += 6 - (rows % 6);
×
295
    }
296

297
    chunk = (sixel_decoder_local_chunk_t *)calloc(
24✔
298
        1, sizeof(sixel_decoder_local_chunk_t));
299
    if (chunk == NULL) {
24!
300
        return NULL;
301
    }
302

303
    bytes = (size_t)(rows * buffer->width) * (size_t)buffer->pixel_size;
24✔
304
    chunk->data = (unsigned char *)calloc(bytes, 1);
24✔
305
    if (chunk->data == NULL) {
24!
UNCOV
306
        free(chunk);
×
UNCOV
307
        return NULL;
×
308
    }
309

310
    chunk->start_row = start_row;
24✔
311
    chunk->rows = rows;
24✔
312
    chunk->next = NULL;
24✔
313

314
    if (buffer->head == NULL) {
24!
315
        buffer->head = chunk;
24✔
316
    } else {
UNCOV
317
        buffer->tail->next = chunk;
×
318
    }
319
    buffer->tail = chunk;
24✔
320
    buffer->cursor = chunk;
24✔
321

322
    return chunk;
24✔
323
}
324

325
static sixel_decoder_local_chunk_t *
326
sixel_local_buffer_reserve_row(sixel_local_buffer_t *buffer,
288✔
327
                               int row)
328
{
329
    sixel_decoder_local_chunk_t *cursor;
288✔
330

331
    cursor = buffer->cursor;
288✔
332
    while (cursor != NULL && row >= cursor->start_row + cursor->rows) {
288!
UNCOV
333
        cursor = cursor->next;
×
334
    }
335
    if (cursor != NULL && row >= cursor->start_row) {
288!
336
        buffer->cursor = cursor;
288✔
337
        return cursor;
288✔
338
    }
339

UNCOV
340
    cursor = buffer->tail;
×
UNCOV
341
    if (cursor == NULL) {
×
342
        return NULL;
343
    }
344

UNCOV
345
    while (row >= cursor->start_row + cursor->rows) {
×
UNCOV
346
        sixel_decoder_local_chunk_t *next;
×
UNCOV
347
        int start_row;
×
348

UNCOV
349
        start_row = cursor->start_row + cursor->rows;
×
UNCOV
350
        next = sixel_local_buffer_append(buffer, cursor->rows, start_row);
×
UNCOV
351
        if (next == NULL) {
×
352
            return NULL;
353
        }
354
        cursor = next;
355
    }
×
356

UNCOV
357
    buffer->cursor = cursor;
×
UNCOV
358
    return cursor;
×
359
}
360

361
/*
362
 * Worker entry for the fast sixel parser.  Each thread jumps to the next
363
 * '-' marker from its assigned offset, then walks tokens until the first
364
 * '-' after its scheduled end offset.
365
 *
366
 * Supported tokens: palette switches ('#n'), carriage return ('$'), next
367
 * line ('-'), and raster data ('?' - '~').  Any raster attribute reset '"',
368
 * palette redefinition '#n;type;...', or CAN/SUB control code marks the
369
 * chunk as invalid and returns a failure status so the caller can fall back.
370
 */
371
static int
372
sixel_decoder_parallel_worker(void *arg)
24✔
373
{
374
    sixel_decoder_worker_context_t *context;
24✔
375
    sixel_decoder_worker_chain_t *chain;
24✔
376
    sixel_local_buffer_t local_buffer;
24✔
377
    sixel_decoder_local_chunk_t *chunk_cursor = NULL;
24✔
378
    unsigned char *anchor = NULL;
24✔
379
    unsigned char *scan = NULL;
24✔
380
    unsigned char *cursor = NULL;
24✔
381
    unsigned char *stop = NULL;
24✔
382
    unsigned char *limit = NULL;
24✔
383
    unsigned char *start = NULL;
24✔
384
    int capacity = 0;
24✔
385
    int written = 0;
24✔
386
    int assigned = 0;
24✔
387
    int bits = 0;
24✔
388
    int repeat = 1;
24✔
389
    int color_index = 0;
24✔
390
    int max_relative = (-1);
24✔
391
    int min_relative = (-1);
24✔
392
    int pos_x = 0;
24✔
393
    int pos_y = 0;
24✔
394
    int relative = 0;
24✔
395
    int row_base = 0;
24✔
396
    int r = 0;
24✔
397
    int row_offset = 0;
24✔
398
    int line_count = 0;
24✔
399
    int copy_offset = 0;
24✔
400
    int copy_span = 0;
24✔
401
    int next_offset = 0;
24✔
402
    int i;
24✔
403
    int fallback = 0;
24✔
404
    int status = (-1);
24✔
405
    sixel_logger_t *logger = NULL;
24✔
406
    int width = 0;
24✔
407
    int pixel_size = 0;
24✔
408
    int depth = 0;
24✔
409
    int height = 0;
24✔
410
    int chain_offset = 0;
24✔
411
    unsigned char ch;
24✔
412

413
    context = (sixel_decoder_worker_context_t *)arg;
24✔
414
    if (context == NULL) {
24!
415
        return (-1);
416
    }
417

418
    context->result = status;
24✔
419

420
    chain = context->chain;
24✔
421
    if (chain == NULL) {
24!
422
        context->result = status;
423
        return status;
424
    }
425

426
    logger = context->logger;
24✔
427
    anchor = context->anchor;
24✔
428
    if (context->input == NULL || context->length <= 0) {
24!
429
        context->result = status;
430
        return status;
431
    }
432

433
    width = context->width;
24✔
434
    if (width <= 0) {
24!
435
        context->result = status;
436
        return status;
437
    }
438

439
    pixel_size = context->pixel_size;
24✔
440
    depth = context->depth;
24✔
441
    height = context->height;
24✔
442
    if (height <= 0) {
24!
443
        context->result = status;
444
        return status;
445
    }
446
    sixel_local_buffer_init(&local_buffer, width, pixel_size);
24✔
447
    if (context->payload_len <= 0) {
24!
448
        context->result = status;
449
        return status;
450
    }
451

452
    start = context->input + context->start_offset;
24✔
453
    if (start < context->input ||
24!
454
            start >= context->input + context->length) {
24!
455
        context->result = status;
456
        return status;
457
    }
458

459
    assigned = context->end_offset - context->start_offset + 1;
24✔
460
    if (assigned <= 0) {
24!
461
        context->result = status;
462
        return status;
463
    }
464

465
    status = 0;
24✔
466

467
    if (logger != NULL) {
24!
468
        /*
469
         * Decode window for this worker. The key keeps decode spans grouped
470
         * per-thread in the timeline output.
471
         */
UNCOV
472
        sixel_logger_logf(logger,
×
473
                          "decode",
474
                          "decoder",
475
                          "start",
476
                          context->index,
477
                          context->index,
478
                          0,
479
                          0,
480
                          context->start_offset,
481
                          context->end_offset,
482
                          "worker %d decode span [%d,%d]",
483
                          context->index,
484
                          context->start_offset,
485
                          context->end_offset);
486
    }
487

488
    cursor = start;
24✔
489
    if (context->index > 0) {
24✔
490
        cursor = (unsigned char *)memchr(start,
18✔
491
                                         '-',
492
                                         (size_t)(context->length -
18✔
493
                                         context->start_offset));
18✔
494
        if (cursor != NULL &&
18!
495
                cursor + 1 < context->input + context->length) {
18!
496
            cursor += 1;
24✔
497
        } else {
498
            cursor = start;
499
        }
500
    }
501
    if (context->index > 0 && cursor == start) {
24!
UNCOV
502
        status = (-1);
×
UNCOV
503
        context->result = status;
×
UNCOV
504
        return status;
×
505
    }
506

507
    if (anchor != NULL && anchor < cursor) {
24!
508
        scan = anchor;
509
        while (scan < cursor) {
5,814✔
510
            if (*scan == '-') {
5,796✔
511
                line_count += 1;
18✔
512
                scan += 1;
18✔
513
                continue;
18✔
514
            }
515

516
            if (*scan == '#') {
5,778✔
517
                int value;
1,152✔
518
                unsigned char *p;
1,152✔
519

520
                value = 0;
1,152✔
521
                p = scan + 1;
1,152✔
522
                while (p < cursor && *p >= '0' && *p <= '9') {
3,276!
523
                    value = value * 10 + (*p - '0');
2,124✔
524
                    p += 1;
2,124✔
525
                }
526
                color_index = value;
1,152✔
527
                if (color_index < 0) {
1,152!
528
                    color_index = 0;
529
                }
530
                if (color_index >= SIXEL_PALETTE_MAX_DECODER) {
1,152!
531
                    color_index = SIXEL_PALETTE_MAX_DECODER - 1;
532
                }
533
                if (p < cursor && *p == ';') {
1,152!
534
                    scan = p;
535
                    continue;
536
                }
537
                scan = p;
1,152✔
538
                continue;
1,152✔
539
            }
540

541
            scan += 1;
4,626✔
542
        }
543
    }
544
    row_offset = line_count * 6;
24✔
545

546
    capacity = (int)((double)chain->global_capacity *
24✔
547
        ((double)assigned / (double)context->payload_len));
24✔
548
    capacity = (int)((double)capacity * 1.10);
24✔
549
    if (capacity < 1) {
24!
550
        capacity = 1;
551
    }
552
    if (capacity < width * 6) {
24!
553
        capacity = width * 6;
554
    }
555
    if (capacity % (width * 6) != 0) {
24!
UNCOV
556
        capacity += (width * 6) - (capacity % (width * 6));
×
557
    }
558

559
    chunk_cursor = sixel_local_buffer_append(&local_buffer,
24✔
560
                                             capacity / width,
561
                                             0);
562
    if (chunk_cursor == NULL) {
24!
UNCOV
563
        status = (-1);
×
UNCOV
564
        context->result = status;
×
UNCOV
565
        return status;
×
566
    }
567
    capacity = chunk_cursor->rows * width;
24✔
568

569
    stop = context->input + context->end_offset;
24✔
570
    limit = context->input + context->length;
24✔
571
    while (cursor < limit) {
252!
572
        /*
573
         * Hot path prefers raster tokens ('?' - '~') to reduce branching.
574
         * Control and attribute tokens fall back to the slow path below.
575
         */
576
        ch = *cursor;
252✔
577

578
        /*
579
         * Branch ordering follows observed frequency:
580
         *   raster ('?' - '~') > '#' > '!' > '$' > '-'
581
         *   >>> control (< 0x20) > '"'.
582
         */
583
        if (ch >= '?' && ch <= '~') {
252!
584
            bits = ch - '?';
54✔
585
            for (i = 0; i < 6; ++i) {
342✔
586
                if ((bits & (1 << i)) != 0) {
294!
587
                    if (pos_x + repeat > width ||
294!
588
                            row_offset + pos_y + i >= height) {
288!
589
                        fallback = 1;
590
                        status = (-1);
591
                        break;
592
                    }
593
                    chunk_cursor = sixel_local_buffer_reserve_row(
288✔
594
                        &local_buffer, pos_y + i);
595
                    if (chunk_cursor == NULL) {
288!
596
                        fallback = 1;
597
                        status = (-1);
598
                        break;
599
                    }
600

601
                    row_base = (pos_y + i - chunk_cursor->start_row) *
288✔
602
                        width + pos_x;
603
                    relative = (pos_y + i) * width + pos_x;
288✔
604

605
                    if (pixel_size == 1 && repeat > 3) {
288!
606
                        memset(chunk_cursor->data + (size_t)row_base,
144✔
607
                               color_index,
608
                               repeat);
609
                    } else {
610
                        for (r = 0; r < repeat; ++r) {
1,008✔
611
                            sixel_decoder_parallel_store_pixel(
864✔
612
                                chunk_cursor->data +
864✔
613
                                (size_t)(row_base + r) *
864✔
614
                                (size_t)pixel_size,
864✔
615
                                depth,
616
                                color_index,
617
                                context->palette);
618
                        }
619
                    }
620
                    written += repeat;
288✔
621
                    if (min_relative < 0 || relative < min_relative) {
288!
622
                        min_relative = relative;
623
                    }
624
                    if (max_relative < relative + repeat - 1) {
288✔
625
                        max_relative = relative + repeat - 1;
626
                    }
627
                }
628
            }
629

630
            if (fallback) {
54✔
631
                break;
632
            }
633

634
            cursor += 1;
48✔
635
            pos_x += repeat;
48✔
636
            repeat = 1;
48✔
637
            continue;
48✔
638
        }
639

640
        if (ch == '#') {
102✔
641
            int value;
54✔
642
            unsigned char *p;
54✔
643

644
            value = 0;
54✔
645
            p = cursor + 1;
54✔
646
            while (p < limit && *p >= '0' && *p <= '9') {
108!
647
                value = value * 10 + (*p - '0');
54✔
648
                p += 1;
54✔
649
            }
650
            if (p < limit && *p == ';') {
54!
651
                fallback = 1;
652
                status = (-1);
653
                break;
654
            }
655
            color_index = value;
54✔
656
            if (color_index < 0) {
54!
657
                color_index = 0;
658
            }
659
            if (color_index >= SIXEL_PALETTE_MAX_DECODER) {
54!
660
                color_index = SIXEL_PALETTE_MAX_DECODER - 1;
661
            }
662
            cursor = p;
54✔
663
            continue;
54✔
664
        }
1!
665

666
        if (ch == '!') {
84✔
667
            int value;
54✔
668
            unsigned char *p;
54✔
669

670
            value = 0;
54✔
671
            p = cursor + 1;
54✔
672
            while (p < limit && *p >= '0' && *p <= '9') {
108!
673
                value = value * 10 + (*p - '0');
54✔
674
                p += 1;
54✔
675
            }
676
            if (value <= 0) {
54!
677
                value = 1;
678
            }
679
            repeat = value;
54✔
680
            cursor = p;
54✔
681
            continue;
54✔
682
        }
683

684
        if (ch == '$') {
30!
UNCOV
685
            cursor += 1;
×
UNCOV
686
            pos_x = 0;
×
UNCOV
687
            continue;
×
688
        }
689

690
        if (ch == '-') {
30!
UNCOV
691
            if (cursor >= stop) {
×
692
                break;
693
            }
UNCOV
694
            cursor += 1;
×
UNCOV
695
            pos_x = 0;
×
UNCOV
696
            pos_y += 6;
×
UNCOV
697
            chunk_cursor = sixel_local_buffer_reserve_row(&local_buffer,
×
698
                                                          pos_y);
UNCOV
699
            if (chunk_cursor == NULL) {
×
700
                fallback = 1;
701
                status = (-1);
702
                break;
703
            }
UNCOV
704
            continue;
×
705
        }
706

707
        if (ch < 0x20) {
78!
708
            if (ch == 0x18 || ch == 0x1a) {
90!
709
                fallback = 1;
710
                status = (-1);
711
                break;
712
            }
713
            cursor += 1;
90✔
714
            if (ch == 0x1b && cursor < limit && *cursor == '\\') {
90!
715
                status = (0);
716
                break;
717
            }
718
            continue;
72✔
719
        }
720

721
        if (ch == '"') {
×
722
            fallback = 1;
723
            status = (-1);
724
            break;
725
        }
726

UNCOV
727
        cursor += 1;
×
728
    }
729

730
    copy_span = max_relative + 1;
24✔
731
    if (copy_span < 0) {
24!
732
        copy_span = 0;
733
    }
734

735
    if (logger != NULL) {
24!
UNCOV
736
        sixel_logger_logf(logger,
×
737
                          "decode",
738
                          "decoder",
739
                          fallback ? "abort" : "finish",
740
                          context->index,
741
                          context->index,
742
                          0,
743
                          0,
744
                          context->start_offset,
745
                          context->end_offset,
746
                          "worker %d decode wrote=%d status=%d",
747
                          context->index,
748
                          written,
749
                          status);
750
    }
751

752
    if (status != 0) {
24✔
753
        sixel_mutex_lock(&chain->mutex);
6✔
754
        chain->abort_requested = 1;
6✔
755
        sixel_cond_broadcast(&chain->cond);
6✔
756
        sixel_mutex_unlock(&chain->mutex);
6✔
757
        sixel_local_buffer_dispose(&local_buffer);
6✔
758
        context->result = status;
6✔
759
        return status;
6✔
760
    }
761

762
    context->local_buffer = local_buffer.head != NULL ?
36!
763
        local_buffer.head->data : NULL;
18!
764
    context->local_capacity = capacity;
18✔
765
    context->local_written = copy_span;
18✔
766

767
    sixel_mutex_lock(&chain->mutex);
18✔
768
    while (!chain->copy_ready[context->index] && !chain->abort_requested) {
19!
769
        sixel_cond_wait(&chain->cond, &chain->mutex);
1✔
770
    }
771
    if (chain->abort_requested) {
18!
772
        sixel_mutex_unlock(&chain->mutex);
18✔
773
        sixel_local_buffer_dispose(&local_buffer);
18✔
774
        context->local_buffer = NULL;
18✔
775
        context->local_capacity = 0;
18✔
776
        context->local_written = 0;
18✔
777
        status = (-1);
18✔
778
        context->result = status;
18✔
779
        return status;
18✔
780
    }
UNCOV
781
    chain_offset = chain->copy_offsets[context->index];
×
UNCOV
782
    copy_offset = chain_offset;
×
UNCOV
783
    next_offset = copy_offset + copy_span;
×
UNCOV
784
    context->local_written = copy_span;
×
UNCOV
785
    if (context->index + 1 < chain->thread_count) {
×
UNCOV
786
        chain->copy_offsets[context->index + 1] = next_offset;
×
UNCOV
787
        chain->copy_ready[context->index + 1] = 1;
×
788
    }
UNCOV
789
    sixel_cond_broadcast(&chain->cond);
×
UNCOV
790
    sixel_mutex_unlock(&chain->mutex);
×
791

UNCOV
792
    if (copy_offset < 0 || chain->global_buffer == NULL) {
×
UNCOV
793
        sixel_mutex_lock(&chain->mutex);
×
UNCOV
794
        chain->abort_requested = 1;
×
UNCOV
795
        sixel_cond_broadcast(&chain->cond);
×
UNCOV
796
        sixel_mutex_unlock(&chain->mutex);
×
UNCOV
797
        sixel_local_buffer_dispose(&local_buffer);
×
UNCOV
798
        context->local_buffer = NULL;
×
UNCOV
799
        context->local_capacity = 0;
×
UNCOV
800
        context->local_written = 0;
×
UNCOV
801
        status = (-1);
×
UNCOV
802
        context->result = status;
×
UNCOV
803
        return status;
×
804
    }
805

UNCOV
806
    if (logger != NULL) {
×
807
        /* Chain memcpy execution so the timeline shows the serialized copy */
UNCOV
808
        sixel_logger_logf(logger,
×
809
                          "copy",
810
                          "decoder",
811
                          "start",
812
                          context->index,
813
                          context->index,
814
                          0,
815
                          0,
816
                          copy_offset,
817
                          next_offset,
818
                          "worker %d memcpy count=%d",
819
                          context->index,
820
                          copy_span);
821
    }
822

UNCOV
823
    if (max_relative >= 0) {
×
UNCOV
824
        chunk_cursor = local_buffer.head;
×
UNCOV
825
        while (chunk_cursor != NULL) {
×
UNCOV
826
            int rows;
×
UNCOV
827
            size_t chunk_bytes;
×
UNCOV
828
            size_t chunk_offset;
×
829

UNCOV
830
            rows = chunk_cursor->rows;
×
UNCOV
831
            if (chunk_cursor->start_row + rows >
×
UNCOV
832
                    (max_relative / width) + 1) {
×
UNCOV
833
                rows = (max_relative / width) + 1 -
×
834
                    chunk_cursor->start_row;
835
            }
UNCOV
836
            if (rows < 0) {
×
837
                rows = 0;
838
            }
UNCOV
839
            if (rows > 0) {
×
UNCOV
840
                chunk_bytes = (size_t)(rows * width) *
×
UNCOV
841
                    (size_t)chain->pixel_size;
×
UNCOV
842
                chunk_offset = (size_t)((row_offset +
×
UNCOV
843
                    chunk_cursor->start_row) * width) *
×
844
                    (size_t)chain->pixel_size;
UNCOV
845
                memcpy(chain->global_buffer + chunk_offset,
×
UNCOV
846
                       chunk_cursor->data,
×
847
                       chunk_bytes);
848
            }
UNCOV
849
            chunk_cursor = chunk_cursor->next;
×
850
        }
851
    }
852

UNCOV
853
    if (logger != NULL) {
×
UNCOV
854
        sixel_logger_logf(logger,
×
855
                          "copy",
856
                          "decoder",
857
                          "finish",
858
                          context->index,
859
                          context->index,
860
                          0,
861
                          0,
862
                          copy_offset,
863
                          next_offset,
864
                          "worker %d memcpy done", 
865
                          context->index);
866
    }
867

UNCOV
868
    sixel_local_buffer_dispose(&local_buffer);
×
UNCOV
869
    context->local_buffer = NULL;
×
UNCOV
870
    context->local_capacity = 0;
×
UNCOV
871
    context->local_written = 0;
×
872

UNCOV
873
    context->result = status;
×
UNCOV
874
    return status;
×
875
}
876
#endif
877

878
static int
879
sixel_decoder_threads_token_is_auto(char const *text)
207✔
880
{
881
    if (text == NULL) {
207!
882
        return 0;
883
    }
884
    if ((text[0] == 'a' || text[0] == 'A') &&
207!
UNCOV
885
            (text[1] == 'u' || text[1] == 'U') &&
×
UNCOV
886
            (text[2] == 't' || text[2] == 'T') &&
×
UNCOV
887
            (text[3] == 'o' || text[3] == 'O') &&
×
UNCOV
888
            text[4] == '\0') {
×
UNCOV
889
        return 1;
×
890
    }
891
    return 0;
892
}
893

894
static int
895
sixel_decoder_threads_normalize(int requested)
207✔
896
{
897
    int normalized;
207✔
898

899
#if SIXEL_ENABLE_THREADS
900
    int hw_threads;
207✔
901

902
    if (requested <= 0) {
207!
UNCOV
903
        hw_threads = sixel_get_hw_threads();
×
UNCOV
904
        if (hw_threads < 1) {
×
905
            hw_threads = 1;
906
        }
907
        normalized = hw_threads;
908
    } else {
909
        normalized = requested;
910
    }
911
#else
912
    (void)requested;
913
    normalized = 1;
914
#endif
915
    if (normalized < 1) {
1!
916
        normalized = 1;
917
    }
UNCOV
918
    return normalized;
×
919
}
920

921
static int
922
sixel_decoder_threads_parse_value(char const *text, int *value)
207✔
923
{
924
    long parsed;
207✔
925
    char *endptr;
207✔
926
    int normalized;
207✔
927

928
    if (text == NULL || value == NULL) {
207!
929
        return 0;
930
    }
931
    if (sixel_decoder_threads_token_is_auto(text)) {
207!
UNCOV
932
        normalized = sixel_decoder_threads_normalize(0);
×
UNCOV
933
        *value = normalized;
×
UNCOV
934
        return 1;
×
935
    }
936
    errno = 0;
207✔
937
    parsed = strtol(text, &endptr, 10);
207✔
938
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
207!
939
        return 0;
940
    }
941
    if (parsed < 1) {
204!
942
        normalized = sixel_decoder_threads_normalize(1);
943
    } else if (parsed > INT_MAX) {
204!
944
        normalized = sixel_decoder_threads_normalize(INT_MAX);
945
    } else {
946
        normalized = sixel_decoder_threads_normalize((int)parsed);
204✔
947
    }
948
    *value = normalized;
204✔
949
    return 1;
204✔
950
}
951

952
static void
953
sixel_decoder_threads_load_env(void)
216✔
954
{
955
    char const *text;
216✔
956
    int parsed;
216✔
957

958
    if (g_decoder_threads.env_checked) {
216✔
959
        return;
12✔
960
    }
961
    g_decoder_threads.env_checked = 1;
213✔
962
    g_decoder_threads.env_valid = 0;
213✔
963
    text = sixel_compat_getenv("SIXEL_THREADS");
213✔
964
    if (text == NULL || text[0] == '\0') {
213!
965
        return;
966
    }
967
    if (sixel_decoder_threads_parse_value(text, &parsed)) {
204!
968
        g_decoder_threads.env_threads = parsed;
204✔
969
        g_decoder_threads.env_valid = 1;
204✔
970
    }
971
}
1!
972

973
SIXELSTATUS
974
sixel_decoder_parallel_override_threads(char const *text)
3✔
975
{
976
    SIXELSTATUS status;
3✔
977
    int parsed;
3✔
978

979
    status = SIXEL_BAD_ARGUMENT;
3✔
980
    if (text == NULL || text[0] == '\0') {
3!
981
        sixel_helper_set_additional_message(
×
982
            "decoder: missing thread count after -=/--threads.");
983
        goto end;
×
984
    }
985
    if (!sixel_decoder_threads_parse_value(text, &parsed)) {
3!
986
        sixel_helper_set_additional_message(
3✔
987
            "decoder: threads must be a positive integer or 'auto'.");
988
        goto end;
3✔
989
    }
UNCOV
990
    g_decoder_threads.override_active = 1;
×
UNCOV
991
    g_decoder_threads.override_threads = parsed;
×
UNCOV
992
    status = SIXEL_OK;
×
993
end:
3✔
994
    return status;
3✔
995
}
996

997
int
998
sixel_decoder_parallel_resolve_threads(void)
216✔
999
{
1000
    int threads;
216✔
1001

1002
    threads = 1;
216✔
1003
    sixel_decoder_threads_load_env();
216✔
1004
    if (g_decoder_threads.override_active) {
216!
UNCOV
1005
        threads = sixel_decoder_threads_normalize(
×
1006
            g_decoder_threads.override_threads);
1007
    } else if (g_decoder_threads.env_valid) {
216✔
1008
        threads = sixel_decoder_threads_normalize(
207!
1009
            g_decoder_threads.env_threads);
1010
    }
UNCOV
1011
    if (threads < 1) {
×
1012
        threads = 1;
1013
    }
1014
    return threads;
216✔
1015
}
1016

1017
SIXELSTATUS
1018
sixel_decoder_parallel_request_start(int direct_mode,
216✔
1019
                                     unsigned char *input,
1020
                                     int length,
1021
                                     unsigned char *anchor,
1022
                                     image_buffer_t *image,
1023
                                     int const *palette,
1024
                                     sixel_logger_t *logger)
1025
{
1026
#if SIXEL_ENABLE_THREADS
1027
    SIXELSTATUS status;
216✔
1028
    int threads;
216✔
1029
    int payload_start;
216✔
1030
    int payload_len;
216✔
1031
    sixel_thread_t *workers;
216✔
1032
    sixel_decoder_worker_context_t *contexts;
216✔
1033
    sixel_decoder_worker_chain_t chain;
216✔
1034
    int *copy_offsets;
216✔
1035
    int *copy_ready;
216✔
1036
    int global_capacity;
216✔
1037
    int *spans;
216✔
1038
    int i;
216✔
1039
    int offset;
216✔
1040
    int created;
216✔
1041
    int parallel_failed;
216✔
1042
    int runtime_error;
216✔
1043
    int sync_ready;
216✔
1044
    int pixel_size;
216✔
1045
    int palette_limit;
216✔
1046

1047
    status = SIXEL_RUNTIME_ERROR;
216✔
1048
    workers = NULL;
216✔
1049
    contexts = NULL;
216✔
1050
    copy_offsets = NULL;
216✔
1051
    copy_ready = NULL;
216✔
1052
    spans = NULL;
216✔
1053
    threads = 0;
216✔
1054
    payload_start = 0;
216✔
1055
    payload_len = 0;
216✔
1056
    global_capacity = 0;
216✔
1057
    offset = 0;
216✔
1058
    created = 0;
216✔
1059
    parallel_failed = 0;
216✔
1060
    runtime_error = 0;
216✔
1061
    sync_ready = 0;
216✔
1062
    pixel_size = 4;
216✔
1063
    palette_limit = SIXEL_PALETTE_MAX_DECODER;
216✔
1064
    memset(&chain, 0, sizeof(chain));
216!
1065

1066
    if (input == NULL || anchor == NULL || length <= 0 || image == NULL) {
216!
UNCOV
1067
        runtime_error = 1;
×
UNCOV
1068
        goto cleanup;
×
1069
    }
1070

1071
    pixel_size = sixel_decoder_parallel_pixel_size(image->depth);
216✔
1072

1073
    payload_start = (int)(anchor - input);
216✔
1074
    if (payload_start < 0 || payload_start >= length) {
216!
UNCOV
1075
        runtime_error = 1;
×
UNCOV
1076
        goto cleanup;
×
1077
    }
1078

1079
    payload_len = length - payload_start;
216✔
1080
    if (payload_len <= 0) {
216!
1081
        runtime_error = 1;
1082
        goto cleanup;
1083
    }
1084

1085
    palette_limit = image->ncolors;
216✔
1086
    if (palette_limit <= 0 || palette_limit > SIXEL_PALETTE_MAX_DECODER) {
216!
UNCOV
1087
        palette_limit = SIXEL_PALETTE_MAX_DECODER;
×
1088
    }
1089

1090
    threads = sixel_decoder_parallel_resolve_threads();
216✔
1091
    if (threads < 1) {
216!
1092
        threads = 1;
1093
    }
1094
    if (threads > payload_len) {
216!
1095
        threads = payload_len;
1096
    }
1097
    if (threads < 2) {
216✔
1098
        status = SIXEL_FALSE;
210✔
1099
        goto cleanup;
210✔
1100
    }
1101

1102
    workers = (sixel_thread_t *)calloc((size_t)threads,
6✔
1103
                                       sizeof(sixel_thread_t));
1104
    contexts = (sixel_decoder_worker_context_t *)calloc(
6✔
1105
        (size_t)threads, sizeof(sixel_decoder_worker_context_t));
1106
    spans = (int *)calloc((size_t)threads, sizeof(int));
6✔
1107
    copy_offsets = (int *)calloc((size_t)threads, sizeof(int));
6✔
1108
    copy_ready = (int *)calloc((size_t)threads, sizeof(int));
6✔
1109
    if (workers == NULL || contexts == NULL || spans == NULL ||
6!
1110
            copy_offsets == NULL || copy_ready == NULL) {
6!
UNCOV
1111
        runtime_error = 1;
×
UNCOV
1112
        goto cleanup;
×
1113
    }
1114

1115
    global_capacity = image->width * image->height;
6✔
1116
    if (global_capacity <= 0) {
6!
UNCOV
1117
        runtime_error = 1;
×
UNCOV
1118
        goto cleanup;
×
1119
    }
1120
    chain.global_buffer = (unsigned char *)image->pixels.p;
6✔
1121
    chain.pixel_size = pixel_size;
6✔
1122
    chain.copy_offsets = copy_offsets;
6✔
1123
    chain.copy_ready = copy_ready;
6✔
1124
    chain.thread_count = threads;
6✔
1125
    chain.global_capacity = global_capacity;
6✔
1126
    sixel_mutex_init(&chain.mutex);
6✔
1127
    sixel_cond_init(&chain.cond);
6✔
1128
    chain.copy_ready[0] = 1;
6✔
1129
    chain.copy_offsets[0] = 0;
6✔
1130
    sync_ready = 1;
6✔
1131

1132
    sixel_decoder_parallel_fill_spans(payload_len, threads, spans);
6✔
1133

1134
    if (logger != NULL) {
6!
1135
        /*
1136
         * Record when the controller hands control to worker threads so the
1137
         * timeline can show the gap between parser startup and worker
1138
         * creation.
1139
         */
UNCOV
1140
        sixel_logger_logf(logger,
×
1141
                          "decoder",
1142
                          "controller",
1143
                          "launch",
1144
                          0,
1145
                          0,
1146
                          0,
1147
                          length,
1148
                          payload_start,
1149
                          length,
1150
                          "spawn %d workers payload=%d",
1151
                          threads,
1152
                          payload_len);
1153
    }
1154

1155
    offset = payload_start;
1156
    created = 0;
1157
    for (i = 0; i < threads; ++i) {
30✔
1158
        contexts[i].chain = &chain;
24✔
1159
        contexts[i].input = input;
24✔
1160
        contexts[i].anchor = anchor;
24✔
1161
        contexts[i].length = length;
24✔
1162
        contexts[i].payload_len = payload_len;
24✔
1163
        contexts[i].start_offset = offset;
24✔
1164
        offset += spans[i];
24✔
1165
        if (offset > length) {
24!
1166
            offset = length;
1167
        }
1168
        if (i == threads - 1) {
24✔
1169
            contexts[i].end_offset = length - 1;
6✔
1170
        } else {
1171
            contexts[i].end_offset = offset - 1;
18✔
1172
            if (contexts[i].end_offset < contexts[i].start_offset) {
18!
UNCOV
1173
                contexts[i].end_offset = contexts[i].start_offset;
×
1174
            }
1175
        }
1176
        contexts[i].index = i;
24✔
1177
        contexts[i].direct_mode = direct_mode;
24✔
1178
        contexts[i].palette = palette;
24✔
1179
        contexts[i].palette_limit = palette_limit;
24✔
1180
        contexts[i].width = image->width;
24✔
1181
        contexts[i].height = image->height;
24✔
1182
        contexts[i].pixel_size = pixel_size;
24✔
1183
        contexts[i].depth = image->depth;
24✔
1184
        contexts[i].logger = logger;
24✔
1185
        contexts[i].result = (-1);
24✔
1186
        status = sixel_thread_create(&workers[i],
24✔
1187
                                     sixel_decoder_parallel_worker,
1188
                                     &contexts[i]);
1189
        if (SIXEL_FAILED(status)) {
24!
1190
            runtime_error = 1;
1191
            break;
1192
        }
1193
        created += 1;
24✔
1194
    }
1195

1196
    for (i = 0; i < created; ++i) {
30✔
1197
        sixel_thread_join(&workers[i]);
24✔
1198
        if (contexts[i].result != 0) {
24!
1199
            parallel_failed = 1;
24✔
1200
        }
1201
    }
1202

1203
    if (chain.abort_requested) {
6!
1204
        parallel_failed = 1;
6✔
1205
    }
1206

1207
    if (runtime_error || created < threads) {
6!
1208
        status = SIXEL_RUNTIME_ERROR;
1209
    } else if (parallel_failed) {
6!
1210
        status = SIXEL_FALSE;
1211
    } else {
1212
        status = SIXEL_OK;
1213
    }
1214

1215
cleanup:
1216
    if (sync_ready) {
210✔
1217
        sixel_mutex_destroy(&chain.mutex);
6✔
1218
        sixel_cond_destroy(&chain.cond);
6✔
1219
    }
1220

1221
    free(workers);
216✔
1222
    free(contexts);
216✔
1223
    free(spans);
216✔
1224
    free(copy_offsets);
216✔
1225
    free(copy_ready);
216✔
1226

1227
    return status;
216✔
1228
#else
1229
    (void)direct_mode;
1230
    (void)input;
1231
    (void)length;
1232
    (void)anchor;
1233
    (void)image;
1234
    (void)palette;
1235
    (void)logger;
1236

1237
    return SIXEL_RUNTIME_ERROR;
1238
#endif
1239
}
1240

1241
/* emacs Local Variables:      */
1242
/* emacs mode: c               */
1243
/* emacs tab-width: 4          */
1244
/* emacs indent-tabs-mode: nil */
1245
/* emacs c-basic-offset: 4     */
1246
/* emacs End:                  */
1247
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1248
/* 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

© 2025 Coveralls, Inc