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

saitoha / libsixel / 29651119742

18 Jul 2026 02:41PM UTC coverage: 85.03% (-0.3%) from 85.292%
29651119742

push

github

saitoha
fix: clip size fill for transparent offset bands

79318 of 144774 branches covered (54.79%)

4 of 6 new or added lines in 1 file covered. (66.67%)

3156 existing lines in 22 files now uncovered.

141353 of 166238 relevant lines covered (85.03%)

6269019.35 hits per line

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

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

25
/*
26
 * this file is derived from "sixel" original version (2014-3-2)
27
 * http://nanno.dip.jp/softlib/man/rlogin/sixel.tar.gz
28
 *
29
 * Initial developer of this file is kmiya@culti.
30
 *
31
 * He distributes it under very permissive license which permits
32
 * using, copying, modification, redistribution, and all other
33
 * public activities without any restrictions.
34
 *
35
 * He declares this is compatible with MIT/BSD/GPL.
36
 *
37
 * Hayaki Saito (saitoha@me.com) modified this and re-licensed
38
 * it under the MIT license.
39
 *
40
 * Araki Ken added high-color encoding mode(sixel_encode_highcolor)
41
 * extension.
42
 *
43
 */
44
#if defined(HAVE_CONFIG_H)
45
#include "config.h"
46
#endif
47

48
/* STDC_HEADERS */
49
#include <stdio.h>
50
#include <stdlib.h>
51
#include <errno.h>
52

53
#if HAVE_STRING_H
54
# include <string.h>
55
#endif  /* HAVE_STRING_H */
56
#if HAVE_LIMITS_H
57
# include <limits.h>
58
#endif  /* HAVE_LIMITS_H */
59
#if HAVE_INTTYPES_H
60
# include <inttypes.h>
61
#endif  /* HAVE_INTTYPES_H */
62

63
#include <sixel.h>
64
#include "compat_stub.h"
65
#include "encoder-core-private.h"
66
#include "output-factory.h"
67
#include "dither.h"
68
#include "pixelformat.h"
69
#include "timeline-logger.h"
70
#include "threading.h"
71
#include "encoder-core-highcolor.h"
72
#include "gpu-palette.h"
73
#if SIXEL_ENABLE_THREADS
74
# include "sixel_atomic.h"
75
# include <6cells.h>
76
#endif
77

78
#define DCS_START_7BIT       "\033P"
79
#define DCS_START_7BIT_SIZE  (sizeof(DCS_START_7BIT) - 1)
80
#define DCS_START_8BIT       "\220"
81
#define DCS_START_8BIT_SIZE  (sizeof(DCS_START_8BIT) - 1)
82
#define DCS_END_7BIT         "\033\\"
83
#define DCS_END_7BIT_SIZE    (sizeof(DCS_END_7BIT) - 1)
84
#define DCS_END_8BIT         "\234"
85
#define DCS_END_8BIT_SIZE    (sizeof(DCS_END_8BIT) - 1)
86
#define DCS_7BIT(x)          DCS_START_7BIT x DCS_END_7BIT
87
#define DCS_8BIT(x)          DCS_START_8BIT x DCS_END_8BIT
88
#define SCREEN_PACKET_SIZE   256
89

90
typedef struct sixel_parallel_dither_config {
91
    int enabled;
92
    int band_height;
93
    int overlap;
94
    int dither_threads;
95
    int encode_threads;
96
    int dither_env_override;
97
    int pin_threads;
98
} sixel_parallel_dither_config_t;
99

100
typedef struct sixel_encode_work {
101
    char *map;
102
    size_t map_size;
103
    sixel_node_t **columns;
104
    size_t columns_size;
105
    unsigned char *active_colors;
106
    size_t active_colors_size;
107
    int *active_color_index;
108
    size_t active_color_index_size;
109
    int requested_threads;
110
} sixel_encode_work_t;
111

112
typedef struct sixel_band_state {
113
    int row_in_band;
114
    int fillable;
115
    /*
116
     * Size-policy fill must cover only source-backed rows.  row_in_band also
117
     * counts transparent offset padding, so using it directly would paint the
118
     * virtual top padding in a partial band.
119
     */
120
    int fill_mask;
121
    int active_color_count;
122
} sixel_band_state_t;
123

124
#if SIXEL_ENABLE_THREADS
125
/*
126
 * Parallel execution relies on dedicated buffers per band and reusable
127
 * per-worker scratch spaces.  The main thread prepares the context and
128
 * pushes one job for each six-line band:
129
 *
130
 *   +------------------------+      enqueue jobs      +------------------+
131
 *   | main thread (producer) | ---------------------> | threadpool queue |
132
 *   +------------------------+                        +------------------+
133
 *            |                                                       |
134
 *            | allocate band buffers                                 |
135
 *            v                                                       v
136
 *   +------------------------+      execute callbacks      +-----------------+
137
 *   | per-worker workspace   | <-------------------------- | worker threads  |
138
 *   +------------------------+                             +-----------------+
139
 *            |
140
 *            | build SIXEL fragments
141
 *            v
142
 *   +------------------------+  ordered combination after join  +-----------+
143
 *   | band buffer array      | -------------------------------> | final I/O |
144
 *   +------------------------+                                  +-----------+
145
 */
146
typedef struct sixel_parallel_band_buffer {
147
    unsigned char *data;
148
    size_t size;
149
    size_t used;
150
    SIXELSTATUS status;
151
    int ready;
152
    int dispatched;
153
} sixel_parallel_band_buffer_t;
154

155
struct sixel_parallel_context;
156
typedef struct sixel_parallel_worker_state
157
    sixel_parallel_worker_state_t;
158

159
typedef struct sixel_parallel_context {
160
    sixel_index_t *pixels;
161
    int width;
162
    int height;
163
    int encoded_width;
164
    int encoded_height;
165
    int offset_left;
166
    int offset_top;
167
    int ncolors;
168
    int keycolor;
169
    unsigned char *palstate;
170
    int encode_policy;
171
    sixel_allocator_t *allocator;
172
    sixel_output_t *output;
173
    int thread_count;
174
    int band_count;
175
    sixel_parallel_band_buffer_t *bands;
176
    int *band_source_rows_total;
177
    int *band_source_rows_ready;
178
    sixel_parallel_worker_state_t **workers;
179
    int worker_capacity;
180
    int worker_registered;
181
    sixel_thread_pool_t *pool;
182
    sixel_timeline_logger_t *logger;
183
    sixel_mutex_t mutex;
184
    int mutex_ready;
185
    sixel_cond_t cond_band_ready;
186
    int cond_ready;
187
    sixel_thread_t writer_thread;
188
    int writer_started;
189
    int next_band_to_flush;
190
    int writer_should_stop;
191
    SIXELSTATUS writer_error;
192
    int queue_capacity;
193
    int pin_threads;
194
} sixel_parallel_context_t;
195

196
typedef struct sixel_parallel_row_notifier {
197
    sixel_parallel_context_t *context;
198
    sixel_timeline_logger_t *logger;
199
    int band_height;
200
    int image_height;
201
} sixel_parallel_row_notifier_t;
202

203
static void sixel_parallel_writer_stop(sixel_parallel_context_t *ctx,
204
                                       int force_abort);
205
static int sixel_parallel_writer_main(void *arg);
206
#endif
207

208
#if SIXEL_ENABLE_THREADS
209
static int sixel_parallel_jobs_allowed(sixel_parallel_context_t *ctx);
210
static void sixel_parallel_context_abort_locked(sixel_parallel_context_t *ctx,
211
                                               SIXELSTATUS status);
212
#endif
213

214
#if SIXEL_ENABLE_THREADS
215
struct sixel_parallel_worker_state {
216
    int initialized;
217
    int index;
218
    SIXELSTATUS writer_error;
219
    sixel_parallel_band_buffer_t *band_buffer;
220
    sixel_parallel_context_t *context;
221
    sixel_output_t *output;
222
    sixel_encode_work_t work;
223
    sixel_band_state_t band;
224
};
225
#endif
226

227
static void sixel_encode_work_init(sixel_encode_work_t *work);
228
static SIXELSTATUS sixel_encode_work_allocate(sixel_encode_work_t *work,
229
                                              int width,
230
                                              int ncolors,
231
                                              sixel_allocator_t *allocator);
232
static void sixel_encode_work_cleanup(sixel_encode_work_t *work,
233
                                      sixel_allocator_t *allocator);
234
static void sixel_band_state_reset(sixel_band_state_t *state);
235
static void sixel_band_finish(sixel_encode_work_t *work,
236
                              sixel_band_state_t *state);
237
static void sixel_band_clear_map(sixel_encode_work_t *work);
238
static int sixel_output_has_transparent_offset(sixel_output_t const *output);
239
static int sixel_count_sixel_bands(int height);
240
static SIXELSTATUS
241
sixel_output_compute_transparent_extent(sixel_output_t const *output,
242
                                        int width,
243
                                        int height,
244
                                        int *encoded_width,
245
                                        int *encoded_height);
246
static SIXELSTATUS sixel_band_classify_row(sixel_encode_work_t *work,
247
                                           sixel_band_state_t *state,
248
                                           sixel_index_t *pixels,
249
                                           int width,
250
                                           int height,
251
                                           int map_width,
252
                                           int absolute_row,
253
                                           int offset_left,
254
                                           int offset_top,
255
                                           int ncolors,
256
                                           int keycolor,
257
                                           unsigned char *palstate,
258
                                           int encode_policy);
259
static SIXELSTATUS sixel_band_compose(sixel_encode_work_t *work,
260
                                      sixel_band_state_t *state,
261
                                      sixel_output_t *output,
262
                                      int width,
263
                                      int ncolors,
264
                                      int keycolor,
265
                                      sixel_allocator_t *allocator);
266
static SIXELSTATUS sixel_band_emit(sixel_encode_work_t *work,
267
                                   sixel_band_state_t *state,
268
                                   sixel_output_t *output,
269
                                   int ncolors,
270
                                   int keycolor,
271
                                   int last_row_index);
272
static SIXELSTATUS sixel_put_flash(sixel_output_t *const output);
273
static void sixel_advance(sixel_output_t *output, int nwrite);
274
static int sixel_encode_body_ormode_nplanes(int ncolors);
275
static SIXELSTATUS
276
sixel_encode_body_ormode_emit_palette(unsigned char const *palette,
277
                                      int ncolors,
278
                                      int keycolor,
279
                                      sixel_output_t *output);
280
static SIXELSTATUS
281
sixel_encode_body_ormode_band(sixel_index_t const *pixels,
282
                              int width,
283
                              int height,
284
                              int band_index,
285
                              int nplanes,
286
                              sixel_output_t *output);
287

288
#if SIXEL_ENABLE_THREADS
289
static void
290
sixel_timeline_logger_prepare_default(sixel_allocator_t *allocator,
27,921✔
291
                                      sixel_timeline_logger_t **logger)
292
{
293
    if (logger == NULL) {
27,921!
294
        return;
295
    }
296

297
    *logger = NULL;
27,921✔
298
    (void)sixel_timeline_logger_prepare_env(allocator, logger);
27,921✔
299
}
2,336✔
300
#endif
301

302
static void
303
sixel_parallel_dither_configure(int height,
19,262✔
304
                                int ncolors,
305
                                int pipeline_threads,
306
                                int pin_threads,
307
                                sixel_parallel_dither_config_t *config)
308
{
309
    char const *text;
8,046✔
310
    long parsed;
8,046✔
311
    char *endptr;
8,046✔
312
    int band_height;
8,046✔
313
    int overlap;
8,046✔
314
    int dither_threads;
8,046✔
315
    int encode_threads;
8,046✔
316
    int dither_env_override;
8,046✔
317

318
    if (config == NULL) {
19,262✔
UNCOV
319
        return;
×
320
    }
321

322
    config->enabled = 0;
19,262✔
323
    config->band_height = 0;
19,262✔
324
    config->overlap = 0;
19,262✔
325
    config->dither_threads = 0;
19,262✔
326
    config->encode_threads = pipeline_threads;
19,262✔
327
    config->pin_threads = (pin_threads != 0) ? 1 : 0;
19,262✔
328

329
    if (pipeline_threads <= 1 || height <= 0) {
19,262!
330
        return;
331
    }
332

333
    dither_env_override = 0;
19,262✔
334
    dither_threads = (pipeline_threads * 7 + 9) / 10;
19,262✔
335
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_THREADS_MAX");
19,262✔
336
    if (text != NULL && text[0] != '\0') {
19,262!
337
        errno = 0;
72✔
338
        parsed = strtol(text, &endptr, 10);
72✔
339
        if (endptr != text && errno != ERANGE && parsed > 0) {
72!
340
            if (parsed > INT_MAX) {
36!
341
                parsed = INT_MAX;
342
            }
343
            dither_threads = (int)parsed;
54✔
344
            dither_env_override = 1;
54✔
345
        }
6✔
346
    }
6✔
347
    if (dither_threads < 1) {
11,216!
348
        dither_threads = 1;
349
    }
350
    if (dither_threads > pipeline_threads) {
19,262!
351
        dither_threads = pipeline_threads;
352
    }
353

354
    if (!dither_env_override && pipeline_threads >= 4 && dither_threads < 2) {
19,262!
355
        /*
356
         * When the total budget is ample, keep at least two dither workers so
357
         * the banded producer can feed the encoder fast enough to pipeline.
358
         */
UNCOV
359
        dither_threads = pipeline_threads - 2;
×
360
    }
361

362
    encode_threads = pipeline_threads - dither_threads;
19,262✔
363
    if (encode_threads < 2 && pipeline_threads > 2) {
19,262✔
364
        /*
365
         * Preserve a minimal pair of encoder workers to keep the pipeline
366
         * alive while leaving the rest to dithering. Small budgets fall back
367
         * to the serial encoder path later in the caller.
368
         */
369
        encode_threads = 2;
18,926✔
370
        dither_threads = pipeline_threads - encode_threads;
18,926✔
371
    }
1,592✔
372
    if (encode_threads < 1) {
19,262✔
373
        encode_threads = 1;
264✔
374
        dither_threads = pipeline_threads - encode_threads;
264✔
375
    }
22✔
376
    if (dither_threads < 1) {
12,883!
377
        return;
378
    }
379

380
    /*
381
     * Choose the band height from the environment when present. Otherwise
382
     * split the image across the initial dither workers so each thread starts
383
     * with a single band. The result is rounded to a six-line multiple to
384
     * stay aligned with the encoder's natural cadence.
385
     */
386
    band_height = 0;
19,262✔
387
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_WIDTH");
19,262✔
388
    if (text != NULL && text[0] != '\0') {
19,262!
389
        errno = 0;
72✔
390
        parsed = strtol(text, &endptr, 10);
72✔
391
        if (endptr != text && errno != ERANGE && parsed > 0) {
72!
392
            if (parsed > INT_MAX) {
36!
393
                parsed = INT_MAX;
394
            }
395
            band_height = (int)parsed;
54✔
396
        }
6✔
397
    }
6✔
398
    if (band_height <= 0) {
11,228✔
399
        band_height = (height + dither_threads - 1) / dither_threads;
19,190✔
400
    }
1,614✔
401
    if (band_height < 6) {
19,262✔
402
        band_height = 6;
1,624✔
403
    }
232✔
404
    if ((band_height % 6) != 0) {
18,106✔
405
        band_height = ((band_height + 5) / 6) * 6;
13,691✔
406
    }
1,155✔
407

408
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_OVERWRAP");
19,262✔
409
    /*
410
     * Default overlap favors quality for small palettes and speed when
411
     * colors are plentiful. The environment can override this policy.
412
     */
413
    if (ncolors <= 32) {
19,262✔
414
        overlap = 6;
6,430✔
415
    } else {
922✔
416
        overlap = 0;
8,238✔
417
    }
418
    if (text != NULL && text[0] != '\0') {
19,262!
419
        errno = 0;
72✔
420
        parsed = strtol(text, &endptr, 10);
72✔
421
        if (endptr != text && errno != ERANGE && parsed >= 0) {
72!
422
            if (parsed > INT_MAX) {
36!
423
                parsed = INT_MAX;
424
            }
425
            overlap = (int)parsed;
54✔
426
        }
6✔
427
    }
6✔
428
    if (overlap < 0) {
11,216!
429
        overlap = 0;
430
    }
431
    if (overlap > band_height / 2) {
19,262✔
432
        overlap = band_height / 2;
2,919✔
433
    }
417✔
434

435
    config->enabled = 1;
19,262✔
436
    config->band_height = band_height;
19,262✔
437
    config->overlap = overlap;
19,262✔
438
    config->dither_threads = dither_threads;
19,262✔
439
    config->encode_threads = encode_threads;
19,262✔
440
}
1,620✔
441

442
static int
443
sixel_output_has_transparent_offset(sixel_output_t const *output)
101,614✔
444
{
445
    if (output == NULL) {
101,614!
446
        return 0;
447
    }
448

449
    return output->transparent_offset_left != 0 ||
184,116✔
450
           output->transparent_offset_top != 0;
88,888!
451
}
6,398✔
452

453
static int
454
sixel_count_sixel_bands(int height)
81,574✔
455
{
456
    int bands;
34,671✔
457

458
    if (height <= 0) {
81,574!
459
        return 0;
460
    }
461

462
    bands = height / 6;
81,574✔
463
    if ((height % 6) != 0) {
81,574✔
464
        bands += 1;
60,889✔
465
    }
5,076✔
466

467
    return bands;
46,903✔
468
}
6,199✔
469

470
static SIXELSTATUS
471
sixel_output_compute_transparent_extent(sixel_output_t const *output,
93,460✔
472
                                        int width,
473
                                        int height,
474
                                        int *encoded_width,
475
                                        int *encoded_height)
476
{
477
    int left;
40,420✔
478
    int top;
40,420✔
479

480
    if (output == NULL || encoded_width == NULL ||
93,460!
481
        encoded_height == NULL || width < 1 || height < 1) {
93,460!
482
        return SIXEL_BAD_ARGUMENT;
483
    }
484

485
    left = output->transparent_offset_left;
93,460✔
486
    top = output->transparent_offset_top;
93,460✔
487
    if (left < 0 || top < 0 ||
93,460!
488
        left > INT_MAX - width || top > INT_MAX - height) {
93,460!
UNCOV
489
        sixel_helper_set_additional_message(
×
490
            "transparent-offset makes encoded size overflow.");
UNCOV
491
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
492
    }
493

494
    *encoded_width = width + left;
93,460✔
495
    *encoded_height = height + top;
93,460✔
496
    return SIXEL_OK;
93,460✔
497
}
6,392✔
498

499
#if SIXEL_ENABLE_THREADS
500
static int sixel_parallel_band_writer(char *data, int size, void *priv);
501
static int sixel_parallel_worker_main(sixel_thread_pool_job_t job,
502
                                      void *userdata,
503
                                      void *workspace);
504
static SIXELSTATUS
505
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
506
                             sixel_index_t *pixels,
507
                             int width,
508
                             int height,
509
                             int ncolors,
510
                             int keycolor,
511
                             unsigned char *palstate,
512
                             sixel_output_t *output,
513
                             sixel_allocator_t *allocator,
514
                             int requested_threads,
515
                             int worker_capacity,
516
                             int queue_capacity,
517
                             int pin_threads,
518
                             sixel_timeline_logger_t *logger);
519
static SIXELSTATUS sixel_parallel_context_grow(sixel_parallel_context_t *ctx,
520
                                              int target_threads);
521
static void sixel_parallel_submit_band(sixel_parallel_context_t *ctx,
522
                                       int band_index);
523
static void
524
sixel_parallel_submit_source_empty_bands(sixel_parallel_context_t *ctx);
525
static SIXELSTATUS sixel_parallel_context_wait(sixel_parallel_context_t *ctx,
526
                                               int force_abort);
527
static void sixel_parallel_palette_row_ready(void *priv, int row_index);
528
static int
529
sixel_parallel_context_is_ormode(sixel_parallel_context_t const *ctx);
530
static SIXELSTATUS
531
sixel_parallel_worker_flush_output(sixel_parallel_worker_state_t *state);
532
static SIXELSTATUS sixel_encode_emit_palette(int bodyonly,
533
                          int ncolors,
534
                          int keycolor,
535
                          unsigned char const *palette,
536
                          float const *palette_float,
537
                          sixel_output_t *output);
538

539
static void
540
sixel_parallel_context_init(sixel_parallel_context_t *ctx)
21,569✔
541
{
542
    memset(ctx, 0, sizeof(*ctx));
21,569✔
543
    ctx->pixels = NULL;
21,569✔
544
    ctx->keycolor = (-1);
21,569✔
545
    ctx->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
21,569✔
546
    ctx->writer_error = SIXEL_OK;
21,569✔
547
}
14,331✔
548

549
static int
550
sixel_parallel_context_is_ormode(sixel_parallel_context_t const *ctx)
252,457✔
551
{
552
    if (ctx == NULL || ctx->output == NULL) {
252,222!
553
        return 0;
554
    }
555
    return ctx->output->ormode != 0 ? 1 : 0;
234,483✔
556
}
25,937✔
557

558
static void
559
sixel_parallel_worker_release_nodes(sixel_parallel_worker_state_t *state,
175,465✔
560
                                    sixel_allocator_t *allocator)
561
{
562
    sixel_node_t *np;
72,975✔
563

564
    if (state == NULL || state->output == NULL) {
175,465!
565
        return;
566
    }
567

568
    while ((np = state->output->node_free) != NULL) {
6,940,194✔
569
        state->output->node_free = np->next;
6,764,723✔
570
        sixel_allocator_free(allocator, np);
6,764,723✔
571
    }
572
    state->output->node_top = NULL;
175,471✔
573
}
14,802✔
574

575
static void
576
sixel_parallel_worker_cleanup(sixel_parallel_worker_state_t *state,
65,828✔
577
                              sixel_allocator_t *allocator)
578
{
579
    if (state == NULL) {
65,828✔
580
        return;
12,602✔
581
    }
582
    sixel_parallel_worker_release_nodes(state, allocator);
43,443✔
583
    if (state->output != NULL) {
43,443!
584
        sixel_output_unref(state->output);
43,443✔
585
        state->output = NULL;
43,442✔
586
    }
3,661✔
587
    sixel_encode_work_cleanup(&state->work, allocator);
43,442✔
588
    sixel_band_state_reset(&state->band);
43,442✔
589
    state->initialized = 0;
43,442✔
590
    state->index = 0;
43,442✔
591
    state->writer_error = SIXEL_OK;
43,442✔
592
    state->band_buffer = NULL;
43,442✔
593
    state->context = NULL;
43,442✔
594
}
5,546✔
595

596
static void
597
sixel_parallel_context_cleanup(sixel_parallel_context_t *ctx)
21,569✔
598
{
599
    int i;
9,008✔
600

601
    if (ctx->workers != NULL) {
21,569!
602
        for (i = 0; i < ctx->worker_capacity; i++) {
87,397✔
603
            sixel_parallel_worker_cleanup(ctx->workers[i], ctx->allocator);
65,828✔
604
        }
5,546✔
605
        free(ctx->workers);
21,569✔
606
        ctx->workers = NULL;
21,569✔
607
    }
1,813✔
608
    sixel_parallel_writer_stop(ctx, 1);
21,569✔
609
    if (ctx->bands != NULL) {
21,569!
610
        if (ctx->band_count < 0) {
21,569!
611
            ctx->band_count = 0;
612
        }
613
#if defined(_MSC_VER)
614
#pragma warning(push)
615
#pragma warning(disable : 6001)
616
#endif
617
        for (i = 0; i < ctx->band_count; i++) {
154,126✔
618
            free(ctx->bands[i].data);
132,557✔
619
            ctx->bands[i].data = NULL;
132,557✔
620
        }
11,185✔
621
#if defined(_MSC_VER)
622
#pragma warning(pop)
623
#endif
624
        free(ctx->bands);
21,569✔
625
        ctx->bands = NULL;
21,569✔
626
    }
1,813✔
627
    free(ctx->band_source_rows_total);
21,569✔
628
    ctx->band_source_rows_total = NULL;
21,569✔
629
    free(ctx->band_source_rows_ready);
21,569✔
630
    ctx->band_source_rows_ready = NULL;
21,569✔
631
    ctx->band_count = 0;
21,569✔
632
    if (ctx->pool != NULL) {
21,569!
633
        ctx->pool->vtbl->unref(ctx->pool);
21,569✔
634
        ctx->pool = NULL;
21,569✔
635
    }
1,813✔
636
    if (ctx->cond_ready) {
21,569!
637
        sixel_cond_destroy(&ctx->cond_band_ready);
21,569✔
638
        ctx->cond_ready = 0;
21,569✔
639
    }
1,813✔
640
    if (ctx->mutex_ready) {
21,569!
641
        sixel_mutex_destroy(&ctx->mutex);
21,569✔
642
        ctx->mutex_ready = 0;
21,569✔
643
    }
1,813✔
644
}
21,569✔
645

646
/*
647
 * Abort the pipeline when either a worker or the writer encounters an error.
648
 * The helper normalizes the `writer_error` bookkeeping so later callers see a
649
 * consistent stop request regardless of whether the mutex has been
650
 * initialized.
651
 */
652
static void
653
sixel_parallel_context_abort_locked(sixel_parallel_context_t *ctx,
654
                                    SIXELSTATUS status)
655
{
656
    if (ctx == NULL) {
×
657
        return;
658
    }
659
    if (!ctx->mutex_ready) {
×
660
        if (ctx->writer_error == SIXEL_OK) {
×
661
            ctx->writer_error = status;
662
        }
663
        ctx->writer_should_stop = 1;
664
        return;
665
    }
666

667
    sixel_mutex_lock(&ctx->mutex);
668
    if (ctx->writer_error == SIXEL_OK) {
×
669
        ctx->writer_error = status;
670
    }
671
    ctx->writer_should_stop = 1;
672
    sixel_cond_broadcast(&ctx->cond_band_ready);
673
    sixel_mutex_unlock(&ctx->mutex);
674
}
675

676
/*
677
 * Determine whether additional bands should be queued or executed.  The
678
 * producer and workers call this guard to avoid redundant work once the writer
679
 * decides to shut the pipeline down.
680
 */
