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

saitoha / libsixel / 29176094696

10 Jul 2026 03:01PM UTC coverage: 85.292% (+0.03%) from 85.263%
29176094696

push

github

saitoha
fix: shorten decoder test identifiers

78943 of 143474 branches covered (55.02%)

2 of 2 new or added lines in 2 files covered. (100.0%)

209 existing lines in 9 files now uncovered.

140656 of 164911 relevant lines covered (85.29%)

6100630.51 hits per line

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

99.84
/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.h"
40
#include "decoder-parallel.h"
41
#if SIXEL_ENABLE_THREADS
42
# include "timeline-logger.h"
43
# include "threading.h"
44
#endif
45
#include "compat_stub.h"
46

47
/*
48
 * Parallel decoding starts only after the serial parser has established
49
 * raster bounds and an initial palette state.  Worker byte spans are allowed
50
 * to fall back to the serial decoder when they see tokens that need global
51
 * parser context, such as raster resets or palette redefinitions.
52
 */
53

54
typedef struct sixel_decoder_thread_config {
55
    int override_active;
56
    int override_threads;
57
} sixel_decoder_thread_config_t;
58

59
#if SIXEL_ENABLE_THREADS
60
typedef enum sixel_decoder_direct_mode {
61
    SIXEL_DECODER_DIRECT_SCAN = 0,
62
    SIXEL_DECODER_DIRECT_PAINT = 1
63
} sixel_decoder_direct_mode_t;
64

65
typedef struct sixel_decoder_worker_context {
66
    struct sixel_decoder_worker_chain *chain;
67
    unsigned char *input;
68
    unsigned char *anchor;
69
    int length;
70
    int payload_len;
71
    int start_offset;
72
    int end_offset;
73
    int index;
74
    int direct_mode;
75
    int ormode;
76
    int initial_color_index;
77
    int const *palette;
78
    int palette_limit;
79
    int width;
80
    int height;
81
    int pixel_size;
82
    int depth;
83
    sixel_timeline_logger_t *logger;
84
    int trust_raster_size;
85
    int painted_outside_raster;
86
    int max_color_index;
87
    int result;
88
    int runtime_error;
89
    sixel_decoder_direct_mode_t direct_mode_kind;
90
    sixel_decoder_undither_context_t *undither;
91
    int body_local_start;
92
} sixel_decoder_worker_context_t;
93

94
typedef struct sixel_decoder_local_chunk {
95
    unsigned char *data;
96
    unsigned char *paint_mask;
97
    int start_row;
98
    int rows;
99
    struct sixel_decoder_local_chunk *next;
100
} sixel_decoder_local_chunk_t;
101

102
typedef struct sixel_local_buffer {
103
    sixel_decoder_local_chunk_t *head;
104
    sixel_decoder_local_chunk_t *tail;
105
    sixel_decoder_local_chunk_t *cursor;
106
    int width;
107
    int pixel_size;
108
    int keep_paint_mask;
109
} sixel_local_buffer_t;
110

111
typedef struct sixel_decoder_worker_chain {
112
    sixel_mutex_t mutex;
113
    sixel_cond_t cond;
114
    int thread_count;
115
    int decode_done;
116
    int decode_failed;
117
    int paint_released;
118
    unsigned char *global_buffer;
119
    unsigned char *global_mask;
120
    int global_capacity;
121
    int pixel_size;
122
    int abort_requested;
123
} sixel_decoder_worker_chain_t;
124
#endif
125

126
static sixel_decoder_thread_config_t g_decoder_threads = {
127
    0,
128
    1
129
};
130

131
#if SIXEL_ENABLE_THREADS
132
static sixel_mutex_t g_decoder_threads_mutex;
133
static int g_decoder_threads_mutex_ready;
134

135
# if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__MSYS__) \
136
    && !defined(WITH_WINPTHREAD)
137
#  if !defined(UNICODE)
138
#   define UNICODE
139
#  endif
140
#  if !defined(_UNICODE)
141
#   define _UNICODE
142
#  endif
143
#  if !defined(WIN32_LEAN_AND_MEAN)
144
#   define WIN32_LEAN_AND_MEAN
145
#  endif
146
#  include <windows.h>
147
static INIT_ONCE g_decoder_threads_once = INIT_ONCE_STATIC_INIT;
148

149
static BOOL CALLBACK
150
sixel_decoder_threads_lock_init_once(PINIT_ONCE once,
6,909✔
151
                                      PVOID parameter,
152
                                      PVOID *context)
153
{
154
    (void)once;
3,948✔
155
    (void)parameter;
3,948✔
156
    (void)context;
3,948✔
157

158
    if (sixel_mutex_init(&g_decoder_threads_mutex) == SIXEL_OK) {
6,909✔
159
        g_decoder_threads_mutex_ready = 1;
6,909✔
160
    }
987✔
161
    return TRUE;
6,909✔
162
}
163
# else
164
#  include <pthread.h>
165
static pthread_once_t g_decoder_threads_once = PTHREAD_ONCE_INIT;
166

167
static void
168
sixel_decoder_threads_lock_init_once(void)
4,798✔
169
{
170
    if (sixel_mutex_init(&g_decoder_threads_mutex) == SIXEL_OK) {
4,798!
171
        g_decoder_threads_mutex_ready = 1;
4,798✔
172
    }
173
}
4,798✔
174
# endif
175

176
static void
177
sixel_decoder_threads_lock(void)
12,967✔
178
{
179
# if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__MSYS__) \
180
    && !defined(WITH_WINPTHREAD)
181
    BOOL initialized;
3,276✔
182

183
    initialized = InitOnceExecuteOnce(&g_decoder_threads_once,
7,644✔
184
                                      sixel_decoder_threads_lock_init_once,
185
                                      NULL,
186
                                      NULL);
187
    if (!initialized || !g_decoder_threads_mutex_ready) {
7,644✔
188
        abort();
189
    }
190
# else
191
    int once_status;
2,140✔
192

193
    once_status = pthread_once(&g_decoder_threads_once,
5,323✔
194
                               sixel_decoder_threads_lock_init_once);
195
    if (once_status != 0 || !g_decoder_threads_mutex_ready) {
5,323!
196
        abort();
197
    }
198
# endif
199
    sixel_mutex_lock(&g_decoder_threads_mutex);
12,967✔
200
}
12,967✔
201

202
static void
203
sixel_decoder_threads_unlock(void)
12,967✔
204
{
205
    if (!g_decoder_threads_mutex_ready) {
12,967!
206
        abort();
207
    }
208
    sixel_mutex_unlock(&g_decoder_threads_mutex);
12,967✔
209
}
12,967✔
210
#else
211
static void
212
sixel_decoder_threads_lock(void)
6✔
213
{
214
}
6✔
215

216
static void
217
sixel_decoder_threads_unlock(void)
6✔
218
{
219
}
6✔
220
#endif
221

222
#if SIXEL_ENABLE_THREADS
223
static void
224
sixel_decoder_parallel_fill_spans(int payload_len,
225
                                  int threads,
226
                                  int *spans);
227

228
static int
229
sixel_decoder_parallel_skew_percent(void)
986✔
230
{
231
    char const *text;
411✔
232
    char *endptr;
411✔
233
    long value;
411✔
234

235
    /*
236
     * SIXEL_PARALLEL_SKEW lets operators bias span lengths by +/-20% so the
237
     * trailing workers take slightly more work.  The default keeps spans
238
     * balanced.
239
     */
240
    text = sixel_compat_getenv("SIXEL_PARALLEL_SKEW");
986✔
241
    if (text == NULL || text[0] == '\0') {
986!
242
        return 0;
575✔
243
    }
244

245
    errno = 0;
246
    value = strtol(text, &endptr, 10);
247
    if (errno != 0 || endptr == text || *endptr != '\0') {
×
248
        return 0;
249
    }
250

251
    if (value < -20) {
×
252
        value = -20;
253
    } else if (value > 20) {
×
254
        value = 20;
255
    }
256

257
    return (int)value;
258
}
83✔
259

260
static void
261
sixel_decoder_parallel_fill_spans(int payload_len,
986✔
262
                                  int threads,
263
                                  int *spans)
264
{
265
    int base_share;
411✔
266
    int skew_percent;
411✔
267
    double skew;
411✔
268
    int i;
411✔
269
    double center;
411✔
270
    int total;
411✔
271
    int remainder;
411✔
272

273
    base_share = payload_len / threads;
986✔
274
    skew_percent = sixel_decoder_parallel_skew_percent();
986✔
275
    skew = ((double)base_share * (double)skew_percent) / 100.0;
986✔
276
    total = 0;
986✔
277
    for (i = 0; i < threads; ++i) {
4,810✔
278
        center = (double)i - (double)(threads - 1) / 2.0;
3,824✔
279
        spans[i] = base_share + (int)(skew * center);
3,824✔
280
        if (spans[i] < 1) {
3,824!
281
            spans[i] = 1;
282
        }
283
        total += spans[i];
3,824✔
284
    }
322✔
285

286
    remainder = payload_len - total;
986✔
287
    while (remainder > 0) {
1,424✔
288
        for (i = threads - 1; i >= 0 && remainder > 0; --i) {
1,188✔
289
            spans[i] += 1;
750✔
290
            --remainder;
750✔
291
        }
63✔
292
    }
293
    while (remainder < 0) {
986!
294
        for (i = threads - 1; i >= 0 && remainder < 0; --i) {
×
295
            if (spans[i] > 1) {
×
296
                spans[i] -= 1;
297
                ++remainder;
298
            }
299
        }
300
        if (remainder < 0) {
×
301
            break;
302
        }
303
    }
304
}
986✔
305

306
static int
307
sixel_decoder_parallel_pixel_size(int depth)
12,751✔
308
{
309
    int pixel_size;
5,326✔
310

311
    pixel_size = 4;
12,751✔
312
    if (depth == 1) {
12,751✔
313
        pixel_size = 1;
7,292✔
314
    } else if (depth == 2) {
1,283✔
315
        pixel_size = 2;
12✔
316
    }
1✔
317

318
    return pixel_size;
12,751✔
319
}
320

321
static void
322
sixel_decoder_parallel_store_pixel(unsigned char *dst,
3,416,998✔
323
                                   int depth,
324
                                   int color_index,
325
                                   int const *palette)
326
{
327
    int color;
1,510,376✔
328

329
    if (depth == 1) {
3,416,998✔
330
        dst[0] = (unsigned char)color_index;
3,424,292✔
331
        return;
3,424,292✔
332
    }
333

334
    if (depth == 2) {
5,112!
335
        unsigned short packed;
336

337
        packed = (unsigned short)color_index;
338
        memcpy(dst, &packed, sizeof(packed));
339
        return;
340
    }
341

342
    color = 0;
5,112✔
343
    if (palette != NULL &&
5,112!
344
            color_index >= 0 &&
6,816!
345
            color_index < SIXEL_PALETTE_MAX_DECODER) {
568✔
346
        color = palette[color_index];
6,816✔
347
    }
568✔
348
    dst[0] = (unsigned char)((color >> 16) & 0xff);
5,112✔
349
    dst[1] = (unsigned char)((color >> 8) & 0xff);
5,112✔
350
    dst[2] = (unsigned char)(color & 0xff);
5,112✔
351
    dst[3] = 255u;
5,112✔
352
}
207,985✔
353

354
static int
355
sixel_decoder_parallel_store_ormode_span(unsigned char *dst,
168✔
356
                                         int depth,
357
                                         int color_index,
358
                                         int repeat)
359
{
360
    unsigned char *bytes;
70✔
361
    unsigned short *shorts;
70✔
362
    int composed_index;
70✔
363
    int max_index;
70✔
364
    int n;
70✔
365

366
    if (dst == NULL || repeat <= 0) {
168!
367
        return (-1);
368
    }
369

370
    max_index = (-1);
168✔
371
    if (depth == 1) {
168!
372
        bytes = dst;
373
        for (n = 0; n < repeat; ++n) {
×
374
            composed_index = bytes[n] | color_index;
375
            bytes[n] = (unsigned char)composed_index;
376
            if (max_index < composed_index) {
×
377
                max_index = composed_index;
378
            }
379
        }
380
    } else {
381
        shorts = (unsigned short *)dst;
98✔
382
        for (n = 0; n < repeat; ++n) {
504✔
383
            composed_index = shorts[n] | color_index;
336✔
384
            shorts[n] = (unsigned short)composed_index;
336✔
385
            if (max_index < composed_index) {
336✔
386
                max_index = composed_index;
98✔
387
            }
14✔
388
        }
28✔
389
    }
390

391
    return max_index;
98✔
392
}
14✔
393