681
static int
682
sixel_parallel_jobs_allowed(sixel_parallel_context_t *ctx)
132,021✔
683
{
684
    int accept;
55,239✔
685

686
    if (ctx == NULL) {
132,021✔
687
        return 0;
688
    }
689
    if (!ctx->mutex_ready) {
132,021!
690
        if (ctx->writer_should_stop || ctx->writer_error != SIXEL_OK) {
×
691
            return 0;
692
        }
693
        return 1;
694
    }
695

696
    sixel_mutex_lock(&ctx->mutex);
132,021✔
697
    accept = (!ctx->writer_should_stop && ctx->writer_error == SIXEL_OK);
132,029!
698
    sixel_mutex_unlock(&ctx->mutex);
132,029✔
699
    return accept;
132,028✔
700
}
11,141✔
701

702
static void
703
sixel_parallel_worker_reset(sixel_parallel_worker_state_t *state)
132,028✔
704
{
705
    if (state == NULL || state->output == NULL) {
132,028!
706
        return;
4✔
707
    }
708

709
    sixel_band_state_reset(&state->band);
132,024✔
710
    if (!sixel_parallel_context_is_ormode(state->context)) {
132,023✔
711
        sixel_band_clear_map(&state->work);
131,329✔
712
    }
11,082✔
713
    /* Parallel workers reset band-local buffers and output. */
714
    state->writer_error = SIXEL_OK;
132,004✔
715
    state->output->pos = 0;
132,004✔
716
    state->output->save_count = 0;
132,004✔
717
    state->output->save_pixel = 0;
132,004✔
718
    state->output->active_palette = (-1);
132,004✔
719
    state->output->node_top = NULL;
132,004✔
720
    state->output->node_free = NULL;
132,004✔
721
}
11,139✔
722

723
static SIXELSTATUS
724
sixel_parallel_worker_prepare(sixel_parallel_worker_state_t *state,
132,020✔
725
                              sixel_parallel_context_t *ctx)
726
{
727
    SIXELSTATUS status;
55,240✔
728

729
    if (state->initialized) {
132,020✔
730
        return SIXEL_OK;
51,078✔
731
    }
732

733
    sixel_encode_work_init(&state->work);
43,436✔
734
    sixel_band_state_reset(&state->band);
43,435✔
735
    state->writer_error = SIXEL_OK;
43,438✔
736
    state->band_buffer = NULL;
43,438✔
737
    state->context = ctx;
43,438✔
738

739
    if (!sixel_parallel_context_is_ormode(ctx)) {
43,438✔
740
        status = sixel_encode_work_allocate(&state->work,
46,940✔
741
                                            ctx->encoded_width,
3,649✔
742
                                            ctx->ncolors,
3,649✔
743
                                            ctx->allocator);
3,649✔
744
        if (SIXEL_FAILED(status)) {
43,299!
745
            return status;
746
        }
747
    }
3,649✔
748

749
    status = sixel_encoder_core_create_output_from_factory(&state->output,
47,107✔
750
                                              sixel_parallel_band_writer,
751
                                              state,
3,661✔
752
                                              ctx->allocator);
3,661✔
753
    if (SIXEL_FAILED(status)) {
43,439!
754
        if (!sixel_parallel_context_is_ormode(ctx)) {
×
755
            sixel_encode_work_cleanup(&state->work, ctx->allocator);
756
        }
757
        return status;
758
    }
759

760
    state->output->has_8bit_control = ctx->output->has_8bit_control;
43,439✔
761
    state->output->has_sixel_scrolling = ctx->output->has_sixel_scrolling;
43,439✔
762
    state->output->has_sdm_glitch = ctx->output->has_sdm_glitch;
43,439✔
763
    state->output->has_gri_arg_limit = ctx->output->has_gri_arg_limit;
43,439✔
764
    state->output->skip_dcs_envelope = 1;
43,439✔
765
    state->output->skip_header = 1;
43,439✔
766
    state->output->palette_type = ctx->output->palette_type;
43,439✔
767
    state->output->colorspace = ctx->output->colorspace;
43,439✔
768
    state->output->source_colorspace = ctx->output->source_colorspace;
43,439✔
769
    state->output->pixelformat = ctx->output->pixelformat;
43,439✔
770
    state->output->penetrate_multiplexer =
43,439✔
771
        ctx->output->penetrate_multiplexer;
43,439✔
772
    state->output->encode_policy = ctx->output->encode_policy;
43,439✔
773
    state->output->ormode = ctx->output->ormode;
43,439✔
774

775
    state->initialized = 1;
43,439✔
776
    state->index = (-1);
43,439✔
777

778
    if (ctx->mutex_ready) {
43,439!
779
        sixel_mutex_lock(&ctx->mutex);
43,439✔
780
    }
3,661✔
781
    if (ctx->worker_registered < ctx->worker_capacity) {
43,443!
782
        state->index = ctx->worker_registered;
43,443✔
783
        ctx->workers[state->index] = state;
43,443✔
784
        ctx->worker_registered += 1;
43,443✔
785
    }
3,661✔
786
    if (ctx->mutex_ready) {
43,443!
787
        sixel_mutex_unlock(&ctx->mutex);
43,443✔
788
    }
3,661✔
789

790
    if (state->index < 0) {
43,444!
791
        sixel_parallel_worker_cleanup(state, ctx->allocator);
792
        return SIXEL_RUNTIME_ERROR;
793
    }
794

795
    return SIXEL_OK;
25,707✔
796
}
11,140✔
797

798
static SIXELSTATUS
799
sixel_parallel_context_grow(sixel_parallel_context_t *ctx, int target_threads)
18,962✔
800
{
801
    int capped_target;
7,921✔
802
    int delta;
7,921✔
803
    int status;
7,921✔
804

805
    if (ctx == NULL || ctx->pool == NULL) {
18,962!
806
        return SIXEL_BAD_ARGUMENT;
807
    }
808

809
    capped_target = target_threads;
18,962✔
810
    if (capped_target > ctx->worker_capacity) {
18,962✔
811
        capped_target = ctx->worker_capacity;
7,510✔
812
    }
1,073✔
813
    if (ctx->band_count > 0 && capped_target > ctx->band_count) {
18,962!
814
        capped_target = ctx->band_count;
7,921✔
815
    }
816
    if (capped_target <= ctx->thread_count) {
18,962✔
817
        return SIXEL_OK;
3,080✔
818
    }
819

820
    delta = capped_target - ctx->thread_count;
13,687✔
821
    status = ctx->pool->vtbl->grow(ctx->pool, delta);
13,687✔
822
    if (SIXEL_FAILED(status)) {
13,687!
823
        return status;
824
    }
825
    ctx->thread_count += delta;
13,687✔
826

827
    if (ctx->logger != NULL) {
13,687✔
828
        sixel_timeline_logger_logf(ctx->logger,
36✔
829
                          "controller",
830
                          "encode",
831
                          "grow_workers",
832
                          -1);
833
    }
3✔
834

835
    return SIXEL_OK;
7,961✔
836
}
1,595✔
837

838
static int
839
sixel_parallel_band_writer(char *data, int size, void *priv)
131,419✔
840
{
841
    sixel_parallel_worker_state_t *state;
54,992✔
842
    sixel_parallel_band_buffer_t *band;
54,992✔
843
    size_t required;
54,992✔
844
    size_t capacity;
54,992✔
845
    size_t new_capacity;
54,992✔
846
    unsigned char *tmp;
54,992✔
847

848
    state = (sixel_parallel_worker_state_t *)priv;
131,419✔
849
    if (state == NULL || data == NULL || size <= 0) {
131,419!
850
        return size;
5✔
851
    }
852
    band = state->band_buffer;
131,414✔
853
    if (band == NULL) {
131,414!
854
        state->writer_error = SIXEL_RUNTIME_ERROR;
855
        return size;
856
    }
857
    if (state->writer_error != SIXEL_OK) {
131,414!
858
        return size;
859
    }
860

861
    required = band->used + (size_t)size;
131,414✔
862
    if (required < band->used) {
131,414!
863
        state->writer_error = SIXEL_BAD_INTEGER_OVERFLOW;
864
        return size;
865
    }
866
    capacity = band->size;
131,414✔
867
    if (required > capacity) {
131,414!
868
        if (capacity == 0) {
131,403!
869
            new_capacity = (size_t)SIXEL_OUTPUT_PACKET_SIZE;
109,690✔
870
        } else {
11,090✔
871
            new_capacity = capacity;
1✔
872
        }
873
        while (new_capacity < required) {
131,403!
874
            if (new_capacity > SIZE_MAX / 2) {
×
875
                new_capacity = required;
876
                break;
877
            }
878
            new_capacity *= 2;
879
        }
880
        tmp = (unsigned char *)realloc(band->data, new_capacity);
131,403✔
881
        if (tmp == NULL) {
131,403!
882
            state->writer_error = SIXEL_BAD_ALLOCATION;
883
            return size;
884
        }
885
        band->data = tmp;
131,403✔
886
        band->size = new_capacity;
131,403✔
887
    }
11,091✔
888

889
    memcpy(band->data + band->used, data, (size_t)size);
131,414✔
890
    band->used += (size_t)size;
131,414✔
891

892
    return size;
131,414✔
893
}
11,091✔
894

895
static SIXELSTATUS
896
sixel_parallel_worker_flush_output(sixel_parallel_worker_state_t *state)
132,011✔
897
{
898
    if (state == NULL || state->output == NULL) {
132,011!
899
        return SIXEL_BAD_ARGUMENT;
20✔
900
    }
901
    if (state->output->pos > 0) {
131,997✔
902
        state->writer_error = sixel_output_write_bytes(
251,735✔
903
            state->output,
11,089✔
904
            (char *)state->output->buffer,
131,399✔
905
            state->output->pos);
76,417✔
906
        state->output->pos = 0;
131,425✔
907
    }
11,089✔
908
    if (state->writer_error != SIXEL_OK) {
132,023!
909
        return state->writer_error;
910
    }
911
    return SIXEL_OK;
76,781✔
912
}
11,141✔
913

914
static SIXELSTATUS
915
sixel_parallel_create_pool(sixel_thread_pool_t **pool,
21,569✔
916
                           int threads,
917
                           int queue_depth,
918
                           size_t workspace_size,
919
                           sixel_thread_pool_worker_function_t worker,
920
                           void *userdata)
921
{
922
    sixel_threadpool_service_t *service;
9,008✔
923
    sixel_thread_pool_create_request_t request;
9,008✔
924
    void *service_object;
9,008✔
925
    SIXELSTATUS status;
9,008✔
926

927
    if (pool != NULL) {
21,569✔
928
        *pool = NULL;
21,569✔
929
    }
1,813✔
930
    if (pool == NULL) {
21,569!
931
        return SIXEL_BAD_ARGUMENT;
932
    }
933

934
    service = NULL;
21,569✔
935
    service_object = NULL;
21,569✔
936
    status = sixel_components_getservice("services/threadpool",
21,569✔
937
                                         &service_object);
938
    if (SIXEL_FAILED(status)) {
21,569!
939
        return status;
940
    }
941
    service = (sixel_threadpool_service_t *)service_object;
21,569✔
942
    if (service == NULL || service->vtbl == NULL ||
21,569!
943
        service->vtbl->create_pool == NULL) {
21,569!
944
        if (service != NULL && service->vtbl != NULL &&
×
945
            service->vtbl->unref != NULL) {
×
946
            service->vtbl->unref(service);
947
        }
948
        return SIXEL_BAD_ARGUMENT;
949
    }
950

951
    request.threads = threads;
21,569✔
952
    request.queue_size = queue_depth;
21,569✔
953
    request.workspace_size = workspace_size;
21,569✔
954
    request.worker = worker;
21,569✔
955
    request.userdata = userdata;
21,569✔
956
    request.workspace_cleanup = NULL;
21,569✔
957
    status = service->vtbl->create_pool(service, &request, pool);
21,569✔
958
    if (service->vtbl->unref != NULL) {
21,569!
959
        service->vtbl->unref(service);
21,569✔
960
    }
1,813✔
961

962
    return status;
12,561✔
963
}
1,813✔
964

965
static void
966
sixel_parallel_prepare_band_source_rows(sixel_parallel_context_t *ctx)
21,569✔
967
{
968
    int band_index;
9,008✔
969
    int band_start;
9,008✔
970
    int band_end;
9,008✔
971
    int source_start;
9,008✔
972
    int source_end;
9,008✔
973

974
    if (ctx == NULL || ctx->band_source_rows_total == NULL ||
21,569!
975
            ctx->band_source_rows_ready == NULL) {
21,569✔
976
        return;
977
    }
978

979
    /*
980
     * PaletteApply reports readiness in source-image coordinates.  The encoder
981
     * consumes fixed six-line bands in encoded-image coordinates, and a
982
     * transparent offset shifts those two spaces apart.  Precomputing the
983
     * source rows required by each encoded band lets the producer dispatch only
984
     * after every row that band can read has actually been written.
985
     */
986
    for (band_index = 0; band_index < ctx->band_count; band_index++) {
154,126✔
987
        band_start = band_index * 6;
132,557✔
988
        band_end = ctx->encoded_height;
132,557✔
989
        if (band_start <= ctx->encoded_height - 6) {
132,557✔
990
            band_end = band_start + 6;
115,195✔
991
        }
9,723✔
992

993
        source_start = band_start - ctx->offset_top;
132,557✔
994
        source_end = band_end - ctx->offset_top;
132,557✔
995
        if (source_start < 0) {
132,557✔
996
            source_start = 0;
21✔
997
        }
3✔
998
        if (source_start > ctx->height) {
132,557!
999
            source_start = ctx->height;
1000
        }
1001
        if (source_end < 0) {
132,557!
1002
            source_end = 0;
1003
        }
1004
        if (source_end > ctx->height) {
132,557!
1005
            source_end = ctx->height;
1006
        }
1007

1008
        ctx->band_source_rows_total[band_index] = source_end - source_start;
132,557✔
1009
        ctx->band_source_rows_ready[band_index] = 0;
132,557✔
1010
    }
11,185✔
1011
}
1,813✔
1012

1013
static void
1014
sixel_parallel_submit_source_empty_bands(sixel_parallel_context_t *ctx)
19,010✔
1015
{
1016
    int band_index;
7,941✔
1017

1018
    if (ctx == NULL || ctx->band_source_rows_total == NULL) {
19,010!
1019
        return;
1020
    }
1021

1022
    /*
1023
     * A positive top offset can create leading bands that contain only
1024
     * transparent padding.  No PaletteApply row can ever close those bands, so
1025
     * enqueue them explicitly before waiting for source-row notifications.
1026
     */
1027
    for (band_index = 0; band_index < ctx->band_count; band_index++) {
133,882✔
1028
        if (ctx->band_source_rows_total[band_index] == 0) {
114,872!
1029
            sixel_parallel_submit_band(ctx, band_index);
1030
        }
1031
    }
9,709✔
1032
}
1,599✔
1033

1034
static SIXELSTATUS
1035
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
21,569✔
1036
                             sixel_index_t *pixels,
1037
                             int width,
1038
                             int height,
1039
                             int ncolors,
1040
                             int keycolor,
1041
                             unsigned char *palstate,
1042
                             sixel_output_t *output,
1043
                             sixel_allocator_t *allocator,
1044
                             int requested_threads,
1045
                             int worker_capacity,
1046
                             int queue_capacity,
1047
                             int pin_threads,
1048
                             sixel_timeline_logger_t *logger)
1049
{
1050
    SIXELSTATUS status;
9,008✔
1051
    int nbands;
9,008✔
1052
    int encoded_width;
9,008✔
1053
    int encoded_height;
9,008✔
1054
    int threads;
9,008✔
1055
    int i;
9,008✔
1056

1057
    if (ctx == NULL || pixels == NULL || output == NULL) {
21,569!
1058
        return SIXEL_BAD_ARGUMENT;
1059
    }
1060
    status = sixel_output_compute_transparent_extent(output,
23,382✔
1061
                                                     width,
1,813✔
1062
                                                     height,
1,813✔
1063
                                                     &encoded_width,
1064
                                                     &encoded_height);
1065
    if (SIXEL_FAILED(status)) {
21,569!
1066
        return status;
1067
    }
1068

1069
    ctx->pixels = pixels;
21,569✔
1070
    ctx->width = width;
21,569✔
1071
    ctx->height = height;
21,569✔
1072
    ctx->encoded_width = encoded_width;
21,569✔
1073
    ctx->encoded_height = encoded_height;
21,569✔
1074
    ctx->offset_left = output->transparent_offset_left;
21,569✔
1075
    ctx->offset_top = output->transparent_offset_top;
21,569✔
1076
    ctx->ncolors = ncolors;
21,569✔
1077
    ctx->keycolor = keycolor;
21,569✔
1078
    ctx->palstate = palstate;
21,569✔
1079
    ctx->encode_policy = output->encode_policy;
21,569✔
1080
    ctx->allocator = allocator;
21,569✔
1081
    ctx->output = output;
21,569✔
1082
    ctx->logger = logger;
21,569✔
1083
    ctx->pin_threads = (pin_threads != 0) ? 1 : 0;
21,569✔
1084
    ctx->bands = NULL;
21,569✔
1085
    ctx->band_count = 0;
21,569✔
1086
    ctx->workers = NULL;
21,569✔
1087
    ctx->worker_capacity = 0;
21,569✔
1088
    ctx->band_source_rows_total = NULL;
21,569✔
1089
    ctx->band_source_rows_ready = NULL;
21,569✔
1090

1091
    nbands = sixel_count_sixel_bands(encoded_height);
25,138!
1092
    if (nbands <= 0) {
21,569!
1093
        return SIXEL_OK;
1094
    }
1095
    threads = requested_threads;
21,569✔
1096
    if (threads > nbands) {
21,569!
1097
        threads = nbands;
1098
    }
1099
    if (threads < 1) {
21,569!
1100
        threads = 1;
1101
    }
1102
    ctx->thread_count = threads;
21,569✔
1103
    if (worker_capacity < threads) {
21,569!
1104
        worker_capacity = threads;
1105
    }
1106
    if (worker_capacity > nbands) {
21,569!
1107
        worker_capacity = nbands;
1108
    }
1109
    ctx->worker_capacity = worker_capacity;
21,569✔
1110

1111
    if (logger != NULL) {
21,569✔
1112
        sixel_timeline_logger_logf(logger,
132✔
1113
                          "controller",
1114
                          "encode",
1115
                          "context_begin",
1116
                          -1);
1117
    }
11✔
1118

1119
    ctx->bands = (sixel_parallel_band_buffer_t *)calloc((size_t)nbands,
21,569✔
1120
                                                        sizeof(*ctx->bands));
1121
    if (ctx->bands == NULL) {
21,569!
1122
        return SIXEL_BAD_ALLOCATION;
1123
    }
1124
    for (i = 0; i < nbands; ++i) {
154,126✔
1125
        ctx->bands[i].data = NULL;
132,557✔
1126
        ctx->bands[i].size = 0;
132,557✔
1127
        ctx->bands[i].used = 0;
132,557✔
1128
        ctx->bands[i].status = SIXEL_OK;
132,557✔
1129
        ctx->bands[i].ready = 0;
132,557✔
1130
        ctx->bands[i].dispatched = 0;
132,557✔
1131
    }
11,185✔
1132
    ctx->band_count = nbands;
21,569✔
1133

1134
    ctx->band_source_rows_total = (int *)calloc((size_t)nbands,
21,569✔
1135
                                                sizeof(int));
1136
    if (ctx->band_source_rows_total == NULL) {
21,569!
1137
        return SIXEL_BAD_ALLOCATION;
1138
    }
1139
    ctx->band_source_rows_ready = (int *)calloc((size_t)nbands,
21,569✔
1140
                                                sizeof(int));
1141
    if (ctx->band_source_rows_ready == NULL) {
21,569!
1142
        return SIXEL_BAD_ALLOCATION;
1143
    }
1144
    sixel_parallel_prepare_band_source_rows(ctx);
21,569✔
1145

1146
    ctx->workers = (sixel_parallel_worker_state_t **)
21,569✔
1147
        calloc((size_t)ctx->worker_capacity, sizeof(*ctx->workers));
21,569✔
1148
    if (ctx->workers == NULL) {
21,569!
1149
        return SIXEL_BAD_ALLOCATION;
1150
    }
1151

1152
    status = sixel_mutex_init(&ctx->mutex);
21,569✔
1153
    if (status != SIXEL_OK) {
21,569!
1154
        return status;
1155
    }
1156
    ctx->mutex_ready = 1;
21,569✔
1157

1158
    status = sixel_cond_init(&ctx->cond_band_ready);
21,569✔
1159
    if (status != SIXEL_OK) {
21,569!
1160
        return status;
1161
    }
1162
    ctx->cond_ready = 1;
21,569✔
1163

1164
    ctx->queue_capacity = queue_capacity;
21,569✔
1165
    if (ctx->queue_capacity < 1) {
21,569!
1166
        ctx->queue_capacity = nbands;
1167
    }
1168
    if (ctx->queue_capacity > nbands) {
21,569!
1169
        ctx->queue_capacity = nbands;
1170
    }
1171

1172
    status = sixel_parallel_create_pool(
21,569✔
1173
        &ctx->pool,
1,813✔
1174
        threads,
1,813✔
1175
        ctx->queue_capacity,
1,813✔
1176
        sizeof(sixel_parallel_worker_state_t),
1177
        sixel_parallel_worker_main,
1178
        ctx);
1,813✔
1179
    if (SIXEL_FAILED(status)) {
21,569!
1180
        return status;
1181
    }
1182

1183
    ctx->pool->vtbl->set_affinity(ctx->pool, ctx->pin_threads);
21,569✔
1184

1185
    /* Initialize writer-visible fields before the writer thread starts.
1186
     * Serialize initialization of writer-visible state so the writer thread
1187
     * cannot observe partially initialized fields on startup.
1188
     */
1189
    sixel_mutex_lock(&ctx->mutex);
21,569✔
1190
    ctx->next_band_to_flush = 0;
21,569✔
1191
    ctx->writer_should_stop = 0;
21,569✔
1192
    ctx->writer_error = SIXEL_OK;
21,569✔
1193

1194
    status = sixel_thread_create(&ctx->writer_thread,
23,382✔
1195
                                 sixel_parallel_writer_main,
1196
                                 ctx);
1,813✔
1197
    if (SIXEL_FAILED(status)) {
21,569!
1198
        sixel_mutex_unlock(&ctx->mutex);
1199
        return status;
1200
    }
1201
    ctx->writer_started = 1;
21,569✔
1202
    sixel_mutex_unlock(&ctx->mutex);
21,569✔
1203

1204
    return SIXEL_OK;
21,569✔
1205
}
1,813✔
1206

1207
static void
1208
sixel_parallel_submit_band(sixel_parallel_context_t *ctx, int band_index)
132,028✔
1209
{
1210
    sixel_thread_pool_job_t job;
55,241✔
1211
    SIXELSTATUS status;
55,241✔
1212
    int dispatch;
55,241✔
1213

1214
    if (ctx == NULL || ctx->pool == NULL) {
132,028!
1215
        return;
1216
    }
1217
    if (band_index < 0 || band_index >= ctx->band_count) {
132,028!
1218
        return;
1219
    }
1220

1221
    dispatch = 0;
132,028✔
1222
    /*
1223
     * Multiple producers may notify the same band when PaletteApply runs in
1224
     * parallel.  Guard the dispatched flag so only the first notifier pushes
1225
     * work into the encoder queue.
1226
     */
1227
    if (ctx->mutex_ready) {
132,028!
1228
        sixel_mutex_lock(&ctx->mutex);
132,028✔
1229
        if (!ctx->bands[band_index].dispatched
132,029!
1230
                && !ctx->writer_should_stop
121,287!
1231
                && ctx->writer_error == SIXEL_OK) {
132,029!
1232
            ctx->bands[band_index].dispatched = 1;
132,029✔
1233
            dispatch = 1;
132,029✔
1234
        }
11,141✔
1235
        sixel_mutex_unlock(&ctx->mutex);
132,029✔
1236
    } else {
11,141✔
1237
        if (!ctx->bands[band_index].dispatched
×
1238
                && sixel_parallel_jobs_allowed(ctx)) {
×
1239
            ctx->bands[band_index].dispatched = 1;
1240
            dispatch = 1;
1241
        }
1242
    }
1243

1244
    if (!dispatch) {
132,029!
1245
        return;
1246
    }
1247

1248
    sixel_fence_release();
132,029✔
1249
    if (ctx->logger != NULL) {
132,029✔
1250
        sixel_timeline_logger_logf(ctx->logger,
611✔
1251
                          "controller",
1252
                          "encode",
1253
                          "dispatch",
1254
                          band_index);
47✔
1255
    }
47✔
1256
    job.band_index = band_index;
132,029✔
1257
    status = ctx->pool->vtbl->push(ctx->pool, job);
132,029✔
1258
    if (SIXEL_FAILED(status)) {
132,029!
1259
        if (ctx->mutex_ready) {
×
1260
            sixel_mutex_lock(&ctx->mutex);
1261
            sixel_parallel_context_abort_locked(ctx, status);
1262
            sixel_mutex_unlock(&ctx->mutex);
1263
        } else {
1264
            ctx->writer_error = status;
1265
        }
1266
    }
1267
}
11,141✔
1268

1269
static SIXELSTATUS
1270
sixel_parallel_context_wait(sixel_parallel_context_t *ctx, int force_abort)
21,569✔
1271
{
1272
    int pool_error;
9,008✔
1273

1274
    if (ctx == NULL || ctx->pool == NULL) {
21,569!
1275
        return SIXEL_BAD_ARGUMENT;
1276
    }
1277

1278
    ctx->pool->vtbl->finish(ctx->pool);
21,569✔
1279
    pool_error = ctx->pool->vtbl->get_error(ctx->pool);
21,569✔
1280
    sixel_parallel_writer_stop(ctx, force_abort || pool_error != SIXEL_OK);
21,569!
1281

1282
    if (pool_error != SIXEL_OK) {
21,569!
1283
        return pool_error;
1284
    }
1285
    if (ctx->writer_error != SIXEL_OK) {
21,569!
1286
        return ctx->writer_error;
1287
    }
1288

1289
    return SIXEL_OK;
12,561✔
1290
}
1,813✔
1291

1292
/*
1293
 * Producer callback invoked after PaletteApply finishes a scanline.  The
1294
 * helper promotes every sixth row (or the final partial band) into the job
1295
 * queue so workers can begin encoding while dithering continues.
1296
 */
1297
static void
1298
sixel_parallel_palette_row_ready(void *priv, int row_index)
645,306✔
1299
{
1300
    sixel_parallel_row_notifier_t *notifier;
270,189✔
1301
    sixel_parallel_context_t *ctx;
270,189✔
1302
    sixel_timeline_logger_t *logger;
270,189✔
1303
    int band_height;
270,189✔
1304
    int band_index;
270,189✔
1305
    int ready_row;
270,189✔
1306
    int should_dispatch;
270,189✔
1307
    int rows_ready;
270,189✔
1308
    int rows_total;
270,189✔
1309

1310
    notifier = (sixel_parallel_row_notifier_t *)priv;
645,306✔
1311
    if (notifier == NULL) {
645,306✔
1312
        return;
1313
    }
1314
    ctx = notifier->context;
645,306✔
1315
    logger = notifier->logger;
645,306✔
1316
    if (ctx == NULL || ctx->band_count <= 0 || ctx->height <= 0) {
645,306!
1317
        return;
4✔
1318
    }
1319
    if (row_index < 0 || row_index >= ctx->height) {
645,313!
1320
        return;
2✔
1321
    }
1322
    band_height = notifier->band_height;
645,313✔
1323
    if (band_height < 1) {
645,313!
1324
        band_height = 6;
1325
    }
1326

1327
    /*
1328
     * Dither workers may complete source-row ranges out of order.  A shifted
1329
     * encoded band can also straddle two dither bands, so the last source row
1330
     * seen by one worker is not enough to prove the encoded band is complete.
1331
     * Count source-row arrivals per encoded six-line band and dispatch only
1332
     * when every source row for that encoded band is present.
1333
     */
1334
    ready_row = ctx->offset_top + row_index;
645,313✔
1335
    band_index = ready_row / band_height;
645,313✔
1336
    if (band_index >= ctx->band_count) {
645,313!
1337
        band_index = ctx->band_count - 1;
1338
    }
1339
    if (band_index < 0) {
645,313!
1340
        return;
1341
    }
1342

1343
    should_dispatch = 0;
645,313✔
1344
    if (ctx->band_source_rows_total != NULL &&
645,313!
1345
            ctx->band_source_rows_ready != NULL) {
645,314!
1346
        if (ctx->mutex_ready) {
645,317!
1347
            sixel_mutex_lock(&ctx->mutex);
645,312✔
1348
        }
54,557✔
1349
        rows_total = ctx->band_source_rows_total[band_index];
645,339✔
1350
        rows_ready = ctx->band_source_rows_ready[band_index];
645,339✔
1351
        if (rows_total > 0 && rows_ready < rows_total) {
645,339!
1352
            rows_ready += 1;
645,336✔
1353
            ctx->band_source_rows_ready[band_index] = rows_ready;
645,336✔
1354
            if (rows_ready == rows_total) {
645,336✔
1355
                should_dispatch = 1;
336,680✔
1356
            }
9,665✔
1357
        }
54,559✔
1358
        if (ctx->mutex_ready) {
645,339!
1359
            sixel_mutex_unlock(&ctx->mutex);
645,336✔
1360
        }
54,559✔
1361
    } else if ((ready_row % band_height) == band_height - 1 ||
54,563!
1362
            row_index == ctx->height - 1) {
×
1363
        should_dispatch = 1;
1364
    }
1365

1366
    if (!should_dispatch) {
645,331✔
1367
        return;
396,310✔
1368
    }
1369

1370
    if (logger != NULL) {
114,340✔
1371
        sixel_timeline_logger_logf(logger,
611✔
1372
                          "controller",
1373
                          "encode",
1374
                          "row_gate",
1375
                          band_index);
47✔
1376
    }
47✔
1377

1378
    sixel_parallel_submit_band(ctx, band_index);
114,340✔
1379
}
54,562✔
1380

1381
static SIXELSTATUS
1382
sixel_parallel_flush_band(sixel_parallel_context_t *ctx, int band_index)
132,029✔
1383
{
1384
    sixel_parallel_band_buffer_t *band;
55,242✔
1385
    size_t offset;
55,242✔
1386
    size_t chunk;
55,242✔
1387

1388
    band = &ctx->bands[band_index];
132,029✔
1389
    if (ctx->logger != NULL) {
132,029✔
1390
        sixel_timeline_logger_logf(ctx->logger,
611✔
1391
                          "worker",
1392
                          "encode",
1393
                          "writer_flush",
1394
                          band_index);
47✔
1395
    }
47✔
1396
    offset = 0;
110,210✔
1397
    while (offset < band->used) {
265,258✔
1398
        chunk = band->used - offset;
133,229✔
1399
        if (chunk > (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos)) {
133,229✔
1400
            chunk = (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos);
1,050✔
1401
        }
150✔
1402
        memcpy(ctx->output->buffer + ctx->output->pos,
155,711✔
1403
               band->data + offset,
133,229✔
1404
               chunk);
11,241✔
1405
        sixel_advance(ctx->output, (int)chunk);
133,229✔
1406
        offset += chunk;
133,229✔
1407
    }
1408
    return SIXEL_OK;
132,029✔
1409
}
1410

1411
static int
1412
sixel_parallel_worker_main(sixel_thread_pool_job_t job,
132,027✔
1413
                           void *userdata,
1414
                           void *workspace)
1415
{
1416
    sixel_parallel_context_t *ctx;
55,241✔
1417
    sixel_parallel_worker_state_t *state;
55,241✔
1418
    sixel_parallel_band_buffer_t *band;
55,241✔
1419
    SIXELSTATUS status;
55,241✔
1420
    int band_index;
55,241✔
1421
    int band_start;
55,241✔
1422
    int band_height;
55,241✔
1423
    int row_index;
55,241✔
1424
    int absolute_row;
55,241✔
1425
    int last_row_index;
55,241✔
1426
    int nplanes;
55,241✔
1427

1428
    ctx = (sixel_parallel_context_t *)userdata;
132,027✔
1429
    state = (sixel_parallel_worker_state_t *)workspace;
132,027✔
1430

1431
    if (ctx == NULL || state == NULL) {
132,027!
1432
        return SIXEL_BAD_ARGUMENT;
2✔
1433
    }
1434

1435
    band = NULL;
132,027✔
1436
    status = SIXEL_OK;
132,027✔
1437
    band_index = job.band_index;
132,027✔
1438
    band_start = 0;
132,027✔
1439
    band_height = 0;
132,027✔
1440
    last_row_index = -1;
132,027✔
1441
    if (band_index < 0 || band_index >= ctx->band_count) {
132,027!
1442
        status = SIXEL_BAD_ARGUMENT;
1✔
1443
        goto cleanup;
1✔
1444
    }
1445

1446
    band = &ctx->bands[band_index];
132,027✔
1447
    if (ctx->mutex_ready) {
132,027!
1448
        /* Synchronize band state reset with the writer thread. */
1449
        sixel_mutex_lock(&ctx->mutex);
132,027✔
1450
        band->used = 0;
132,029✔
1451
        band->status = SIXEL_OK;
132,029✔
1452
        band->ready = 0;
132,029✔
1453
        sixel_mutex_unlock(&ctx->mutex);
132,029✔
1454
    } else {
11,141✔
1455
        band->used = 0;
1456
        band->status = SIXEL_OK;
1457
        band->ready = 0;
1458
    }
1459

1460
    sixel_fence_acquire();
132,028✔
1461

1462
    status = sixel_parallel_worker_prepare(state, ctx);
132,028✔
1463
    if (SIXEL_FAILED(status)) {
132,027!
1464
        goto cleanup;
1465
    }
1466

1467
    if (!sixel_parallel_jobs_allowed(ctx)) {
132,027!
1468
        if (ctx->mutex_ready) {
×
1469
            sixel_mutex_lock(&ctx->mutex);
1470
            if (ctx->writer_error != SIXEL_OK) {
×
1471
                status = ctx->writer_error;
1472
            } else {
1473
                status = SIXEL_RUNTIME_ERROR;
1474
            }
1475
            sixel_mutex_unlock(&ctx->mutex);
1476
        } else if (ctx->writer_error != SIXEL_OK) {
×
1477
            status = ctx->writer_error;
1478
        } else {
1479
            status = SIXEL_RUNTIME_ERROR;
1480
        }
1481
        goto cleanup;
1482
    }
1483

1484
    state->band_buffer = band;
132,026✔
1485
    sixel_parallel_worker_reset(state);
132,026✔
1486

1487
    band_start = band_index * 6;
132,002✔
1488
    band_height = ctx->encoded_height - band_start;
132,002✔
1489
    if (band_height > 6) {
132,002✔
1490
        band_height = 6;
64,243✔
1491
    }
9,329✔
1492
    if (band_height <= 0) {
85,761!
1493
        goto cleanup;
1494
    }
1495

1496
    if (ctx->logger != NULL) {
132,002✔
1497
        sixel_timeline_logger_logf(ctx->logger,
611✔
1498
                          "worker",
1499
                          "encode",
1500
                          "worker_start",
1501
                          band_index);
47✔
1502
    }
47✔
1503

1504
    if (sixel_parallel_context_is_ormode(ctx)) {
187,234!
1505
        nplanes = sixel_encode_body_ormode_nplanes(ctx->ncolors);
984✔
1506
        status = sixel_encode_body_ormode_band(ctx->pixels,
1,044✔
1507
                                               ctx->width,
58✔
1508
                                               ctx->height,
58✔
1509
                                               band_index,
58✔
1510
                                               nplanes,
58✔
1511
                                               state->output);
58✔
1512
        if (SIXEL_FAILED(status)) {
696!
1513
            goto cleanup;
1514
        }
1515
        status = sixel_parallel_worker_flush_output(state);
696✔
1516
        if (SIXEL_FAILED(status)) {
696!
1517
            goto cleanup;
1518
        }
1519
        goto done;
696✔
1520
    }
1521

1522
    for (row_index = 0; row_index < band_height; row_index++) {
875,100✔
1523
        absolute_row = band_start + row_index;
743,777✔
1524
        status = sixel_band_classify_row(&state->work,
806,550✔
1525
                                         &state->band,
62,773✔
1526
                                         ctx->pixels,
62,773✔
1527
                                         ctx->width,
62,773✔
1528
                                         ctx->height,
62,773✔
1529
                                         ctx->encoded_width,
62,773✔
1530
                                         absolute_row,
62,773✔
1531
                                         ctx->offset_left,
62,773✔
1532
                                         ctx->offset_top,
62,773✔
1533
                                         ctx->ncolors,
62,773✔
1534
                                         ctx->keycolor,
62,773✔
1535
                                         ctx->palstate,
62,773✔
1536
                                         ctx->encode_policy);
62,773✔
1537
        if (SIXEL_FAILED(status)) {
743,785!
1538
            goto cleanup;
1539
        }
1540
    }
62,773✔
1541

1542
    status = sixel_band_compose(&state->work,
142,406✔
1543
                                &state->band,
11,083✔
1544
                                state->output,
11,083✔
1545
                                ctx->encoded_width,
11,083✔
1546
                                ctx->ncolors,
11,083✔
1547
                                ctx->keycolor,
11,083✔
1548
                                ctx->allocator);
11,083✔
1549
    if (SIXEL_FAILED(status)) {
131,321!
1550
        goto cleanup;
1551
    }
1552

1553
    last_row_index = band_start + band_height - 1;
131,321✔
1554
    status = sixel_band_emit(&state->work,
142,404✔
1555
                             &state->band,
11,083✔
1556
                             state->output,
11,083✔
1557
                             ctx->ncolors,
11,083✔
1558
                             ctx->keycolor,
11,083✔
1559
                             last_row_index);
11,083✔
1560
    if (SIXEL_FAILED(status)) {
131,301!
1561
        goto cleanup;
1562
    }
1563

1564
    status = sixel_put_flash(state->output);
131,301✔
1565
    if (SIXEL_FAILED(status)) {
131,297!
1566
        goto cleanup;
1567
    }
1568

1569
    status = sixel_parallel_worker_flush_output(state);
131,297✔
1570
    if (SIXEL_FAILED(status)) {
131,330!
1571
        goto cleanup;
1572
    }
1573

1574
    sixel_band_finish(&state->work, &state->band);
131,330✔
1575
done:
99,061✔
1576
    status = SIXEL_OK;
76,783✔
1577

1578
cleanup:
120,879✔
1579
    sixel_parallel_worker_release_nodes(state, ctx->allocator);
132,020✔
1580
    if (band != NULL && ctx->mutex_ready && ctx->cond_ready) {
132,026!
1581
        sixel_fence_release();
132,025✔
1582
        sixel_mutex_lock(&ctx->mutex);
132,025✔
1583
        band->status = status;
132,028✔
1584
        band->ready = 1;
132,028✔
1585
        sixel_cond_broadcast(&ctx->cond_band_ready);
132,028✔
1586
        sixel_mutex_unlock(&ctx->mutex);
132,028✔
1587
    }
11,140✔
1588
    if (ctx->logger != NULL) {
132,025✔
1589
        sixel_timeline_logger_logf(ctx->logger,
611✔
1590
                          "worker",
1591
                          "encode",
1592
                          "worker_done",
1593
                          band_index);
47✔
1594
    }
47✔
1595
    if (SIXEL_FAILED(status)) {
132,022!
1596
        return status;
1597
    }
1598
    return SIXEL_OK;
76,781✔
1599
}
11,139✔
1600

1601
static void
1602
sixel_parallel_writer_stop(sixel_parallel_context_t *ctx, int force_abort)
43,138✔
1603
{
1604
    int should_signal;
18,016✔
1605

1606
    if (ctx == NULL || !ctx->writer_started) {
43,138!
1607
        return;
12,561✔
1608
    }
1609

1610
    should_signal = ctx->mutex_ready && ctx->cond_ready;
21,569!
1611
    if (should_signal) {
21,569!
1612
        sixel_mutex_lock(&ctx->mutex);
21,569✔
1613
        if (force_abort) {
21,569✔
1614
            ctx->writer_should_stop = 1;
48✔
1615
        }
4✔
1616
        sixel_cond_broadcast(&ctx->cond_band_ready);
21,569✔
1617
        sixel_mutex_unlock(&ctx->mutex);
21,569✔
1618
    } else if (force_abort) {
1,813!
1619
        ctx->writer_should_stop = 1;
1620
    }
1621

1622
    sixel_thread_join(&ctx->writer_thread);
21,569✔
1623
    ctx->writer_started = 0;
21,569✔
1624
    ctx->writer_should_stop = 0;
21,569✔
1625
    if (ctx->logger != NULL) {
21,569✔
1626
        sixel_timeline_logger_logf(ctx->logger,
132✔
1627
                          "writer",
1628
                          "encode",
1629
                          "writer_stop",
1630
                          -1);
1631
    }
11✔
1632
}
3,626✔
1633

1634
static int
1635
sixel_parallel_writer_main(void *arg)
21,569✔
1636
{
1637
    sixel_parallel_context_t *ctx;
9,008✔
1638
    sixel_parallel_band_buffer_t *band;
9,008✔
1639
    SIXELSTATUS status;
9,008✔
1640
    int band_index;
9,008✔
1641

1642
    ctx = (sixel_parallel_context_t *)arg;
21,569✔
1643
    if (ctx == NULL) {
21,569✔
1644
        return SIXEL_BAD_ARGUMENT;
1645
    }
1646

1647
    if (ctx->logger != NULL) {
21,569✔
1648
        sixel_timeline_logger_logf(ctx->logger,
132✔
1649
                                   "writer",
1650
                                   "encode",
1651
                                   "writer_start",
1652
                                   -1);
1653
    }
11✔
1654

1655
    for (;;) {
77,204✔
1656
        sixel_mutex_lock(&ctx->mutex);
153,598✔
1657
        while (!ctx->writer_should_stop &&
272,548✔
1658
               ctx->next_band_to_flush < ctx->band_count) {
251,660✔
1659
    band_index = ctx->next_band_to_flush;
230,139✔
1660
    band = &ctx->bands[band_index];
230,139✔
1661
    if (band->ready) {
230,139✔
1662
        break;
76,787✔
1663
    }
1664
            sixel_cond_wait(&ctx->cond_band_ready, &ctx->mutex);
98,110✔
1665
        }
1666

1667
        if (ctx->writer_should_stop) {
153,598✔
1668
            sixel_mutex_unlock(&ctx->mutex);
48✔
1669
            break;
48✔
1670
        }
1671

1672
        if (ctx->next_band_to_flush >= ctx->band_count) {
153,550✔
1673
            sixel_mutex_unlock(&ctx->mutex);
21,521✔
1674
            break;
21,521✔
1675
        }
1676

1677
        band_index = ctx->next_band_to_flush;
132,029✔
1678
        band = &ctx->bands[band_index];
132,029✔
1679
        if (!band->ready) {
132,029!
1680
            sixel_mutex_unlock(&ctx->mutex);
1681
            continue;
1682
        }
1683
        band->ready = 0;
132,029✔
1684
        ctx->next_band_to_flush += 1;
132,029✔
1685
        sixel_mutex_unlock(&ctx->mutex);
132,029✔
1686

1687
        sixel_fence_acquire();
132,029✔
1688
        status = band->status;
132,029✔
1689
        if (ctx->logger != NULL) {
132,029✔
1690
            sixel_timeline_logger_logf(ctx->logger,
611✔
1691
                              "writer",
1692
                              "encode",
1693
                              "writer_dequeue",
1694
                              band_index);
47✔
1695
        }
47✔
1696
        if (SIXEL_SUCCEEDED(status)) {
132,029!
1697
            status = sixel_parallel_flush_band(ctx, band_index);
132,029✔
1698
        }
11,141✔
1699
        if (SIXEL_FAILED(status)) {
132,029!
1700
            sixel_parallel_context_abort_locked(ctx, status);
1701
            break;
1702
        }
1703
    }
1704

1705
    return SIXEL_OK;
12,561✔
1706
}
1,813✔
1707

1708
static SIXELSTATUS
1709
sixel_encode_body_parallel(sixel_index_t *pixels,
2,559✔
1710
                           int width,
1711
                           int height,
1712
                           int ncolors,
1713
                           int keycolor,
1714
                           sixel_output_t *output,
1715
                           unsigned char *palstate,
1716
                           sixel_allocator_t *allocator,
1717
                           int requested_threads,
1718
                           int pin_threads)
1719
{
1720
    sixel_parallel_context_t ctx = {0};
2,559✔
1721
    SIXELSTATUS status;
1,067✔
1722
    int nbands;
1,067✔
1723
    int threads;
1,067✔
1724
    int i;
1,067✔
1725
    int queue_depth;
1,067✔
1726
    int encoded_width;
1,067✔
1727
    int encoded_height;
1,067✔
1728
    sixel_timeline_logger_t *logger;
1,067✔
1729

1730
    sixel_parallel_context_init(&ctx);
2,559✔
1731
    sixel_timeline_logger_prepare_default(allocator, &logger);
2,559✔
1732
    status = sixel_output_compute_transparent_extent(output,
2,773✔
1733
                                                     width,
214✔
1734
                                                     height,
214✔
1735
                                                     &encoded_width,
1736
                                                     &encoded_height);
1737
    if (SIXEL_FAILED(status)) {
2,559!
1738
        sixel_timeline_logger_unref(logger);
1739
        return status;
1740
    }
1741
    (void)encoded_width;
1,281✔
1742
    nbands = sixel_count_sixel_bands(encoded_height);
2,559!
1743
    if (nbands <= 0) {
2,559!
1744
        sixel_timeline_logger_unref(logger);
1745
        return SIXEL_OK;
1746
    }
1747

1748
    threads = requested_threads;
2,559✔
1749
    if (threads > nbands) {
2,559✔
1750
        threads = nbands;
631✔
1751
    }
91✔
1752
    if (threads < 1) {
2,559!
1753
        threads = 1;
1754
    }
1755
    ctx.thread_count = threads;
2,559✔
1756
    queue_depth = threads * 3;
2,559✔
1757
    if (queue_depth > nbands) {
2,559✔
1758
        queue_depth = nbands;
1,275✔
1759
    }
183✔
1760
    if (queue_depth < 1) {
2,559!
1761
        queue_depth = 1;
1762
    }
1763

1764
    status = sixel_parallel_context_begin(&ctx,
2,559✔
1765
                                          pixels,
214✔
1766
                                          width,
214✔
1767
                                          height,
214✔
1768
                                          ncolors,
214✔
1769
                                          keycolor,
214✔
1770
                                          palstate,
214✔
1771
                                          output,
214✔
1772
                                          allocator,
214✔
1773
                                          threads,
214✔
1774
                                          threads,
214✔
1775
                                          queue_depth,
214✔
1776
                                          pin_threads,
214✔
1777
                                          logger);
214✔
1778
    if (SIXEL_FAILED(status)) {
2,559!
1779
        sixel_parallel_context_cleanup(&ctx);
1780
        sixel_timeline_logger_unref(logger);
1781
        return status;
1782
    }
1783

1784
    for (i = 0; i < nbands; i++) {
20,244✔
1785
        sixel_parallel_submit_band(&ctx, i);
17,685✔
1786
    }
1,476✔
1787

1788
    status = sixel_parallel_context_wait(&ctx, 0);
2,559✔
1789
    if (SIXEL_FAILED(status)) {
2,559!
1790
        sixel_parallel_context_cleanup(&ctx);
1791
        sixel_timeline_logger_unref(logger);
1792
        return status;
1793
    }
1794

1795
    sixel_parallel_context_cleanup(&ctx);
2,559✔
1796
    sixel_timeline_logger_unref(logger);
2,559✔
1797
    return SIXEL_OK;
2,559✔
1798
}
214✔
1799
#endif
1800

1801
#if SIXEL_ENABLE_THREADS
1802
/*
1803
 * Execute PaletteApply, band encoding, and output emission as a pipeline.
1804
 * The producer owns the dithered index buffer and enqueues bands once every
1805
 * six rows have been produced.  Worker threads encode in parallel while the
1806
 * writer emits completed bands in-order to preserve deterministic output.
1807
 */
1808
static SIXELSTATUS
1809
sixel_encode_body_pipeline(unsigned char *pixels,
18,938✔
1810
                           int width,
1811
                           int height,
1812
                           unsigned char const *palette,
1813
                           float const *palette_float,
1814
                           sixel_dither_t *dither,
1815
                           sixel_output_t *output,
1816
                           int encode_threads)