394
static void
395
sixel_local_buffer_init(sixel_local_buffer_t *buffer,
190✔
396
                        int width,
397
                        int pixel_size,
398
                        int keep_paint_mask)
399
{
400
    buffer->head = NULL;
190✔
401
    buffer->tail = NULL;
190✔
402
    buffer->cursor = NULL;
190✔
403
    buffer->width = width;
190✔
404
    buffer->pixel_size = pixel_size;
190✔
405
    buffer->keep_paint_mask = keep_paint_mask != 0 ? 1 : 0;
190✔
406
}
110✔
407

408
static void
409
sixel_local_buffer_dispose(sixel_local_buffer_t *buffer)
168✔
410
{
411
    sixel_decoder_local_chunk_t *cursor;
70✔
412

413
    cursor = buffer->head;
168✔
414
    while (cursor != NULL) {
396✔
415
        sixel_decoder_local_chunk_t *tmp;
95✔
416

417
        free(cursor->data);
228✔
418
        free(cursor->paint_mask);
228✔
419
        tmp = cursor->next;
228✔
420
        free(cursor);
228✔
421
        cursor = tmp;
228✔
422
    }
423

424
    buffer->head = NULL;
168✔
425
    buffer->tail = NULL;
168✔
426
    buffer->cursor = NULL;
168✔
427
}
168✔
428

429
static sixel_decoder_local_chunk_t *
430
sixel_local_buffer_append(sixel_local_buffer_t *buffer,
228✔
431
                          int rows,
432
                          int start_row)
433
{
434
    sixel_decoder_local_chunk_t *chunk;
95✔
435
    size_t bytes;
95✔
436

437
    if (rows % 6 != 0) {
228!
438
        rows += 6 - (rows % 6);
439
    }
440

441
    chunk = (sixel_decoder_local_chunk_t *)calloc(
228✔
442
        1, sizeof(sixel_decoder_local_chunk_t));
443
    if (chunk == NULL) {
228!
444
        return NULL;
445
    }
446

447
    bytes = (size_t)(rows * buffer->width) * (size_t)buffer->pixel_size;
228✔
448
    chunk->data = (unsigned char *)calloc(bytes, 1);
228✔
449
    if (chunk->data == NULL) {
228!
450
        free(chunk);
451
        return NULL;
452
    }
453
    if (buffer->keep_paint_mask) {
228!
454
        bytes = (size_t)(rows * buffer->width);
455
        chunk->paint_mask = (unsigned char *)calloc(bytes, 1);
456
        if (chunk->paint_mask == NULL) {
×
457
            free(chunk->data);
458
            free(chunk);
459
            return NULL;
460
        }
461
    }
462

463
    chunk->start_row = start_row;
228✔
464
    chunk->rows = rows;
228✔
465
    chunk->next = NULL;
228✔
466

467
    if (buffer->head == NULL) {
228✔
468
        buffer->head = chunk;
168✔
469
    } else {
14✔
470
        buffer->tail->next = chunk;
60✔
471
    }
472
    buffer->tail = chunk;
228✔
473
    buffer->cursor = chunk;
228✔
474

475
    return chunk;
228✔
476
}
19✔
477

478
static sixel_decoder_local_chunk_t *
479
sixel_local_buffer_reserve_row(sixel_local_buffer_t *buffer,
2,880,467✔
480
                               int row)
481
{
482
    sixel_decoder_local_chunk_t *cursor;
1,287,493✔
483

484
    cursor = buffer->cursor;
2,880,467✔
485
    while (cursor != NULL && row >= cursor->start_row + cursor->rows) {
2,880,527✔
486
        cursor = cursor->next;
60✔
487
    }
488
    if (cursor != NULL && row >= cursor->start_row) {
2,880,467!
489
        buffer->cursor = cursor;
2,894,955✔
490
        return cursor;
2,894,955✔
491
    }
492

493
    cursor = buffer->tail;
2,011✔
494
    if (cursor == NULL) {
2,011!
495
        return NULL;
496
    }
497

498
    while (row >= cursor->start_row + cursor->rows) {
1,208✔
499
        sixel_decoder_local_chunk_t *next;
1,143✔
500
        int start_row;
1,143✔
501

502
        start_row = cursor->start_row + cursor->rows;
1,178✔
503
        next = sixel_local_buffer_append(buffer, cursor->rows, start_row);
1,178✔
504
        if (next == NULL) {
60!
505
            return NULL;
506
        }
507
        cursor = next;
35✔
508
    }
509

510
    buffer->cursor = cursor;
45✔
511
    return cursor;
45✔
512
}
166,203✔
513

514
static unsigned char *
515
sixel_decoder_parallel_find_fast4_halo(unsigned char *anchor,
168✔
516
                                       unsigned char *body)
517
{
518
    unsigned char *scan;
70✔
519

520
    if (anchor == NULL || body == NULL || body <= anchor) {
168!
521
        return body;
28✔
522
    }
523
    if (body[-1] != '-') {
120!
524
        return body;
525
    }
526

527
    scan = body - 1;
120✔
528
    while (scan > anchor) {
262,999✔
529
        --scan;
262,975✔
530
        if (*scan == '-') {
262,975✔
531
            return scan + 1;
56✔
532
        }
533
    }
534

535
    return anchor;
14✔
536
}
14✔
537

538
static int
539
sixel_decoder_parallel_add_line_count(int *line_count)
9,373✔
540
{
541
    /*
542
     * Row anchors are stored as int pixel offsets. Reject a band count before
543
     * multiplying it by the six-pixel band height would overflow.
544
     */
545
    if (line_count == NULL || *line_count >= INT_MAX / 6) {
9,373!
546
        return (-1);
3✔
547
    }
548

549
    *line_count += 1;
9,372✔
550
    return 0;
9,372✔
551
}
790✔
552

553
static int
554
sixel_decoder_parallel_count_newlines(unsigned char const *begin,
168✔
555
                                      unsigned char const *end)
556
{
557
    unsigned char const *scan;
70✔
558
    int lines;
70✔
559

560
    lines = 0;
168✔
561
    scan = begin;
168✔
562
    while (scan != NULL && scan < end) {
264,193!
563
        if (*scan == '-') {
264,025✔
564
            if (sixel_decoder_parallel_add_line_count(&lines) != 0) {
119!
565
                return (-1);
566
            }
567
        }
10✔
568
        ++scan;
264,025✔
569
    }
570

571
    return lines;
98✔
572
}
14✔
573

574
static void
575
sixel_decoder_parallel_cancel_decode(sixel_decoder_worker_chain_t *chain)
576
{
577
    if (chain == NULL) {
×
578
        return;
579
    }
580

581
    sixel_mutex_lock(&chain->mutex);
582
    chain->decode_failed = 1;
583
    chain->abort_requested = 1;
584
    sixel_cond_broadcast(&chain->cond);
585
    sixel_mutex_unlock(&chain->mutex);
586
}
587

588
static int
589
sixel_decoder_parallel_finish_decode(sixel_decoder_worker_chain_t *chain,
24✔
590
                                     int status)
591
{
592
    int publish_ready;
10✔
593

594
    if (chain == NULL) {
24!
595
        return (-1);
596
    }
597

598
    publish_ready = 0;
24✔
599
    sixel_mutex_lock(&chain->mutex);
24✔
600
    chain->decode_done += 1;
24✔
601
    if (status != 0) {
24!
602
        chain->decode_failed = 1;
24✔
603
        chain->abort_requested = 1;
24✔
604
    }
2✔
605
    sixel_cond_broadcast(&chain->cond);
24✔
606

607
    /*
608
     * Non-fast4 workers publish into the shared image only after every
609
     * worker has finished decoding successfully.  A failed worker marks the
610
     * chain aborted and wakes peers so request_start() can return
611
     * SIXEL_FALSE without dirtying the serial fallback image.
612
     */
613
    while (!chain->abort_requested &&
24!
614
            chain->decode_done < chain->thread_count) {
×
615
        sixel_cond_wait(&chain->cond, &chain->mutex);
616
    }
617
    if (!chain->abort_requested && !chain->decode_failed &&
24!
618
            chain->decode_done >= chain->thread_count) {
×
619
        publish_ready = 1;
10✔
620
    }
621

622
    sixel_mutex_unlock(&chain->mutex);
24✔
623
    return publish_ready ? 0 : (-1);
24✔
624
}
2✔
625