1817
{
1818
    SIXELSTATUS status;
7,911✔
1819
    SIXELSTATUS wait_status;
7,911✔
1820
    sixel_parallel_context_t ctx = {0};
18,938✔
1821
    sixel_index_t *indexes;
7,911✔
1822
    sixel_index_t *result;
7,911✔
1823
    sixel_allocator_t *allocator;
7,911✔
1824
    size_t pixel_count;
7,911✔
1825
    size_t buffer_size;
7,911✔
1826
    int threads;
7,911✔
1827
    int nbands;
7,911✔
1828
    int queue_depth;
7,911✔
1829
    int waited;
7,911✔
1830
    int dither_threads_budget;
7,911✔
1831
    int worker_capacity;
7,911✔
1832
    int boost_target;
7,911✔
1833
    int encoded_width;
7,911✔
1834
    int encoded_height;
7,911✔
1835
    sixel_timeline_logger_t *logger;
7,911✔
1836
    int owns_logger;
7,911✔
1837
    sixel_parallel_row_notifier_t notifier;
7,911✔
1838

1839
    if (pixels == NULL
18,938!
1840
            || (palette == NULL && palette_float == NULL)
18,938!
1841
            || dither == NULL
18,938!
1842
            || output == NULL) {
18,938!
1843
        return SIXEL_BAD_ARGUMENT;
1844
    }
1845

1846
    threads = encode_threads;
18,938✔
1847
    status = sixel_output_compute_transparent_extent(output,
20,531✔
1848
                                                     width,
1,593✔
1849
                                                     height,
1,593✔
1850
                                                     &encoded_width,
1851
                                                     &encoded_height);
1852
    if (SIXEL_FAILED(status)) {
18,938!
1853
        return status;
1854
    }
1855
    (void)encoded_width;
9,504✔
1856
    nbands = sixel_count_sixel_bands(encoded_height);
18,938!
1857
    if (threads <= 1 || nbands <= 1) {
18,938!
1858
        return SIXEL_RUNTIME_ERROR;
1859
    }
1860

1861
    pixel_count = (size_t)width * (size_t)height;
18,938✔
1862
    if (height != 0 && pixel_count / (size_t)height != (size_t)width) {
18,938!
1863
        return SIXEL_BAD_INTEGER_OVERFLOW;
1864
    }
1865
    buffer_size = pixel_count * sizeof(*indexes);
18,938✔
1866
    allocator = dither->allocator;
18,938✔
1867
    indexes = (sixel_index_t *)sixel_allocator_malloc(allocator, buffer_size);
18,938✔
1868
    if (indexes == NULL) {
18,938!
1869
        return SIXEL_BAD_ALLOCATION;
1870
    }
1871

1872
    sixel_parallel_context_init(&ctx);
18,938✔
1873
    logger = dither->pipeline_logger;
18,938✔
1874
    owns_logger = 0;
18,938✔
1875
    if (logger == NULL) {
18,938!
1876
        logger = NULL;
18,938✔
1877
        sixel_timeline_logger_prepare_default(allocator, &logger);
18,938✔
1878
        owns_logger = logger != NULL ? 1 : 0;
18,938✔
1879
    }
1,593✔
1880
    notifier.context = &ctx;
18,938✔
1881
    notifier.logger = logger;
18,938✔
1882
    notifier.band_height = 6;
18,938✔
1883
    notifier.image_height = encoded_height;
18,938✔
1884
    waited = 0;
18,938✔
1885
    status = SIXEL_OK;
18,938✔
1886

1887
    status = sixel_encode_emit_palette(dither->bodyonly,
20,531✔
1888
                                       dither->ncolors,
1,593✔
1889
                                       dither->keycolor,
1,593✔
1890
                                       palette,
1,593✔
1891
                                       palette_float,
1,593✔
1892
                                       output);
1,593✔
1893
    if (SIXEL_FAILED(status)) {
18,938!
1894
        goto cleanup;
1895
    }
1896

1897
    queue_depth = threads * 3;
18,938✔
1898
    if (queue_depth > nbands) {
18,938✔
1899
        queue_depth = nbands;
7,566✔
1900
    }
1,081✔
1901
    if (queue_depth < 1) {
18,938!
1902
        queue_depth = 1;
1903
    }
1904

1905
    dither_threads_budget = dither->pipeline_dither_threads;
18,938✔
1906
    worker_capacity = threads + dither_threads_budget;
18,938✔
1907
    if (worker_capacity < threads) {
18,938!
1908
        worker_capacity = threads;
1909
    }
1910
    if (worker_capacity > nbands) {
18,938✔
1911
        worker_capacity = nbands;
7,503✔
1912
    }
1,072✔
1913

1914
    dither->pipeline_index_buffer = indexes;
18,938✔
1915
    dither->pipeline_index_size = buffer_size;
18,938✔
1916
    dither->pipeline_row_callback = sixel_parallel_palette_row_ready;
18,938✔
1917
    dither->pipeline_row_priv = &notifier;
18,938✔
1918
    dither->pipeline_logger = logger;
18,938✔
1919
    dither->pipeline_image_width = width;
18,938✔
1920
    dither->pipeline_image_height = height;
18,938✔
1921

1922
    if (logger != NULL) {
18,938✔
1923
        /*
1924
         * Record the thread split and band geometry before spawning workers.
1925
         * This clarifies why only a subset of hardware threads might appear
1926
         * in the log when the encoder side is clamped to keep the pipeline
1927
         * draining.
1928
         */
1929
        sixel_timeline_logger_logf(logger,
132✔
1930
                          "controller",
1931
                          "pipeline",
1932
                          "configure",
1933
                          -1);
1934
    }
11✔
1935

1936
    status = sixel_parallel_context_begin(&ctx,
18,938✔
1937
                                          indexes,
1,593✔
1938
                                          width,
1,593✔
1939
                                          height,
1,593✔
1940
                                          dither->ncolors,
1,593✔
1941
                                          dither->keycolor,
1,593✔
1942
                                          NULL,
1943
                                          output,
1,593✔
1944
                                          allocator,
1,593✔
1945
                                          threads,
1,593✔
1946
                                          worker_capacity,
1,593✔
1947
                                          queue_depth,
1,593✔
1948
                                          dither->pipeline_pin_threads,
1,593✔
1949
                                          logger);
1,593✔
1950
    if (SIXEL_FAILED(status)) {
18,938!
1951
        goto cleanup;
1952
    }
1953
    sixel_parallel_submit_source_empty_bands(&ctx);
18,938✔
1954

1955
    result = sixel_dither_apply_palette(dither, pixels, width, height);
18,938✔
1956
    if (result == NULL) {
18,938✔
1957
        status = SIXEL_RUNTIME_ERROR;
48✔
1958
        goto cleanup;
48✔
1959
    }
1960
    if (result != indexes) {
18,890!
1961
        status = SIXEL_RUNTIME_ERROR;
1962
        goto cleanup;
1963
    }
1964
    if (dither->pipeline_accumulation_result_enabled != 0) {
18,890✔
1965
        status = sixel_dither_set_pipeline_accumulation_result_rgb(
34✔
1966
            dither,
2✔
1967
            indexes,
2✔
1968
            pixel_count,
2✔
1969
            palette,
2✔
1970
            (size_t)dither->ncolors);
24✔
1971
        if (SIXEL_FAILED(status)) {
24!
1972
            goto cleanup;
1973
        }
1974
    }
2✔
1975

1976
    /*
1977
     * All dithering work has finished at this point.  Reclaim the idle dither
1978
     * workers for encoding so the tail of the pipeline drains with additional
1979
     * parallelism instead of leaving those CPU resources unused.
1980
     */
1981
    boost_target = threads + dither_threads_budget;
18,890✔
1982
    status = sixel_parallel_context_grow(&ctx, boost_target);
18,890✔
1983
    if (SIXEL_FAILED(status)) {
18,890!
1984
        goto cleanup;
1985
    }
1986

1987
    status = sixel_parallel_context_wait(&ctx, 0);
18,890✔
1988
    waited = 1;
18,890✔
1989
    if (SIXEL_FAILED(status)) {
18,890!
1990
        goto cleanup;
1991
    }
1992

1993
cleanup:
17,321✔
1994
    dither->pipeline_row_callback = NULL;
18,938✔
1995
    dither->pipeline_row_priv = NULL;
18,938✔
1996
    dither->pipeline_index_buffer = NULL;
18,938✔
1997
    dither->pipeline_index_size = 0;
18,938✔
1998
    dither->pipeline_image_width = 0;
18,938✔
1999
    dither->pipeline_image_height = 0;
18,938✔
2000
    dither->pipeline_transparent_mask = NULL;
18,938✔
2001
    dither->pipeline_transparent_mask_size = 0;
18,938✔
2002
    dither->pipeline_transparent_keycolor = (-1);
18,938✔
2003
    dither->pipeline_accumulation_result_enabled = 0;
18,938✔
2004
    if (!waited && ctx.pool != NULL) {
18,938!
2005
        wait_status = sixel_parallel_context_wait(&ctx, status != SIXEL_OK);
48✔
2006
        if (status == SIXEL_OK) {
48!
2007
            status = wait_status;
7,911✔
2008
        }
2009
    }
4✔
2010
    sixel_parallel_context_cleanup(&ctx);
18,938✔
2011
    if (owns_logger) {
18,938✔
2012
        sixel_timeline_logger_unref(logger);
132✔
2013
    }
11✔
2014
    if (indexes != NULL) {
18,938!
2015
        sixel_allocator_free(allocator, indexes);
18,938✔
2016
    }
1,593✔
2017
    return status;
14,159✔
2018
}
1,593✔
2019
#else
2020
static SIXELSTATUS
2021
sixel_encode_body_pipeline(unsigned char *pixels,
2022
                           int width,
2023
                           int height,
2024
                           unsigned char const *palette,
2025
                           float const *palette_float,
2026
                           sixel_dither_t *dither,
2027
                           sixel_output_t *output,
2028
                           int encode_threads)
2029
{
2030
    (void)pixels;
2031
    (void)width;
2032
    (void)height;
2033
    (void)palette;
2034
    (void)palette_float;
2035
    (void)dither;
2036
    (void)output;
2037
    (void)encode_threads;
2038
    return SIXEL_RUNTIME_ERROR;
2039
}
2040
#endif
2041

2042
#if SIXEL_ENABLE_THREADS
2043
/*
2044
 * OR mode shares the producer/writer pipeline with the normal encoder, but its
2045
 * worker body emits bit-planes from the finished palette-index rows directly.
2046
 * The palette is emitted before workers start so every band can stay
2047
 * independent and the writer only has to concatenate ordered body fragments.
2048
 */
2049
SIXEL_INTERNAL_API SIXELSTATUS
2050
sixel_encode_body_ormode_pipeline(unsigned char *pixels,
72✔
2051
                                  int width,
2052
                                  int height,
2053
                                  unsigned char const *palette,
2054
                                  sixel_dither_t *dither,
2055
                                  sixel_output_t *output,
2056
                                  int encode_threads)
2057
{
2058
    SIXELSTATUS status;
30✔
2059
    SIXELSTATUS wait_status;
30✔
2060
    sixel_parallel_context_t ctx = {0};
72✔
2061
    sixel_index_t *indexes;
30✔
2062
    sixel_index_t *result;
30✔
2063
    sixel_allocator_t *allocator;
30✔
2064
    size_t pixel_count;
30✔
2065
    size_t buffer_size;
30✔
2066
    int threads;
30✔
2067
    int nbands;
30✔
2068
    int queue_depth;
30✔
2069
    int waited;
30✔
2070
    int dither_threads_budget;
30✔
2071
    int worker_capacity;
30✔
2072
    int boost_target;
30✔
2073
    sixel_timeline_logger_t *logger;
30✔
2074
    int owns_logger;
30✔
2075
    sixel_parallel_row_notifier_t notifier;
30✔
2076

2077
    if (pixels == NULL || palette == NULL || dither == NULL ||
72!
2078
            output == NULL) {
36!
2079
        return SIXEL_BAD_ARGUMENT;
2080
    }
2081

2082
    threads = encode_threads;
72✔
2083
    nbands = (height + 5) / 6;
72✔
2084
    if (threads <= 1 || nbands <= 1) {
72!
2085
        return SIXEL_RUNTIME_ERROR;
2086
    }
2087

2088
    pixel_count = (size_t)width * (size_t)height;
72✔
2089
    if (height != 0 && pixel_count / (size_t)height != (size_t)width) {
72!
2090
        return SIXEL_BAD_INTEGER_OVERFLOW;
2091
    }
2092
    buffer_size = pixel_count * sizeof(*indexes);
72✔
2093
    allocator = dither->allocator;
72✔
2094
    indexes = (sixel_index_t *)sixel_allocator_malloc(allocator, buffer_size);
72✔
2095
    if (indexes == NULL) {
72!
2096
        return SIXEL_BAD_ALLOCATION;
2097
    }
2098

2099
    sixel_parallel_context_init(&ctx);
72✔
2100
    logger = dither->pipeline_logger;
72✔
2101
    owns_logger = 0;
72✔
2102
    if (logger == NULL) {
72!
2103
        logger = NULL;
72✔
2104
        sixel_timeline_logger_prepare_default(allocator, &logger);
72✔
2105
        owns_logger = logger != NULL ? 1 : 0;
72✔
2106
    }
6✔
2107
    notifier.context = &ctx;
72✔
2108
    notifier.logger = logger;
72✔
2109
    notifier.band_height = 6;
72✔
2110
    notifier.image_height = height;
72✔
2111
    waited = 0;
72✔
2112
    status = SIXEL_OK;
72✔
2113

2114
    status = sixel_encode_body_ormode_emit_palette(palette,
78✔
2115
                                                   dither->ncolors,
6✔
2116
                                                   dither->keycolor,
6✔
2117
                                                   output);
6✔
2118
    if (SIXEL_FAILED(status)) {
72!
2119
        goto cleanup;
2120
    }
2121

2122
    queue_depth = threads * 3;
72✔
2123
    if (queue_depth > nbands) {
72✔
2124
        queue_depth = nbands;
7✔
2125
    }
1✔
2126
    if (queue_depth < 1) {
72!
2127
        queue_depth = 1;
2128
    }
2129

2130
    dither_threads_budget = dither->pipeline_dither_threads;
72✔
2131
    worker_capacity = threads + dither_threads_budget;
72✔
2132
    if (worker_capacity < threads) {
72!
2133
        worker_capacity = threads;
2134
    }
2135
    if (worker_capacity > nbands) {
72✔
2136
        worker_capacity = nbands;
7✔
2137
    }
1✔
2138

2139
    dither->pipeline_index_buffer = indexes;
72✔
2140
    dither->pipeline_index_size = buffer_size;
72✔
2141
    dither->pipeline_row_callback = sixel_parallel_palette_row_ready;
72✔
2142
    dither->pipeline_row_priv = &notifier;
72✔
2143
    dither->pipeline_logger = logger;
72✔
2144
    dither->pipeline_image_width = width;
72✔
2145
    dither->pipeline_image_height = height;
72✔
2146

2147
    if (logger != NULL) {
72!
2148
        sixel_timeline_logger_logf(logger,
2149
                          "controller",
2150
                          "pipeline",
2151
                          "configure",
2152
                          -1);
2153
    }
2154

2155
    status = sixel_parallel_context_begin(&ctx,
72✔
2156
                                          indexes,
6✔
2157
                                          width,
6✔
2158
                                          height,
6✔
2159
                                          dither->ncolors,
6✔
2160
                                          dither->keycolor,
6✔
2161
                                          NULL,
2162
                                          output,
6✔
2163
                                          allocator,
6✔
2164
                                          threads,
6✔
2165
                                          worker_capacity,
6✔
2166
                                          queue_depth,
6✔
2167
                                          dither->pipeline_pin_threads,
6✔
2168
                                          logger);
6✔
2169
    if (SIXEL_FAILED(status)) {
72!
2170
        goto cleanup;
2171
    }
2172
    sixel_parallel_submit_source_empty_bands(&ctx);
72✔
2173

2174
    result = sixel_dither_apply_palette(dither, pixels, width, height);
72✔
2175
    if (result == NULL) {
72!
2176
        status = SIXEL_RUNTIME_ERROR;
2177
        goto cleanup;
2178
    }
2179
    if (result != indexes) {
72!
2180
        status = SIXEL_RUNTIME_ERROR;
2181
        goto cleanup;
2182
    }
2183
    if (dither->pipeline_accumulation_result_enabled != 0) {
72!
2184
        status = sixel_dither_set_pipeline_accumulation_result_rgb(
2185
            dither,
2186
            indexes,
2187
            pixel_count,
2188
            palette,
2189
            (size_t)dither->ncolors);
2190
        if (SIXEL_FAILED(status)) {
×
2191
            goto cleanup;
2192
        }
2193
    }
2194

2195
    /*
2196
     * PaletteApply is complete, so the encode queue can borrow the dither-side
2197
     * thread budget and finish any remaining bands with a wider worker set.
2198
     */
2199
    boost_target = threads + dither_threads_budget;
72✔
2200
    status = sixel_parallel_context_grow(&ctx, boost_target);
72✔
2201
    if (SIXEL_FAILED(status)) {
72!
2202
        goto cleanup;
2203
    }
2204

2205
    status = sixel_parallel_context_wait(&ctx, 0);
72✔
2206
    waited = 1;
72✔
2207
    if (SIXEL_FAILED(status)) {
72!
2208
        goto cleanup;
2209
    }
2210

2211
cleanup:
66✔
2212
    dither->pipeline_row_callback = NULL;
72✔
2213
    dither->pipeline_row_priv = NULL;
72✔
2214
    dither->pipeline_index_buffer = NULL;
72✔
2215
    dither->pipeline_index_size = 0;
72✔
2216
    dither->pipeline_image_width = 0;
72✔
2217
    dither->pipeline_image_height = 0;
72✔
2218
    dither->pipeline_transparent_mask = NULL;
72✔
2219
    dither->pipeline_transparent_mask_size = 0;
72✔
2220
    dither->pipeline_transparent_keycolor = (-1);
72✔
2221
    dither->pipeline_accumulation_result_enabled = 0;
72✔
2222
    if (!waited && ctx.pool != NULL) {
72!
2223
        wait_status = sixel_parallel_context_wait(&ctx, status != SIXEL_OK);
2224
        if (status == SIXEL_OK) {
×
2225
            status = wait_status;
30✔
2226
        }
2227
    }
2228
    sixel_parallel_context_cleanup(&ctx);
72✔
2229
    if (owns_logger) {
72!
2230
        sixel_timeline_logger_unref(logger);
2231
    }
2232
    if (indexes != NULL) {
72!
2233
        sixel_allocator_free(allocator, indexes);
72✔
2234
    }
6✔
2235
    return status;
54✔
2236
}
6✔
2237
#else
2238
SIXEL_INTERNAL_API SIXELSTATUS
2239
sixel_encode_body_ormode_pipeline(unsigned char *pixels,
2240
                                  int width,
2241
                                  int height,
2242
                                  unsigned char const *palette,
2243
                                  sixel_dither_t *dither,
2244
                                  sixel_output_t *output,
2245
                                  int encode_threads)
2246
{
2247
    (void)pixels;
2248
    (void)width;
2249
    (void)height;
2250
    (void)palette;
2251
    (void)dither;
2252
    (void)output;
2253
    (void)encode_threads;
2254
    return SIXEL_RUNTIME_ERROR;
2255
}
2256
#endif
2257

2258
/* implementation */
2259