626
static int
627
sixel_decoder_parallel_direct_parse(sixel_decoder_worker_context_t *context)
5,819✔
628
{
629
    sixel_decoder_worker_chain_t *chain;
2,104✔
630
    sixel_decoder_direct_mode_t mode;
2,104✔
631
    unsigned char *anchor;
2,104✔
632
    unsigned char *scan;
2,104✔
633
    unsigned char *cursor;
2,104✔
634
    unsigned char *stop;
2,104✔
635
    unsigned char *limit;
2,104✔
636
    unsigned char *start;
2,104✔
637
    unsigned char *p;
2,104✔
638
    unsigned char *dst;
2,104✔
639
    unsigned char ch;
2,104✔
640
    size_t global_index;
2,104✔
641
    int assigned;
2,104✔
642
    int bits;
2,104✔
643
    int repeat;
2,104✔
644
    int color_index;
2,104✔
645
    int max_color_index;
2,104✔
646
    int span_max_index;
2,104✔
647
    int pos_x;
2,104✔
648
    int pos_y;
2,104✔
649
    int line_count;
2,104✔
650
    int row_offset;
2,104✔
651
    int value;
2,104✔
652
    int effective_repeat;
2,104✔
653
    int global_y;
2,104✔
654
    int width;
2,104✔
655
    int height;
2,104✔
656
    int pixel_size;
2,104✔
657
    int depth;
2,104✔
658
    int starts_after_newline;
2,104✔
659
    int written;
2,104✔
660
    int status;
2,104✔
661
    int i;
2,104✔
662
    int r;
2,104✔
663

664
    if (context == NULL || context->chain == NULL) {
5,819!
665
        return (-1);
1,722✔
666
    }
667

668
    chain = context->chain;
5,820✔
669
    mode = context->direct_mode_kind;
5,820✔
670
    anchor = context->anchor;
5,820✔
671
    scan = NULL;
5,820✔
672
    cursor = NULL;
5,820✔
673
    stop = NULL;
5,820✔
674
    limit = NULL;
5,820✔
675
    start = NULL;
5,820✔
676
    p = NULL;
5,820✔
677
    dst = NULL;
5,820✔
678
    global_index = 0u;
5,820✔
679
    assigned = 0;
5,820✔
680
    bits = 0;
5,820✔
681
    repeat = 1;
5,820✔
682
    color_index = context->initial_color_index;
5,820✔
683
    max_color_index = (-1);
5,820✔
684
    span_max_index = (-1);
5,820✔
685
    pos_x = 0;
5,820✔
686
    pos_y = 0;
5,820✔
687
    line_count = 0;
5,820✔
688
    row_offset = 0;
5,820✔
689
    value = 0;
5,820✔
690
    effective_repeat = 0;
5,820✔
691
    global_y = 0;
5,820✔
692
    width = context->width;
5,820✔
693
    height = context->height;
5,820✔
694
    pixel_size = context->pixel_size;
5,820✔
695
    depth = context->depth;
5,820✔
696
    starts_after_newline = 0;
5,820✔
697
    written = 0;
5,820✔
698
    status = (-1);
5,820✔
699

700
    context->runtime_error = 0;
5,820✔
701
    context->painted_outside_raster = 0;
5,820✔
702
    context->max_color_index = (-1);
5,820✔
703

704
    if (context->input == NULL || anchor == NULL ||
5,820!
705
            context->length <= 0 || context->payload_len <= 0 ||
4,958!
706
            width <= 0 || height <= 0 || chain->global_buffer == NULL) {
4,961!
707
        goto fail;
1,722✔
708
    }
709

710
    start = context->input + context->start_offset;
5,822✔
711
    if (start < context->input ||
5,822!
712
            start >= context->input + context->length) {
4,961!
713
        goto fail;
1,721✔
714
    }
715

716
    assigned = context->end_offset - context->start_offset + 1;
5,821✔
717
    if (assigned <= 0) {
5,821!
718
        goto fail;
719
    }
720

721
    if (color_index < 0) {
5,821!
722
        color_index = 0;
723
    } else if (color_index >= SIXEL_PALETTE_MAX_DECODER) {
5,821!
724
        color_index = SIXEL_PALETTE_MAX_DECODER - 1;
725
    }
726

727
    cursor = start;
5,821✔
728
    if (context->index > 0) {
5,821✔
729
        starts_after_newline = start > anchor && start[-1] == '-';
3,625!
730
        if (!starts_after_newline) {
3,595✔
731
            cursor = (unsigned char *)memchr(start,
3,854✔
732
                                             '-',
733
                                             (size_t)(context->length -
3,854✔
734
                                             context->start_offset));
2,075✔
735
            if (cursor != NULL &&
3,555✔
736
                    cursor + 1 < context->input + context->length) {
2,892!
737
                cursor += 1;
3,793✔
738
            } else {
243✔
739
                cursor = start;
386✔
740
            }
741
        }
299✔
742
    }
305✔
743
    if (context->index > 0 && cursor == start && !starts_after_newline) {
5,821✔
744
        goto fail;
662✔
745
    }
746

747
    if (anchor < cursor) {
5,159✔
748
        scan = anchor;
1,797✔
749
        while (scan < cursor) {
6,883,188✔
750
            if (*scan == '-') {
6,880,225✔
751
                if (sixel_decoder_parallel_add_line_count(
7,694!
752
                        &line_count) != 0) {
647✔
753
                    status = (-1);
754
                    goto fail;
755
                }
756
                scan += 1;
7,693✔
757
                continue;
7,693✔
758
            }
759
            if (*scan == '#') {
6,872,531✔
760
                value = 0;
561,305✔
761
                p = scan + 1;
561,305✔
762
                while (p < cursor && *p >= '0' && *p <= '9') {
1,920,202!
763
                    if (value > SIXEL_PALETTE_MAX_DECODER / 10 ||
1,358,897!
764
                            (value == SIXEL_PALETTE_MAX_DECODER / 10 &&
113,428!
765
                            (*p - '0') >
×
766
                            SIXEL_PALETTE_MAX_DECODER % 10)) {
767
                        status = (-1);
30✔
768
                        goto fail;
30✔
769
                    }
770
                    value = value * 10 + (*p - '0');
1,358,897✔
771
                    p += 1;
1,358,897✔
772
                }
773
                color_index = value;
561,305✔
774
                if (color_index < 0) {
561,305!
775
                    color_index = 0;
776
                }
777
                if (color_index >= SIXEL_PALETTE_MAX_DECODER) {
561,305!
778
                    color_index = SIXEL_PALETTE_MAX_DECODER - 1;
779
                }
780
                if (p < cursor && *p == ';') {
561,305!
781
                    scan = p;
782
                    continue;
783
                }
784
                scan = p;
561,305✔
785
                continue;
561,305✔
786
            }
787
            scan += 1;
6,311,226✔
788
        }
789
    }
249✔
790

791
    row_offset = line_count * 6;
5,092✔
792
    stop = context->input + context->end_offset;
5,092✔
793
    limit = context->input + context->length;
5,092✔
794
    status = 0;
5,092✔
795

796
    while (cursor < limit) {
3,252,568✔
797
        ch = *cursor;
3,254,200✔
798
        if (ch >= '?' && ch <= '~') {
3,254,200!
799
            bits = ch - '?';
2,342,767✔
800
            effective_repeat = repeat;
2,342,767✔
801
            if (pos_x < 0 || repeat < 0 ||
2,342,767!
802
                    pos_x > INT_MAX - repeat) {
2,341,777✔
803
                status = (-1);
7,808✔
804
                goto fail;
7,808✔
805
            }
806
            if (context->trust_raster_size) {
2,335,671!
807
                if (pos_x >= width) {
×
808
                    if (bits != 0) {
×
809
                        context->painted_outside_raster = 1;
810
                    }
811
                    effective_repeat = 0;
812
                } else if (pos_x + effective_repeat > width) {
×
813
                    if (bits != 0) {
×
814
                        context->painted_outside_raster = 1;
815
                    }
816
                    effective_repeat = width - pos_x;
817
                }
818
            }
819
            for (i = 0; i < 6; ++i) {
16,060,180✔
820
                if ((bits & (1 << i)) == 0) {
13,722,597✔
821
                    continue;
12,549,026✔
822
                }
823
                if (pos_y > INT_MAX - i ||
1,173,571!
824
                        row_offset > INT_MAX - (pos_y + i)) {
1,175,502!
825
                    status = (-1);
196✔
826
                    goto fail;
196✔
827
                }
828
                global_y = row_offset + pos_y + i;
1,175,873✔
829
                if (global_y < 0) {
1,175,873!
830
                    status = (-1);
831
                    goto fail;
832
                }
833
                if (global_y >= height) {
1,175,873!
834
                    if (context->trust_raster_size) {
×
835
                        context->painted_outside_raster = 1;
836
                        continue;
837
                    }
838
                    status = (-1);
839
                    goto fail;
840
                }
841
                if (pos_x + repeat > width) {
1,175,873✔
842
                    if (context->trust_raster_size) {
144!
843
                        context->painted_outside_raster = 1;
844
                        effective_repeat = width > pos_x ?
845
                            width - pos_x : 0;
×
846
                    } else {
847
                        status = (-1);
144✔
848
                        goto fail;
144✔
849
                    }
850
                }
851
                if (effective_repeat <= 0) {
1,175,729!
852
                    continue;
853
                }
854
                if (mode == SIXEL_DECODER_DIRECT_PAINT) {
1,175,729✔
855
                    global_index = (size_t)global_y *
649,834✔
856
                        (size_t)width + (size_t)pos_x;
603,451✔
857
                    dst = chain->global_buffer + global_index *
603,451✔
858
                        (size_t)pixel_size;
557,068✔
859
                    if (context->ormode) {
557,068✔
860
                        span_max_index =
84✔
861
                            sixel_decoder_parallel_store_ormode_span(
168✔
862
                                dst,
14✔
863
                                depth,
14✔
864
                                color_index,
14✔
865
                                effective_repeat);
14✔
866
                        if (span_max_index > max_color_index) {
168✔
867
                            max_color_index = span_max_index;
230,612✔
868
                        }
4✔
869
                    } else if (pixel_size == 1 && effective_repeat > 3) {
556,914✔
870
                        memset(dst, color_index, (size_t)effective_repeat);
3,835✔
871
                        if (color_index > max_color_index) {
3,835✔
872
                            max_color_index = color_index;
140,916✔
873
                        }
11✔
874
                    } else {
320✔
875
                        for (r = 0; r < effective_repeat; ++r) {
1,112,471✔
876
                            sixel_decoder_parallel_store_pixel(
559,652✔
877
                                dst + (size_t)r * (size_t)pixel_size,
559,652✔
878
                                depth,
46,642✔
879
                                color_index,
46,642✔
880
                                context->palette);
46,642✔
881
                        }
46,642✔
882
                        if (pixel_size == 1 &&
552,819✔
883
                                color_index > max_color_index) {
274,909✔
884
                            max_color_index = color_index;
145,444✔
885
                        }
658✔
886
                    }
887
                    if (chain->global_mask != NULL) {
556,822✔
888
                        memset(chain->global_mask + global_index,
13✔
889
                               0xff,
890
                               (size_t)effective_repeat);
1✔
891
                    }
1✔
892
                } else if (!context->ormode && pixel_size == 1 &&
665,044✔
893
                        color_index > max_color_index) {
309,359✔
894
                    max_color_index = color_index;
499,385✔
895
                }
1,433✔
896
                written += effective_repeat;
1,175,483✔
897
            }
97,178✔
898

899
            cursor += 1;
2,337,583✔
900
            if (pos_x > INT_MAX - repeat) {
2,337,583!
901
                status = (-1);
902
                goto fail;
903
            }
904
            pos_x += repeat;
2,337,583✔
905
            repeat = 1;
2,337,583✔
906
            continue;
2,337,583✔
907
        }
908

909
        if (ch == '#') {
670,172✔
910
            value = 0;
329,195✔
911
            p = cursor + 1;
329,195✔
912
            while (p < limit && *p >= '0' && *p <= '9') {
1,102,245!
913
                if (value > SIXEL_PALETTE_MAX_DECODER / 10 ||
773,050!
914
                        (value == SIXEL_PALETTE_MAX_DECODER / 10 &&
64,881!
915
                        (*p - '0') > SIXEL_PALETTE_MAX_DECODER % 10)) {
×
916
                    status = (-1);
38✔
917
                    goto fail;
38✔
918
                }
919
                value = value * 10 + (*p - '0');
773,050✔
920
                p += 1;
773,050✔
921
            }
922
            if (p < limit && *p == ';') {
329,195!
923
                status = (-1);
924
                goto fail;
925
            }
926
            color_index = value;
329,195✔
927
            if (color_index < 0) {
329,195!
928
                color_index = 0;
929
            }
930
            if (color_index >= SIXEL_PALETTE_MAX_DECODER) {
329,169!
931
                color_index = SIXEL_PALETTE_MAX_DECODER - 1;
932
            }
933
            cursor = p;
329,169✔
934
            continue;
329,169✔
935
        }
936

937
        if (ch == '!') {
558,361✔
938
            value = 0;
521,781✔
939
            p = cursor + 1;
521,781✔
940
            while (p < limit && *p >= '0' && *p <= '9') {
2,643,114!
941
                if (value > 6553 ||
2,154,106!
942
                        (value == 6553 && (*p - '0') > 5)) {
537,266!
943
                    status = (-1);
4✔
944
                    goto fail;
4✔
945
                }
946
                value = value * 10 + (*p - '0');
2,121,333✔
947
                p += 1;
2,121,333✔
948
            }
949
            if (value <= 0) {
521,791!
950
                value = 1;
951
            }
952
            repeat = value;
521,791✔
953
            cursor = p;
521,791✔
954
            continue;
521,791✔
955
        }
956

957
        if (ch == '$') {
60,085✔
958
            cursor += 1;
56,508✔
959
            pos_x = 0;
56,508✔
960
            continue;
56,508✔
961
        }
962

963
        if (ch == '-') {
4,337✔
964
            if (cursor >= stop) {
4,163✔
965
                break;
1,365✔
966
            }
967
            cursor += 1;
1,823✔
968
            pos_x = 0;
1,823✔
969
            if (pos_y > INT_MAX - 6) {
1,823!
970
                status = (-1);
971
                goto fail;
972
            }
973
            pos_y += 6;
1,823✔
974
            continue;
1,823✔
975
        }
976

977
        if (ch < 0x20) {
1,672✔
978
            if (ch == 0x18 || ch == 0x1a) {
2,650!
979
                status = (-1);
980
                goto fail;
981
            }
982
            cursor += 1;
2,650✔
983
            if (ch == 0x1b && cursor < limit && *cursor == '\\') {
2,650!
984
                status = 0;
960✔
985
                break;
960✔
986
            }
987
            continue;
1,008✔
988
        }
989

990
        if (ch == '"') {
4!
991
            status = (-1);
12✔
992
            goto fail;
12✔
993
        }
994

995
        cursor += 1;
996
    }
997

998
    if (context->logger != NULL) {
5,256!
999
        sixel_timeline_logger_logf(context->logger,
×
1000
                          mode == SIXEL_DECODER_DIRECT_SCAN ?
1001
                          "scan" : "paint",
1002
                          "decoder",
1003
                          "finish",
1004
                          context->index,
1005
                          context->index,
1006
                          0,
1007
                          0,
1008
                          context->start_offset,
1009
                          context->end_offset,
1010
                          "worker %d direct wrote=%d status=%d",
1011
                          context->index,
1012
                          written,
1013
                          status);
1014
    }
1015

1016
    context->max_color_index = max_color_index;
4,067✔
1017
    return status;
4,067✔
1018

1019
fail:
6,117✔
1020
    if (mode == SIXEL_DECODER_DIRECT_PAINT) {
6,187!
1021
        context->runtime_error = 1;
1022
    }
1023
    if (context->logger != NULL) {
6,187!
1024
        sixel_timeline_logger_logf(context->logger,
×
1025
                          mode == SIXEL_DECODER_DIRECT_SCAN ?
1026
                          "scan" : "paint",
1027
                          "decoder",
1028
                          "abort",
1029
                          context->index,
1030
                          context->index,
1031
                          0,
1032
                          0,
1033
                          context->start_offset,
1034
                          context->end_offset,
1035
                          "worker %d direct abort status=%d",
1036
                          context->index,
1037
                          status);
1038
    }
1039
    return status;
484✔
1040
}
412✔
1041

1042
static int
1043
sixel_decoder_parallel_direct_worker(void *arg)
4,898✔
1044
{
1045
    sixel_decoder_worker_context_t *context;
2,040✔
1046
    sixel_decoder_worker_chain_t *chain;
2,040✔
1047
    int status;
2,040✔
1048

1049
    context = (sixel_decoder_worker_context_t *)arg;
4,898✔
1050
    if (context == NULL || context->chain == NULL) {
4,898!
1051
        return (-1);
1052
    }
1053

1054
    chain = context->chain;
4,899✔
1055
    status = (-1);
4,899✔
1056
    context->result = status;
4,899✔
1057
    context->runtime_error = 0;
4,899✔
1058
    context->max_color_index = (-1);
4,899✔
1059
    context->painted_outside_raster = 0;
4,899✔
1060

1061
    if (context->direct_mode_kind == SIXEL_DECODER_DIRECT_PAINT) {
4,899✔
1062
        sixel_mutex_lock(&chain->mutex);
1,268✔
1063
        while (!chain->paint_released && !chain->abort_requested) {
1,595!
1064
            sixel_cond_wait(&chain->cond, &chain->mutex);
327✔
1065
        }
1066
        if (chain->abort_requested) {
1,268!
1067
            sixel_mutex_unlock(&chain->mutex);
1068
            context->result = status;
1069
            return status;
1070
        }
1071
        sixel_mutex_unlock(&chain->mutex);
1,268✔
1072
    }
106✔
1073

1074
    status = sixel_decoder_parallel_direct_parse(context);
4,899✔
1075
    context->result = status;
4,899✔
1076
    return status;
4,899✔
1077
}
412✔
1078

1079
#if !defined(SIXEL_DECODE_PIXELS_NO_FAST4)
1080
static int
1081
sixel_decoder_parallel_copy_local_rows(sixel_local_buffer_t *buffer,
168✔
1082
                                       unsigned char *indexed,
1083
                                       int rows)
1084
{
1085
    sixel_decoder_local_chunk_t *chunk;
70✔
1086
    int copy_rows;
70✔
1087
    size_t offset;
70✔
1088
    size_t bytes;
70✔
1089

1090
    if (buffer == NULL || indexed == NULL || rows <= 0 ||
168!
1091
            buffer->pixel_size != 1) {
168!
1092
        return 0;
1093
    }
1094

1095
    chunk = buffer->head;
168✔
1096
    while (chunk != NULL) {
396✔
1097
        copy_rows = chunk->rows;
228✔
1098
        if (chunk->start_row >= rows) {
228!
1099
            copy_rows = 0;
1100
        } else if (chunk->start_row + copy_rows > rows) {
228✔
1101
            copy_rows = rows - chunk->start_row;
72✔
1102
        }
6✔
1103
        if (copy_rows > 0) {
228!
1104
            offset = (size_t)chunk->start_row * (size_t)buffer->width;
228✔
1105
            bytes = (size_t)copy_rows * (size_t)buffer->width;
228✔
1106
            memcpy(indexed + offset, chunk->data, bytes);
228✔
1107
        }
19✔
1108
        chunk = chunk->next;
228✔
1109
    }
1110

1111
    return 1;
98✔
1112
}
14✔
1113

1114
static int
1115
sixel_decoder_parallel_write_fast4_rows(
168✔
1116
    sixel_decoder_worker_context_t *context,
1117
    sixel_local_buffer_t *buffer,
1118
    int row_offset,
1119
    int local_rows,
1120
    int max_color_index)
1121
{
1122
    sixel_decoder_undither_context_t *undither;
70✔
1123
    unsigned char *indexed;
70✔
1124
    unsigned char *rgb;
70✔
1125
    unsigned char *src;
70✔
1126
    unsigned char *dst;
70✔
1127
    size_t pixels;
70✔
1128
    size_t rgb_bytes;
70✔
1129
    size_t out_offset;
70✔
1130
    int body_start;
70✔
1131
    int y;
70✔
1132
    int x;
70✔
1133
    int width;
70✔
1134
    int height;
70✔
1135
    int global_y;
70✔
1136
    int status;
70✔
1137
    int active_ncolors;
70✔
1138

1139
    if (context == NULL || buffer == NULL || context->undither == NULL) {
168!
1140
        return (-1);
1141
    }
1142

1143
    undither = context->undither;
168✔
1144
    width = context->width;
168✔
1145
    height = context->height;
168✔
1146
    body_start = context->body_local_start;
168✔
1147
    if (!undither->enabled || undither->pixels == NULL ||
168!
1148
            undither->palette == NULL || width <= 0 || height <= 0 ||
168!
1149
            local_rows <= body_start || body_start < 0) {
168!
1150
        return 0;
1151
    }
1152
    if (row_offset + body_start >= height) {
168!
1153
        return 0;
1154
    }
1155
    if (row_offset + local_rows > height) {
168✔
1156
        local_rows = height - row_offset;
24✔
1157
    }
2✔
1158
    if (local_rows <= body_start) {
168!
1159
        return 0;
1160
    }
1161

1162
    active_ncolors = undither->ncolors;
168✔
1163
    if (max_color_index >= 0 && active_ncolors < max_color_index + 1) {
168!
1164
        active_ncolors = max_color_index + 1;
1165
    }
1166
    if (active_ncolors <= 0 || active_ncolors > undither->palette_size) {
168!
1167
        return (-1);
1168
    }
1169

1170
    pixels = (size_t)local_rows * (size_t)width;
168✔
1171
    if (pixels > ((size_t)-1 / 3u)) {
168!
1172
        return (-1);
1173
    }
1174
    rgb_bytes = pixels * 3u;
168✔
1175
    indexed = (unsigned char *)calloc(pixels, 1);
168✔
1176
    rgb = (unsigned char *)calloc(rgb_bytes, 1);
168✔
1177
    if (indexed == NULL || rgb == NULL) {
168!
1178
        free(indexed);
1179
        free(rgb);
1180
        return (-1);
1181
    }
1182

1183
    if (!sixel_decoder_parallel_copy_local_rows(buffer, indexed,
182!
1184
                                                local_rows)) {
14✔
1185
        free(indexed);
1186
        free(rgb);
1187
        return (-1);
1188
    }
1189

1190
    status = sixel_dequantize_k_undither_fast4_rows(
238✔
1191
        indexed,
14✔
1192
        width,
14✔
1193
        local_rows,
14✔
1194
        undither->palette,
168✔
1195
        active_ncolors,
14✔
1196
        undither->similarity_bias,
14✔
1197
        body_start,
14✔
1198
        local_rows,
14✔
1199
        undither->allocator,
14✔
1200
        rgb);
14✔
1201
    if (SIXEL_FAILED(status)) {
168!
1202
        free(indexed);
1203
        free(rgb);
1204
        return (-1);
1205
    }
1206

1207
    for (y = body_start; y < local_rows; ++y) {
6,768✔
1208
        global_y = row_offset + y;
6,600✔
1209
        if (global_y < 0 || global_y >= height) {
6,600!
1210
            continue;
1211
        }
1212
        src = rgb + (size_t)y * (size_t)width * 3u;
6,600✔
1213
        out_offset = (size_t)global_y * (size_t)width *
7,150✔
1214
            (size_t)undither->pixel_size;
6,600✔
1215
        dst = undither->pixels + out_offset;
6,600✔
1216
        if (undither->direct_output) {
6,600✔
1217
            for (x = 0; x < width; ++x) {
3,329,208✔
1218
                dst[x * 4 + 0] = src[x * 3 + 0];
3,322,944✔
1219
                dst[x * 4 + 1] = src[x * 3 + 1];
3,322,944✔
1220
                dst[x * 4 + 2] = src[x * 3 + 2];
3,322,944✔
1221
                dst[x * 4 + 3] = 255u;
3,322,944✔
1222
            }
276,912✔
1223
        } else {
522✔
1224
            memcpy(dst, src, (size_t)width * 3u);
336✔
1225
        }
1226
    }
550✔
1227

1228
    free(indexed);
168✔
1229
    free(rgb);
168✔
1230
    return 0;
168✔
1231
}
14✔
1232
#endif
1233

1234
/*
1235
 * Worker entry for the fast sixel parser.  Each thread jumps to the next
1236
 * '-' marker from its assigned offset, then walks tokens until the first
1237
 * '-' after its scheduled end offset.
1238
 *
1239
 * Supported tokens: palette switches ('#n'), carriage return ('$'), next
1240
 * line ('-'), and raster data ('?' - '~').  Any raster attribute reset '"',
1241
 * palette redefinition '#n;type;...', or CAN/SUB control code marks the
1242
 * chunk as invalid and returns a failure status so the caller can fall back.
1243
 */