2260
static void
2261
sixel_advance(sixel_output_t *output, int nwrite)
96,157,016✔
2262
{
2263
    if ((output->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) {
96,157,016✔
2264
        (void)sixel_output_write_bytes(output,
2,916✔
2265
                                       (char *)output->buffer,
2,748✔
2266
                                       SIXEL_OUTPUT_PACKET_SIZE);
2267
        memcpy(output->buffer,
3,084✔
2268
               output->buffer + SIXEL_OUTPUT_PACKET_SIZE,
1,542✔
2269
               (size_t)(output->pos -= SIXEL_OUTPUT_PACKET_SIZE));
2,748✔
2270
    }
168✔
2271
}
96,157,016✔
2272

2273

2274
static void
2275
sixel_putc(unsigned char *buffer, unsigned char value)
27,014,947✔
2276
{
2277
    *buffer = value;
27,014,947✔
2278
}
15,141,104✔
2279

2280

2281
static void
2282
sixel_puts(unsigned char *buffer, char const *value, int size)
2,674,385✔
2283
{
2284
    memcpy(buffer, (void *)value, (size_t)size);
2,674,385✔
2285
}
1,824,921✔
2286

2287
/*
2288
 * Append a literal byte several times while respecting the output packet
2289
 * boundary.  The helper keeps `sixel_advance` responsible for flushing and
2290
 * preserves the repeating logic used by DECGRI sequences.
2291
 */
2292
static void
2293
sixel_output_emit_literal(sixel_output_t *output,
40,411,185✔
2294
                          unsigned char value,
2295
                          int count)
2296
{
2297
    int chunk;
17,742,636✔
2298

2299
    if (count <= 0) {
40,411,185✔
2300
        return;
2301
    }
2302

2303
    while (count > 0) {
80,841,683✔
2304
        chunk = SIXEL_OUTPUT_PACKET_SIZE - output->pos;
40,417,011✔
2305
        if (chunk > count) {
40,417,011✔
2306
            chunk = count;
22,677,557✔
2307
        }
2,547,139✔
2308
        memset(output->buffer + output->pos, value, (size_t)chunk);
40,417,011✔
2309
        sixel_advance(output, chunk);
40,417,011✔
2310
        count -= chunk;
40,430,498✔
2311
    }
2312
}
2,547,958✔
2313

2314
/*
2315
 * Compose helpers accelerate palette sweeps by skipping zero columns in
2316
 * word-sized chunks.
2317
 */
2318

2319
static int
2320
sixel_compose_find_run_start(unsigned char const *row,
15,459,262✔
2321
                             int width,
2322
                             int start)
2323
{
2324
    int idx;
6,799,913✔
2325
    size_t chunk_size;
6,799,913✔
2326
    unsigned char const *cursor;
6,799,913✔
2327
    unsigned long block;
6,799,913✔
2328

2329
    idx = start;
15,459,262✔
2330
    chunk_size = sizeof(unsigned long);
15,459,262✔
2331
    cursor = row + start;
15,459,262✔
2332

2333
    while ((width - idx) >= (int)chunk_size) {
166,891,427✔
2334
        memcpy(&block, cursor, chunk_size);
160,825,137✔
2335
        if (block != 0UL) {
160,825,137✔
2336
            break;
5,262,004✔
2337
        }
2338
        idx += (int)chunk_size;
151,432,165✔
2339
        cursor += chunk_size;
151,432,165✔
2340
    }
2341

2342
    while (idx < width) {
50,496,961✔
2343
        if (*cursor != 0) {
44,574,578✔
2344
            break;
5,343,375✔
2345
        }
2346
        idx += 1;
35,037,699✔
2347
        cursor += 1;
35,037,699✔
2348
    }
2349

2350
    return idx;
15,459,262✔
2351
}
2352

2353

2354
static int
2355
sixel_compose_measure_gap(unsigned char const *row,
16,715,568✔
2356
                          int width,
2357
                          int start,
2358
                          int *reached_end)
2359
{
2360
    int gap;
7,334,271✔
2361
    size_t chunk_size;
7,334,271✔
2362
    unsigned char const *cursor;
7,334,271✔
2363
    unsigned long block;
7,334,271✔
2364
    int remaining;
7,334,271✔
2365

2366
    gap = 0;
16,715,568✔
2367
    *reached_end = 0;
16,715,568✔
2368
    if (start >= width) {
16,715,568✔
2369
        *reached_end = 1;
207,049✔
2370
        return gap;
207,049✔
2371
    }
2372

2373
    chunk_size = sizeof(unsigned long);
16,508,519✔
2374
    cursor = row + start;
16,508,519✔
2375
    remaining = width - start;
16,508,519✔
2376

2377
    while (remaining >= (int)chunk_size) {
99,901,130✔
2378
        memcpy(&block, cursor, chunk_size);
93,795,438✔
2379
        if (block != 0UL) {
93,795,438✔
2380
            break;
5,847,203✔
2381
        }
2382
        gap += (int)chunk_size;
83,392,611✔
2383
        cursor += chunk_size;
83,392,611✔
2384
        remaining -= (int)chunk_size;
83,392,611✔
2385
    }
2386

2387
    while (remaining > 0) {
46,243,020✔
2388
        if (*cursor != 0) {
40,523,990✔
2389
            return gap;
6,064,969✔
2390
        }
2391
        gap += 1;
29,734,501✔
2392
        cursor += 1;
29,734,501✔
2393
        remaining -= 1;
29,734,501✔
2394
    }
2395

2396
    *reached_end = 1;
5,719,030✔
2397
    return gap;
5,719,030✔
2398
}
1,062,585✔
2399

2400

2401
#if HAVE_LDIV
2402
static int
2403
sixel_putnum_impl(char *buffer, long value, int pos)
49,090,775✔
2404
{
2405
    ldiv_t r;
21,576,685✔
2406

2407
    r = ldiv(value, 10);
49,090,775✔
2408
    if (r.quot > 0) {
49,093,263✔
2409
        pos = sixel_putnum_impl(buffer, r.quot, pos);
24,215,837✔
2410
    }
1,541,973✔
2411
    /*
2412
     * r.rem is guaranteed to be in [0, 9] because the divisor is 10, so the
2413
     * explicit cast documents the safe narrowing from long to char.
2414
     */
2415
    *(buffer + pos) = (char)('0' + r.rem);
49,096,407✔
2416
    return pos + 1;
49,096,407✔
2417
}
2418
#endif  /* HAVE_LDIV */
2419

2420

2421
static int
2422
sixel_putnum(char *buffer, int value)
24,889,151✔
2423
{
2424
    int pos;
10,940,014✔
2425

2426
#if HAVE_LDIV
2427
    pos = sixel_putnum_impl(buffer, value, 0);
27,915,237✔
2428
#else
2429
    pos = sprintf(buffer, "%d", value);
2430
#endif  /* HAVE_LDIV */
2431

2432
    return pos;
24,891,399✔
2433
}
2434

2435

2436
static SIXELSTATUS
2437
sixel_put_flash(sixel_output_t *const output)
45,310,690✔
2438
{
2439
    int nwrite;
19,886,180✔
2440

2441
    if (output->save_count <= 0) {
45,310,690✔
2442
        return SIXEL_OK;
76,355✔
2443
    }
2444

2445
    if (output->has_gri_arg_limit) {  /* VT240 Max 255 ? */
45,179,404✔
2446
            while (output->save_count > 255) {
52,938!
2447
                /* argument of DECGRI('!') is limited to 255 in real VT */
2448
                sixel_puts(output->buffer + output->pos, "!255", 4);
×
2449
                sixel_advance(output, 4);
×
2450
                sixel_putc(output->buffer + output->pos,
×
2451
                           (unsigned char)output->save_pixel);
×
UNCOV
2452
                sixel_advance(output, 1);
×
2453
                output->save_count -= 255;
35,034✔
2454
            }
2455
        }
1,130✔
2456

2457
    if (output->save_count > 3) {
45,214,438✔
2458
        /* DECGRI Graphics Repeat Introducer ! Pn Ch */
2459
        sixel_putc(output->buffer + output->pos, '!');
4,800,136✔
2460
        sixel_advance(output, 1);
4,799,920✔
2461
        nwrite = sixel_putnum((char *)output->buffer + output->pos, output->save_count);
4,800,130✔
2462
        sixel_advance(output, nwrite);
4,800,540✔
2463
        sixel_putc(output->buffer + output->pos,
5,105,402✔
2464
                   (unsigned char)output->save_pixel);
4,800,161✔
2465
        sixel_advance(output, 1);
4,800,093✔
2466
    } else {
305,241✔
2467
        sixel_output_emit_literal(output,
42,961,051✔
2468
                                  (unsigned char)output->save_pixel,
40,414,302✔
2469
                                  output->save_count);
2,546,749✔
2470
    }
2471

2472
    output->save_pixel = 0;
45,192,117✔
2473
    output->save_count = 0;
45,192,117✔
2474

2475
    return SIXEL_OK;
45,192,117✔
2476
}
2,863,068✔
2477

2478

2479
/*
2480
 * Emit a run of identical SIXEL cells while keeping the existing repeat
2481
 * accumulator intact.  The helper extends the current run when possible and
2482
 * falls back to flushing through DECGRI before starting a new symbol.
2483
 */
2484
static SIXELSTATUS
2485
sixel_emit_run(sixel_output_t *output, int symbol, int count)
45,227,162✔
2486
{
2487
    SIXELSTATUS status = SIXEL_FALSE;
45,227,162✔
2488

2489
    if (count <= 0) {
45,227,162✔
2490
        return SIXEL_OK;
9✔
2491
    }
2492

2493
    if (output->save_count > 0) {
45,227,146✔
2494
        if (output->save_pixel == symbol) {
35,742,122✔
2495
            output->save_count += count;
46,211✔
2496
            return SIXEL_OK;
46,211✔
2497
        }
2498

2499
        status = sixel_put_flash(output);
35,695,911✔
2500
        if (SIXEL_FAILED(status)) {
35,674,075!
2501
            return status;
2502
        }
2503
    }
2,244,234✔
2504

2505
    output->save_pixel = symbol;
45,163,112✔
2506
    output->save_count = count;
45,163,112✔
2507

2508
    return SIXEL_OK;
45,163,112✔
2509
}
2,845,183✔
2510

2511

2512
/*
2513
 * Walk a composed node and coalesce identical columns into runs so the
2514
 * encoder core touches the repeat accumulator only once per symbol.
2515
 */
2516
static SIXELSTATUS
2517
sixel_emit_span_from_map(sixel_output_t *output,
9,547,057✔
2518
                         unsigned char const *map,
2519
                         int length)
2520
{
2521
    SIXELSTATUS status = SIXEL_FALSE;
9,547,057✔
2522
    int index;
4,193,795✔
2523
    int run_length;
4,193,795✔
2524
    unsigned char value;
4,193,795✔
2525
    size_t chunk_size;
4,193,795✔
2526
    unsigned long pattern;
4,193,795✔
2527
    unsigned long block;
4,193,795✔
2528
    int chunk_mismatch;
4,193,795✔
2529
    int remain;
4,193,795✔
2530
    int byte_index;
4,193,795✔
2531

2532
    if (length <= 0) {
9,547,057✔
2533
        return SIXEL_OK;
2534
    }
2535

2536
    for (index = 0; index < length; index += run_length) {
50,937,616✔
2537
        value = map[index];
41,421,292✔
2538
        if (value > '?') {
41,421,292!
UNCOV
2539
            value = 0;
×
2540
        }
2541

2542
        run_length = 1;
41,421,292✔
2543
        chunk_size = sizeof(unsigned long);
41,421,292✔
2544
        chunk_mismatch = 0;
41,421,292✔
2545
        if (chunk_size > 1) {
41,421,292!
2546
            remain = length - (index + run_length);
41,406,822✔
2547
            pattern = (~0UL / 0xffUL) * (unsigned long)value;
41,406,822✔
2548

2549
            while (remain >= (int)chunk_size) {
43,952,358✔
2550
                memcpy(&block,
27,782,247✔
2551
                       map + index + run_length,
26,025,206✔
2552
                       chunk_size);
1,757,041✔
2553
                block ^= pattern;
24,268,165✔
2554
                if (block != 0UL) {
24,268,165✔
2555
                    for (byte_index = 0;
12,718,474✔
2556
                         byte_index < (int)chunk_size;
30,884,171!
2557
                         byte_index++) {
9,161,542✔
2558
                        if ((block & 0xffUL) != 0UL) {
30,891,244✔
2559
                            chunk_mismatch = 1;
12,190,112✔
2560
                            break;
12,190,112✔
2561
                        }
2562
                        block >>= 8;
9,161,542✔
2563
                        run_length += 1;
9,161,542✔
2564
                    }
535,435✔
2565
                    break;
12,185,239✔
2566
                }
2567
                run_length += (int)chunk_size;
2,545,536✔
2568
                remain -= (int)chunk_size;
2,545,536✔
2569
            }
2570
        }
2,611,935✔
2571

2572
        if (!chunk_mismatch) {
41,423,492✔
2573
            while (index + run_length < length) {
25,098,423✔
2574
                unsigned char next;
6,803,611✔
2575

2576
                next = map[index + run_length];
15,570,121✔
2577
                if (next > '?') {
15,570,121!
UNCOV
2578
                    next = 0;
×
2579
                }
2580
                if (next != value) {
15,570,121✔
2581
                    break;
5,762,154✔
2582
                }
2583
                run_length += 1;
5,322,809✔
2584
            }
2585
        }
1,094,799✔
2586

2587
        status = sixel_emit_run(output,
44,016,335✔
2588
                                 (int)value + '?',
23,231,294✔
2589
                                 run_length);
2,605,571✔
2590
        if (SIXEL_FAILED(status)) {
41,390,559!
2591
            return status;
2592
        }
2593
    }
2,605,571✔
2594

2595
    return SIXEL_OK;
5,322,791✔
2596
}
605,019✔
2597

2598

2599
static SIXELSTATUS
2600
sixel_put_pixel(sixel_output_t *const output, int pix)
248,245✔
2601
{
2602
    if (pix < 0 || pix > '?') {
248,245!
2603
        pix = 0;
16✔
2604
    }
16✔
2605

2606
    return sixel_emit_run(output, pix + '?', 1);
248,245✔
2607
}
2608

2609
static SIXELSTATUS
2610
sixel_node_new(sixel_node_t **np, sixel_allocator_t *allocator)
7,447,279✔
2611
{
2612
    SIXELSTATUS status = SIXEL_FALSE;
7,447,279✔
2613

2614
    *np = (sixel_node_t *)sixel_allocator_malloc(allocator,
7,447,279✔
2615
                                                 sizeof(sixel_node_t));
2616
    if (np == NULL) {
7,448,713!
2617
        sixel_helper_set_additional_message(
69✔
2618
            "sixel_node_new: sixel_allocator_malloc() failed.");
2619
        status = SIXEL_BAD_ALLOCATION;
2620
        goto end;
2621
    }
2622

2623
    status = SIXEL_OK;
4,286,274✔
2624

2625
end:
6,856,763✔
2626
    return status;
7,448,644✔
2627
}
2628

2629
static void
2630
sixel_node_del(sixel_output_t *output, sixel_node_t *np)
9,534,850✔
2631
{
2632
    sixel_node_t *tp;
4,193,735✔
2633

2634
    if ((tp = output->node_top) == np) {
9,534,850✔
2635
        output->node_top = np->next;
2,322,975✔
2636
    } else {
145,640✔
2637
        while (tp->next != NULL) {
330,220,992!
2638
            if (tp->next == np) {
330,225,046✔
2639
                tp->next = np->next;
7,215,929✔
2640
                break;
7,215,929✔
2641
            }
2642
            tp = tp->next;
181,429,505✔
2643
        }
2644
    }
2645

2646
    np->next = output->node_free;
9,535,472✔
2647
    output->node_free = np;
9,535,472✔
2648
}
9,535,472✔
2649

2650

2651
static SIXELSTATUS
2652
sixel_put_node(
9,536,597✔
2653
    sixel_output_t /* in */     *output,  /* output context */
2654
    int            /* in/out */ *x,       /* header position */
2655
    sixel_node_t   /* in */     *np,      /* node object */
2656
    int            /* in */     ncolors,  /* number of palette colors */
2657
    int            /* in */     keycolor) /* transparent color number */
2658
{
2659
    SIXELSTATUS status = SIXEL_FALSE;
9,536,597✔
2660
    int nwrite;
4,193,322✔
2661

2662
    if (ncolors != 2 || keycolor == (-1)) {
9,536,597✔
2663
        /* designate palette index */
2664
        if (output->active_palette != np->pal) {
9,533,670✔
2665
            sixel_putc(output->buffer + output->pos, '#');
9,392,091✔
2666
            sixel_advance(output, 1);
9,391,658✔
2667
            nwrite = sixel_putnum((char *)output->buffer + output->pos, np->pal);
9,392,084✔
2668
            sixel_advance(output, nwrite);
9,392,361✔
2669
            output->active_palette = np->pal;
9,392,089✔
2670
        }
596,544✔
2671
    }
604,929✔
2672

2673
    if (*x < np->sx) {
9,536,595✔
2674
        int span;
1,579,553✔
2675

2676
        span = np->sx - *x;
3,595,665✔
2677
        status = sixel_emit_run(output, '?', span);
3,595,665✔
2678
        if (SIXEL_FAILED(status)) {
3,595,455!
2679
            goto end;
2680
        }
2681
        *x = np->sx;
3,595,455✔
2682
    }
228,764✔
2683

2684
    if (*x < np->mx) {
9,536,385!
2685
        int span;
4,193,904✔
2686

2687
        span = np->mx - *x;
9,536,923✔
2688
        status = sixel_emit_span_from_map(output,
14,335,378✔
2689
                                          (unsigned char const *)np->map + *x,
9,536,923✔
2690
                                          span);
604,937✔
2691
        if (SIXEL_FAILED(status)) {
9,535,330!
2692
            goto end;
2693
        }
2694
        *x = np->mx;
9,535,330✔
2695
    }
604,937✔
2696

2697
    status = sixel_put_flash(output);
9,534,792✔
2698
    if (SIXEL_FAILED(status)) {
9,534,525!
2699
        goto end;
2700
    }
2701

2702
end:
8,929,404✔
2703
    return status;
9,534,157✔
2704
}
2705

2706

2707
SIXELSTATUS
2708
sixel_encode_header(int width, int height, int keycolor, sixel_output_t *output)
33,994✔
2709
{
2710
    SIXELSTATUS status = SIXEL_FALSE;
33,994✔
2711
    int p[3] = {0, 0, 0};
33,994✔
2712
    int pcount = 3;
33,994✔
2713
    int use_raster_attributes = 1;
33,994✔
2714

2715
    if (output->ormode) {
33,994✔
2716
        p[0] = 7;
80✔
2717
        p[1] = 5;
80✔
2718
    } else if (sixel_output_has_transparent_offset(output)) {
33,919✔
2719
        /*
2720
         * Transparent offset uses omitted zero cells as positional padding.
2721
         * It therefore needs P2=1 even when the source image has no keycolor.
2722
         */
2723
        p[1] = 1;
56✔
2724
    } else if (keycolor >= 0) {
33,862✔
2725
        if (output->transparent_policy == SIXEL_TRANSPARENT_POLICY_KEEP) {
2,687✔
2726
            /*
2727
             * P2=1 asks the terminal to keep the current image-plane pixel
2728
             * when a zero/transparent SIXEL cell is omitted.
2729
             */
2730
            p[1] = 1;
208✔
2731
        } else if (output->transparent_policy ==
2,492✔
2732
                   SIXEL_TRANSPARENT_POLICY_BACKGROUND) {
2733
            /*
2734
             * Spell out P2=0 for transparent output so the header carries
2735
             * the clear-to-background contract even when P2's default value
2736
             * would be equivalent.
2737
             */
2738
            pcount = 2;
2,463✔
2739
        }
154✔
2740
    }
168✔
2741

2742
    output->pos = 0;
33,994✔
2743

2744
    if (pcount == 3 && p[2] == 0) {
33,994!
2745
        pcount--;
31,531✔
2746
        if (p[1] == 0) {
31,531✔
2747
            pcount--;
31,187✔
2748
            if (p[0] == 0) {
31,187!
2749
                pcount--;
31,187✔
2750
            }
1,965✔
2751
        }
1,965✔
2752
    }
1,987✔
2753

2754
    status = sixel_output_begin_image(output,
36,135✔
2755
                                      width,
2,141✔
2756
                                      height,
2,141✔
2757
                                      p[0],
2,141✔
2758
                                      p[1],
2,141✔
2759
                                      p[2],
2,141✔
2760
                                      pcount,
2,141✔
2761
                                      use_raster_attributes);
2,141✔
2762

2763
    return status;
33,994✔
2764
}
2765

2766

2767
static int
2768
sixel_palette_float_pixelformat_for_colorspace(int colorspace)
52,934✔
2769
{
2770
    switch (colorspace) {
52,934✔
2771
    case SIXEL_COLORSPACE_LINEAR:
112✔
2772
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
126✔
2773
    case SIXEL_COLORSPACE_OKLAB:
312✔
2774
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
351✔
2775
    case SIXEL_COLORSPACE_CIELAB:
88✔
2776
        return SIXEL_PIXELFORMAT_CIELABFLOAT32;
99✔
2777
    case SIXEL_COLORSPACE_DIN99D:
80✔
2778
        return SIXEL_PIXELFORMAT_DIN99DFLOAT32;
90✔
2779
    default:
33,204✔
2780
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
37,375✔
2781
    }
2782
}
4,245✔
2783

2784
static int
2785
sixel_palette_float32_matches_u8(unsigned char const *palette,
3,566✔
2786
                                 float const *palette_float,
2787
                                 size_t count,
2788
                                 int float_pixelformat)
2789
{
2790
    size_t index;
1,668✔
2791
    size_t limit;
1,668✔
2792
    unsigned char expected;
1,668✔
2793
    int channel;
1,668✔
2794

2795
    if (palette == NULL || palette_float == NULL || count == 0U) {
3,566!
2796
        return 1;
2797
    }
2798
    if (count > SIZE_MAX / 3U) {
3,566!
2799
        return 0;
2800
    }
2801
    limit = count * 3U;
3,566✔
2802
    for (index = 0U; index < limit; ++index) {
700,016✔
2803
        channel = (int)(index % 3U);
696,450✔
2804
        expected = sixel_pixelformat_float_channel_to_byte(
1,042,440✔
2805
            float_pixelformat,
2,232✔
2806
            channel,
2,232✔
2807
            palette_float[index]);
696,450✔
2808
        if (palette[index] != expected) {
696,450!
2809
            return 0;
2810
        }
2811
    }
2,232✔
2812
    return 1;
1,898✔
2813
}
114✔
2814

2815
static void
UNCOV
2816
sixel_palette_sync_float32_from_u8(unsigned char const *palette,
×
2817
                                   float *palette_float,
2818
                                   size_t count,
2819
                                   int float_pixelformat)
2820
{
2821
    size_t index;
2822
    size_t limit;
2823
    int channel;
2824

UNCOV
2825
    if (palette == NULL || palette_float == NULL || count == 0U) {
×
2826
        return;
2827
    }
UNCOV
2828
    if (count > SIZE_MAX / 3U) {
×
2829
        return;
2830
    }
2831
    limit = count * 3U;
×
2832
    for (index = 0U; index < limit; ++index) {
×
2833
        channel = (int)(index % 3U);
×
UNCOV
2834
        palette_float[index] = sixel_pixelformat_byte_to_float(
×
2835
            float_pixelformat,
2836
            channel,
UNCOV
2837
            palette[index]);
×
2838
    }
2839
}
2840

2841
static int
2842
sixel_output_palette_channel_to_pct(unsigned char const *palette,
7,912,851✔
2843
                                    float const *palette_float,
2844
                                    int n,
2845
                                    int channel)
2846
{
2847
    size_t index;
3,477,396✔
2848
    float value;
3,477,396✔
2849
    int percent;
3,477,396✔
2850

2851
    index = (size_t)n * 3U + (size_t)channel;
7,912,851✔
2852
    if (palette_float != NULL) {
7,912,851✔
2853
        value = palette_float[index];
689,526✔
2854
        if (value < 0.0f) {
689,526✔
2855
            value = 0.0f;
2856
        } else if (value > 1.0f) {
689,526!
2857
            value = 1.0f;
2858
        }
2859
        percent = (int)(value * 100.0f + 0.5f);
689,526✔
2860
        if (percent < 0) {
689,526!
2861
            percent = 0;
2862
        } else if (percent > 100) {
689,526!
2863
            percent = 100;
2864
        }
2865
        return percent;
689,526✔
2866
    }
2867

2868
    if (palette != NULL) {
7,223,325!
2869
        return (palette[index] * 100 + 127) / 255;
7,223,325✔
2870
    }
2871

2872
    return 0;
2873
}
501,750✔
2874

2875
static double
2876
sixel_output_palette_channel_to_float(unsigned char const *palette,
110,304✔
2877
                                      float const *palette_float,
2878
                                      int n,
2879
                                      int channel)
2880
{
2881
    size_t index;
48,258✔
2882
    double value;
48,258✔
2883

2884
    index = (size_t)n * 3U + (size_t)channel;
110,304✔
2885
    value = 0.0;
110,304✔
2886
    if (palette_float != NULL) {
110,304✔
2887
        value = palette_float[index];
6,144✔
2888
    } else if (palette != NULL) {
104,160!
2889
        value = (double)palette[index] / 255.0;
104,160✔
2890
    }
6,894✔
2891
    if (value < 0.0) {
110,304✔
2892
        value = 0.0;
2893
    } else if (value > 1.0) {
110,304!
2894
        value = 1.0;
2895
    }
2896

2897
    return value;
110,304✔
2898
}
2899

2900
static SIXELSTATUS
2901
output_rgb_palette_definition(
2,639,504✔
2902
    sixel_output_t /* in */ *output,
2903
    unsigned char const /* in */ *palette,
2904
    float const /* in */ *palette_float,
2905
    int            /* in */ n,
2906
    int            /* in */ keycolor
2907
)
2908
{
2909
    SIXELSTATUS status = SIXEL_FALSE;
2,639,504✔
2910
    int nwrite;
1,159,957✔
2911

2912
    if (n != keycolor) {
2,639,504✔
2913
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
2914
        sixel_putc(output->buffer + output->pos, '#');
2,637,617✔
2915
        sixel_advance(output, 1);
2,637,617✔
2916
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
2,637,617✔
2917
        sixel_advance(output, nwrite);
2,637,617✔
2918
        sixel_puts(output->buffer + output->pos, ";2;", 3);
2,637,617✔
2919
        sixel_advance(output, 3);
2,637,617✔
2920
        nwrite = sixel_putnum(
3,796,749✔
2921
            (char *)output->buffer + output->pos,
2,637,617✔
2922
            sixel_output_palette_channel_to_pct(palette,
334,500✔
2923
                                                palette_float,
167,250✔
2924
                                                n,
167,250✔
2925
                                                0));
2926
        sixel_advance(output, nwrite);
2,637,617✔
2927
        sixel_putc(output->buffer + output->pos, ';');
2,637,617✔
2928
        sixel_advance(output, 1);
2,637,617✔
2929
        nwrite = sixel_putnum(
3,796,749✔
2930
            (char *)output->buffer + output->pos,
2,637,617✔
2931
            sixel_output_palette_channel_to_pct(palette,
334,500✔
2932
                                                palette_float,
167,250✔
2933
                                                n,
167,250✔
2934
                                                1));
2935
        sixel_advance(output, nwrite);
2,637,617✔
2936
        sixel_putc(output->buffer + output->pos, ';');
2,637,617✔
2937
        sixel_advance(output, 1);
2,637,617✔
2938
        nwrite = sixel_putnum(
3,796,749✔
2939
            (char *)output->buffer + output->pos,
2,637,617✔
2940
            sixel_output_palette_channel_to_pct(palette,
334,500✔
2941
                                                palette_float,
167,250✔
2942
                                                n,
167,250✔
2943
                                                2));
2944
        sixel_advance(output, nwrite);
2,637,617✔
2945
    }
167,250✔
2946

2947
    status = SIXEL_OK;
2,639,504✔
2948

2949
    return status;
2,639,504✔
2950
}
2951

2952

2953
static SIXELSTATUS
2954
output_hls_palette_definition(
36,768✔
2955
    sixel_output_t /* in */ *output,
2956
    unsigned char const /* in */ *palette,
2957
    float const /* in */ *palette_float,
2958
    int            /* in */ n,
2959
    int            /* in */ keycolor
2960
)
2961
{
2962
    SIXELSTATUS status = SIXEL_FALSE;
36,768✔
2963
    double r;
16,086✔
2964
    double g;
16,086✔
2965
    double b;
16,086✔
2966
    double maxc;
16,086✔
2967
    double minc;
16,086✔
2968
    double lightness;
16,086✔
2969
    double saturation;
16,086✔
2970
    double hue;
16,086✔
2971
    double diff;
16,086✔
2972
    int h;
16,086✔
2973
    int l;
16,086✔
2974
    int s;
16,086✔
2975
    int nwrite;
16,086✔
2976

2977
    if (n != keycolor) {
36,768!
2978
        r = sixel_output_palette_channel_to_float(palette,
39,066✔
2979
                                                  palette_float,
2,298✔
2980
                                                  n,
2,298✔
2981
                                                  0);
2982
        g = sixel_output_palette_channel_to_float(palette,
39,066✔
2983
                                                  palette_float,
2,298✔
2984
                                                  n,
2,298✔
2985
                                                  1);
2986
        b = sixel_output_palette_channel_to_float(palette,
39,066✔
2987
                                                  palette_float,
2,298✔
2988
                                                  n,
2,298✔
2989
                                                  2);
2990
        maxc = r > g ? (r > b ? r : b) : (g > b ? g : b);
36,768✔
2991
        minc = r < g ? (r < b ? r : b) : (g < b ? g : b);
36,768✔
2992
        lightness = (maxc + minc) * 0.5;
36,768✔
2993
        saturation = 0.0;
36,768✔
2994
        hue = 0.0;
36,768✔
2995
        diff = maxc - minc;
36,768✔
2996
        if (diff <= 0.0) {
36,768✔
2997
            h = 0;
2,241✔
2998
            s = 0;
2,241✔
2999
        } else {
249✔
3000
            if (lightness < 0.5) {
32,784✔
3001
                saturation = diff / (maxc + minc);
27,876✔
3002
            } else {
1,741✔
3003
                saturation = diff / (2.0 - maxc - minc);
4,908✔
3004
            }
3005
            if (maxc == r) {
32,784✔
3006
                hue = (g - b) / diff;
22,380✔
3007
            } else if (maxc == g) {
11,803✔
3008
                hue = 2.0 + (b - r) / diff;
10,104✔
3009
            } else {
631✔
3010
                hue = 4.0 + (r - g) / diff;
300✔
3011
            }
3012
            hue *= 60.0;
32,784✔
3013
            if (hue < 0.0) {
32,784✔
3014
                hue += 360.0;
16✔
3015
            }
1✔
3016
            if (hue >= 360.0) {
32,784!
UNCOV
3017
                hue -= 360.0;
×
3018
            }
3019
            /*
3020
             * The DEC HLS color wheel used by DECGCI considers
3021
             * hue==0 to be blue instead of red.  Rotate the hue by
3022
             * +120 degrees so that RGB primaries continue to match
3023
             * the historical img2sixel output and VT340 behavior.
3024
             */
3025
            hue += 120.0;
32,784✔
3026
            while (hue >= 360.0) {
32,844✔
3027
                hue -= 360.0;
60✔
3028
            }
3029
            while (hue < 0.0) {
32,784!
UNCOV
3030
                hue += 360.0;
×
3031
            }
3032
            s = (int)(saturation * 100.0 + 0.5);
32,784✔
3033
            if (s < 0) {
32,784!
3034
                s = 0;
3035
            } else if (s > 100) {
32,784!
3036
                s = 100;
3037
            }
3038
            h = (int)(hue + 0.5);
32,784✔
3039
            if (h >= 360) {
32,784!
UNCOV
3040
                h -= 360;
×
3041
            } else if (h < 0) {
32,784!
3042
                h = 0;
3043
            }
3044
        }
3045
        l = (int)(lightness * 100.0 + 0.5);
36,768✔
3046
        if (l < 0) {
36,768!
3047
            l = 0;
3048
        } else if (l > 100) {
36,768!
3049
            l = 100;
3050
        }
3051
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
3052
        sixel_putc(output->buffer + output->pos, '#');
36,768✔
3053
        sixel_advance(output, 1);
36,768✔
3054
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
36,768✔
3055
        sixel_advance(output, nwrite);
36,768✔
3056
        sixel_puts(output->buffer + output->pos, ";1;", 3);
36,768✔
3057
        sixel_advance(output, 3);
36,768✔
3058
        nwrite = sixel_putnum((char *)output->buffer + output->pos, h);
36,768✔
3059
        sixel_advance(output, nwrite);
36,768✔
3060
        sixel_putc(output->buffer + output->pos, ';');
36,768✔
3061
        sixel_advance(output, 1);
36,768✔
3062
        nwrite = sixel_putnum((char *)output->buffer + output->pos, l);
36,768✔
3063
        sixel_advance(output, nwrite);
36,768✔
3064
        sixel_putc(output->buffer + output->pos, ';');
36,768✔
3065
        sixel_advance(output, 1);
36,768✔
3066
        nwrite = sixel_putnum((char *)output->buffer + output->pos, s);
36,768✔
3067
        sixel_advance(output, nwrite);
36,768✔
3068
    }
2,298✔
3069

3070
    status = SIXEL_OK;
36,768✔
3071
    return status;
36,768✔
3072
}
3073

3074

3075
static void
3076
sixel_encode_work_init(sixel_encode_work_t *work)
60,141✔
3077
{
3078
    work->map = NULL;
60,141✔
3079
    work->map_size = 0;
60,141✔
3080
    work->columns = NULL;
60,141✔
3081
    work->columns_size = 0;
60,141✔
3082
    work->active_colors = NULL;
60,141✔
3083
    work->active_colors_size = 0;
60,141✔
3084
    work->active_color_index = NULL;
60,141✔
3085
    work->active_color_index_size = 0;
60,141✔
3086
    work->requested_threads = 1;
60,141✔
3087
}
34,719✔
3088

3089
static SIXELSTATUS
3090
sixel_encode_work_allocate(sixel_encode_work_t *work,
57,437✔
3091
                           int width,
3092
                           int ncolors,
3093
                           sixel_allocator_t *allocator)
3094
{
3095
    SIXELSTATUS status = SIXEL_FALSE;
57,437✔
3096
    int len;
24,291✔
3097
    size_t columns_size;
24,291✔
3098
    size_t active_colors_size;
24,291✔
3099
    size_t active_color_index_size;
24,291✔
3100

3101
    len = ncolors * width;
57,437✔
3102
    work->map = (char *)sixel_allocator_calloc(allocator,
61,524✔
3103
                                               (size_t)len,
4,087✔
3104
                                               sizeof(char));
3105
    if (work->map == NULL && len > 0) {
57,443!
UNCOV
3106
        sixel_helper_set_additional_message(
×
3107
            "sixel_encode_body: sixel_allocator_calloc() failed.");
3108
        status = SIXEL_BAD_ALLOCATION;
×
UNCOV
3109
        goto end;
×
3110
    }
3111
    work->map_size = (size_t)len;
57,443✔
3112

3113
    columns_size = sizeof(sixel_node_t *) * (size_t)width;
57,443✔
3114
    if (width > 0) {
57,443!
3115
        work->columns = (sixel_node_t **)sixel_allocator_malloc(
57,441✔
3116
            allocator,
4,087✔
3117
            columns_size);
4,087✔
3118
        if (work->columns == NULL) {
57,443!
UNCOV
3119
            sixel_helper_set_additional_message(
×
3120
                "sixel_encode_body: sixel_allocator_malloc() failed.");
3121
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
3122
            goto end;
×
3123
        }
3124
        memset(work->columns, 0, columns_size);
57,443✔
3125
        work->columns_size = columns_size;
57,443✔
3126
    } else {
4,087✔
3127
        work->columns = NULL;
2✔
3128
        work->columns_size = 0;
2✔
3129
    }
3130

3131
    active_colors_size = (size_t)ncolors;
57,445✔
3132
    if (active_colors_size > 0) {
57,445!
3133
        work->active_colors =
81,741✔
3134
            (unsigned char *)sixel_allocator_malloc(allocator,
61,531✔
3135
                                                    active_colors_size);
4,087✔
3136
        if (work->active_colors == NULL) {
57,445!
UNCOV
3137
            sixel_helper_set_additional_message(
×
3138
                "sixel_encode_body: sixel_allocator_malloc() failed.");
3139
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
3140
            goto end;
×
3141
        }
3142
        memset(work->active_colors, 0, active_colors_size);
57,445✔
3143
        work->active_colors_size = active_colors_size;
57,445✔
3144
    } else {
4,087✔
3145
        work->active_colors = NULL;
2✔
3146
        work->active_colors_size = 0;
2✔
3147
    }
3148

3149
    active_color_index_size = sizeof(int) * (size_t)ncolors;
57,446✔
3150
    if (active_color_index_size > 0) {
57,446!
3151
        work->active_color_index = (int *)sixel_allocator_malloc(
57,444✔
3152
            allocator,
4,087✔
3153
            active_color_index_size);
4,087✔
3154
        if (work->active_color_index == NULL) {
57,445!
3155
            sixel_helper_set_additional_message(
1✔
3156
                "sixel_encode_body: sixel_allocator_malloc() failed.");
3157
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
3158
            goto end;
×
3159
        }
3160
        memset(work->active_color_index, 0, active_color_index_size);
57,444✔
3161
        work->active_color_index_size = active_color_index_size;
57,444✔
3162
    } else {
4,087✔
3163
        work->active_color_index = NULL;
3✔
3164
        work->active_color_index_size = 0;
3✔
3165
    }
3166

3167
    status = SIXEL_OK;
33,149✔
3168

3169
end:
29,062✔
3170
    if (SIXEL_FAILED(status)) {
47,429!
3171
        if (work->active_color_index != NULL) {
×
3172
            sixel_allocator_free(allocator, work->active_color_index);
×
UNCOV
3173
            work->active_color_index = NULL;
×
3174
        }
3175
        work->active_color_index_size = 0;
×
3176
        if (work->active_colors != NULL) {
×
3177
            sixel_allocator_free(allocator, work->active_colors);
×
UNCOV
3178
            work->active_colors = NULL;
×
3179
        }
3180
        work->active_colors_size = 0;
×
3181
        if (work->columns != NULL) {
×
3182
            sixel_allocator_free(allocator, work->columns);
×
UNCOV
3183
            work->columns = NULL;
×
3184
        }
3185
        work->columns_size = 0;
×
3186
        if (work->map != NULL) {
×
3187
            sixel_allocator_free(allocator, work->map);
×
UNCOV
3188
            work->map = NULL;
×
3189
        }
UNCOV
3190
        work->map_size = 0;
×
3191
    }
3192

3193
    return status;
57,446✔
3194
}
3195

3196
static void
3197
sixel_encode_work_cleanup(sixel_encode_work_t *work,
60,147✔
3198
                          sixel_allocator_t *allocator)
3199
{
3200
    if (work->active_color_index != NULL) {
60,147✔
3201
        sixel_allocator_free(allocator, work->active_color_index);
57,446✔
3202
        work->active_color_index = NULL;
57,446✔
3203
    }
4,087✔
3204
    work->active_color_index_size = 0;
60,147✔
3205
    if (work->active_colors != NULL) {
60,147✔
3206
        sixel_allocator_free(allocator, work->active_colors);
57,446✔
3207
        work->active_colors = NULL;
57,446✔
3208
    }
4,087✔
3209
    work->active_colors_size = 0;
60,147✔
3210
    if (work->columns != NULL) {
60,147✔
3211
        sixel_allocator_free(allocator, work->columns);
57,446✔
3212
        work->columns = NULL;
57,446✔
3213
    }
4,087✔
3214
    work->columns_size = 0;
60,147✔
3215
    if (work->map != NULL) {
60,147✔
3216
        sixel_allocator_free(allocator, work->map);
57,446✔
3217
        work->map = NULL;
57,446✔
3218
    }
4,087✔
3219
    work->map_size = 0;
60,147✔
3220
}
60,147✔
3221

3222
static void
3223
sixel_band_state_reset(sixel_band_state_t *state)
281,194✔
3224
{
3225
    state->row_in_band = 0;
281,194✔
3226
    state->fillable = 0;
281,194✔
3227
    state->fill_mask = 0;
281,194✔
3228
    state->active_color_count = 0;
278,866!
3229
}
173,687✔
3230

3231
static void
3232
sixel_band_finish(sixel_encode_work_t *work, sixel_band_state_t *state)
200,939✔
3233
{
3234
    int color_index;
88,079✔
3235
    int c;
88,079✔
3236

3237
    if (work->active_colors == NULL
200,939!
3238
        || work->active_color_index == NULL) {
200,938!
3239
        state->active_color_count = 0;
×
UNCOV
3240
        return;
×
3241
    }
3242

3243
    for (color_index = 0;
519,434✔
3244
         color_index < state->active_color_count;
6,580,467✔
3245
         color_index++) {
6,379,528✔
3246
        c = work->active_color_index[color_index];
6,379,544✔
3247
        if (c >= 0
6,379,544!
3248
            && (size_t)c < work->active_colors_size) {
6,379,546!
3249
            work->active_colors[c] = 0;
6,379,547✔
3250
        }
406,582✔
3251
    }
406,814✔
3252
    state->active_color_count = 0;
200,923✔
3253
}
12,708✔
3254

3255
static void
3256
sixel_band_clear_map(sixel_encode_work_t *work)
200,925✔
3257
{
3258
    if (work->map != NULL && work->map_size > 0) {
200,925!
3259
        memset(work->map, 0, work->map_size);
200,927✔
3260
    }
12,722✔
3261
}
200,925✔
3262

3263
static SIXELSTATUS
3264
sixel_encode_emit_palette(int bodyonly,
35,642✔
3265
                          int ncolors,
3266
                          int keycolor,
3267
                          unsigned char const *palette,
3268
                          float const *palette_float,
3269
                          sixel_output_t *output)
3270
{
3271
    SIXELSTATUS status = SIXEL_FALSE;
35,642✔
3272
    int n;
15,599✔
3273

3274
    if (bodyonly || (ncolors == 2 && keycolor != (-1))) {
35,642!
3275
        return SIXEL_OK;
450✔
3276
    }
3277

3278
    if (palette == NULL && palette_float == NULL) {
34,842!
UNCOV
3279
        sixel_helper_set_additional_message(
×
3280
            "sixel_encode_emit_palette: missing palette data.");
UNCOV
3281
        return SIXEL_BAD_ARGUMENT;
×
3282
    }
3283

3284
    if (output->palette_type == SIXEL_PALETTETYPE_HLS) {
34,842✔
3285
        for (n = 0; n < ncolors; n++) {
36,912✔
3286
            status = output_hls_palette_definition(output,
39,066✔
3287
                                                   palette,
2,298✔
3288
                                                   palette_float,
2,298✔
3289
                                                   n,
2,298✔
3290
                                                   keycolor);
2,298✔
3291
            if (SIXEL_FAILED(status)) {
36,768!
3292
                goto end;
3293
            }
3294
        }
2,298✔
3295
    } else {
9✔
3296
        for (n = 0; n < ncolors; n++) {
2,669,050✔
3297
            status = output_rgb_palette_definition(output,
2,801,396✔
3298
                                                   palette,
167,044✔
3299
                                                   palette_float,
167,044✔
3300
                                                   n,
167,044✔
3301
                                                   keycolor);
167,044✔
3302
            if (SIXEL_FAILED(status)) {
2,634,352!
3303
                goto end;
3304
            }
3305
        }
167,044✔
3306
    }
3307

3308
    status = SIXEL_OK;
19,593✔
3309

3310
end:
32,998✔
3311
    return status;
19,593✔
3312
}
2,244✔
3313

3314
static SIXELSTATUS
3315
sixel_band_classify_row(sixel_encode_work_t *work,
1,122,974✔
3316
                        sixel_band_state_t *state,
3317
                        sixel_index_t *pixels,
3318
                        int width,
3319
                        int height,
3320
                        int map_width,
3321
                        int absolute_row,
3322
                        int offset_left,
3323
                        int offset_top,
3324
                        int ncolors,
3325
                        int keycolor,
3326
                        unsigned char *palstate,
3327
                        int encode_policy)
3328
{
3329
    SIXELSTATUS status = SIXEL_FALSE;
1,122,974✔
3330
    int row_bit;
492,289✔
3331
    int band_start;
492,289✔
3332
    int source_row;
492,289✔
3333
    int source_band_start;
492,289✔
3334
    int target_x;
492,289✔
3335
    int pix;
492,289✔
3336
    int x;
492,289✔
3337
    int check_integer_overflow;
492,289✔
3338
    int source_index_base;
492,289✔
3339
    char *map;
492,289✔
3340
    unsigned char *active_colors;
492,289✔
3341
    int *active_color_index;
492,289✔
3342

3343
    map = work->map;
1,122,974✔
3344
    active_colors = work->active_colors;
1,122,974✔
3345
    active_color_index = work->active_color_index;
1,122,974✔
3346
    row_bit = state->row_in_band;
1,122,974✔
3347
    band_start = absolute_row - row_bit;
1,122,974✔
3348

3349
    if (row_bit == 0) {
1,122,974✔
3350
        if (encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
200,930✔
3351
            state->fillable = 0;
200,708✔
3352
        } else if (palstate) {
12,930!
3353
            source_band_start = band_start - offset_top;
×
NEW
3354
            if (source_band_start < 0) {
×
3355
                source_band_start = 0;
3356
            }
UNCOV
3357
            if (width > 0) {
×
NEW
3358
                if (source_band_start >= height) {
×
3359
                    state->fillable = 0;
×
3360
                } else {
3361
                    pix = pixels[source_band_start * width];
×
3362
                    if (pix >= ncolors) {
×
UNCOV
3363
                        state->fillable = 0;
×
3364
                    } else {
UNCOV
3365
                        state->fillable = 1;
×
3366
                    }
3367
                }
3368
            } else {
UNCOV
3369
                state->fillable = 0;
×
3370
            }
3371
        } else {
3372
            state->fillable = 1;
222✔
3373
        }
3374
        state->active_color_count = 0;
200,930✔
3375
    }
12,722✔
3376

3377
    source_row = absolute_row - offset_top;
1,122,974✔
3378
    if (source_row < 0 || source_row >= height) {
1,122,974!
3379
        state->row_in_band += 1;
259✔
3380
        return SIXEL_OK;
259✔
3381
    }
3382
    state->fill_mask |= (1 << row_bit);
1,122,781✔
3383
    if (source_row > INT_MAX / width) {
1,122,781!
UNCOV
3384
        sixel_helper_set_additional_message(
×
3385
            "sixel_encode_body: integer overflow detected."
3386
            " (source_y > INT_MAX)");
3387
        status = SIXEL_BAD_INTEGER_OVERFLOW;
×
UNCOV
3388
        goto end;
×
3389
    }
3390
    source_index_base = source_row * width;
1,122,781✔
3391

3392
    for (x = 0; x < width; x++) {
135,678,470✔
3393
        target_x = offset_left + x;
134,573,854✔
3394
        if (target_x < 0 || target_x >= map_width) {
134,573,854!
3395
            sixel_helper_set_additional_message(
83,907✔
3396
                "sixel_encode_body: transparent offset is out of range.");
3397
            status = SIXEL_BAD_INTEGER_OVERFLOW;
65,780✔
3398
            goto end;
65,780✔
3399
        }
3400
        check_integer_overflow = source_index_base;
134,555,727✔
3401
        if (check_integer_overflow > INT_MAX - x) {
134,555,727!
UNCOV
3402
            sixel_helper_set_additional_message(
×
3403
                "sixel_encode_body: integer overflow detected."
3404
                " (source_y * width > INT_MAX - x)");
3405
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
UNCOV
3406
            goto end;
×
3407
        }
3408
        pix = pixels[check_integer_overflow + x];
134,555,727✔
3409
        if (pix >= 0 && pix < ncolors && pix != keycolor) {
134,555,727!
3410
            if (pix > INT_MAX / map_width) {
114,425,516!
UNCOV
3411
                sixel_helper_set_additional_message(
×
3412
                    "sixel_encode_body: integer overflow detected."
3413
                    " (pix > INT_MAX / width)");
3414
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
UNCOV
3415
                goto end;
×
3416
            }
3417
            check_integer_overflow = pix * map_width;
114,425,516✔
3418
            if (check_integer_overflow > INT_MAX - target_x) {
114,425,516!
UNCOV
3419
                sixel_helper_set_additional_message(
×
3420
                    "sixel_encode_body: integer overflow detected."
3421
                    " (pix * width > INT_MAX - x)");
3422
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
UNCOV
3423
                goto end;
×
3424
            }
3425
            map[check_integer_overflow + target_x] |= (1 << row_bit);
114,425,516✔
3426
            if (active_colors != NULL && active_colors[pix] == 0) {
114,425,516!
3427
                active_colors[pix] = 1;
6,370,457✔
3428
                if (state->active_color_count < ncolors
6,370,457!
3429
                    && active_color_index != NULL) {
6,376,872!
3430
                    active_color_index[state->active_color_count] = pix;
6,376,873✔
3431
                    state->active_color_count += 1;
6,376,873✔
3432
                }
406,386✔
3433
            }
406,429✔
3434
        } else if (!palstate) {
27,162,972✔
3435
            state->fillable = 0;
15,436,748✔
3436
        }
952,883✔
3437
    }
8,286,861✔
3438

3439
    state->row_in_band += 1;
1,104,616✔
3440
    status = SIXEL_OK;
1,104,616✔
3441

3442
end:
825,515✔
3443
    return status;
612,413✔
3444
}
71,148✔
3445

3446
static SIXELSTATUS
3447
sixel_band_compose(sixel_encode_work_t *work,
200,941✔
3448
                   sixel_band_state_t *state,
3449
                   sixel_output_t *output,
3450
                   int width,
3451
                   int ncolors,
3452
                   int keycolor,
3453
                   sixel_allocator_t *allocator)
3454
{
3455
    SIXELSTATUS status = SIXEL_FALSE;
200,941✔
3456
    int color_index;
88,079✔
3457
    int c;
88,079✔
3458
    unsigned char *row;
88,079✔
3459
    int sx;
88,079✔
3460
    int mx;
88,079✔
3461
    int gap;
88,079✔
3462
    int gap_reached_end;
88,079✔
3463
    sixel_node_t *np;
88,079✔
3464
    sixel_node_t *column_head;
88,079✔
3465
    sixel_node_t *column_tail;
88,079✔
3466
    sixel_node_t top;
88,079✔
3467

3468
    (void)ncolors;
100,804✔
3469
    (void)keycolor;
100,804✔
3470
    row = NULL;
200,941✔
3471
    gap_reached_end = 0;
200,941✔
3472
    np = NULL;
200,941✔
3473
    column_head = NULL;
200,941✔
3474
    column_tail = NULL;
200,941✔
3475
    top.next = NULL;
200,941✔
3476

3477
    if (work->columns != NULL) {
200,941!
3478
        memset(work->columns, 0, work->columns_size);
200,938✔
3479
    }
12,724✔
3480
    output->node_top = NULL;
200,941✔
3481

3482
    for (color_index = 0;
607,329✔
3483
         color_index < state->active_color_count;
6,582,277✔
3484
         color_index++) {
6,381,338✔
3485
        c = work->active_color_index[color_index];
6,377,709✔
3486
        row = (unsigned char *)(work->map + c * width);
6,377,709✔
3487
        sx = 0;
6,377,709✔
3488
        while (sx < width) {
15,917,097✔
3489
            sx = sixel_compose_find_run_start(
15,465,445✔
3490
                row,
983,493✔
3491
                width,
983,493✔
3492
                sx);
983,493✔
3493
            if (sx >= width) {
15,465,690✔
3494
                break;
3,320,059✔
3495
            }
3496

3497
            mx = sx + 1;
9,536,004✔
3498
            while (mx < width) {
53,974,160✔
3499
                if (row[mx] != 0) {
53,526,019✔
3500
                    mx += 1;
36,793,062✔
3501
                    continue;
36,793,062✔
3502
                }
3503

3504
                gap = sixel_compose_measure_gap(
16,732,957✔
3505
                    row,
1,062,859✔
3506
                    width,
1,062,859✔
3507
                    mx + 1,
1,062,859✔
3508
                    &gap_reached_end);
3509
                if (gap >= 9 || gap_reached_end) {
16,730,343✔
3510
                    break;
576,640✔
3511
                }
3512
                mx += gap + 1;
7,645,094✔
3513
            }
3514

3515
            if ((np = output->node_free) != NULL) {
9,533,390✔
3516
                output->node_free = np->next;
2,089,353✔
3517
            } else {
13,762✔
3518
                status = sixel_node_new(&np, allocator);
7,444,037✔
3519
                if (SIXEL_FAILED(status)) {
7,448,165!
3520
                    goto end;
3521
                }
3522
            }
3523

3524
            np->pal = c;
9,539,388✔
3525
            np->sx = sx;
9,539,388✔
3526
            np->mx = mx;
9,539,388✔
3527
            np->map = (char *)row;
9,539,388✔
3528
            np->next = NULL;
9,539,388✔
3529

3530
            if (work->columns != NULL) {
9,539,388!
3531
                column_head = work->columns[sx];
9,539,249✔
3532
                if (column_head == NULL
9,539,249✔
3533
                    || column_head->mx <= np->mx) {
4,597,545✔
3534
                    np->next = column_head;
7,489,859✔
3535
                    work->columns[sx] = np;
7,489,859✔
3536
                } else {
473,843✔
3537
                    column_tail = column_head;
1,147,405✔
3538
                    while (column_tail->next != NULL
2,503,394✔
3539
                           && column_tail->next->mx > np->mx) {
2,742,557✔
3540
                        column_tail = column_tail->next;
388,155✔
3541
                    }
3542
                    np->next = column_tail->next;
2,049,390✔
3543
                    column_tail->next = np;
2,049,390✔
3544
                }
3545
            } else {
605,608✔
3546
                top.next = output->node_top;
195✔
3547
                column_tail = &top;
195✔
3548

3549
                while (column_tail->next != NULL) {
195!
UNCOV
3550
                    if (np->sx < column_tail->next->sx) {
×
3551
                        break;
3552
                    } else if (np->sx == column_tail->next->sx
×
UNCOV
3553
                               && np->mx > column_tail->next->mx) {
×
3554
                        break;
3555
                    }
3556
                    column_tail = column_tail->next;
3557
                }
3558

3559
                np->next = column_tail->next;
195✔
3560
                column_tail->next = np;
195✔
3561
                output->node_top = top.next;
195✔
3562
            }
3563

3564
            sx = mx;
7,779,614✔
3565
        }
3566
    }
406,388✔
3567

3568
    if (work->columns != NULL) {
204,568!
3569
        top.next = NULL;
200,940✔
3570
        column_tail = &top;
200,940✔
3571
        for (sx = 0; sx < width; sx++) {
23,346,853✔
3572
            column_head = work->columns[sx];
23,145,913✔
3573
            if (column_head == NULL) {
23,145,913✔
3574
                continue;
17,874,642✔
3575
            }
3576
            column_tail->next = column_head;
5,271,271✔
3577
            while (column_tail->next != NULL) {
14,806,174✔
3578
                column_tail = column_tail->next;
5,344,193✔
3579
            }
3580
            work->columns[sx] = NULL;
5,271,271✔
3581
        }
332,324✔
3582
        output->node_top = top.next;
200,940✔
3583
    }
12,724✔
3584

3585
    status = SIXEL_OK;
204,570✔
3586

3587
end:
191,845✔
3588
    return status;
204,570✔
3589
}
3590

3591
static SIXELSTATUS
3592
sixel_band_emit(sixel_encode_work_t *work,
200,926✔
3593
                sixel_band_state_t *state,
3594
                sixel_output_t *output,
3595
                int ncolors,
3596
                int keycolor,
3597
                int last_row_index)
3598
{
3599
    SIXELSTATUS status = SIXEL_FALSE;
200,926✔
3600
    sixel_node_t *np;
88,075✔
3601
    sixel_node_t *next;
88,075✔
3602
    int x;
88,075✔
3603
    int emit_next_line;
88,075✔
3604

3605
    emit_next_line = (last_row_index >= 6);
200,926✔
3606
    if (emit_next_line) {
200,926✔
3607
        /*
3608
         * Emit DECGNL only after the first band. The first band starts at the
3609
         * origin, so leading DECGNL would shift short images down by 6 rows.
3610
         */
3611
        output->buffer[output->pos] = '-';
165,346✔
3612
        sixel_advance(output, 1);
165,346✔
3613
    }
10,484✔
3614

3615
    for (x = 0; (np = output->node_top) != NULL;) {
1,489,187✔
3616
        if (x > np->sx) {
1,288,316✔
3617
            output->buffer[output->pos] = '$';
1,094,107✔
3618
            sixel_advance(output, 1);
1,094,107✔
3619
            x = 0;
1,094,095✔
3620
        }
70,024✔
3621

3622
        if (state->fillable) {
1,288,304✔
3623
            memset(np->map + np->sx,
252✔
3624
                   state->fill_mask,
14✔
3625
                   (size_t)(np->mx - np->sx));
224✔
3626
        }
14✔
3627
        status = sixel_put_node(output,
1,370,625✔
3628
                                &x,
3629
                                np,
82,321✔
3630
                                ncolors,
82,321✔
3631
                                keycolor);
82,321✔
3632
        if (SIXEL_FAILED(status)) {
1,288,240!
3633
            goto end;
3634
        }
3635
        next = np->next;
1,288,240✔
3636
        sixel_node_del(output, np);
1,288,240✔
3637
        np = next;
1,288,246✔
3638

3639
        while (np != NULL) {
50,665,152✔
3640
            if (np->sx < x) {
49,376,883✔
3641
                np = np->next;
41,127,859✔
3642
                continue;
41,127,859✔
3643
            }
3644

3645
            if (state->fillable) {
8,249,024!
3646
                memset(np->map + np->sx,
×
3647
                       state->fill_mask,
UNCOV
3648
                       (size_t)(np->mx - np->sx));
×
3649
            }
3650
            status = sixel_put_node(output,
8,771,578✔
3651
                                    &x,
3652
                                    np,
522,554✔
3653
                                    ncolors,
522,554✔
3654
                                    keycolor);
522,554✔
3655
            if (SIXEL_FAILED(status)) {
8,247,233!
3656
                goto end;
3657
            }
3658
            next = np->next;
8,247,233✔
3659
            sixel_node_del(output, np);
8,247,233✔
3660
            np = next;
8,248,785✔
3661
        }
3662

3663
        state->fillable = 0;
1,288,268✔
3664
    }
3665

3666
    status = SIXEL_OK;
112,811✔
3667

3668
end:
188,152✔
3669
    (void)work;
100,779✔
3670
    return status;
200,871✔
3671
}
3672

3673

3674
SIXELSTATUS
3675
sixel_encode_body(
16,704✔
3676
    sixel_index_t       /* in */ *pixels,
3677
    int                 /* in */ width,
3678
    int                 /* in */ height,
3679
    unsigned char       /* in */ *palette,
3680
    float const         /* in */ *palette_float,
3681
    int                 /* in */ ncolors,
3682
    int                 /* in */ keycolor,
3683
    int                 /* in */ bodyonly,
3684
    sixel_output_t      /* in */ *output,
3685
    unsigned char       /* in */ *palstate,
3686
    sixel_allocator_t   /* in */ *allocator,
3687
    int                 /* in */ pin_threads,
3688
    sixel_timeline_logger_t      /* in */ *logger)
3689
{
3690
    SIXELSTATUS status = SIXEL_FALSE;
16,704✔
3691
    int band_start;
7,688✔
3692
    int band_height;
7,688✔
3693
    int row_index;
7,688✔
3694
    int absolute_row;
7,688✔
3695
    int last_row_index;
7,688✔
3696
    int encoded_width;
7,688✔
3697
    int encoded_height;
7,688✔
3698
    int offset_left;
7,688✔
3699
    int offset_top;
7,688✔
3700
    sixel_node_t *np;
7,688✔
3701
    sixel_encode_work_t work;
7,688✔
3702
    sixel_band_state_t band;
7,688✔
3703
    int logging_active;
7,688✔
3704
    int job_index;
7,688✔
3705

3706
#if !SIXEL_ENABLE_THREADS
3707
    (void) pin_threads;
4,408✔
3708
#endif
3709

3710
    sixel_encode_work_init(&work);
16,704✔
3711
    sixel_band_state_reset(&band);
16,704✔
3712

3713
    /* Record the caller/environment preference even before we fan out. */
3714
    work.requested_threads = sixel_threads_resolve();
16,704✔
3715

3716
    if (ncolors < 1) {
16,704!
3717
        status = SIXEL_BAD_ARGUMENT;
×
UNCOV
3718
        goto cleanup;
×
3719
    }
3720
    output->active_palette = (-1);
16,704✔
3721

3722
    status = sixel_output_compute_transparent_extent(output,
17,355✔
3723
                                                     width,
651✔
3724
                                                     height,
651✔
3725
                                                     &encoded_width,
3726
                                                     &encoded_height);
3727
    if (SIXEL_FAILED(status)) {
16,704!
UNCOV
3728
        goto cleanup;
×
3729
    }
3730
    offset_left = output->transparent_offset_left;
16,704✔
3731
    offset_top = output->transparent_offset_top;
16,704✔
3732

3733
    logging_active = logger != NULL;
16,704✔
3734
    job_index = 0;
16,704✔
3735

3736
    status = sixel_encode_emit_palette(bodyonly,
17,355✔
3737
                                       ncolors,
651✔
3738
                                       keycolor,
651✔
3739
                                       palette,
651✔
3740
                                       palette_float,
651✔
3741
                                       output);
651✔
3742
    if (SIXEL_FAILED(status)) {
16,704!
UNCOV
3743
        goto cleanup;
×
3744
    }
3745

3746
#if SIXEL_ENABLE_THREADS
3747
    {
3748
        int nbands;
3,280✔
3749
        int threads;
3,280✔
3750

3751
        nbands = sixel_count_sixel_bands(encoded_height);
7,888!
3752
        threads = work.requested_threads;
7,888✔
3753
        if (nbands > 1 && threads > 1) {
7,888✔
3754
            status = sixel_encode_body_parallel(pixels,
2,773✔
3755
                                                width,
214✔
3756
                                                height,
214✔
3757
                                                ncolors,
214✔
3758
                                                keycolor,
214✔
3759
                                                output,
214✔
3760
                                                palstate,
214✔
3761
                                                allocator,
214✔
3762
                                                threads,
214✔
3763
                                                pin_threads);
214✔
3764
            if (SIXEL_FAILED(status)) {
2,559!
3765
                goto cleanup;
3766
            }
3767
            goto finalize;
2,559✔
3768
        }
3769
    }
3770
#endif
3771

3772
    if (logging_active) {
14,145!
3773
        sixel_timeline_logger_logf(logger,
24✔
3774
                          "controller",
3775
                          "encode",
3776
                          "configure",
3777
                          -1);
3778
    }
2✔
3779

3780
    status = sixel_encode_work_allocate(&work,
14,145✔
3781
                                        encoded_width,
437✔
3782
                                        ncolors,
437✔
3783
                                        allocator);
437✔
3784
    if (SIXEL_FAILED(status)) {
14,145!
UNCOV
3785
        goto cleanup;
×
3786
    }
3787

3788
    band_start = 0;
7,524✔
3789
    while (band_start < encoded_height) {
83,756✔
3790
        band_height = encoded_height - band_start;
69,611✔
3791
        if (band_height > 6) {
69,611✔
3792
            band_height = 6;
28,956✔
3793
        }
1,204✔
3794

3795
        band.row_in_band = 0;
69,611✔
3796
        band.fillable = 0;
69,611✔
3797
        band.fill_mask = 0;
69,611✔
3798
        band.active_color_count = 0;
69,611✔
3799

3800
        if (logging_active) {
69,611!
3801
            sixel_timeline_logger_logf(logger,
52✔
3802
                              "worker",
3803
                              "encode",
3804
                              "start",
3805
                              job_index);
4✔
3806
        }
4✔
3807

3808
        for (row_index = 0; row_index < band_height; row_index++) {
448,813✔
3809
            absolute_row = band_start + row_index;
379,202✔
3810
            status = sixel_band_classify_row(&work,
379,202✔
3811
                                             &band,
3812
                                             pixels,
8,374✔
3813
                                             width,
8,374✔
3814
                                             height,
8,374✔
3815
                                             encoded_width,
8,374✔
3816
                                             absolute_row,
8,374✔
3817
                                             offset_left,
8,374✔
3818
                                             offset_top,
8,374✔
3819
                                             ncolors,
8,374✔
3820
                                             keycolor,
8,374✔
3821
                                             palstate,
8,374✔
3822
                                             output->encode_policy);
8,374✔
3823
            if (SIXEL_FAILED(status)) {
379,202!
UNCOV
3824
                goto cleanup;
×
3825
            }
3826
        }
8,374✔
3827

3828
        status = sixel_band_compose(&work,
69,611✔
3829
                                    &band,
3830
                                    output,
1,641✔
3831
                                    encoded_width,
1,641✔
3832
                                    ncolors,
1,641✔
3833
                                    keycolor,
1,641✔
3834
                                    allocator);
1,641✔
3835
        if (SIXEL_FAILED(status)) {
69,611!
3836
            goto cleanup;
3837
        }
3838

3839
        last_row_index = band_start + band_height - 1;
69,611✔
3840
        status = sixel_band_emit(&work,
69,611✔
3841
                                 &band,
3842
                                 output,
1,641✔
3843
                                 ncolors,
1,641✔
3844
                                 keycolor,
1,641✔
3845
                                 last_row_index);
1,641✔
3846
        if (SIXEL_FAILED(status)) {
69,611!
3847
            goto cleanup;
3848
        }
3849

3850
        sixel_band_finish(&work, &band);
69,611✔
3851

3852
        sixel_band_clear_map(&work);
69,611✔
3853

3854
        if (logging_active) {
69,611!
3855
            sixel_timeline_logger_logf(logger,
52✔
3856
                              "worker",
3857
                              "encode",
3858
                              "finish",
3859
                              job_index);
4✔
3860
        }
4✔
3861

3862
        band_start += band_height;
69,611✔
3863
        sixel_band_state_reset(&band);
69,611✔
3864
        job_index += 1;
69,611✔
3865
    }
3866

3867
    status = SIXEL_OK;
14,145✔
3868
    goto finalize;
14,145✔
3869

3870
finalize:
16,053✔
3871
    if (palstate) {
16,832✔
3872
        output->buffer[output->pos] = '$';
2,048✔
3873
        sixel_advance(output, 1);
2,048✔
3874
    }
128✔
3875

3876
cleanup:
18,164✔
3877
    while ((np = output->node_free) != NULL) {
702,348✔
3878
        output->node_free = np->next;
685,644✔
3879
        sixel_allocator_free(allocator, np);
685,644✔
3880
    }
3881
    output->node_top = NULL;
16,704✔
3882

3883
    sixel_encode_work_cleanup(&work, allocator);
16,704✔
3884

3885
    return status;
16,704✔
3886
}
3887
SIXELSTATUS
3888
sixel_encode_footer(sixel_output_t *output)
33,946✔
3889
{
3890
    SIXELSTATUS status = SIXEL_FALSE;
33,946✔
3891

3892
    if (output->pos > 0) {
33,946✔
3893
        status = sixel_output_write_bytes(output,
50,797✔
3894
                                          (char *)output->buffer,
33,850✔
3895
                                          output->pos);
2,131✔
3896
        if (SIXEL_FAILED(status)) {
33,850✔
3897
            return status;
3898
        }
3899
        output->pos = 0;
33,850✔
3900
    }
2,131✔
3901

3902
    status = sixel_output_end_image(output);
33,946✔
3903

3904
    return status;
33,946✔
3905
}
2,137✔
3906

3907
static int
3908
sixel_encode_body_ormode_nplanes(int ncolors)
740✔
3909
{
3910
    int nplanes;
290✔
3911

3912
    for (nplanes = 8; nplanes > 1; nplanes--) {
3,794!
3913
        if (ncolors > (1 << (nplanes - 1))) {
3,794✔
3914
            break;
450✔
3915
        }
3916
    }
240✔
3917

3918
    return nplanes;
756✔
3919
}
3920

3921
static SIXELSTATUS
3922
sixel_encode_body_ormode_emit_palette(unsigned char const *palette,
152✔
3923
                                      int ncolors,
3924
                                      int keycolor,
3925
                                      sixel_output_t *output)
3926
{
3927
    SIXELSTATUS status;
66✔
3928
    int n;
66✔
3929

3930
    if (palette == NULL || output == NULL) {
152!
3931
        return SIXEL_BAD_ARGUMENT;
3932
    }
3933
    for (n = 0; n < ncolors; n++) {
5,304✔
3934
        status = output_rgb_palette_definition(output,
5,476✔
3935
                                               palette,
324✔
3936
                                               NULL,
3937
                                               n,
324✔
3938
                                               keycolor);
324✔
3939
        if (SIXEL_FAILED(status)) {
5,152!
3940
            return status;
3941
        }
3942
    }
324✔
3943

3944
    return SIXEL_OK;
86✔
3945
}
10✔
3946

3947
static SIXELSTATUS
3948
sixel_encode_body_ormode_band(sixel_index_t const *pixels,
1,000✔
3949
                              int width,
3950
                              int height,
3951
                              int band_index,
3952
                              int nplanes,
3953
                              sixel_output_t *output)
3954
{
3955
    SIXELSTATUS status;
436✔
3956
    sixel_index_t const *band_pixels;
436✔
3957
    sixel_index_t const *column_pixels;
436✔
3958
    sixel_index_t const *row0;
436✔
3959
    sixel_index_t const *row1;
436✔
3960
    sixel_index_t const *row2;
436✔
3961
    sixel_index_t const *row3;
436✔
3962
    sixel_index_t const *row4;
436✔
3963
    sixel_index_t const *row5;
436✔
3964
    int band_start;
436✔
3965
    int band_height;
436✔
3966
    int nwrite;
436✔
3967
    int plane;
436✔
3968
    int plane_bit;
436✔
3969
    int full_mask;
436✔
3970
    int sample_mask;
436✔
3971
    int sample_width;
436✔
3972
    int first_x;
436✔
3973
    int first_pix;
436✔
3974
    int x;
436✔
3975
    int y;
436✔
3976
    int pix;
436✔
3977

3978
    if (pixels == NULL || output == NULL || width < 1 || height < 0 ||
1,000!
3979
            band_index < 0 || nplanes < 1) {
1,000!
3980
        return SIXEL_BAD_ARGUMENT;
3981
    }
3982

3983
    band_start = band_index * 6;
1,000✔
3984
    if (band_start >= height) {
1,000!
3985
        return SIXEL_OK;
3986
    }
3987
    band_height = height - band_start;
1,000✔
3988
    if (band_height > 6) {
1,000✔
3989
        band_height = 6;
478✔
3990
    }
54✔
3991
    band_pixels = pixels + (size_t)band_start * (size_t)width;
1,000✔
3992

3993
    /*
3994
     * Full six-row bands are the hot path for real images.  Keep the partial
3995
     * tail on the generic loop below, but avoid the inner row loop and repeated
3996
     * width multiplication for complete bands.
3997
     */
3998
    if (band_height == 6) {
1,000✔
3999
        row0 = band_pixels;
880✔
4000
        row1 = row0 + width;
880✔
4001
        row2 = row1 + width;
880✔
4002
        row3 = row2 + width;
880✔
4003
        row4 = row3 + width;
880✔
4004
        row5 = row4 + width;
880✔
4005
        if (output->encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
880✔
4006
            for (plane = 0; plane < nplanes; plane++) {
4,511✔
4007
                sixel_putc(output->buffer + output->pos, '#');
3,647✔
4008
                sixel_advance(output, 1);
3,647✔
4009
                nwrite = sixel_putnum((char *)output->buffer + output->pos,
3,878✔
4010
                                      1 << plane);
230✔
4011
                sixel_advance(output, nwrite);
3,647✔
4012

4013
                for (x = 0; x < width; x++) {
230,645✔
4014
                    pix = (((row0[x] >> plane) & 0x1) << 0) |
253,635✔
4015
                          (((row1[x] >> plane) & 0x1) << 1) |
239,519✔
4016
                          (((row2[x] >> plane) & 0x1) << 2) |
239,519✔
4017
                          (((row3[x] >> plane) & 0x1) << 3) |
239,519✔
4018
                          (((row4[x] >> plane) & 0x1) << 4) |
239,519✔
4019
                          (((row5[x] >> plane) & 0x1) << 5);
225,403✔
4020
                    sixel_put_pixel(output, pix);
225,403✔
4021
                }
14,116✔
4022
                status = sixel_put_flash(output);
3,648✔
4023
                if (SIXEL_FAILED(status)) {
3,648!
4024
                    return status;
4025
                }
4026

4027
                sixel_putc(output->buffer + output->pos, '$');
3,648✔
4028
                sixel_advance(output, 1);
3,648✔
4029
            }
230✔
4030
            sixel_putc(output->buffer + output->pos, '-');
864✔
4031
            sixel_advance(output, 1);
864✔
4032
            return SIXEL_OK;
864✔
4033
        }
4034

4035
        full_mask = (1 << nplanes) - 1;
16✔
4036
        sample_mask = 0;
16✔
4037
        sample_width = width < 64 ? width : 64;
16✔
4038
        for (x = 0; x < sample_width; x++) {
96✔
4039
            sample_mask |= row0[x] | row1[x] | row2[x] |
90✔
4040
                           row3[x] | row4[x] | row5[x];
85✔
4041
        }
5✔
4042

4043
        if ((sample_mask & full_mask) == full_mask) {
16!
4044
            for (plane = 0; plane < nplanes; plane++) {
×
4045
                plane_bit = 1 << plane;
×
4046
                sixel_putc(output->buffer + output->pos, '#');
×
4047
                sixel_advance(output, 1);
×
UNCOV
4048
                nwrite = sixel_putnum((char *)output->buffer + output->pos,
×
4049
                                      plane_bit);
UNCOV
4050
                sixel_advance(output, nwrite);
×
4051

4052
                for (x = 0; x < width; x++) {
×
4053
                    pix = ((row0[x] & plane_bit) ? 0x01 : 0) |
×
4054
                          ((row1[x] & plane_bit) ? 0x02 : 0) |
×
4055
                          ((row2[x] & plane_bit) ? 0x04 : 0) |
×
4056
                          ((row3[x] & plane_bit) ? 0x08 : 0) |
×
4057
                          ((row4[x] & plane_bit) ? 0x10 : 0) |
×
4058
                          ((row5[x] & plane_bit) ? 0x20 : 0);
×
UNCOV
4059
                    sixel_put_pixel(output, pix);
×
4060
                }
4061
                status = sixel_put_flash(output);
×
UNCOV
4062
                if (SIXEL_FAILED(status)) {
×
4063
                    return status;
4064
                }
4065

4066
                sixel_putc(output->buffer + output->pos, '$');
×
UNCOV
4067
                sixel_advance(output, 1);
×
4068
            }
4069
            sixel_putc(output->buffer + output->pos, '-');
×
4070
            sixel_advance(output, 1);
×
UNCOV
4071
            return SIXEL_OK;
×
4072
        }
4073

4074
        for (plane = 0; plane < nplanes; plane++) {
48✔
4075
            plane_bit = 1 << plane;
32✔
4076
            first_x = (-1);
32✔
4077
            pix = 0;
32✔
4078
            for (x = 0; x < width; x++) {
112✔
4079
                pix = ((row0[x] & plane_bit) ? 0x01 : 0) |
288✔
4080
                      ((row1[x] & plane_bit) ? 0x02 : 0) |
102✔
4081
                      ((row2[x] & plane_bit) ? 0x04 : 0) |
102✔
4082
                      ((row3[x] & plane_bit) ? 0x08 : 0) |
102✔
4083
                      ((row4[x] & plane_bit) ? 0x10 : 0) |
102✔
4084
                      ((row5[x] & plane_bit) ? 0x20 : 0);
96✔
4085
                if (pix != 0) {
96!
4086
                    first_x = x;
9✔
4087
                    break;
9✔
4088
                }
4089
            }
5✔
4090
            if (first_x < 0) {
32✔
4091
                continue;
16✔
4092
            }
4093

4094
            sixel_putc(output->buffer + output->pos, '#');
16✔
4095
            sixel_advance(output, 1);
16✔
4096
            nwrite = sixel_putnum((char *)output->buffer + output->pos,
17✔
4097
                                  plane_bit);
1✔
4098
            sixel_advance(output, nwrite);
16✔
4099

4100
            status = sixel_emit_run(output, '?', first_x);
16✔
4101
            if (SIXEL_FAILED(status)) {
16!
4102
                return status;
4103
            }
4104
            sixel_put_pixel(output, pix);
16✔
4105
            for (x = first_x + 1; x < width; x++) {
80✔
4106
                pix = ((row0[x] & plane_bit) ? 0x01 : 0) |
192✔
4107
                      ((row1[x] & plane_bit) ? 0x02 : 0) |
68!
4108
                      ((row2[x] & plane_bit) ? 0x04 : 0) |
68!
4109
                      ((row3[x] & plane_bit) ? 0x08 : 0) |
68!
4110
                      ((row4[x] & plane_bit) ? 0x10 : 0) |
68!
4111
                      ((row5[x] & plane_bit) ? 0x20 : 0);
64!
4112
                sixel_put_pixel(output, pix);
64✔
4113
            }
4✔
4114
            status = sixel_put_flash(output);
16✔
4115
            if (SIXEL_FAILED(status)) {
16!
4116
                return status;
4117
            }
4118

4119
            sixel_putc(output->buffer + output->pos, '$');
16✔
4120
            sixel_advance(output, 1);
16✔
4121
        }
1✔
4122
        sixel_putc(output->buffer + output->pos, '-');
16✔
4123
        sixel_advance(output, 1);
16✔
4124
        return SIXEL_OK;
16✔
4125
    }
4126

4127
    /*
4128
     * OR mode composes the final color by drawing powers-of-two bit-planes.
4129
     * Size policy may spend a little more branch work to avoid empty planes.
4130
     * Delay the plane header until the first non-zero cell.  A plane that never
4131
     * draws any cell cannot affect the OR-composited result, while a non-empty
4132
     * plane keeps its horizontal position by emitting the leading zero run
4133
     * before the first drawn cell.
4134
     */
4135
    if (output->encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
120!
4136
        for (plane = 0; plane < nplanes; plane++) {
552✔
4137
            sixel_putc(output->buffer + output->pos, '#');
432✔
4138
            sixel_advance(output, 1);
432✔
4139
            nwrite = sixel_putnum((char *)output->buffer + output->pos,
460✔
4140
                                  1 << plane);
28✔
4141
            sixel_advance(output, nwrite);
432✔
4142

4143
            column_pixels = band_pixels;
432✔
4144
            for (x = 0; x < width; x++, column_pixels++) {
23,404✔
4145
                pix = 0;
12,820✔
4146
                for (y = 0; y < band_height; y++) {
113,280✔
4147
                    pix |= (((column_pixels[width * y] >> plane) & 0x1) << y);
90,496✔
4148
                }
5,660✔
4149
                sixel_put_pixel(output, pix);
22,784✔
4150
            }
1,428✔
4151
            status = sixel_put_flash(output);
432✔
4152
            if (SIXEL_FAILED(status)) {
432!
4153
                return status;
4154
            }
4155

4156
            sixel_putc(output->buffer + output->pos, '$');
432✔
4157
            sixel_advance(output, 1);
432✔
4158
        }
28✔
4159
        return SIXEL_OK;
68✔
4160
    }
4161

4162
    full_mask = (1 << nplanes) - 1;
×
4163
    sample_mask = 0;
×
4164
    sample_width = width < 64 ? width : 64;
×
4165
    column_pixels = band_pixels;
×
4166
    for (x = 0; x < sample_width; x++, column_pixels++) {
×
4167
        for (y = 0; y < band_height; y++) {
×
UNCOV
4168
            sample_mask |= column_pixels[width * y];
×
4169
        }
4170
    }
4171

4172
    if ((sample_mask & full_mask) == full_mask) {
×
4173
        for (plane = 0; plane < nplanes; plane++) {
×
4174
            sixel_putc(output->buffer + output->pos, '#');
×
4175
            sixel_advance(output, 1);
×
UNCOV
4176
            nwrite = sixel_putnum((char *)output->buffer + output->pos,
×
4177
                                  1 << plane);
UNCOV
4178
            sixel_advance(output, nwrite);
×
4179

4180
            column_pixels = band_pixels;
×
UNCOV
4181
            for (x = 0; x < width; x++, column_pixels++) {
×
4182
                pix = 0;
4183
                for (y = 0; y < band_height; y++) {
×
UNCOV
4184
                    pix |= (((column_pixels[width * y] >> plane) & 0x1) << y);
×
4185
                }
UNCOV
4186
                sixel_put_pixel(output, pix);
×
4187
            }
4188
            status = sixel_put_flash(output);
×
UNCOV
4189
            if (SIXEL_FAILED(status)) {
×
4190
                return status;
4191
            }
4192

4193
            sixel_putc(output->buffer + output->pos, '$');
×
UNCOV
4194
            sixel_advance(output, 1);
×
4195
        }
4196
        return SIXEL_OK;
4197
    }
4198

4199
    for (plane = 0; plane < nplanes; plane++) {
×
4200
        plane_bit = 1 << plane;
×
4201
        first_x = (-1);
×
4202
        first_pix = 0;
×
4203
        column_pixels = band_pixels;
×
UNCOV
4204
        for (x = 0; x < width; x++, column_pixels++) {
×
4205
            pix = 0;
4206
            for (y = 0; y < band_height; y++) {
×
UNCOV
4207
                pix |= (((column_pixels[width * y] >> plane) & 0x1) << y);
×
4208
            }
UNCOV
4209
            if (pix != 0) {
×
4210
                first_x = x;
4211
                first_pix = pix;
4212
                break;
4213
            }
4214
        }
4215
        if (first_x < 0) {
×
UNCOV
4216
            continue;
×
4217
        }
4218

4219
        sixel_putc(output->buffer + output->pos, '#');
×
4220
        sixel_advance(output, 1);
×
UNCOV
4221
        nwrite = sixel_putnum((char *)output->buffer + output->pos,
×
4222
                              plane_bit);
UNCOV
4223
        sixel_advance(output, nwrite);
×
4224

4225
        status = sixel_emit_run(output, '?', first_x);
×
UNCOV
4226
        if (SIXEL_FAILED(status)) {
×
4227
            return status;
4228
        }
4229
        sixel_put_pixel(output, first_pix);
×
4230
        column_pixels = band_pixels + first_x + 1;
×
UNCOV
4231
        for (x = first_x + 1; x < width; x++, column_pixels++) {
×
4232
            pix = 0;
4233
            for (y = 0; y < band_height; y++) {
×
UNCOV
4234
                pix |= (((column_pixels[width * y] >> plane) & 0x1) << y);
×
4235
            }
UNCOV
4236
            sixel_put_pixel(output, pix);
×
4237
        }
4238
        status = sixel_put_flash(output);
×
UNCOV
4239
        if (SIXEL_FAILED(status)) {
×
4240
            return status;
4241
        }
4242

4243
        sixel_putc(output->buffer + output->pos, '$');
×
UNCOV
4244
        sixel_advance(output, 1);
×
4245
    }
4246
    if (band_height == 6) {
×
4247
        sixel_putc(output->buffer + output->pos, '-');
4248
        sixel_advance(output, 1);
4249
    }
4250

4251
    return SIXEL_OK;
4252
}
64✔
4253

4254
SIXEL_INTERNAL_API SIXELSTATUS
4255
sixel_encode_body_ormode(
96✔
4256
    sixel_index_t       /* in */ *pixels,
4257
    int                 /* in */ width,
4258
    int                 /* in */ height,
4259
    unsigned char       /* in */ *palette,
4260
    int                 /* in */ ncolors,
4261
    int                 /* in */ keycolor,
4262
    sixel_output_t      /* in */ *output)
4263
{
4264
    SIXELSTATUS status;
43✔
4265
    int nplanes;
43✔
4266
    int nbands;
43✔
4267
    int band_index;
43✔
4268

4269
    if (pixels == NULL) {
96✔
4270
        return SIXEL_BAD_ARGUMENT;
9✔
4271
    }
4272

4273
    status = sixel_encode_body_ormode_emit_palette(palette,
84✔
4274
                                                   ncolors,
4✔
4275
                                                   keycolor,
4✔
4276
                                                   output);
4✔
4277
    if (SIXEL_FAILED(status)) {
80!
4278
        return status;
4279
    }
4280

4281
    nplanes = sixel_encode_body_ormode_nplanes(ncolors);
80✔
4282
    nbands = (height + 5) / 6;
80✔
4283
    for (band_index = 0; band_index < nbands; band_index++) {
384✔
4284
        status = sixel_encode_body_ormode_band(pixels,
310✔
4285
                                               width,
6✔
4286
                                               height,
6✔
4287
                                               band_index,
6✔
4288
                                               nplanes,
6✔
4289
                                               output);
6✔
4290
        if (SIXEL_FAILED(status)) {
304!
4291
            return status;
4292
        }
4293
    }
6✔
4294

4295
    return SIXEL_OK;
44✔
4296
}
5✔
4297

4298

4299
static SIXELSTATUS
4300
sixel_encode_dither(
33,690✔
4301
    unsigned char   /* in */ *pixels,   /* pixel bytes to be encoded */
4302
    int             /* in */ width,     /* width of source image */
4303
    int             /* in */ height,    /* height of source image */
4304
    sixel_dither_t  /* in */ *dither,   /* dither context */
4305
    sixel_output_t  /* in */ *output)   /* output context */
4306
{
4307
    SIXELSTATUS status = SIXEL_FALSE;
33,690✔
4308
    sixel_index_t *paletted_pixels = NULL;
33,690✔
4309
    sixel_index_t *input_pixels;
14,746✔
4310
    size_t bufsize;
14,746✔
4311
    unsigned char *palette_entries = NULL;
33,690✔
4312
    float *palette_entries_float32 = NULL;
33,690✔
4313
    sixel_palette_t *palette_obj = NULL;
33,690✔
4314
    size_t palette_count = 0U;
33,690✔
4315
    size_t palette_float_count = 0U;
33,690✔
4316
    size_t palette_bytes = 0U;
33,690✔
4317
    size_t palette_float_bytes = 0U;
33,690✔
4318
    size_t palette_channels = 0U;
33,690✔
4319
    size_t palette_index = 0U;
33,690✔
4320
    size_t pixel_count = 0U;
33,690✔
4321
    int palette_source_colorspace;
14,746✔
4322
    int palette_float_pixelformat;
14,746✔
4323
    int output_float_pixelformat;
14,746✔
4324
    int palette_float_depth;
14,746✔
4325
    int pipeline_active;
14,746✔
4326
    int pipeline_threads = 0;  /* set to a deterministic default before use */
33,690✔
4327
    int pipeline_nbands;
14,746✔
4328
    int encoded_width;
14,746✔
4329
    int encoded_height;
14,746✔
4330
    sixel_parallel_dither_config_t dither_parallel;
14,746✔
4331
    sixel_palette_entries_view_t palette_view;
14,746✔
4332
    sixel_palette_float32_entries_view_t palette_float_view;
14,746✔
4333
#if SIXEL_ENABLE_THREADS
4334
    sixel_timeline_logger_t *serial_logger;
10,576✔
4335
    int logger_owned = 0;
25,350✔
4336
#endif  /* SIXEL_ENABLE_THREADS */
4337
    sixel_timeline_logger_t *logger = NULL;
33,690✔
4338

4339
    if (output == NULL || dither == NULL) {
33,690!
4340
        return SIXEL_BAD_ARGUMENT;
4341
    }
4342

4343
#if SIXEL_ENABLE_THREADS
4344
    serial_logger = NULL;
25,350✔
4345
#endif  /* SIXEL_ENABLE_THREADS */
4346
    palette_source_colorspace = SIXEL_COLORSPACE_GAMMA;
33,690✔
4347
    if (width <= 0 || height <= 0 ||
33,690!
4348
        (size_t)width > SIZE_MAX / (size_t)height) {
33,690!
UNCOV
4349
        sixel_helper_set_additional_message(
×
4350
            "sixel_encode_dither: image size overflow.");
4351
        status = SIXEL_BAD_INTEGER_OVERFLOW;
×
UNCOV
4352
        goto end;
×
4353
    }
4354
    if (sixel_output_has_transparent_offset(output) != 0) {
33,690✔
4355
        if (output->ormode != 0) {
56!
UNCOV
4356
            sixel_helper_set_additional_message(
×
4357
                "transparent-offset cannot be used with ormode.");
4358
            status = SIXEL_BAD_ARGUMENT;
×
UNCOV
4359
            goto end;
×
4360
        }
4361
        if (output->transparent_policy != SIXEL_TRANSPARENT_POLICY_KEEP) {
56!
UNCOV
4362
            sixel_helper_set_additional_message(
×
4363
                "transparent-offset requires transparent-policy=keep.");
4364
            status = SIXEL_BAD_ARGUMENT;
×
4365
            goto end;
×
4366
        }
4367
    }
4✔
4368
    status = sixel_output_compute_transparent_extent(output,
35,811✔
4369
                                                     width,
2,121✔
4370
                                                     height,
2,121✔
4371
                                                     &encoded_width,
4372
                                                     &encoded_height);
4373
    if (SIXEL_FAILED(status)) {
33,690!
UNCOV
4374
        goto end;
×
4375
    }
4376
    pixel_count = (size_t)width * (size_t)height;
33,690✔
4377
    palette_float_pixelformat =
16,867✔
4378
        sixel_palette_float_pixelformat_for_colorspace(
33,690✔
4379
            palette_source_colorspace);
2,121✔
4380
    palette_float_depth =
16,867✔
4381
        sixel_helper_compute_depth(palette_float_pixelformat);
33,690✔
4382
    output_float_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
33,690✔
4383
    memset(&palette_view, 0, sizeof(palette_view));
33,690!
4384
    memset(&palette_float_view, 0, sizeof(palette_float_view));
33,690✔
4385
    palette_obj = dither->palette;
33,690✔
4386
    if (palette_obj == NULL || palette_obj->vtbl == NULL ||
33,690!
4387
            palette_obj->vtbl->get_entries == NULL ||
33,690!
4388
            palette_obj->vtbl->get_entries_float32 == NULL) {
33,690!
UNCOV
4389
        sixel_helper_set_additional_message(
×
4390
            "sixel_encode_dither: palette acquisition failed.");
UNCOV
4391
        status = SIXEL_BAD_ARGUMENT;
×
UNCOV
4392
        goto end;
×
4393
    }
4394

4395
    pipeline_active = 0;
33,690✔
4396
#if SIXEL_ENABLE_THREADS
4397
    #endif
4398
    dither_parallel.enabled = 0;
33,690✔
4399
    dither_parallel.band_height = 0;
33,690✔
4400
    dither_parallel.overlap = 0;
33,690✔
4401
    dither_parallel.dither_threads = 0;
33,690✔
4402
    dither_parallel.encode_threads = 0;
33,690✔
4403
    /*
4404
     * Normalize the planner-provided pinning request so both palette and
4405
     * encode workers see the same 0/1 flag.
4406
     */
4407
    dither->pipeline_pin_threads =
33,690✔
4408
        dither->pipeline_pin_threads != 0 ? 1 : 0;
33,690✔
4409
    dither_parallel.pin_threads = dither->pipeline_pin_threads;
33,690✔
4410
    switch (dither->pixelformat) {
33,690!
4411
    case SIXEL_PIXELFORMAT_PAL1:
4412
    case SIXEL_PIXELFORMAT_PAL2:
4413
    case SIXEL_PIXELFORMAT_PAL4:
4414
    case SIXEL_PIXELFORMAT_G1:
4415
    case SIXEL_PIXELFORMAT_G2:
4416
    case SIXEL_PIXELFORMAT_G4:
UNCOV
4417
        bufsize = (sizeof(sixel_index_t) * (size_t)width * (size_t)height * 3UL);
×
UNCOV
4418
        paletted_pixels = (sixel_index_t *)sixel_allocator_malloc(dither->allocator, bufsize);
×
UNCOV
4419
        if (paletted_pixels == NULL) {
×
UNCOV
4420
            sixel_helper_set_additional_message(
×
4421
                "sixel_encode_dither: sixel_allocator_malloc() failed.");
UNCOV
4422
            status = SIXEL_BAD_ALLOCATION;
×
4423
            goto end;
×
4424
        }
4425
        status = sixel_helper_normalize_pixelformat(paletted_pixels,
×
4426
                                                    &dither->pixelformat,
4427
                                                    pixels,
4428
                                                    dither->pixelformat,
4429
                                                    width, height);
UNCOV
4430
        if (SIXEL_FAILED(status)) {
×
4431
            goto end;
×
4432
        }
4433
        input_pixels = paletted_pixels;
4434
        break;
4435
    case SIXEL_PIXELFORMAT_PAL8:
1,536✔
4436
    case SIXEL_PIXELFORMAT_G8:
4437
    case SIXEL_PIXELFORMAT_GA88:
4438
    case SIXEL_PIXELFORMAT_AG88:
4439
        input_pixels = pixels;
1,729✔
4440
        break;
1,729✔
4441
    default:
28,692✔
4442
        /* apply palette */
4443
        pipeline_threads = sixel_threads_resolve();
30,620✔
4444
        pipeline_nbands = sixel_count_sixel_bands(encoded_height);
30,620!
4445
        /*
4446
         * Pipeline mode lets PaletteApply produce contiguous index rows while
4447
         * band workers consume completed six-line slices. OR mode now has its
4448
         * own band encoder, so it can share this producer/writer path without
4449
         * dereferencing input_pixels on the caller side.
4450
         */
4451
        if (pipeline_threads > 1 && pipeline_nbands > 1) {
30,620!
4452
            pipeline_active = 1;
11,216✔
4453
            input_pixels = NULL;
11,216✔
4454
        } else {
1,620✔
4455
            paletted_pixels = sixel_dither_apply_palette(dither, pixels,
11,666✔
4456
                                                         width, height);
308✔
4457
            if (paletted_pixels == NULL) {
11,358!
4458
                status = SIXEL_RUNTIME_ERROR;
16✔
4459
                goto end;
16✔
4460
            }
4461
            input_pixels = paletted_pixels;
5,991✔
4462
        }
4463
        break;
17,207✔
4464
    }
4465

4466
    if (pipeline_active) {
26,982✔
4467
        sixel_parallel_dither_configure(height,
20,882✔
4468
                                        dither->ncolors,
1,620✔
4469
                                        pipeline_threads,
1,620✔
4470
                                        dither->pipeline_pin_threads,
1,620✔
4471
                                        &dither_parallel);
4472
        if (dither_parallel.enabled) {
19,262!
4473
            if (sixel_gpu_palette_policy_claims_apply_stage(
19,262!
4474
                    dither->gpu_policy,
1,620✔
4475
                    dither->lut_policy,
1,620✔
4476
                    dither->method_for_diffuse,
1,620✔
4477
                    dither->method_for_scan,
1,620✔
4478
                    pixel_count)) {
1,620✔
4479
                /*
4480
                 * GPU PaletteApply produces the whole index plane after a
4481
                 * command-buffer wait, not a stream of CPU dither bands.
4482
                 * Keep all CPU workers on the encode side so the first band
4483
                 * can start with the full worker budget once the GPU result is
4484
                 * visible.
4485
                 */
4486
                dither_parallel.dither_threads = 0;
48✔
4487
                dither_parallel.encode_threads = pipeline_threads;
48✔
4488
                dither_parallel.band_height = 6;
48✔
4489
                dither_parallel.overlap = 0;
48✔
4490
            }
4✔
4491
            dither->pipeline_parallel_active = 1;
19,262✔
4492
            dither->pipeline_band_height = dither_parallel.band_height;
19,262✔
4493
            dither->pipeline_band_overlap = dither_parallel.overlap;
19,262✔
4494
            dither->pipeline_dither_threads =
19,262✔
4495
                dither_parallel.dither_threads;
19,262✔
4496
            pipeline_threads = dither_parallel.encode_threads;
19,262✔
4497
        }
1,620✔
4498
        if (pipeline_threads <= 1) {
19,262!
4499
            /*
4500
             * Disable the pipeline when the encode side cannot spawn at
4501
             * least two workers.  A single encode thread cannot consume the
4502
             * six-line jobs that PaletteApply produces, so fall back to the
4503
             * serialized encoder path.
4504
             */
4505
            pipeline_active = 0;
264✔
4506
            dither->pipeline_parallel_active = 0;
264✔
4507
            if (paletted_pixels == NULL) {
264!
4508
                paletted_pixels = sixel_dither_apply_palette(dither, pixels,
286✔
4509
                                                             width, height);
22✔
4510
                if (paletted_pixels == NULL) {
264!
UNCOV
4511
                    status = SIXEL_RUNTIME_ERROR;
×
UNCOV
4512
                    goto end;
×
4513
                }
4514
            }
22✔
4515
            input_pixels = paletted_pixels;
154✔
4516
        }
22✔
4517
    }
1,620✔
4518

4519
#if SIXEL_ENABLE_THREADS
4520
    if (!pipeline_active) {
14,774✔
4521
        logger = dither->pipeline_logger;
6,352✔
4522
        if (logger == NULL) {
6,352!
4523
            sixel_timeline_logger_prepare_default(dither->allocator,
6,352✔
4524
                                                  &serial_logger);
4525
            if (serial_logger != NULL) {
6,352✔
4526
                logger_owned = 1;
24✔
4527
                dither->pipeline_logger = serial_logger;
24✔
4528
                logger = serial_logger;
24✔
4529
            } else {
2✔
4530
                logger = NULL;
3,698✔
4531
            }
4532
        }
523✔
4533
        if (logger != NULL) {
3,722✔
4534
            sixel_timeline_logger_logf(logger,
26✔
4535
                              "controller",
4536
                              "pipeline",
4537
                              "configure",
4538
                              -1,
4539
                              -1,
4540
                              0,
4541
                              height,
2✔
4542
                              0,
4543
                              height,
2✔
4544
                              "serial path threads=1");
4545
        }
2✔
4546
    }
523✔
4547
#endif
4548

4549
    if (output != NULL) {
33,674!
4550
        palette_source_colorspace = output->source_colorspace;
33,674✔
4551
        palette_float_pixelformat =
16,859✔
4552
            sixel_palette_float_pixelformat_for_colorspace(
33,674✔
4553
                palette_source_colorspace);
2,121✔
4554
        palette_float_depth =
16,859✔
4555
            sixel_helper_compute_depth(palette_float_pixelformat);
33,674✔
4556
    }
2,121✔
4557

4558
    status = palette_obj->vtbl->get_entries(palette_obj, &palette_view);
33,674✔
4559
    if (SIXEL_FAILED(status) || palette_view.entries == NULL ||
33,674!
4560
            palette_view.depth != 3 || palette_view.entry_count == 0U) {
33,674!
UNCOV
4561
        sixel_helper_set_additional_message(
×
4562
            "sixel_encode_dither: palette view failed.");
4563
        status = SIXEL_RUNTIME_ERROR;
×
UNCOV
4564
        goto end;
×
4565
    }
4566
    palette_count = palette_view.entry_count;
33,674✔
4567
    palette_bytes = palette_count * 3U;
33,674✔
4568
    palette_entries = (unsigned char *)sixel_allocator_malloc(
33,674✔
4569
        dither->allocator,
2,121✔
4570
        palette_bytes);
2,121✔
4571
    if (palette_entries == NULL) {
33,674!
UNCOV
4572
        sixel_helper_set_additional_message(
×
4573
            "sixel_encode_dither: palette copy allocation failed.");
UNCOV
4574
        status = SIXEL_BAD_ALLOCATION;
×
UNCOV
4575
        goto end;
×
4576
    }
4577
    memcpy(palette_entries, palette_view.entries, palette_bytes);
33,674✔
4578

4579
    status = palette_obj->vtbl->get_entries_float32(palette_obj,
33,674✔
4580
                                                    &palette_float_view);
4581
    if (SIXEL_FAILED(status)) {
33,674!
4582
        goto end;
×
4583
    }
4584
    if (palette_float_view.entries != NULL &&
33,674✔
4585
            palette_float_view.entry_count == palette_count &&
3,566!
4586
            palette_float_view.depth == palette_float_depth) {
3,566!
4587
        palette_float_count = palette_float_view.entry_count;
3,566✔
4588
        palette_float_bytes =
3,566✔
4589
            palette_float_count * (size_t)palette_float_view.depth;
3,566✔
4590
        palette_entries_float32 = (float *)sixel_allocator_malloc(
3,566✔
4591
            dither->allocator,
114✔
4592
            palette_float_bytes);
114✔
4593
        if (palette_entries_float32 == NULL) {
3,566!
UNCOV
4594
            sixel_helper_set_additional_message(
×
4595
                "sixel_encode_dither: float palette copy allocation failed.");
UNCOV
4596
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
4597
            goto end;
×
4598
        }
4599
        memcpy(palette_entries_float32,
3,680✔
4600
               palette_float_view.entries,
3,566✔
4601
               palette_float_bytes);
114✔
4602
    }
114✔
4603
    if (palette_entries != NULL && palette_entries_float32 != NULL
33,674!
4604
            && palette_count == palette_float_count
5,573!
4605
            && palette_count > 0U
3,566!
4606
            && !sixel_palette_float32_matches_u8(
3,566!
4607
                    palette_entries,
114✔
4608
                    palette_entries_float32,
114✔
4609
                    palette_count,
114✔
4610
                    palette_float_pixelformat)) {
114✔
UNCOV
4611
        sixel_palette_sync_float32_from_u8(palette_entries,
×
4612
                                           palette_entries_float32,
4613
                                           palette_count,
4614
                                           palette_float_pixelformat);
4615
    }
4616
    if (palette_entries != NULL && palette_count > 0U
33,674!
4617
            && output != NULL
18,936!
4618
            && output->source_colorspace != output->colorspace) {
33,674✔
4619
        palette_bytes = palette_count * 3U;
1,135✔
4620
        if (palette_entries_float32 != NULL
1,135✔
4621
                && palette_float_count == palette_count) {
725!
4622
            /*
4623
             * Use the higher-precision palette to change color spaces once and
4624
             * then quantize those float channels down to bytes.  The previous
4625
             * implementation converted the 8bit entries before overwriting
4626
             * them from float again, doubling the amount of work and rounding
4627
             * the palette twice.
4628
             */
4629
            palette_float_bytes = palette_bytes * sizeof(float);
316✔
4630
            status = sixel_helper_convert_colorspace(
316✔
4631
                (unsigned char *)palette_entries_float32,
3✔
4632
                palette_float_bytes,
3✔
4633
                palette_float_pixelformat,
3✔
4634
                output->source_colorspace,
3✔
4635
                output->colorspace);
3✔
4636
            if (SIXEL_FAILED(status)) {
316!
UNCOV
4637
                sixel_helper_set_additional_message(
×
4638
                    "sixel_encode_dither: float palette colorspace conversion failed.");
UNCOV
4639
                goto end;
×
4640
            }
4641
            output_float_pixelformat =
158✔
4642
                sixel_palette_float_pixelformat_for_colorspace(
316!
4643
                    output->colorspace);
3✔
4644
            palette_channels = palette_count * 3U;
316✔
4645
            for (palette_index = 0U; palette_index < palette_channels;
206,572✔
4646
                    ++palette_index) {
206,256✔
4647
                int channel;
103,101✔
4648

4649
                channel = (int)(palette_index % 3U);
206,256✔
4650
                palette_entries[palette_index] =
206,256✔
4651
                    sixel_pixelformat_float_channel_to_byte(
206,256✔
4652
                        output_float_pixelformat,
27✔
4653
                        channel,
27✔
4654
                        palette_entries_float32[palette_index]);
206,256✔
4655
            }
27✔
4656
        } else {
3✔
4657
            status = sixel_helper_convert_colorspace(palette_entries,
887✔
4658
                                                     palette_bytes,
68✔
4659
                                                     SIXEL_PIXELFORMAT_RGB888,
4660
                                                     output->source_colorspace,
68✔
4661
                                                     output->colorspace);
68✔
4662
            if (SIXEL_FAILED(status)) {
819!
UNCOV
4663
                sixel_helper_set_additional_message(
×
4664
                    "sixel_encode_dither: palette colorspace "
4665
                    "conversion failed.");
UNCOV
4666
                goto end;
×
4667
            }
4668
        }
4669
    }
71✔
4670
    if (SIXEL_FAILED(status) || palette_entries == NULL) {
18,936!
4671
        sixel_helper_set_additional_message(
4672
            "sixel_encode_dither: palette copy failed.");
4673
        goto end;
4674
    }
4675

4676
    if (input_pixels != NULL &&
33,674!
4677
            dither->pipeline_accumulation_result_enabled != 0) {
14,676✔
4678
        status = sixel_dither_set_pipeline_accumulation_result_rgb(
136✔
4679
            dither,
8✔
4680
            input_pixels,
8✔
4681
            pixel_count,
8✔
4682
            palette_entries,
8✔
4683
            palette_count);
8✔
4684
        if (SIXEL_FAILED(status)) {
136!
UNCOV
4685
            goto end;
×
4686
        }
4687
    }
8✔
4688

4689
    status = sixel_encode_header(encoded_width,
35,795✔
4690
                                 encoded_height,
2,121✔
4691
                                 dither->keycolor,
2,121✔
4692
                                 output);
2,121✔
4693
    if (SIXEL_FAILED(status)) {
33,674!
UNCOV
4694
        goto end;
×
4695
    }
4696

4697
    if (pipeline_active) {
33,674!
4698
        if (output->ormode) {
18,998!
4699
            status = sixel_encode_body_ormode_pipeline(pixels,
65✔
4700
                                                       width,
5✔
4701
                                                       height,
5✔
4702
                                                       palette_entries,
5✔
4703
                                                       dither,
5✔
4704
                                                       output,
5✔
4705
                                                       pipeline_threads);
5✔
4706
        } else {
5✔
4707
            status = sixel_encode_body_pipeline(pixels,
20,531✔
4708
                                                width,
1,593✔
4709
                                                height,
1,593✔
4710
                                                palette_entries,
1,593✔
4711
                                                palette_entries_float32,
1,593✔
4712
                                                dither,
1,593✔
4713
                                                output,
1,593✔
4714
                                                pipeline_threads);
1,593✔
4715
        }
4716
    } else if (output->ormode) {
16,274!
4717
        status = sixel_encode_body_ormode(input_pixels,
20✔
4718
                                          width,
4719
                                          height,
4720
                                          palette_entries,
4721
                                          dither->ncolors,
4722
                                          dither->keycolor,
4723
                                          output);
4724
    } else {
4725
        status = sixel_encode_body(input_pixels,
15,179✔
4726
                                   width,
523✔
4727
                                   height,
523✔
4728
                                   palette_entries,
523✔
4729
                                   palette_entries_float32,
523✔
4730
                                   dither->ncolors,
523✔
4731
                                   dither->keycolor,
523✔
4732
                                   dither->bodyonly,
523✔
4733
                                   output,
523✔
4734
                                   NULL,
4735
                                   dither->allocator,
523✔
4736
                                   dither->pipeline_pin_threads,
523✔
4737
                                   logger != NULL ?
523✔
4738
                                       logger :
2✔
4739
                                       NULL);
4740
    }
4741

4742
    if (SIXEL_FAILED(status)) {
33,674!
4743
        goto end;
48✔
4744
    }
4745

4746
    status = sixel_encode_footer(output);
33,626✔
4747
    if (SIXEL_FAILED(status)) {
33,626!
4748
        goto end;
4749
    }
4750

4751
end:
31,529✔
4752
#if SIXEL_ENABLE_THREADS
4753
    if (logger_owned) {
25,350✔
4754
        dither->pipeline_logger = NULL;
24✔
4755
        sixel_timeline_logger_unref(serial_logger);
24✔
4756
    }
2✔
4757
#endif
4758
    if (palette_entries != NULL) {
33,690!
4759
        sixel_allocator_free(dither->allocator, palette_entries);
33,674✔
4760
    }
2,121✔
4761
    if (palette_entries_float32 != NULL) {
33,690!
4762
        sixel_allocator_free(dither->allocator, palette_entries_float32);
3,566✔
4763
    }
114✔
4764
    sixel_allocator_free(dither->allocator, paletted_pixels);
33,690✔
4765

4766
    return status;
33,690✔
4767
}
2,121✔
4768

4769
SIXEL_INTERNAL_API SIXELSTATUS
4770
sixel_encoder_core_encode_dispatch(
34,074✔
4771
    sixel_encoder_core_encode_request_t const *request)
4772
{
4773
    SIXELSTATUS status;
14,914✔
4774

4775
    if (request == NULL || request->pixels == NULL ||
34,074!
4776
        request->dither == NULL || request->output == NULL) {
34,010!
4777
        return SIXEL_BAD_ARGUMENT;
36✔
4778
    }
4779
    if (request->width < 1 || request->height < 1) {
34,010!
4780
        return SIXEL_BAD_INPUT;
4781
    }
4782
    if (sixel_output_has_transparent_offset(request->output) != 0 &&
34,010✔
4783
        request->dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
56!
UNCOV
4784
        sixel_helper_set_additional_message(
×
4785
            "transparent-offset cannot be used with high-color output.");
UNCOV
4786
        return SIXEL_BAD_ARGUMENT;
×
4787
    }
4788

4789
    (void)request->depth;
17,027✔
4790
    if (request->dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
34,010✔
4791
        status = sixel_encode_highcolor(request->pixels,
340✔
4792
                                        request->width,
180✔
4793
                                        request->height,
180✔
4794
                                        request->dither,
180✔
4795
                                        request->output);
180✔
4796
    } else {
20✔
4797
        status = sixel_encode_dither(request->pixels,
35,811✔
4798
                                     request->width,
18,944✔
4799
                                     request->height,
18,944✔
4800
                                     request->dither,
18,944✔
4801
                                     request->output);
18,944✔
4802
    }
4803

4804
    return status;
19,124✔
4805
}
2,145✔
4806

4807
SIXELAPI SIXELSTATUS
4808
sixel_encode(
33,994✔
4809
    unsigned char  /* in */ *pixels,   /* pixel bytes */
4810
    int            /* in */ width,     /* image width */
4811
    int            /* in */ height,    /* image height */
4812
    int const      /* in */ depth,     /* color depth */
4813
    sixel_dither_t /* in */ *dither,   /* dither context */
4814
    sixel_output_t /* in */ *output)   /* output context */
4815
{
4816
    SIXELSTATUS status = SIXEL_FALSE;
33,994✔
4817
    sixel_encoder_core_t *core;
14,879✔
4818
    sixel_encoder_core_encode_request_t request;
14,879✔
4819

4820
    if (pixels == NULL) {
33,994!
UNCOV
4821
        sixel_helper_set_additional_message(
×
4822
            "sixel_encode: bad pixels parameter."
4823
            " (pixels == NULL)");
UNCOV
4824
        return SIXEL_BAD_ARGUMENT;
×
4825
    }
4826

4827
    if (dither == NULL) {
33,994!
UNCOV
4828
        sixel_helper_set_additional_message(
×
4829
            "sixel_encode: bad dither parameter."
4830
            " (dither == NULL)");
UNCOV
4831
        return SIXEL_BAD_ARGUMENT;
×
4832
    }
4833

4834
    if (output == NULL) {
33,994!
UNCOV
4835
        sixel_helper_set_additional_message(
×
4836
            "sixel_encode: bad output parameter."
4837
            " (output == NULL)");
4838
        return SIXEL_BAD_ARGUMENT;
×
4839
    }
4840

4841
    /* TODO: reference counting should be thread-safe */
4842
    sixel_dither_ref(dither);
33,994✔
4843
    sixel_output_ref(output);
33,994✔
4844

4845
    if (width < 1) {
33,994!
4846
        sixel_helper_set_additional_message(
×
4847
            "sixel_encode: bad width parameter."
4848
            " (width < 1)");
UNCOV
4849
        status = SIXEL_BAD_INPUT;
×
UNCOV
4850
        goto end;
×
4851
    }
4852

4853
    if (height < 1) {
33,994!
UNCOV
4854
        sixel_helper_set_additional_message(
×
4855
            "sixel_encode: bad height parameter."
4856
            " (height < 1)");
UNCOV
4857
        status = SIXEL_BAD_INPUT;
×
UNCOV
4858
        goto end;
×
4859
    }
4860

4861
    core = sixel_output_as_encoder_core(output);
33,994✔
4862
    if (core == NULL || core->vtbl == NULL ||
33,994!
4863
        core->vtbl->encode == NULL) {
33,994!
UNCOV
4864
        status = SIXEL_BAD_ARGUMENT;
×
UNCOV
4865
        goto end;
×
4866
    }
4867
    request.pixels = pixels;
33,994✔
4868
    request.width = width;
33,994✔
4869
    request.height = height;
33,994✔
4870
    request.depth = depth;
33,994✔
4871
    request.dither = dither;
33,994✔
4872
    request.output = output;
33,994✔
4873
    status = core->vtbl->encode(core, &request);
33,994✔
4874

4875
end:
31,854✔
4876
    sixel_output_unref(output);
33,994✔
4877
    sixel_dither_unref(dither);
33,994✔
4878

4879
    return status;
33,994✔
4880
}
2,140✔
4881

4882

4883
/* emacs Local Variables:      */
4884
/* emacs mode: c               */
4885
/* emacs tab-width: 4          */
4886
/* emacs indent-tabs-mode: nil */
4887
/* emacs c-basic-offset: 4     */
4888
/* emacs End:                  */
4889
/* vim: set expandtab ts=4 sts=4 sw=4 : */
4890
/* 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