1244
static int
1245
sixel_decoder_parallel_worker(void *arg)
28,016✔
1246
{
1247
    sixel_decoder_worker_context_t *context;
80✔
1248
    sixel_decoder_worker_chain_t *chain;
80✔
1249
    sixel_local_buffer_t local_buffer;
80✔
1250
    sixel_decoder_local_chunk_t *chunk_cursor = NULL;
28,016✔
1251
    unsigned char *anchor = NULL;
28,016✔
1252
    unsigned char *scan = NULL;
28,016✔
1253
    unsigned char *cursor = NULL;
28,016✔
1254
    unsigned char *body_cursor = NULL;
28,016✔
1255
    unsigned char *decode_cursor = NULL;
28,016✔
1256
    unsigned char *stop = NULL;
28,016✔
1257
    unsigned char *limit = NULL;
28,016✔
1258
    unsigned char *start = NULL;
28,016✔
1259
    int capacity = 0;
28,016✔
1260
    int written = 0;
28,016✔
1261
    int assigned = 0;
28,016✔
1262
    int bits = 0;
28,016✔
1263
    int repeat = 1;
28,016✔
1264
    int color_index = 0;
28,016✔
1265
    int max_relative = (-1);
28,016✔
1266
    int min_relative = (-1);
28,016✔
1267
    int pos_x = 0;
28,016✔
1268
    int pos_y = 0;
28,016✔
1269
    int relative = 0;
28,016✔
1270
    int row_base = 0;
28,016✔
1271
    int r = 0;
28,016✔
1272
    int row_offset = 0;
28,016✔
1273
    int line_count = 0;
28,016✔
1274
    int i;
80✔
1275
    int fallback = 0;
28,016✔
1276
    int status = (-1);
28,016✔
1277
    sixel_timeline_logger_t *logger = NULL;
28,016✔
1278
    int width = 0;
28,016✔
1279
    int pixel_size = 0;
28,016✔
1280
    int depth = 0;
28,016✔
1281
    int height = 0;
28,016✔
1282
    int starts_after_newline = 0;
28,016✔
1283
    int max_color_index = (-1);
28,016✔
1284
    int span_max_index = (-1);
28,016✔
1285
    int effective_repeat = 0;
28,016✔
1286
    int global_y = 0;
28,016✔
1287
    int local_rows = 0;
28,016✔
1288
    int publish_start = 0;
28,016✔
1289
    int publish_end = 0;
28,016✔
1290
    unsigned char ch;
80✔
1291

1292
    context = (sixel_decoder_worker_context_t *)arg;
28,016✔
1293
    if (context == NULL) {
28,016✔
1294
        return (-1);
1295
    }
1296

1297
    context->result = status;
28,016✔
1298
    context->max_color_index = (-1);
28,016✔
1299
    context->painted_outside_raster = 0;
28,016✔
1300

1301
    chain = context->chain;
28,016✔
1302
    if (chain == NULL) {
28,016!
1303
        context->result = status;
1304
        return status;
1305
    }
1306

1307
    logger = context->logger;
28,016✔
1308
    anchor = context->anchor;
28,016✔
1309
    if (context->input == NULL || context->length <= 0) {
28,016!
1310
        sixel_decoder_parallel_finish_decode(chain, status);
55,649✔
1311
        context->result = status;
55,648✔
1312
        return status;
55,648✔
1313
    }
1314

1315
    width = context->width;
28,015✔
1316
    if (width <= 0) {
28,015!
1317
        sixel_decoder_parallel_finish_decode(chain, status);
1318
        context->result = status;
1319
        return status;
1320
    }
1321

1322
    pixel_size = context->pixel_size;
28,015✔
1323
    depth = context->depth;
28,015✔
1324
    height = context->height;
28,015✔
1325
    if (height <= 0) {
28,015!
1326
        sixel_decoder_parallel_finish_decode(chain, status);
1327
        context->result = status;
1328
        return status;
1329
    }
1330
    sixel_local_buffer_init(&local_buffer,
28,015✔
1331
                            width,
27,840✔
1332
                            pixel_size,
27,840✔
1333
                            chain->global_mask != NULL);
28,015✔
1334
    if (context->payload_len <= 0) {
28,014!
1335
        sixel_decoder_parallel_finish_decode(chain, status);
1336
        context->result = status;
1337
        return status;
1338
    }
1339

1340
    start = context->input + context->start_offset;
28,014✔
1341
    if (start < context->input ||
28,014!
1342
            start >= context->input + context->length) {
191!
1343
        sixel_decoder_parallel_finish_decode(chain, status);
55,648✔
1344
        context->result = status;
55,648✔
1345
        return status;
55,648✔
1346
    }
1347

1348
    assigned = context->end_offset - context->start_offset + 1;
28,014✔
1349
    if (assigned <= 0) {
28,014!
1350
        sixel_decoder_parallel_finish_decode(chain, status);
1351
        context->result = status;
1352
        return status;
1353
    }
1354

1355
    /*
1356
     * The controller starts workers after palette/raster attributes are
1357
     * established.  Carry the current palette index across that anchor;
1358
     * otherwise chunks that begin with raster data would silently use color 0.
1359
     */
1360
    color_index = context->initial_color_index;
28,014✔
1361
    if (color_index < 0) {
28,014!
1362
        color_index = 0;
1363
    } else if (color_index >= SIXEL_PALETTE_MAX_DECODER) {
28,014!
1364
        color_index = SIXEL_PALETTE_MAX_DECODER - 1;
1365
    }
1366

1367
    status = 0;
28,014✔
1368

1369
    if (logger != NULL) {
28,014!
1370
        /*
1371
         * Decode window for this worker. The key keeps decode spans grouped
1372
         * per-thread in the timeline output.
1373
         */
1374
        sixel_timeline_logger_logf(logger,
1375
                          "decode",
1376
                          "decoder",
1377
                          "start",
1378
                          context->index,
1379
                          context->index,
1380
                          0,
1381
                          0,
1382
                          context->start_offset,
1383
                          context->end_offset,
1384
                          "worker %d decode span [%d,%d]",
1385
                          context->index,
1386
                          context->start_offset,
1387
                          context->end_offset);
1388
    }
1389

1390
    cursor = start;
28,014✔
1391
    if (context->index > 0) {
28,014✔
1392
        /*
1393
         * A byte span can start immediately after DECGNL ('-') when the
1394
         * previous worker's inclusive end offset landed on that delimiter.
1395
         * In that case this worker is already positioned at the next sixel
1396
         * band. Searching for another '-' would skip exactly one 6-pixel band.
1397
         */
1398
        starts_after_newline = start > anchor && start[-1] == '-';
144!
1399
        if (!starts_after_newline) {
144!
1400
            cursor = (unsigned char *)memchr(start,
156✔
1401
                                             '-',
1402
                                             (size_t)(context->length -
156✔
1403
                                             context->start_offset));
144✔
1404
            if (cursor != NULL &&
144✔
1405
                    cursor + 1 < context->input + context->length) {
120!
1406
                cursor += 1;
150✔
1407
            } else {
10✔
1408
                cursor = start;
14✔
1409
            }
1410
        }
12✔
1411
    }
12✔
1412
    if (context->index > 0 && cursor == start && !starts_after_newline) {
28,014!
1413
        status = (-1);
24✔
1414
        sixel_decoder_parallel_finish_decode(chain, status);
24✔
1415
        context->result = status;
24✔
1416
        return status;
24✔
1417
    }
1418

1419
    body_cursor = cursor;
27,990✔
1420
    decode_cursor = body_cursor;
27,990✔
1421
    context->body_local_start = 0;
27,990✔
1422
    if (context->undither != NULL && context->undither->enabled) {
27,990!
1423
        decode_cursor = sixel_decoder_parallel_find_fast4_halo(
168✔
1424
            anchor,
14✔
1425
            body_cursor);
14✔
1426
        context->body_local_start =
238✔
1427
            sixel_decoder_parallel_count_newlines(
168✔
1428
                decode_cursor,
14✔
1429
                body_cursor);
14✔
1430
        if (context->body_local_start < 0) {
168!
1431
            status = (-1);
1432
            sixel_decoder_parallel_finish_decode(chain, status);
1433
            context->result = status;
1434
            return status;
1435
        }
1436
        context->body_local_start *= 6;
168✔
1437
        cursor = decode_cursor;
168✔
1438
    }
14✔
1439

1440
    if (anchor != NULL && anchor < cursor) {
27,990!
1441
        scan = anchor;
56✔
1442
        while (scan < cursor) {
4,361,543✔
1443
            if (*scan == '-') {
4,361,447✔
1444
                if (sixel_decoder_parallel_add_line_count(
1,560!
1445
                        &line_count) != 0) {
133✔
1446
                    status = (-1);
1447
                    sixel_decoder_parallel_finish_decode(chain, status);
1448
                    context->result = status;
1449
                    return status;
1450
                }
1451
                scan += 1;
1,560✔
1452
                continue;
1,560✔
1453
            }
1454

1455
            if (*scan == '#') {
4,359,887✔
1456
                int value;
113,108✔
1457
                unsigned char *p;
113,108✔
1458

1459
                value = 0;
270,456✔
1460
                p = scan + 1;
270,456✔
1461
                while (p < cursor && *p >= '0' && *p <= '9') {
828,093!
1462
                    value = value * 10 + (*p - '0');
557,637✔
1463
                    p += 1;
557,637✔
1464
                }
1465
                color_index = value;
270,456✔
1466
                if (color_index < 0) {
270,456!
1467
                    color_index = 0;
1468
                }
1469
                if (color_index >= SIXEL_PALETTE_MAX_DECODER) {
270,456!
1470
                    color_index = SIXEL_PALETTE_MAX_DECODER - 1;
1471
                }
1472
                if (p < cursor && *p == ';') {
270,456!
1473
                    scan = p;
1474
                    continue;
1475
                }
1476
                scan = p;
270,456✔
1477
                continue;
270,456✔
1478
            }
1479

1480
            scan += 1;
4,089,431✔
1481
        }
1482
    }
8✔
1483
    row_offset = line_count * 6;
27,990✔
1484

1485
    capacity = (int)((double)chain->global_capacity *
55,828✔
1486
        ((double)assigned / (double)context->payload_len));
27,990✔
1487
    capacity = (int)((double)capacity * 1.10);
27,990✔
1488
    if (capacity < 1) {
27,990!
1489
        capacity = 1;
1490
    }
1491
    if (capacity < width * 6) {
27,990✔
1492
        capacity = width * 6;
42✔
1493
    }
6✔
1494
    if (capacity % (width * 6) != 0) {
27,990✔
1495
        capacity += (width * 6) - (capacity % (width * 6));
96✔
1496
    }
8✔
1497

1498
    chunk_cursor = sixel_local_buffer_append(&local_buffer,
27,990✔
1499
                                             capacity / width,
27,838✔
1500
                                             0);
1501
    if (chunk_cursor == NULL) {
30,855!
1502
        status = (-1);
1503
        sixel_decoder_parallel_finish_decode(chain, status);
1504
        context->result = status;
1505
        return status;
1506
    }
1507
    capacity = chunk_cursor->rows * width;
30,855✔
1508

1509
    stop = context->input + context->end_offset;
30,855✔
1510
    limit = context->input + context->length;
30,855✔
1511
    while (cursor < limit) {
2,925,116✔
1512
        /*
1513
         * Hot path prefers raster tokens ('?' - '~') to reduce branching.
1514
         * Control and attribute tokens fall back to the slow path below.
1515
         */
1516
        ch = *cursor;
2,870,075✔
1517

1518
        /*
1519
         * Branch ordering follows observed frequency:
1520
         *   raster ('?' - '~') > '#' > '!' > '$' > '-'
1521
         *   >>> control (< 0x20) > '"'.
1522
         */
1523
        if (ch >= '?' && ch <= '~') {
2,870,075!
1524
            bits = ch - '?';
2,559,230✔
1525
            effective_repeat = repeat;
2,559,230✔
1526
            if (pos_x < 0 || repeat < 0 ||
2,559,230!
1527
                    pos_x > INT_MAX - repeat) {
2,540,287!
1528
                fallback = 1;
47,436✔
1529
                status = (-1);
47,436✔
1530
                break;
47,436✔
1531
            }
1532
            if (context->trust_raster_size) {
2,565,516!
1533
                if (pos_x >= width) {
×
1534
                    if (bits != 0) {
×
1535
                        context->painted_outside_raster = 1;
1536
                    }
1537
                    effective_repeat = 0;
1538
                } else if (pos_x + effective_repeat > width) {
×
1539
                    if (bits != 0) {
×
1540
                        context->painted_outside_raster = 1;
1541
                    }
1542
                    effective_repeat = width - pos_x;
1543
                }
1544
            }
1545
            for (i = 0; i < 6; ++i) {
16,024,571✔
1546
                if ((bits & (1 << i)) != 0) {
13,497,475✔
1547
                    if (pos_y > INT_MAX - i ||
2,897,556!
1548
                            row_offset > INT_MAX - (pos_y + i)) {
2,906,266!
1549
                        fallback = 1;
3,470✔
1550
                        status = (-1);
3,470✔
1551
                        break;
3,470✔
1552
                    }
1553
                    global_y = row_offset + pos_y + i;
2,907,504✔
1554
                    if (global_y >= height) {
2,907,504!
1555
                        if (context->trust_raster_size) {
×
1556
                            context->painted_outside_raster = 1;
1557
                            continue;
1558
                        }
1559
                        fallback = 1;
1560
                        status = (-1);
1561
                        break;
1562
                    }
1563
                    if (!context->trust_raster_size &&
2,907,504!
1564
                            pos_x + repeat > width) {
2,902,716!
1565
                        fallback = 1;
1566
                        status = (-1);
1567
                        break;
1568
                    }
1569
                    if (effective_repeat <= 0) {
2,907,504!
1570
                        continue;
1571
                    }
1572
                    chunk_cursor = sixel_local_buffer_reserve_row(
2,907,504✔
1573
                        &local_buffer, pos_y + i);
163,960✔
1574
                    if (chunk_cursor == NULL) {
2,879,975!
1575
                        fallback = 1;
1576
                        status = (-1);
1577
                        break;
1578
                    }
1579

1580
                    row_base = (pos_y + i - chunk_cursor->start_row) *
3,207,895✔
1581
                        width + pos_x;
327,920✔
1582
                    /*
1583
                     * Track the actual row touched by this sixel bit.  A
1584
                     * single sixel byte may paint any of the six rows in the
1585
                     * current band, and the copy stage truncates local chunks
1586
                     * from this maximum touched position.
1587
                     */
1588
                    relative = (pos_y + i) * width + pos_x;
2,879,975✔
1589

1590
                    if (context->ormode) {
2,879,975!
1591
                        span_max_index =
1592
                            sixel_decoder_parallel_store_ormode_span(
1593
                                chunk_cursor->data +
1594
                                (size_t)row_base * (size_t)pixel_size,
1595
                                depth,
1596
                                color_index,
1597
                                effective_repeat);
1598
                        if (span_max_index > max_color_index) {
×
1599
                            max_color_index = span_max_index;
1600
                        }
1601
                        if (chunk_cursor->paint_mask != NULL) {
×
1602
                            memset(chunk_cursor->paint_mask +
1603
                                   (size_t)row_base,
1604
                                   0xff,
1605
                                   (size_t)effective_repeat);
1606
                        }
1607
                    } else if (pixel_size == 1 && effective_repeat > 3) {
2,879,975!
1608
                        memset(chunk_cursor->data + (size_t)row_base,
26,232!
1609
                               color_index,
1,874✔
1610
                               effective_repeat);
1,874✔
1611
                        if (chunk_cursor->paint_mask != NULL) {
22,484!
1612
                            memset(chunk_cursor->paint_mask +
1613
                                   (size_t)row_base,
1614
                                   0xff,
1615
                                   (size_t)effective_repeat);
1616
                        }
1617
                        if (color_index > max_color_index) {
22,484✔
1618
                            max_color_index = color_index;
1,527✔
1619
                        }
48✔
1620
                    } else {
1,874✔
1621
                        for (r = 0; r < effective_repeat; ++r) {
5,716,976✔
1622
                            sixel_decoder_parallel_store_pixel(
2,862,946✔
1623
                                chunk_cursor->data +
3,022,427✔
1624
                                (size_t)(row_base + r) *
3,022,427✔
1625
                                (size_t)pixel_size,
2,862,946✔
1626
                                depth,
159,481✔
1627
                                color_index,
159,481✔
1628
                                context->palette);
159,481✔
1629
                            if (chunk_cursor->paint_mask != NULL) {
2,859,485!
1630
                                chunk_cursor->paint_mask[row_base + r] =
1631
                                    0xffu;
1632
                            }
1633
                        }
159,481✔
1634
                        if (pixel_size == 1 &&
2,854,030!
1635
                                color_index > max_color_index) {
1,431,778✔
1636
                            max_color_index = color_index;
2,531✔
1637
                        }
191✔
1638
                    }
1639
                    written += effective_repeat;
2,876,514✔
1640
                    if (min_relative < 0 || relative < min_relative) {
2,876,514✔
1641
                        min_relative = relative;
17,714✔
1642
                    }
17,406✔
1643
                    if (max_relative < relative + effective_repeat - 1) {
2,859,136✔
1644
                        max_relative = relative + effective_repeat - 1;
81,513✔
1645
                    }
11,526✔
1646
                }
146,582✔
1647
            }
706,414✔
1648

1649
            if (fallback) {
2,513,678!
1650
                break;
1651
            }
1652

1653
            cursor += 1;
2,534,841✔
1654
            pos_x += repeat;
2,534,841✔
1655
            repeat = 1;
2,534,841✔
1656
            continue;
2,534,841✔
1657
        }
1658

1659
        if (ch == '#') {
291,526✔
1660
            int value;
82,541✔
1661
            unsigned char *p;
82,541✔
1662

1663
            value = 0;
197,027✔
1664
            p = cursor + 1;
197,027✔
1665
            while (p < limit && *p >= '0' && *p <= '9') {
623,376!
1666
                value = value * 10 + (*p - '0');
426,349✔
1667
                p += 1;
426,349✔
1668
            }
1669
            if (p < limit && *p == ';') {
197,027!
1670
                fallback = 1;
1671
                status = (-1);
1672
                break;
1673
            }
1674
            color_index = value;
197,058✔
1675
            if (color_index < 0) {
197,058!
1676
                color_index = 0;
1677
            }
1678
            if (color_index >= SIXEL_PALETTE_MAX_DECODER) {
197,058!
1679
                color_index = SIXEL_PALETTE_MAX_DECODER - 1;
1680
            }
1681
            cursor = p;
197,058✔
1682
            continue;
197,058✔
1683
        }
1684

1685
        if (ch == '!') {
155,527✔
1686
            int value;
61,059✔
1687
            unsigned char *p;
61,059✔
1688

1689
            value = 0;
145,848✔
1690
            p = cursor + 1;
145,848✔
1691
            while (p < limit && *p >= '0' && *p <= '9') {
349,754!
1692
                value = value * 10 + (*p - '0');
203,906✔
1693
                p += 1;
203,906✔
1694
            }
1695
            if (value <= 0) {
145,866!
1696
                value = 1;
1697
            }
1698
            repeat = value;
145,866✔
1699
            cursor = p;
145,866✔
1700
            continue;
145,866✔
1701
        }
1702

1703
        if (ch == '$') {
16,142✔
1704
            cursor += 1;
15,509✔
1705
            pos_x = 0;
15,509✔
1706
            continue;
15,509✔
1707
        }
1708

1709
        if (ch == '-') {
1,078✔
1710
            if (cursor >= stop) {
1,188✔
1711
                break;
70✔
1712
            }
1713
            cursor += 1;
1,068✔
1714
            pos_x = 0;
1,068✔
1715
            if (pos_y > INT_MAX - 6) {
1,068!
1716
                fallback = 1;
1717
                status = (-1);
1718
                break;
1719
            }
1720
            pos_y += 6;
1,068✔
1721
            chunk_cursor = sixel_local_buffer_reserve_row(&local_buffer,
1,068✔
1722
                                                          pos_y);
89✔
1723
            if (chunk_cursor == NULL) {
1,068!
1724
                fallback = 1;
1725
                status = (-1);
1726
                break;
1727
            }
1728
            continue;
1,068✔
1729
        }
1730

1731
        if (ch < 0x20) {
12!
1732
            if (ch == 0x18 || ch == 0x1a) {
36!
1733
                fallback = 1;
1734
                status = (-1);
1735
                break;
1736
            }
1737
            cursor += 1;
36✔
1738
            if (ch == 0x1b && cursor < limit && *cursor == '\\') {
36!
1739
                status = (0);
21✔
1740
                break;
21✔
1741
            }
1742
            continue;
1743
        }
1744

1745
        if (ch == '"') {
×
1746
            fallback = 1;
1747
            status = (-1);
1748
            break;
1749
        }
1750

1751
        cursor += 1;
1752
    }
1753

1754
    local_rows = pos_y + 6;
84✔
1755
    if (max_relative >= 0 && local_rows < (max_relative / width) + 1) {
84!
1756
        local_rows = (max_relative / width) + 1;
1757
    }
1758
    if (local_rows < context->body_local_start) {
84!
1759
        local_rows = context->body_local_start;
1760
    }
1761

1762
    if (logger != NULL) {
84!
1763
        sixel_timeline_logger_logf(logger,
×
1764
                          "decode",
1765
                          "decoder",
1766
                          fallback ? "abort" : "finish",
1767
                          context->index,
1768
                          context->index,
1769
                          0,
1770
                          0,
1771
                          context->start_offset,
1772
                          context->end_offset,
1773
                          "worker %d decode wrote=%d status=%d",
1774
                          context->index,
1775
                          written,
1776
                          status);
1777
    }
1778

1779
    if (status != 0) {
168!
1780
        sixel_decoder_parallel_finish_decode(chain, status);
1781
        sixel_local_buffer_dispose(&local_buffer);
1782
        context->result = status;
1783
        return status;
1784
    }
1785

1786
#if !defined(SIXEL_DECODE_PIXELS_NO_FAST4)
1787
    if (context->undither != NULL && context->undither->enabled) {
168!
1788
        status = sixel_decoder_parallel_write_fast4_rows(
168✔
1789
            context,
14✔
1790
            &local_buffer,
1791
            row_offset,
14✔
1792
            local_rows,
14✔
1793
            max_color_index);
14✔
1794
        if (status != 0) {
168!
1795
            sixel_decoder_parallel_cancel_decode(chain);
1796
            sixel_local_buffer_dispose(&local_buffer);
1797
            context->result = status;
1798
            return status;
1799
        }
1800
        sixel_local_buffer_dispose(&local_buffer);
168✔
1801
        context->max_color_index = max_color_index;
168✔
1802
        context->result = status;
168✔
1803
        return status;
168✔
1804
    }
1805
#endif
1806

1807
    status = sixel_decoder_parallel_finish_decode(chain, status);
1808
    if (status != 0) {
×
1809
        sixel_local_buffer_dispose(&local_buffer);
1810
        status = (-1);
1811
        context->result = status;
1812
        return status;
1813
    }
1814

1815
    if (max_relative >= 0) {
×
1816
        publish_start = (row_offset * width + min_relative) * pixel_size;
1817
        publish_end = (row_offset * width + max_relative + 1) * pixel_size;
1818
    }
1819
    if (logger != NULL) {
×
1820
        sixel_timeline_logger_logf(logger,
1821
                          "copy",
1822
                          "decoder",
1823
                          "start",
1824
                          context->index,
1825
                          context->index,
1826
                          0,
1827
                          0,
1828
                          publish_start,
1829
                          publish_end,
1830
                          "worker %d publish rows=%d..%d",
1831
                          context->index,
1832
                          row_offset + (min_relative / width),
1833
                          row_offset + (max_relative / width));
1834
    }
1835

1836
    if (max_relative >= 0) {
×
1837
        chunk_cursor = local_buffer.head;
1838
        while (chunk_cursor != NULL) {
×
1839
            int rows;
1840
            size_t chunk_bytes;
1841
            size_t chunk_offset;
1842

1843
            rows = chunk_cursor->rows;
1844
            if (chunk_cursor->start_row + rows >
1845
                    (max_relative / width) + 1) {
×
1846
                rows = (max_relative / width) + 1 -
1847
                    chunk_cursor->start_row;
1848
            }
1849
            if (rows < 0) {
×
1850
                rows = 0;
1851
            }
1852
            if (rows > 0) {
×
1853
                chunk_bytes = (size_t)(rows * width) *
1854
                    (size_t)chain->pixel_size;
1855
                chunk_offset = (size_t)((row_offset +
1856
                    chunk_cursor->start_row) * width) *
1857
                    (size_t)chain->pixel_size;
1858
                memcpy(chain->global_buffer + chunk_offset,
1859
                       chunk_cursor->data,
×
1860
                       chunk_bytes);
1861
                if (chain->global_mask != NULL &&
×
1862
                        chunk_cursor->paint_mask != NULL) {
×
1863
                    chunk_bytes = (size_t)(rows * width);
1864
                    chunk_offset = (size_t)(row_offset +
1865
                        chunk_cursor->start_row) * (size_t)width;
1866
                    memcpy(chain->global_mask + chunk_offset,
1867
                           chunk_cursor->paint_mask,
1868
                           chunk_bytes);
1869
                }
1870
            }
1871
            chunk_cursor = chunk_cursor->next;
1872
        }
1873
    }
1874

1875
    if (logger != NULL) {
×
1876
        sixel_timeline_logger_logf(logger,
1877
                          "copy",
1878
                          "decoder",
1879
                          "finish",
1880
                          context->index,
1881
                          context->index,
1882
                          0,
1883
                          0,
1884
                          publish_start,
1885
                          publish_end,
1886
                          "worker %d publish done",
1887
                          context->index);
1888
    }
1889

1890
    sixel_local_buffer_dispose(&local_buffer);
1891

1892
    context->max_color_index = max_color_index;
1893
    context->result = status;
1894
    return status;
1895
}
16✔
1896
#endif
1897

1898
static int
1899
sixel_decoder_threads_token_is_auto(char const *text)
1,226✔
1900
{
1901
    if (text == NULL) {
1,226✔
1902
        return 0;
1903
    }
1904
    if ((text[0] == 'a' || text[0] == 'A') &&
1,226!
1905
            (text[1] == 'u' || text[1] == 'U') &&
830!
1906
            (text[2] == 't' || text[2] == 'T') &&
830!
1907
            (text[3] == 'o' || text[3] == 'O') &&
830!
1908
            text[4] == '\0') {
830!
1909
        return 1;
830✔
1910
    }
1911
    return 0;
230✔
1912
}
102✔
1913

1914
static int
1915
sixel_decoder_threads_normalize(int requested)
2,181✔
1916
{
1917
    int normalized;
817✔
1918

1919
#if SIXEL_ENABLE_THREADS
1920
    int hw_threads;
817✔
1921

1922
    if (requested <= 0) {
2,177✔
1923
        hw_threads = sixel_get_hw_threads();
484✔
1924
        if (hw_threads < 1) {
484!
1925
            hw_threads = 1;
1926
        }
1927
        normalized = hw_threads;
484✔
1928
    } else {
70✔
1929
        normalized = requested;
876✔
1930
    }
1931
#else
1932
    (void)requested;
1933
    normalized = 1;
4✔
1934
#endif
1935
    if (normalized < 1) {
1,364!
1936
        normalized = 1;
1937
    }
1938
    return normalized;
1,896✔
1939
}
1940

1941
static int
1942
sixel_decoder_threads_parse_value(char const *text, int *value)
1,226✔
1943
{
1944
    long parsed;
512✔
1945
    char *endptr;
512✔
1946
    int normalized;
512✔
1947

1948
    if (text == NULL || value == NULL) {
1,226!
1949
        return 0;
1950
    }
1951
    if (sixel_decoder_threads_token_is_auto(text)) {
1,226✔
1952
        normalized = sixel_decoder_threads_normalize(0);
830✔
1953
        *value = normalized;
830✔
1954
        return 1;
830✔
1955
    }
1956
    errno = 0;
396✔
1957
    parsed = strtol(text, &endptr, 10);
396✔
1958
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
396!
1959
        return 0;
10✔
1960
    }
1961
    if (parsed < 1) {
380!
1962
        normalized = sixel_decoder_threads_normalize(1);
1963
    } else if (parsed > INT_MAX) {
190!
1964
        normalized = sixel_decoder_threads_normalize(INT_MAX);
1965
    } else {
1966
        normalized = sixel_decoder_threads_normalize((int)parsed);
285✔
1967
    }
1968
    *value = normalized;
380✔
1969
    return 1;
380✔
1970
}
102✔
1971

1972
static int
1973
sixel_decoder_threads_resolve_env(void)
12,635✔
1974
{
1975
    char const *text;
5,278✔
1976
    int parsed;
5,278✔
1977

1978
    text = sixel_compat_getenv("SIXEL_THREADS");
12,635✔
1979
    if (text == NULL || text[0] == '\0') {
12,635✔
1980
        return 1;
6,775✔
1981
    }
1982
    if (sixel_decoder_threads_parse_value(text, &parsed)) {
998✔
1983
        return sixel_decoder_threads_normalize(parsed);
998!
1984
    }
1985

1986
    return 1;
1987
}
1,064✔
1988

1989
SIXEL_INTERNAL_API SIXELSTATUS
1990
sixel_decoder_parallel_override_threads(char const *text)
228✔
1991
{
1992
    SIXELSTATUS status;
96✔
1993
    int parsed;
96✔
1994

1995
    status = SIXEL_BAD_ARGUMENT;
228✔
1996
    if (text == NULL || text[0] == '\0') {
228!
UNCOV
1997
        sixel_helper_set_additional_message(
×
1998
            "decoder: missing thread count after -=/--threads.");
UNCOV
1999
        goto end;
×
2000
    }
2001
    if (!sixel_decoder_threads_parse_value(text, &parsed)) {
228✔
2002
        sixel_helper_set_additional_message(
16✔
2003
            "decoder: threads must be a positive integer or 'auto'.");
2004
        goto end;
16✔
2005
    }
2006
    sixel_decoder_threads_lock();
212✔
2007
    g_decoder_threads.override_active = 1;
212✔
2008
    g_decoder_threads.override_threads = parsed;
212✔
2009
    sixel_decoder_threads_unlock();
212✔
2010
    status = SIXEL_OK;
212✔
2011
end:
210✔
2012
    return status;
228✔
2013
}
2014

2015
SIXEL_INTERNAL_API int
2016
sixel_decoder_parallel_resolve_threads(void)
12,767✔
2017
{
2018
    int threads;
5,333✔
2019

2020
    threads = 1;
12,767✔
2021
    sixel_decoder_threads_lock();
12,767✔
2022
    if (g_decoder_threads.override_active) {
12,767!
2023
        threads = sixel_decoder_threads_normalize(
132!
2024
            g_decoder_threads.override_threads);
11✔
2025
    } else {
11✔
2026
        threads = sixel_decoder_threads_resolve_env();
12,635✔
2027
    }
2028
    sixel_decoder_threads_unlock();
12,767✔
2029
    if (threads < 1) {
12,767!
2030
        threads = 1;
2031
    }
2032
    return threads;
12,767✔
2033
}
2034

2035
#if SIXEL_ENABLE_THREADS
2036
static SIXELSTATUS
2037
sixel_decoder_parallel_prepare_undither(
986✔
2038
    sixel_decoder_undither_context_t *undither,
2039
    image_buffer_t const *image,
2040
    int const *source_palette,
2041
    int palette_limit)
2042
{
2043
#if defined(SIXEL_DECODE_PIXELS_NO_FAST4)
2044
    if (undither == NULL || !undither->enabled) {
2045
        return SIXEL_OK;
2046
    }
2047
    (void)image;
2048
    (void)source_palette;
2049
    (void)palette_limit;
2050
    /*
2051
     * Embedded decoder cores may link this file without decoder.c.  Report
2052
     * that fused fast4 is unavailable so the caller uses the serial path.
2053
     */
2054
    return SIXEL_FALSE;
2055
#else
2056
    unsigned char *dst;
411✔
2057
    size_t pixels;
411✔
2058
    size_t bytes;
411✔
2059
    size_t pixel_index;
411✔
2060
    int color;
411✔
2061
    int n;
411✔
2062
    int active_ncolors;
411✔
2063
    int palette_size;
411✔
2064

2065
    if (undither == NULL || !undither->enabled) {
986!
2066
        return SIXEL_OK;
547✔
2067
    }
2068
    if (image == NULL || source_palette == NULL ||
48!
2069
            image->width <= 0 || image->height <= 0 ||
48!
2070
            undither->allocator == NULL) {
48!
2071
        return SIXEL_BAD_ARGUMENT;
2072
    }
2073
    if (image->depth != 1U) {
48!
2074
        return SIXEL_FALSE;
2075
    }
2076
    if ((size_t)image->width > ((size_t)-1 / (size_t)image->height)) {
48!
2077
        return SIXEL_BAD_ALLOCATION;
2078
    }
2079

2080
    active_ncolors = palette_limit;
48✔
2081
    if (active_ncolors <= 0 ||
48!
2082
            active_ncolors > SIXEL_PALETTE_MAX_DECODER) {
4✔
2083
        active_ncolors = image->ncolors;
2084
    }
2085
    if (active_ncolors <= 0) {
28!
2086
        active_ncolors = 1;
2087
    } else if (active_ncolors > SIXEL_PALETTE_MAX_DECODER) {
48!
2088
        active_ncolors = SIXEL_PALETTE_MAX_DECODER;
2089
    }
2090
    /*
2091
     * image_buffer_init() starts indexed decodes at two colors, but SIXEL
2092
     * color selections may refer to the 256-color default palette without
2093
     * redefining entries.  Keep every fused worker on the same practical
2094
     * active palette floor so thread splits do not change similarity tests.
2095
     */
2096
    if (active_ncolors < 256) {
48!
2097
        active_ncolors = 256;
48✔
2098
    }
4✔
2099

2100
    palette_size = SIXEL_PALETTE_MAX_DECODER;
48✔
2101
    undither->ncolors = active_ncolors;
48✔
2102
    undither->palette_size = palette_size;
48✔
2103
    undither->pixel_size = undither->direct_output ? 4 : 3;
48✔
2104

2105
    if ((size_t)palette_size > ((size_t)-1 / 3u)) {
48!
2106
        return SIXEL_BAD_ALLOCATION;
2107
    }
2108
    undither->palette = (unsigned char *)calloc(
48✔
2109
        (size_t)palette_size * 3u,
28✔
2110
        1);
2111
    if (undither->palette == NULL) {
48!
2112
        return SIXEL_BAD_ALLOCATION;
2113
    }
2114

2115
    for (n = 0; n < palette_size; ++n) {
3,145,776✔
2116
        color = source_palette[n];
3,145,728✔
2117
        undither->palette[n * 3 + 0] =
3,145,728✔
2118
            (unsigned char)((color >> 16) & 0xff);
3,145,728✔
2119
        undither->palette[n * 3 + 1] =
3,145,728✔
2120
            (unsigned char)((color >> 8) & 0xff);
3,145,728✔
2121
        undither->palette[n * 3 + 2] =
3,145,728✔
2122
            (unsigned char)(color & 0xff);
3,145,728✔
2123
    }
262,144✔
2124

2125
    pixels = (size_t)image->width * (size_t)image->height;
48✔
2126
    if (pixels > ((size_t)-1 / (size_t)undither->pixel_size)) {
48!
2127
        free(undither->palette);
2128
        undither->palette = NULL;
2129
        return SIXEL_BAD_ALLOCATION;
2130
    }
2131
    bytes = pixels * (size_t)undither->pixel_size;
48✔
2132
    undither->pixels = (unsigned char *)sixel_allocator_malloc(
48✔
2133
        undither->allocator,
4✔
2134
        bytes);
4✔
2135
    if (undither->pixels == NULL) {
48!
2136
        free(undither->palette);
2137
        undither->palette = NULL;
2138
        return SIXEL_BAD_ALLOCATION;
2139
    }
2140

2141
    dst = undither->pixels;
28✔
2142
    for (pixel_index = 0u; pixel_index < pixels; ++pixel_index) {
3,354,240✔
2143
        dst[0] = undither->palette[0];
3,354,192✔
2144
        dst[1] = undither->palette[1];
3,354,192✔
2145
        dst[2] = undither->palette[2];
3,354,192✔
2146
        if (undither->direct_output) {
3,354,192✔
2147
            dst[3] = 255u;
3,322,944✔
2148
        }
276,912✔
2149
        dst += undither->pixel_size;
3,354,192✔
2150
    }
279,516✔
2151

2152
    return SIXEL_OK;
28✔
2153
#endif
2154
}
83✔
2155
#endif
2156

2157
SIXEL_INTERNAL_API SIXELSTATUS
2158
sixel_decoder_parallel_request_start(int direct_mode,
12,751✔
2159
                                     int ormode,
2160
                                     unsigned char *input,
2161
                                     int length,
2162
                                     unsigned char *anchor,
2163
                                     image_buffer_t *image,
2164
                                     int initial_color_index,
2165
                                     int const *palette,
2166
                                     sixel_timeline_logger_t *logger,
2167
                                     unsigned int decode_flags,
2168
                                     int *painted_outside_raster,
2169
                                     sixel_decoder_undither_context_t
2170
                                         *undither)
2171
{
2172
#if SIXEL_ENABLE_THREADS
2173
    SIXELSTATUS status;
5,326✔
2174
    SIXELSTATUS prepare_status;
5,326✔
2175
    int threads;
5,326✔
2176
    int payload_start;
5,326✔
2177
    int payload_len;
5,326✔
2178
    sixel_thread_t *workers;
5,326✔
2179
    sixel_decoder_worker_context_t *contexts;
5,326✔
2180
    sixel_decoder_worker_chain_t chain;
5,326✔
2181
    int global_capacity;
5,326✔
2182
    int *spans;
5,326✔
2183
    int i;
5,326✔
2184
    int offset;
5,326✔
2185
    int created;
5,326✔
2186
    int parallel_failed;
5,326✔
2187
    int runtime_error;
5,326✔
2188
    int sync_ready;
5,326✔
2189
    int pixel_size;
5,326✔
2190
    int palette_limit;
5,326✔
2191
    int max_color_index;
5,326✔
2192
    int trust_raster_size;
5,326✔
2193
    int undither_prepared;
5,326✔
2194
    int paint_released;
5,326✔
2195

2196
    status = SIXEL_RUNTIME_ERROR;
12,751✔
2197
    prepare_status = SIXEL_OK;
12,751✔
2198
    workers = NULL;
12,751✔
2199
    contexts = NULL;
12,751✔
2200
    spans = NULL;
12,751✔
2201
    threads = 0;
12,751✔
2202
    payload_start = 0;
12,751✔
2203
    payload_len = 0;
12,751✔
2204
    global_capacity = 0;
12,751✔
2205
    offset = 0;
12,751✔
2206
    created = 0;
12,751✔
2207
    parallel_failed = 0;
12,751✔
2208
    runtime_error = 0;
12,751✔
2209
    sync_ready = 0;
12,751✔
2210
    pixel_size = 4;
12,751✔
2211
    palette_limit = 0;
12,751✔
2212
    max_color_index = (-1);
12,751✔
2213
    undither_prepared = 0;
12,751✔
2214
    paint_released = 0;
12,751✔
2215
    trust_raster_size = (decode_flags &
13,825✔
2216
        SIXEL_DECODE_PIXELS_OPTION_TRUST_RASTER_SIZE) != 0U;
7,425✔
2217
    if (painted_outside_raster != NULL) {
12,751✔
2218
        *painted_outside_raster = 0;
12,703✔
2219
    }
1,070✔
2220
    memset(&chain, 0, sizeof(chain));
12,751!
2221

2222
    if (input == NULL || anchor == NULL || length <= 0 || image == NULL) {
12,751!
2223
        runtime_error = 1;
2224
        goto cleanup;
2225
    }
2226

2227
    pixel_size = sixel_decoder_parallel_pixel_size(image->depth);
12,751✔
2228
    if (ormode && image->depth == 4U) {
12,751✔
2229
        if (image->ormode_indexes == NULL) {
24!
2230
            runtime_error = 1;
2231
            goto cleanup;
2232
        }
2233
        pixel_size = (int)sizeof(*image->ormode_indexes);
14✔
2234
    }
2✔
2235

2236
    payload_start = (int)(anchor - input);
12,751✔
2237
    if (payload_start < 0 || payload_start >= length) {
12,751!
2238
        runtime_error = 1;
2239
        goto cleanup;
2240
    }
2241

2242
    payload_len = length - payload_start;
12,751✔
2243
    if (payload_len <= 0) {
12,751!
2244
        runtime_error = 1;
2245
        goto cleanup;
2246
    }
2247

2248
    palette_limit = image->ncolors;
12,751✔
2249

2250
    threads = sixel_decoder_parallel_resolve_threads();
12,751✔
2251
    if (threads < 1) {
12,751!
2252
        threads = 1;
2253
    }
2254
    if (threads > payload_len) {
12,751!
2255
        threads = payload_len;
2256
    }
2257
    if (threads < 2) {
12,751✔
2258
        status = SIXEL_FALSE;
11,765✔
2259
        goto cleanup;
11,765✔
2260
    }
2261

2262
    prepare_status = sixel_decoder_parallel_prepare_undither(
986✔
2263
        undither,
83✔
2264
        image,
83✔
2265
        palette,
83✔
2266
        palette_limit);
83✔
2267
    if (prepare_status == SIXEL_FALSE) {
986!
2268
        status = SIXEL_FALSE;
2269
        goto cleanup;
2270
    }
2271
    if (SIXEL_FAILED(prepare_status)) {
986!
2272
        runtime_error = 1;
2273
        goto cleanup;
2274
    }
2275
    if (undither != NULL && undither->enabled && undither->pixels != NULL) {
986!
2276
        undither_prepared = 1;
439✔
2277
    }
4✔
2278

2279
    workers = (sixel_thread_t *)calloc((size_t)threads,
986✔
2280
                                       sizeof(sixel_thread_t));
2281
    contexts = (sixel_decoder_worker_context_t *)calloc(
986✔
2282
        (size_t)threads, sizeof(sixel_decoder_worker_context_t));
83✔
2283
    spans = (int *)calloc((size_t)threads, sizeof(int));
986✔
2284
    if (workers == NULL || contexts == NULL || spans == NULL) {
986!
2285
        runtime_error = 1;
2286
        goto cleanup;
2287
    }
2288

2289
    global_capacity = image->width * image->height;
986✔
2290
    if (global_capacity <= 0) {
986!
2291
        runtime_error = 1;
2292
        goto cleanup;
2293
    }
2294
    if (ormode && image->depth == 4U) {
986!
2295
        chain.global_buffer = (unsigned char *)image->ormode_indexes;
24✔
2296
    } else {
2✔
2297
        chain.global_buffer = (unsigned char *)image->pixels.p;
962✔
2298
    }
2299
    if (chain.global_buffer == NULL) {
986!
2300
        runtime_error = 1;
2301
        goto cleanup;
2302
    }
2303
    chain.global_mask = image->paint_mask;
986✔
2304
    chain.pixel_size = pixel_size;
986✔
2305
    chain.thread_count = threads;
986✔
2306
    chain.global_capacity = global_capacity;
986✔
2307
    sixel_mutex_init(&chain.mutex);
986✔
2308
    sixel_cond_init(&chain.cond);
986✔
2309
    sync_ready = 1;
986✔
2310

2311
    sixel_decoder_parallel_fill_spans(payload_len, threads, spans);
986✔
2312

2313
    if (logger != NULL) {
986!
2314
        /*
2315
         * Record when the controller hands control to worker threads so the
2316
         * timeline can show the gap between parser startup and worker
2317
         * creation.
2318
         */
2319
        sixel_timeline_logger_logf(logger,
2320
                          "decoder",
2321
                          "controller",
2322
                          "launch",
2323
                          0,
2324
                          0,
2325
                          0,
2326
                          length,
2327
                          payload_start,
2328
                          length,
2329
                          "spawn %d workers payload=%d",
2330
                          threads,
2331
                          payload_len);
2332
    }
2333

2334
    offset = payload_start;
824✔
2335
    for (i = 0; i < threads; ++i) {
4,810✔
2336
        contexts[i].chain = &chain;
3,824✔
2337
        contexts[i].input = input;
3,824✔
2338
        contexts[i].anchor = anchor;
3,824✔
2339
        contexts[i].length = length;
3,824✔
2340
        contexts[i].payload_len = payload_len;
3,824✔
2341
        contexts[i].start_offset = offset;
3,824✔
2342
        offset += spans[i];
3,824✔
2343
        if (offset > length) {
3,824!
2344
            offset = length;
2345
        }
2346
        if (i == threads - 1) {
3,824✔
2347
            contexts[i].end_offset = length - 1;
986✔
2348
        } else {
83✔
2349
            contexts[i].end_offset = offset - 1;
2,838✔
2350
            if (contexts[i].end_offset < contexts[i].start_offset) {
2,838!
2351
                contexts[i].end_offset = contexts[i].start_offset;
2352
            }
2353
        }
2354
        contexts[i].index = i;
3,824✔
2355
        contexts[i].direct_mode = direct_mode;
3,824✔
2356
        contexts[i].ormode = ormode;
3,824✔
2357
        contexts[i].initial_color_index = initial_color_index;
3,824✔
2358
        contexts[i].palette = palette;
3,824✔
2359
        contexts[i].palette_limit = palette_limit;
3,824✔
2360
        contexts[i].width = image->width;
3,824✔
2361
        contexts[i].height = image->height;
3,824✔
2362
        contexts[i].pixel_size = pixel_size;
3,824✔
2363
        contexts[i].depth = image->depth;
3,824✔
2364
        contexts[i].logger = logger;
3,824✔
2365
        contexts[i].trust_raster_size = trust_raster_size;
3,824✔
2366
        contexts[i].result = (-1);
3,824✔
2367
        contexts[i].runtime_error = 0;
3,824✔
2368
        contexts[i].direct_mode_kind = SIXEL_DECODER_DIRECT_SCAN;
3,824✔
2369
        contexts[i].undither = undither_prepared ? undither : NULL;
5,338✔
2370
    }
322✔
2371

2372
    if (!undither_prepared) {
986✔
2373
        created = 0;
547✔
2374
        for (i = 0; i < threads; ++i) {
4,570✔
2375
            contexts[i].result = (-1);
3,632✔
2376
            contexts[i].runtime_error = 0;
3,632✔
2377
            contexts[i].max_color_index = (-1);
3,632✔
2378
            contexts[i].painted_outside_raster = 0;
3,632✔
2379
            contexts[i].direct_mode_kind = SIXEL_DECODER_DIRECT_SCAN;
3,632✔
2380
            status = sixel_thread_create(&workers[i],
3,938✔
2381
                                         sixel_decoder_parallel_direct_worker,
2382
                                         &contexts[i]);
2,118✔
2383
            if (SIXEL_FAILED(status)) {
3,632!
2384
                parallel_failed = 1;
2385
                break;
2386
            }
2387
            created += 1;
3,632✔
2388
        }
306✔
2389

2390
        for (i = 0; i < created; ++i) {
4,570✔
2391
            sixel_thread_join(&workers[i]);
3,632✔
2392
            if (contexts[i].result != 0) {
3,632✔
2393
                parallel_failed = 1;
831✔
2394
            }
70✔
2395
            if (contexts[i].runtime_error) {
3,632!
2396
                runtime_error = 1;
2397
            }
2398
            if (contexts[i].painted_outside_raster &&
3,632!
2399
                    painted_outside_raster != NULL) {
2400
                *painted_outside_raster = 1;
2401
            }
2402
        }
306✔
2403

2404
        if (!runtime_error && !parallel_failed && created == threads) {
938!
2405
            max_color_index = (-1);
335✔
2406
            created = 0;
335✔
2407
            paint_released = 0;
335✔
2408
            sixel_mutex_lock(&chain.mutex);
335✔
2409
            chain.abort_requested = 0;
335✔
2410
            chain.paint_released = 0;
335✔
2411
            sixel_mutex_unlock(&chain.mutex);
335✔
2412

2413
            for (i = 0; i < threads; ++i) {
1,742✔
2414
                contexts[i].result = (-1);
1,268✔
2415
                contexts[i].runtime_error = 0;
1,268✔
2416
                contexts[i].max_color_index = (-1);
1,268✔
2417
                contexts[i].painted_outside_raster = 0;
1,268✔
2418
                contexts[i].direct_mode_kind = SIXEL_DECODER_DIRECT_PAINT;
1,268✔
2419
                status = sixel_thread_create(
1,794✔
2420
                    &workers[i],
1,268✔
2421
                    sixel_decoder_parallel_direct_worker,
2422
                    &contexts[i]);
742✔
2423
                if (SIXEL_FAILED(status)) {
1,268!
2424
                    parallel_failed = 1;
2425
                    sixel_decoder_parallel_cancel_decode(&chain);
2426
                    break;
2427
                }
2428
                created += 1;
1,268✔
2429
            }
106✔
2430

2431
            if (!parallel_failed && created == threads) {
335!
2432
                sixel_mutex_lock(&chain.mutex);
335✔
2433
                chain.paint_released = 1;
335✔
2434
                paint_released = 1;
335✔
2435
                sixel_cond_broadcast(&chain.cond);
335✔
2436
                sixel_mutex_unlock(&chain.mutex);
335✔
2437
            }
28✔
2438

2439
            for (i = 0; i < created; ++i) {
1,603✔
2440
                sixel_thread_join(&workers[i]);
1,268✔
2441
                if (contexts[i].result != 0) {
1,268!
2442
                    parallel_failed = 1;
2443
                    if (paint_released) {
×
2444
                        runtime_error = 1;
2445
                    }
2446
                }
2447
                if (contexts[i].runtime_error) {
1,268!
2448
                    runtime_error = 1;
2449
                }
2450
                if (contexts[i].max_color_index > max_color_index) {
1,268✔
2451
                    max_color_index = contexts[i].max_color_index;
259✔
2452
                }
37✔
2453
                if (contexts[i].painted_outside_raster &&
1,268!
2454
                        painted_outside_raster != NULL) {
2455
                    *painted_outside_raster = 1;
2456
                }
2457
            }
106✔
2458
        }
28✔
2459

2460
        if (runtime_error) {
938!
2461
            status = SIXEL_RUNTIME_ERROR;
2462
        } else if (parallel_failed || created < threads) {
938!
2463
            status = SIXEL_FALSE;
351✔
2464
        } else {
51✔
2465
            if (ormode && max_color_index >= 0 &&
335!
2466
                    max_color_index + 1 > image->ncolors) {
12!
2467
                image->ncolors = max_color_index + 1;
12✔
2468
            }
1✔
2469
            status = SIXEL_OK;
196✔
2470
        }
2471
        goto cleanup;
938✔
2472
    }
2473

2474
    created = 0;
28✔
2475
    for (i = 0; i < threads; ++i) {
240✔
2476
        status = sixel_thread_create(&workers[i],
288✔
2477
                                     sixel_decoder_parallel_worker,
2478
                                     &contexts[i]);
192✔
2479
        if (SIXEL_FAILED(status)) {
192!
2480
            parallel_failed = 1;
2481
            sixel_decoder_parallel_cancel_decode(&chain);
2482
            break;
2483
        }
2484
        created += 1;
192✔
2485
    }
16✔
2486

2487
    for (i = 0; i < created; ++i) {
240✔
2488
        sixel_thread_join(&workers[i]);
192✔
2489
        if (contexts[i].result != 0) {
192✔
2490
            parallel_failed = 1;
24✔
2491
        }
2✔
2492
        if (contexts[i].max_color_index > max_color_index) {
192✔
2493
            max_color_index = contexts[i].max_color_index;
35✔
2494
        }
5✔
2495
        if (contexts[i].painted_outside_raster &&
192!
2496
                painted_outside_raster != NULL) {
2497
            *painted_outside_raster = 1;
2498
        }
2499
    }
16✔
2500

2501
    if (chain.abort_requested) {
48✔
2502
        parallel_failed = 1;
24✔
2503
    }
2✔
2504

2505
    if (runtime_error) {
52!
2506
        status = SIXEL_RUNTIME_ERROR;
2507
    } else if (parallel_failed || created < threads) {
48!
2508
        status = SIXEL_FALSE;
14✔
2509
    } else {
2✔
2510
        if (ormode && max_color_index >= 0 &&
24!
2511
                max_color_index + 1 > image->ncolors) {
×
2512
            image->ncolors = max_color_index + 1;
2513
        }
2514
        status = SIXEL_OK;
14✔
2515
    }
2516

2517
cleanup:
6,351✔
2518
    if (sync_ready) {
12,731✔
2519
        sixel_mutex_destroy(&chain.mutex);
986✔
2520
        sixel_cond_destroy(&chain.cond);
986✔
2521
    }
83✔
2522
    if (undither != NULL) {
12,751✔
2523
        free(undither->palette);
60✔
2524
        undither->palette = NULL;
60✔
2525
        if (status != SIXEL_OK && undither->pixels != NULL) {
60✔
2526
            sixel_allocator_free(undither->allocator, undither->pixels);
24✔
2527
            undither->pixels = NULL;
24✔
2528
        }
2✔
2529
    }
5✔
2530

2531
    free(workers);
12,751✔
2532
    free(contexts);
12,751✔
2533
    free(spans);
12,751✔
2534

2535
    return status;
12,751✔
2536
#else
2537
    (void)direct_mode;
2538
    (void)ormode;
2539
    (void)input;
2540
    (void)length;
2541
    (void)anchor;
2542
    (void)image;
2543
    (void)initial_color_index;
2544
    (void)palette;
2545
    (void)logger;
2546
    (void)decode_flags;
2547
    (void)painted_outside_raster;
2548
    (void)undither;
2549

2550
    return SIXEL_RUNTIME_ERROR;
2551
#endif
2552
}
2553

2554
/* emacs Local Variables:      */
2555
/* emacs mode: c               */
2556
/* emacs tab-width: 4          */
2557
/* emacs indent-tabs-mode: nil */
2558
/* emacs c-basic-offset: 4     */
2559
/* emacs End:                  */
2560
/* vim: set expandtab ts=4 sts=4 sw=4 : */
2561
/* 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