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

saitoha / libsixel / 29924251480

21 Jul 2026 01:29PM UTC coverage: 85.02% (-0.01%) from 85.032%
29924251480

push

github

saitoha
fix: close sixel dcs after late encode errors

79226 of 144643 branches covered (54.77%)

10 of 10 new or added lines in 1 file covered. (100.0%)

42 existing lines in 7 files now uncovered.

141359 of 166265 relevant lines covered (85.02%)

5823136.0 hits per line

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

92.3
/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,
25,628✔
291
                                      sixel_timeline_logger_t **logger)
292
{
293
    if (logger == NULL) {
25,628!
294
        return;
295
    }
296

297
    *logger = NULL;
25,628✔
298
    (void)sixel_timeline_logger_prepare_env(allocator, logger);
25,628✔
299
}
2,338✔
300
#endif
301

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

318
    if (config == NULL) {
17,699✔
319
        return;
×
320
    }
321

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

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

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

354
    if (!dither_env_override && pipeline_threads >= 4 && dither_threads < 2) {
17,699!
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
         */
359
        dither_threads = pipeline_threads - 2;
×
360
    }
361

362
    encode_threads = pipeline_threads - dither_threads;
17,699✔
363
    if (encode_threads < 2 && pipeline_threads > 2) {
17,699✔
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;
17,391✔
370
        dither_threads = pipeline_threads - encode_threads;
17,391✔
371
    }
1,594✔
372
    if (encode_threads < 1) {
17,699✔
373
        encode_threads = 1;
242✔
374
        dither_threads = pipeline_threads - encode_threads;
242✔
375
    }
22✔
376
    if (dither_threads < 1) {
11,318!
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;
17,699✔
387
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_WIDTH");
17,699✔
388
    if (text != NULL && text[0] != '\0') {
17,699!
389
        errno = 0;
66✔
390
        parsed = strtol(text, &endptr, 10);
66✔
391
        if (endptr != text && errno != ERANGE && parsed > 0) {
66!
392
            if (parsed > INT_MAX) {
30!
393
                parsed = INT_MAX;
394
            }
395
            band_height = (int)parsed;
48✔
396
        }
6✔
397
    }
6✔
398
    if (band_height <= 0) {
9,661✔
399
        band_height = (height + dither_threads - 1) / dither_threads;
17,633✔
400
    }
1,616✔
401
    if (band_height < 6) {
17,699✔
402
        band_height = 6;
1,392✔
403
    }
232✔
404
    if ((band_height % 6) != 0) {
16,545✔
405
        band_height = ((band_height + 5) / 6) * 6;
12,598✔
406
    }
1,157✔
407

408
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_OVERWRAP");
17,699✔
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) {
17,699✔
414
        overlap = 6;
5,522✔
415
    } else {
923✔
416
        overlap = 0;
7,585✔
417
    }
418
    if (text != NULL && text[0] != '\0') {
17,699!
419
        errno = 0;
66✔
420
        parsed = strtol(text, &endptr, 10);
66✔
421
        if (endptr != text && errno != ERANGE && parsed >= 0) {
66!
422
            if (parsed > INT_MAX) {
30!
423
                parsed = INT_MAX;
424
            }
425
            overlap = (int)parsed;
48✔
426
        }
6✔
427
    }
6✔
428
    if (overlap < 0) {
9,649!
429
        overlap = 0;
430
    }
431
    if (overlap > band_height / 2) {
17,699✔
432
        overlap = band_height / 2;
2,502✔
433
    }
417✔
434

435
    config->enabled = 1;
17,699✔
436
    config->band_height = band_height;
17,699✔
437
    config->overlap = overlap;
17,699✔
438
    config->dither_threads = dither_threads;
17,699✔
439
    config->encode_threads = encode_threads;
17,699✔
440
}
1,622✔
441

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

449
    return output->transparent_offset_left != 0 ||
171,580✔
450
           output->transparent_offset_top != 0;
82,625!
451
}
6,404✔
452

453
static int
454
sixel_count_sixel_bands(int height)
75,521✔
455
{
456
    int bands;
34,688✔
457

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

462
    bands = height / 6;
75,521✔
463
    if ((height % 6) != 0) {
75,521✔
464
        bands += 1;
55,962✔
465
    }
5,082✔
466

467
    return bands;
40,833✔
468
}
6,205✔
469

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

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

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

494
    *encoded_width = width + left;
87,215✔
495
    *encoded_height = height + top;
87,215✔
496
    return SIXEL_OK;
87,215✔
497
}
6,398✔
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)
19,815✔
541
{
542
    memset(ctx, 0, sizeof(*ctx));
19,815✔
543
    ctx->pixels = NULL;
19,815✔
544
    ctx->keycolor = (-1);
19,815✔
545
    ctx->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
19,815✔
546
    ctx->writer_error = SIXEL_OK;
19,815✔
547
}
12,575✔
548

549
static int
550
sixel_parallel_context_is_ormode(sixel_parallel_context_t const *ctx)
227,373✔
551
{
552
    if (ctx == NULL || ctx->output == NULL) {
227,138!
553
        return 0;
4✔
554
    }
555
    return ctx->output->ormode != 0 ? 1 : 0;
209,344✔
556
}
25,932✔
557

558
static void
559
sixel_parallel_worker_release_nodes(sixel_parallel_worker_state_t *state,
161,134✔
560
                                    sixel_allocator_t *allocator)
561
{
562
    sixel_node_t *np;
73,005✔
563

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

568
    while ((np = state->output->node_free) != NULL) {
6,384,196✔
569
        state->output->node_free = np->next;
6,223,061✔
570
        sixel_allocator_free(allocator, np);
6,223,061✔
571
    }
572
    state->output->node_top = NULL;
161,135✔
573
}
14,801✔
574

575
static void
576
sixel_parallel_worker_cleanup(sixel_parallel_worker_state_t *state,
60,528✔
577
                              sixel_allocator_t *allocator)
578
{
579
    if (state == NULL) {
60,528✔
580
        return;
10,895✔
581
    }
582
    sixel_parallel_worker_release_nodes(state, allocator);
39,859✔
583
    if (state->output != NULL) {
39,859!
584
        sixel_output_unref(state->output);
39,859✔
585
        state->output = NULL;
39,859✔
586
    }
3,660✔
587
    sixel_encode_work_cleanup(&state->work, allocator);
39,859✔
588
    sixel_band_state_reset(&state->band);
39,859✔
589
    state->initialized = 0;
39,859✔
590
    state->index = 0;
39,859✔
591
    state->writer_error = SIXEL_OK;
39,859✔
592
    state->band_buffer = NULL;
39,859✔
593
    state->context = NULL;
39,859✔
594
}
5,554✔
595

596
static void
597
sixel_parallel_context_cleanup(sixel_parallel_context_t *ctx)
19,815✔
598
{
599
    int i;
9,012✔
600

601
    if (ctx->workers != NULL) {
19,815!
602
        for (i = 0; i < ctx->worker_capacity; i++) {
80,343✔
603
            sixel_parallel_worker_cleanup(ctx->workers[i], ctx->allocator);
60,528✔
604
        }
5,554✔
605
        free(ctx->workers);
19,815✔
606
        ctx->workers = NULL;
19,815✔
607
    }
1,815✔
608
    sixel_parallel_writer_stop(ctx, 1);
19,815✔
609
    if (ctx->bands != NULL) {
19,815!
610
        if (ctx->band_count < 0) {
19,815!
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++) {
141,819✔
618
            free(ctx->bands[i].data);
122,004✔
619
            ctx->bands[i].data = NULL;
122,004✔
620
        }
11,207✔
621
#if defined(_MSC_VER)
622
#pragma warning(pop)
623
#endif
624
        free(ctx->bands);
19,815✔
625
        ctx->bands = NULL;
19,815✔
626
    }
1,815✔
627
    free(ctx->band_source_rows_total);
19,815✔
628
    ctx->band_source_rows_total = NULL;
19,815✔
629
    free(ctx->band_source_rows_ready);
19,815✔
630
    ctx->band_source_rows_ready = NULL;
19,815✔
631
    ctx->band_count = 0;
19,815✔
632
    if (ctx->pool != NULL) {
19,815!
633
        ctx->pool->vtbl->unref(ctx->pool);
19,815✔
634
        ctx->pool = NULL;
19,815✔
635
    }
1,815✔
636
    if (ctx->cond_ready) {
19,815!
637
        sixel_cond_destroy(&ctx->cond_band_ready);
19,815✔
638
        ctx->cond_ready = 0;
19,815✔
639
    }
1,815✔
640
    if (ctx->mutex_ready) {
19,815!
641
        sixel_mutex_destroy(&ctx->mutex);
19,815✔
642
        ctx->mutex_ready = 0;
19,815✔
643
    }
1,815✔
644
}
19,815✔
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)
121,268✔
683
{
684
    int accept;
55,229✔
685

686
    if (ctx == NULL) {
121,268✔
687
        return 0;
688
    }
689
    if (!ctx->mutex_ready) {
121,268!
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);
121,268✔
697
    accept = (!ctx->writer_should_stop && ctx->writer_error == SIXEL_OK);
121,278!
698
    sixel_mutex_unlock(&ctx->mutex);
121,278✔
699
    return accept;
121,276✔
700
}
11,141✔
701

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

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

723
static SIXELSTATUS
724
sixel_parallel_worker_prepare(sixel_parallel_worker_state_t *state,
121,273✔
725
                              sixel_parallel_context_t *ctx)
726
{
727
    SIXELSTATUS status;
55,231✔
728

729
    if (state->initialized) {
121,273✔
730
        return SIXEL_OK;
43,957✔
731
    }
732

733
    sixel_encode_work_init(&state->work);
39,856✔
734
    sixel_band_state_reset(&state->band);
39,850✔
735
    state->writer_error = SIXEL_OK;
39,850✔
736
    state->band_buffer = NULL;
39,850✔
737
    state->context = ctx;
39,850✔
738

739
    if (!sixel_parallel_context_is_ormode(ctx)) {
39,850✔
740
        status = sixel_encode_work_allocate(&state->work,
43,368✔
741
                                            ctx->encoded_width,
3,647✔
742
                                            ctx->ncolors,
3,647✔
743
                                            ctx->allocator);
3,647✔
744
        if (SIXEL_FAILED(status)) {
39,731!
745
            return status;
746
        }
747
    }
3,647✔
748

749
    status = sixel_encoder_core_create_output_from_factory(&state->output,
43,523✔
750
                                              sixel_parallel_band_writer,
751
                                              state,
3,660✔
752
                                              ctx->allocator);
3,660✔
753
    if (SIXEL_FAILED(status)) {
39,853!
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;
39,853✔
761
    state->output->has_sixel_scrolling = ctx->output->has_sixel_scrolling;
39,853✔
762
    state->output->has_sdm_glitch = ctx->output->has_sdm_glitch;
39,853✔
763
    state->output->has_gri_arg_limit = ctx->output->has_gri_arg_limit;
39,853✔
764
    state->output->skip_dcs_envelope = 1;
39,853✔
765
    state->output->skip_header = 1;
39,853✔
766
    state->output->palette_type = ctx->output->palette_type;
39,853✔
767
    state->output->colorspace = ctx->output->colorspace;
39,853✔
768
    state->output->source_colorspace = ctx->output->source_colorspace;
39,853✔
769
    state->output->pixelformat = ctx->output->pixelformat;
39,853✔
770
    state->output->penetrate_multiplexer =
39,853✔
771
        ctx->output->penetrate_multiplexer;
39,853✔
772
    state->output->encode_policy = ctx->output->encode_policy;
39,853✔
773
    state->output->ormode = ctx->output->ormode;
39,853✔
774

775
    state->initialized = 1;
39,853✔
776
    state->index = (-1);
39,853✔
777

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

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

795
    return SIXEL_OK;
22,086✔
796
}
11,141✔
797

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

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

809
    capped_target = target_threads;
17,402✔
810
    if (capped_target > ctx->worker_capacity) {
17,402✔
811
        capped_target = ctx->worker_capacity;
6,437✔
812
    }
1,073✔
813
    if (ctx->band_count > 0 && capped_target > ctx->band_count) {
17,402!
814
        capped_target = ctx->band_count;
7,915✔
815
    }
816
    if (capped_target <= ctx->thread_count) {
17,402✔
817
        return SIXEL_OK;
2,640✔
818
    }
819

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

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

835
    return SIXEL_OK;
6,847✔
836
}
1,595✔
837

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

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

861
    required = band->used + (size_t)size;
120,714✔
862
    if (required < band->used) {
120,714!
863
        state->writer_error = SIXEL_BAD_INTEGER_OVERFLOW;
864
        return size;
865
    }
866
    capacity = band->size;
120,714✔
867
    if (required > capacity) {
120,714!
868
        if (capacity == 0) {
120,700!
869
            new_capacity = (size_t)SIXEL_OUTPUT_PACKET_SIZE;
98,997✔
870
        } else {
11,088✔
871
            new_capacity = capacity;
3✔
872
        }
873
        while (new_capacity < required) {
120,700!
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);
120,700✔
881
        if (tmp == NULL) {
120,700!
882
            state->writer_error = SIXEL_BAD_ALLOCATION;
883
            return size;
884
        }
885
        band->data = tmp;
120,700✔
886
        band->size = new_capacity;
120,700✔
887
    }
11,091✔
888

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

892
    return size;
120,714✔
893
}
11,091✔
894

895
static SIXELSTATUS
896
sixel_parallel_worker_flush_output(sixel_parallel_worker_state_t *state)
121,260✔
897
{
898
    if (state == NULL || state->output == NULL) {
121,260!
899
        return SIXEL_BAD_ARGUMENT;
13✔
900
    }
901
    if (state->output->pos > 0) {
121,251✔
902
        state->writer_error = sixel_output_write_bytes(
230,335✔
903
            state->output,
11,088✔
904
            (char *)state->output->buffer,
120,699✔
905
            state->output->pos);
65,731✔
906
        state->output->pos = 0;
120,724✔
907
    }
11,088✔
908
    if (state->writer_error != SIXEL_OK) {
121,276!
909
        return state->writer_error;
910
    }
911
    return SIXEL_OK;
66,039✔
912
}
11,141✔
913

914
static SIXELSTATUS
915
sixel_parallel_create_pool(sixel_thread_pool_t **pool,
19,815✔
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,012✔
923
    sixel_thread_pool_create_request_t request;
9,012✔
924
    void *service_object;
9,012✔
925
    SIXELSTATUS status;
9,012✔
926

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

934
    service = NULL;
19,815✔
935
    service_object = NULL;
19,815✔
936
    status = sixel_components_getservice("services/threadpool",
19,815✔
937
                                         &service_object);
938
    if (SIXEL_FAILED(status)) {
19,815!
939
        return status;
940
    }
941
    service = (sixel_threadpool_service_t *)service_object;
19,815✔
942
    if (service == NULL || service->vtbl == NULL ||
19,815!
943
        service->vtbl->create_pool == NULL) {
19,815!
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;
19,815✔
952
    request.queue_size = queue_depth;
19,815✔
953
    request.workspace_size = workspace_size;
19,815✔
954
    request.worker = worker;
19,815✔
955
    request.userdata = userdata;
19,815✔
956
    request.workspace_cleanup = NULL;
19,815✔
957
    status = service->vtbl->create_pool(service, &request, pool);
19,815✔
958
    if (service->vtbl->unref != NULL) {
19,815!
959
        service->vtbl->unref(service);
19,815✔
960
    }
1,815✔
961

962
    return status;
10,803✔
963
}
1,815✔
964

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

974
    if (ctx == NULL || ctx->band_source_rows_total == NULL ||
19,815!
975
            ctx->band_source_rows_ready == NULL) {
19,815✔
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++) {
141,819✔
987
        band_start = band_index * 6;
122,004✔
988
        band_end = ctx->encoded_height;
122,004✔
989
        if (band_start <= ctx->encoded_height - 6) {
122,004✔
990
            band_end = band_start + 6;
106,042✔
991
        }
9,743✔
992

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

1008
        ctx->band_source_rows_total[band_index] = source_end - source_start;
122,004✔
1009
        ctx->band_source_rows_ready[band_index] = 0;
122,004✔
1010
    }
11,207✔
1011
}
1,815✔
1012

1013
static void
1014
sixel_parallel_submit_source_empty_bands(sixel_parallel_context_t *ctx)
17,468✔
1015
{
1016
    int band_index;
7,945✔
1017

1018
    if (ctx == NULL || ctx->band_source_rows_total == NULL) {
17,468!
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++) {
123,257✔
1028
        if (ctx->band_source_rows_total[band_index] == 0) {
105,789!
1029
            sixel_parallel_submit_band(ctx, band_index);
1030
        }
1031
    }
9,731✔
1032
}
1,601✔
1033

1034
static SIXELSTATUS
1035
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
19,815✔
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,012✔
1051
    int nbands;
9,012✔
1052
    int encoded_width;
9,012✔
1053
    int encoded_height;
9,012✔
1054
    int threads;
9,012✔
1055
    int i;
9,012✔
1056

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

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

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

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

1119
    ctx->bands = (sixel_parallel_band_buffer_t *)calloc((size_t)nbands,
19,815✔
1120
                                                        sizeof(*ctx->bands));
1121
    if (ctx->bands == NULL) {
19,815!
1122
        return SIXEL_BAD_ALLOCATION;
1123
    }
1124
    for (i = 0; i < nbands; ++i) {
141,819✔
1125
        ctx->bands[i].data = NULL;
122,004✔
1126
        ctx->bands[i].size = 0;
122,004✔
1127
        ctx->bands[i].used = 0;
122,004✔
1128
        ctx->bands[i].status = SIXEL_OK;
122,004✔
1129
        ctx->bands[i].ready = 0;
122,004✔
1130
        ctx->bands[i].dispatched = 0;
122,004✔
1131
    }
11,207✔
1132
    ctx->band_count = nbands;
19,815✔
1133

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

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

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

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

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

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

1183
    ctx->pool->vtbl->set_affinity(ctx->pool, ctx->pin_threads);
19,815✔
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);
19,815✔
1190
    ctx->next_band_to_flush = 0;
19,815✔
1191
    ctx->writer_should_stop = 0;
19,815✔
1192
    ctx->writer_error = SIXEL_OK;
19,815✔
1193

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

1204
    return SIXEL_OK;
19,815✔
1205
}
1,815✔
1206

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

1214
    if (ctx == NULL || ctx->pool == NULL) {
121,278!
1215
        return;
1✔
1216
    }
1217
    if (band_index < 0 || band_index >= ctx->band_count) {
121,278!
1218
        return;
3✔
1219
    }
1220

1221
    dispatch = 0;
121,277✔
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) {
121,277!
1228
        sixel_mutex_lock(&ctx->mutex);
121,277✔
1229
        if (!ctx->bands[band_index].dispatched
121,278!
1230
                && !ctx->writer_should_stop
110,536!
1231
                && ctx->writer_error == SIXEL_OK) {
121,278!
1232
            ctx->bands[band_index].dispatched = 1;
121,278✔
1233
            dispatch = 1;
121,278✔
1234
        }
11,141✔
1235
        sixel_mutex_unlock(&ctx->mutex);
121,278✔
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) {
121,278!
1245
        return;
1246
    }
1247

1248
    sixel_fence_release();
121,278✔
1249
    if (ctx->logger != NULL) {
121,278✔
1250
        sixel_timeline_logger_logf(ctx->logger,
564✔
1251
                          "controller",
1252
                          "encode",
1253
                          "dispatch",
1254
                          band_index);
47✔
1255
    }
47✔
1256
    job.band_index = band_index;
121,278✔
1257
    status = ctx->pool->vtbl->push(ctx->pool, job);
121,278✔
1258
    if (SIXEL_FAILED(status)) {
121,278!
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)
19,815✔
1271
{
1272
    int pool_error;
9,012✔
1273

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

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

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

1289
    return SIXEL_OK;
10,803✔
1290
}
1,815✔
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)
592,960✔
1299
{
1300
    sixel_parallel_row_notifier_t *notifier;
270,146✔
1301
    sixel_parallel_context_t *ctx;
270,146✔
1302
    sixel_timeline_logger_t *logger;
270,146✔
1303
    int band_height;
270,146✔
1304
    int band_index;
270,146✔
1305
    int ready_row;
270,146✔
1306
    int should_dispatch;
270,146✔
1307
    int rows_ready;
270,146✔
1308
    int rows_total;
270,146✔
1309

1310
    notifier = (sixel_parallel_row_notifier_t *)priv;
592,960✔
1311
    if (notifier == NULL) {
592,960✔
1312
        return;
1313
    }
1314
    ctx = notifier->context;
592,960✔
1315
    logger = notifier->logger;
592,960✔
1316
    if (ctx == NULL || ctx->band_count <= 0 || ctx->height <= 0) {
592,960!
1317
        return;
1✔
1318
    }
1319
    if (row_index < 0 || row_index >= ctx->height) {
592,962!
1320
        return;
4✔
1321
    }
1322
    band_height = notifier->band_height;
592,963✔
1323
    if (band_height < 1) {
592,963!
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;
592,963✔
1335
    band_index = ready_row / band_height;
592,963✔
1336
    if (band_index >= ctx->band_count) {
592,963!
1337
        band_index = ctx->band_count - 1;
1338
    }
1339
    if (band_index < 0) {
592,963!
1340
        return;
1341
    }
1342

1343
    should_dispatch = 0;
592,963✔
1344
    if (ctx->band_source_rows_total != NULL &&
592,963!
1345
            ctx->band_source_rows_ready != NULL) {
592,959!
1346
        if (ctx->mutex_ready) {
592,963!
1347
            sixel_mutex_lock(&ctx->mutex);
592,951✔
1348
        }
54,556✔
1349
        rows_total = ctx->band_source_rows_total[band_index];
592,998✔
1350
        rows_ready = ctx->band_source_rows_ready[band_index];
592,998✔
1351
        if (rows_total > 0 && rows_ready < rows_total) {
592,998!
1352
            rows_ready += 1;
592,989✔
1353
            ctx->band_source_rows_ready[band_index] = rows_ready;
592,989✔
1354
            if (rows_ready == rows_total) {
592,989✔
1355
                should_dispatch = 1;
327,364✔
1356
            }
9,665✔
1357
        }
54,559✔
1358
        if (ctx->mutex_ready) {
592,998!
1359
            sixel_mutex_unlock(&ctx->mutex);
592,989✔
1360
        }
54,559✔
1361
    } else if ((ready_row % band_height) == band_height - 1 ||
54,571!
1362
            row_index == ctx->height - 1) {
×
1363
        should_dispatch = 1;
1364
    }
1365

1366
    if (!should_dispatch) {
592,988✔
1367
        return;
353,244✔
1368
    }
1369

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

1378
    sixel_parallel_submit_band(ctx, band_index);
105,064✔
1379
}
54,560✔
1380

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

1388
    band = &ctx->bands[band_index];
121,278✔
1389
    if (ctx->logger != NULL) {
121,278✔
1390
        sixel_timeline_logger_logf(ctx->logger,
564✔
1391
                          "worker",
1392
                          "encode",
1393
                          "writer_flush",
1394
                          band_index);
47✔
1395
    }
47✔
1396
    offset = 0;
99,468✔
1397
    while (offset < band->used) {
243,655✔
1398
        chunk = band->used - offset;
122,377✔
1399
        if (chunk > (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos)) {
122,377✔
1400
            chunk = (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos);
900✔
1401
        }
150✔
1402
        memcpy(ctx->output->buffer + ctx->output->pos,
144,859✔
1403
               band->data + offset,
122,377✔
1404
               chunk);
11,241✔
1405
        sixel_advance(ctx->output, (int)chunk);
122,377✔
1406
        offset += chunk;
122,377✔
1407
    }
1408
    return SIXEL_OK;
121,278✔
1409
}
1410

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

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

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

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

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

1460
    sixel_fence_acquire();
121,276✔
1461

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

1467
    if (!sixel_parallel_jobs_allowed(ctx)) {
121,276!
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;
121,276✔
1485
    sixel_parallel_worker_reset(state);
121,276✔
1486

1487
    band_start = band_index * 6;
121,253✔
1488
    band_height = ctx->encoded_height - band_start;
121,253✔
1489
    if (band_height > 6) {
121,253✔
1490
        band_height = 6;
55,270✔
1491
    }
9,329✔
1492
    if (band_height <= 0) {
75,020!
1493
        goto cleanup;
1494
    }
1495

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

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

1522
    for (row_index = 0; row_index < band_height; row_index++) {
803,822✔
1523
        absolute_row = band_start + row_index;
683,197✔
1524
        status = sixel_band_classify_row(&state->work,
745,971✔
1525
                                         &state->band,
62,774✔
1526
                                         ctx->pixels,
62,774✔
1527
                                         ctx->width,
62,774✔
1528
                                         ctx->height,
62,774✔
1529
                                         ctx->encoded_width,
62,774✔
1530
                                         absolute_row,
62,774✔
1531
                                         ctx->offset_left,
62,774✔
1532
                                         ctx->offset_top,
62,774✔
1533
                                         ctx->ncolors,
62,774✔
1534
                                         ctx->keycolor,
62,774✔
1535
                                         ctx->palstate,
62,774✔
1536
                                         ctx->encode_policy);
62,774✔
1537
        if (SIXEL_FAILED(status)) {
683,214!
1538
            goto cleanup;
1539
        }
1540
    }
62,774✔
1541

1542
    status = sixel_band_compose(&state->work,
131,708✔
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)) {
120,633!
1550
        goto cleanup;
1551
    }
1552

1553
    last_row_index = band_start + band_height - 1;
120,633✔
1554
    status = sixel_band_emit(&state->work,
131,716✔
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)) {
120,616!
1561
        goto cleanup;
1562
    }
1563

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

1569
    status = sixel_parallel_worker_flush_output(state);
120,618✔
1570
    if (SIXEL_FAILED(status)) {
120,636!
1571
        goto cleanup;
1572
    }
1573

1574
    sixel_band_finish(&state->work, &state->band);
120,636✔
1575
done:
88,322✔
1576
    status = SIXEL_OK;
66,041✔
1577

1578
cleanup:
110,133✔
1579
    sixel_parallel_worker_release_nodes(state, ctx->allocator);
121,274✔
1580
    if (band != NULL && ctx->mutex_ready && ctx->cond_ready) {
121,275!
1581
        sixel_fence_release();
121,271✔
1582
        sixel_mutex_lock(&ctx->mutex);
121,271✔
1583
        band->status = status;
121,278✔
1584
        band->ready = 1;
121,278✔
1585
        sixel_cond_broadcast(&ctx->cond_band_ready);
121,278✔
1586
        sixel_mutex_unlock(&ctx->mutex);
121,278✔
1587
    }
11,141✔
1588
    if (ctx->logger != NULL) {
121,267✔
1589
        sixel_timeline_logger_logf(ctx->logger,
564✔
1590
                          "worker",
1591
                          "encode",
1592
                          "worker_done",
1593
                          band_index);
47✔
1594
    }
47✔
1595
    if (SIXEL_FAILED(status)) {
121,271!
1596
        return status;
1597
    }
1598
    return SIXEL_OK;
66,041✔
1599
}
11,141✔
1600

1601
static void
1602
sixel_parallel_writer_stop(sixel_parallel_context_t *ctx, int force_abort)
39,630✔
1603
{
1604
    int should_signal;
18,024✔
1605

1606
    if (ctx == NULL || !ctx->writer_started) {
39,630!
1607
        return;
10,803✔
1608
    }
1609

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

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

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

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

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

1655
    for (;;) {
77,201✔
1656
        sixel_mutex_lock(&ctx->mutex);
141,093✔
1657
        while (!ctx->writer_should_stop &&
250,827✔
1658
               ctx->next_band_to_flush < ctx->band_count) {
229,877✔
1659
    band_index = ctx->next_band_to_flush;
210,128✔
1660
    band = &ctx->bands[band_index];
210,128✔
1661
    if (band->ready) {
210,128✔
1662
        break;
66,045✔
1663
    }
1664
            sixel_cond_wait(&ctx->cond_band_ready, &ctx->mutex);
88,850✔
1665
        }
1666

1667
        if (ctx->writer_should_stop) {
141,093✔
1668
            sixel_mutex_unlock(&ctx->mutex);
66✔
1669
            break;
66✔
1670
        }
1671

1672
        if (ctx->next_band_to_flush >= ctx->band_count) {
141,027✔
1673
            sixel_mutex_unlock(&ctx->mutex);
19,749✔
1674
            break;
19,749✔
1675
        }
1676

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

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

1705
    return SIXEL_OK;
10,803✔
1706
}
1,815✔
1707

1708
static SIXELSTATUS
1709
sixel_encode_body_parallel(sixel_index_t *pixels,
2,347✔
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,347✔
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,347✔
1731
    sixel_timeline_logger_prepare_default(allocator, &logger);
2,347✔
1732
    status = sixel_output_compute_transparent_extent(output,
2,561✔
1733
                                                     width,
214✔
1734
                                                     height,
214✔
1735
                                                     &encoded_width,
1736
                                                     &encoded_height);
1737
    if (SIXEL_FAILED(status)) {
2,347!
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,347!
1743
    if (nbands <= 0) {
2,347!
1744
        sixel_timeline_logger_unref(logger);
1745
        return SIXEL_OK;
1746
    }
1747

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

1764
    status = sixel_parallel_context_begin(&ctx,
2,347✔
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,347!
1779
        sixel_parallel_context_cleanup(&ctx);
1780
        sixel_timeline_logger_unref(logger);
1781
        return status;
1782
    }
1783

1784
    for (i = 0; i < nbands; i++) {
18,562✔
1785
        sixel_parallel_submit_band(&ctx, i);
16,215✔
1786
    }
1,476✔
1787

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

1795
    sixel_parallel_context_cleanup(&ctx);
2,347✔
1796
    sixel_timeline_logger_unref(logger);
2,347✔
1797
    return SIXEL_OK;
2,347✔
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,
17,402✔
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,915✔
1819
    SIXELSTATUS wait_status;
7,915✔
1820
    sixel_parallel_context_t ctx = {0};
17,402✔
1821
    sixel_index_t *indexes;
7,915✔
1822
    sixel_index_t *result;
7,915✔
1823
    sixel_allocator_t *allocator;
7,915✔
1824
    size_t pixel_count;
7,915✔
1825
    size_t buffer_size;
7,915✔
1826
    int threads;
7,915✔
1827
    int nbands;
7,915✔
1828
    int queue_depth;
7,915✔
1829
    int waited;
7,915✔
1830
    int dither_threads_budget;
7,915✔
1831
    int worker_capacity;
7,915✔
1832
    int boost_target;
7,915✔
1833
    int encoded_width;
7,915✔
1834
    int encoded_height;
7,915✔
1835
    sixel_timeline_logger_t *logger;
7,915✔
1836
    int owns_logger;
7,915✔
1837
    sixel_parallel_row_notifier_t notifier;
7,915✔
1838

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

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

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

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

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

1897
    queue_depth = threads * 3;
17,402✔
1898
    if (queue_depth > nbands) {
17,402✔
1899
        queue_depth = nbands;
6,497✔
1900
    }
1,083✔
1901
    if (queue_depth < 1) {
17,402!
1902
        queue_depth = 1;
1903
    }
1904

1905
    dither_threads_budget = dither->pipeline_dither_threads;
17,402✔
1906
    worker_capacity = threads + dither_threads_budget;
17,402✔
1907
    if (worker_capacity < threads) {
17,402!
1908
        worker_capacity = threads;
1909
    }
1910
    if (worker_capacity > nbands) {
17,402✔
1911
        worker_capacity = nbands;
6,431✔
1912
    }
1,072✔
1913

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

1922
    if (logger != NULL) {
17,402✔
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,
121✔
1930
                          "controller",
1931
                          "pipeline",
1932
                          "configure",
1933
                          -1);
1934
    }
11✔
1935

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

1955
    result = sixel_dither_apply_palette(dither, pixels, width, height);
17,402✔
1956
    if (result == NULL) {
17,402✔
1957
        status = SIXEL_RUNTIME_ERROR;
66✔
1958
        goto cleanup;
66✔
1959
    }
1960
    if (result != indexes) {
17,336!
1961
        status = SIXEL_RUNTIME_ERROR;
1962
        goto cleanup;
1963
    }
1964
    if (dither->pipeline_accumulation_result_enabled != 0) {
17,336✔
1965
        status = sixel_dither_set_pipeline_accumulation_result_rgb(
32✔
1966
            dither,
2✔
1967
            indexes,
2✔
1968
            pixel_count,
2✔
1969
            palette,
2✔
1970
            (size_t)dither->ncolors);
22✔
1971
        if (SIXEL_FAILED(status)) {
22!
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;
17,336✔
1982
    status = sixel_parallel_context_grow(&ctx, boost_target);
17,336✔
1983
    if (SIXEL_FAILED(status)) {
17,336!
1984
        goto cleanup;
1985
    }
1986

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

1993
cleanup:
15,777✔
1994
    dither->pipeline_row_callback = NULL;
17,402✔
1995
    dither->pipeline_row_priv = NULL;
17,402✔
1996
    dither->pipeline_index_buffer = NULL;
17,402✔
1997
    dither->pipeline_index_size = 0;
17,402✔
1998
    dither->pipeline_image_width = 0;
17,402✔
1999
    dither->pipeline_image_height = 0;
17,402✔
2000
    dither->pipeline_transparent_mask = NULL;
17,402✔
2001
    dither->pipeline_transparent_mask_size = 0;
17,402✔
2002
    dither->pipeline_transparent_keycolor = (-1);
17,402✔
2003
    dither->pipeline_accumulation_result_enabled = 0;
17,402✔
2004
    if (!waited && ctx.pool != NULL) {
17,402!
2005
        wait_status = sixel_parallel_context_wait(&ctx, status != SIXEL_OK);
66✔
2006
        if (status == SIXEL_OK) {
66!
2007
            status = wait_status;
7,915✔
2008
        }
2009
    }
6✔
2010
    sixel_parallel_context_cleanup(&ctx);
17,402✔
2011
    if (owns_logger) {
17,402✔
2012
        sixel_timeline_logger_unref(logger);
121✔
2013
    }
11✔
2014
    if (indexes != NULL) {
17,402!
2015
        sixel_allocator_free(allocator, indexes);
17,402✔
2016
    }
1,595✔
2017
    return status;
12,617✔
2018
}
1,595✔
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,
66✔
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};
66✔
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 ||
66!
2078
            output == NULL) {
36!
2079
        return SIXEL_BAD_ARGUMENT;
2080
    }
2081

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

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

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

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

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

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

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

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

2155
    status = sixel_parallel_context_begin(&ctx,
66✔
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)) {
66!
2170
        goto cleanup;
2171
    }
2172
    sixel_parallel_submit_source_empty_bands(&ctx);
66✔
2173

2174
    result = sixel_dither_apply_palette(dither, pixels, width, height);
66✔
2175
    if (result == NULL) {
66!
2176
        status = SIXEL_RUNTIME_ERROR;
2177
        goto cleanup;
2178
    }
2179
    if (result != indexes) {
66!
2180
        status = SIXEL_RUNTIME_ERROR;
2181
        goto cleanup;
2182
    }
2183
    if (dither->pipeline_accumulation_result_enabled != 0) {
66!
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;
66✔
2200
    status = sixel_parallel_context_grow(&ctx, boost_target);
66✔
2201
    if (SIXEL_FAILED(status)) {
66!
2202
        goto cleanup;
2203
    }
2204

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

2211
cleanup:
60✔
2212
    dither->pipeline_row_callback = NULL;
66✔
2213
    dither->pipeline_row_priv = NULL;
66✔
2214
    dither->pipeline_index_buffer = NULL;
66✔
2215
    dither->pipeline_index_size = 0;
66✔
2216
    dither->pipeline_image_width = 0;
66✔
2217
    dither->pipeline_image_height = 0;
66✔
2218
    dither->pipeline_transparent_mask = NULL;
66✔
2219
    dither->pipeline_transparent_mask_size = 0;
66✔
2220
    dither->pipeline_transparent_keycolor = (-1);
66✔
2221
    dither->pipeline_accumulation_result_enabled = 0;
66✔
2222
    if (!waited && ctx.pool != NULL) {
66!
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);
66✔
2229
    if (owns_logger) {
66!
2230
        sixel_timeline_logger_unref(logger);
2231
    }
2232
    if (indexes != NULL) {
66!
2233
        sixel_allocator_free(allocator, indexes);
66✔
2234
    }
6✔
2235
    return status;
48✔
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)
90,124,374✔
2262
{
2263
    if ((output->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) {
90,124,374✔
2264
        (void)sixel_output_write_bytes(output,
2,747✔
2265
                                       (char *)output->buffer,
2,579✔
2266
                                       SIXEL_OUTPUT_PACKET_SIZE);
2267
        memcpy(output->buffer,
2,915✔
2268
               output->buffer + SIXEL_OUTPUT_PACKET_SIZE,
1,374✔
2269
               (size_t)(output->pos -= SIXEL_OUTPUT_PACKET_SIZE));
2,579✔
2270
    }
168✔
2271
}
90,124,374✔
2272

2273

2274
static void
2275
sixel_putc(unsigned char *buffer, unsigned char value)
25,383,820✔
2276
{
2277
    *buffer = value;
25,383,820✔
2278
}
13,509,612✔
2279

2280

2281
static void
2282
sixel_puts(unsigned char *buffer, char const *value, int size)
2,516,130✔
2283
{
2284
    memcpy(buffer, (void *)value, (size_t)size);
2,516,130✔
2285
}
1,665,451✔
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,
37,838,335✔
2294
                          unsigned char value,
2295
                          int count)
2296
{
2297
    int chunk;
17,667,448✔
2298

2299
    if (count <= 0) {
37,838,335✔
2300
        return;
2301
    }
2302

2303
    while (count > 0) {
75,701,286✔
2304
        chunk = SIXEL_OUTPUT_PACKET_SIZE - output->pos;
37,842,606✔
2305
        if (chunk > count) {
37,842,606✔
2306
            chunk = count;
20,179,747✔
2307
        }
2,544,462✔
2308
        memset(output->buffer + output->pos, value, (size_t)chunk);
37,842,606✔
2309
        sixel_advance(output, chunk);
37,842,606✔
2310
        count -= chunk;
37,862,951✔
2311
    }
2312
}
2,540,638✔
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,
14,534,460✔
2321
                             int width,
2322
                             int start)
2323
{
2324
    int idx;
6,801,091✔
2325
    size_t chunk_size;
6,801,091✔
2326
    unsigned char const *cursor;
6,801,091✔
2327
    unsigned long block;
6,801,091✔
2328

2329
    idx = start;
14,534,460✔
2330
    chunk_size = sizeof(unsigned long);
14,534,460✔
2331
    cursor = row + start;
14,534,460✔
2332

2333
    while ((width - idx) >= (int)chunk_size) {
160,396,053✔
2334
        memcpy(&block, cursor, chunk_size);
154,695,274✔
2335
        if (block != 0UL) {
154,695,274✔
2336
            break;
4,702,452✔
2337
        }
2338
        idx += (int)chunk_size;
145,861,593✔
2339
        cursor += chunk_size;
145,861,593✔
2340
    }
2341

2342
    while (idx < width) {
46,481,042✔
2343
        if (*cursor != 0) {
40,910,750✔
2344
            break;
4,770,524✔
2345
        }
2346
        idx += 1;
31,946,582✔
2347
        cursor += 1;
31,946,582✔
2348
    }
2349

2350
    return idx;
14,534,460✔
2351
}
2352

2353

2354
static int
2355
sixel_compose_measure_gap(unsigned char const *row,
15,687,586✔
2356
                          int width,
2357
                          int start,
2358
                          int *reached_end)
2359
{
2360
    int gap;
7,314,379✔
2361
    size_t chunk_size;
7,314,379✔
2362
    unsigned char const *cursor;
7,314,379✔
2363
    unsigned long block;
7,314,379✔
2364
    int remaining;
7,314,379✔
2365

2366
    gap = 0;
15,687,586✔
2367
    *reached_end = 0;
15,687,586✔
2368
    if (start >= width) {
15,687,586✔
2369
        *reached_end = 1;
194,538✔
2370
        return gap;
194,538✔
2371
    }
2372

2373
    chunk_size = sizeof(unsigned long);
15,493,048✔
2374
    cursor = row + start;
15,493,048✔
2375
    remaining = width - start;
15,493,048✔
2376

2377
    while (remaining >= (int)chunk_size) {
95,699,258✔
2378
        memcpy(&block, cursor, chunk_size);
89,969,047✔
2379
        if (block != 0UL) {
89,969,047✔
2380
            break;
5,228,120✔
2381
        }
2382
        gap += (int)chunk_size;
80,206,210✔
2383
        cursor += chunk_size;
80,206,210✔
2384
        remaining -= (int)chunk_size;
80,206,210✔
2385
    }
2386

2387
    while (remaining > 0) {
42,646,770✔
2388
        if (*cursor != 0) {
37,267,237✔
2389
            return gap;
5,409,643✔
2390
        }
2391
        gap += 1;
27,153,722✔
2392
        cursor += 1;
27,153,722✔
2393
        remaining -= 1;
27,153,722✔
2394
    }
2395

2396
    *reached_end = 1;
5,379,533✔
2397
    return gap;
5,379,533✔
2398
}
1,062,699✔
2399

2400

2401
#if HAVE_LDIV
2402
static int
2403
sixel_putnum_impl(char *buffer, long value, int pos)
46,146,091✔
2404
{
2405
    ldiv_t r;
21,579,511✔
2406

2407
    r = ldiv(value, 10);
46,146,091✔
2408
    if (r.quot > 0) {
46,149,774✔
2409
        pos = sixel_putnum_impl(buffer, r.quot, pos);
22,771,460✔
2410
    }
1,543,228✔
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);
46,152,805✔
2416
    return pos + 1;
46,152,805✔
2417
}
2418
#endif  /* HAVE_LDIV */
2419

2420

2421
static int
2422
sixel_putnum(char *buffer, int value)
23,395,476✔
2423
{
2424
    int pos;
10,943,198✔
2425

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

2432
    return pos;
23,397,433✔
2433
}
2434

2435

2436
static SIXELSTATUS
2437
sixel_put_flash(sixel_output_t *const output)
42,412,019✔
2438
{
2439
    int nwrite;
19,795,338✔
2440

2441
    if (output->save_count <= 0) {
42,412,019✔
2442
        return SIXEL_OK;
65,685✔
2443
    }
2444

2445
    if (output->has_gri_arg_limit) {  /* VT240 Max 255 ? */
42,291,406✔
2446
            while (output->save_count > 255) {
78,167!
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);
×
2452
                sixel_advance(output, 1);
×
2453
                output->save_count -= 255;
61,394✔
2454
            }
2455
        }
1,130✔
2456

2457
    if (output->save_count > 3) {
42,352,800✔
2458
        /* DECGRI Graphics Repeat Introducer ! Pn Ch */
2459
        sixel_putc(output->buffer + output->pos, '!');
4,505,943✔
2460
        sixel_advance(output, 1);
4,505,782✔
2461
        nwrite = sixel_putnum((char *)output->buffer + output->pos, output->save_count);
4,506,137✔
2462
        sixel_advance(output, nwrite);
4,506,182✔
2463
        sixel_putc(output->buffer + output->pos,
4,810,910✔
2464
                   (unsigned char)output->save_pixel);
4,505,653✔
2465
        sixel_advance(output, 1);
4,505,569✔
2466
    } else {
305,257✔
2467
        sixel_output_emit_literal(output,
40,389,053✔
2468
                                  (unsigned char)output->save_pixel,
37,846,857✔
2469
                                  output->save_count);
2,542,196✔
2470
    }
2471

2472
    output->save_pixel = 0;
42,305,052✔
2473
    output->save_count = 0;
42,305,052✔
2474

2475
    return SIXEL_OK;
42,305,052✔
2476
}
2,858,534✔
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)
42,346,531✔
2486
{
2487
    SIXELSTATUS status = SIXEL_FALSE;
42,346,531✔
2488

2489
    if (count <= 0) {
42,346,531✔
2490
        return SIXEL_OK;
8✔
2491
    }
2492

2493
    if (output->save_count > 0) {
42,346,516✔
2494
        if (output->save_pixel == symbol) {
33,446,348✔
2495
            output->save_count += count;
43,340✔
2496
            return SIXEL_OK;
43,340✔
2497
        }
2498

2499
        status = sixel_put_flash(output);
33,403,008✔
2500
        if (SIXEL_FAILED(status)) {
33,392,353!
2501
            return status;
2502
        }
2503
    }
2,241,623✔
2504

2505
    output->save_pixel = symbol;
42,293,862✔
2506
    output->save_count = count;
42,293,862✔
2507

2508
    return SIXEL_OK;
42,293,862✔
2509
}
2,843,092✔
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,
8,966,506✔
2518
                         unsigned char const *map,
2519
                         int length)
2520
{
2521
    SIXELSTATUS status = SIXEL_FALSE;
8,966,506✔
2522
    int index;
4,192,214✔
2523
    int run_length;
4,192,214✔
2524
    unsigned char value;
4,192,214✔
2525
    size_t chunk_size;
4,192,214✔
2526
    unsigned long pattern;
4,192,214✔
2527
    unsigned long block;
4,192,214✔
2528
    int chunk_mismatch;
4,192,214✔
2529
    int remain;
4,192,214✔
2530
    int byte_index;
4,192,214✔
2531

2532
    if (length <= 0) {
8,966,506✔
2533
        return SIXEL_OK;
2534
    }
2535

2536
    for (index = 0; index < length; index += run_length) {
47,722,906✔
2537
        value = map[index];
38,773,739✔
2538
        if (value > '?') {
38,773,739!
2539
            value = 0;
×
2540
        }
2541

2542
        run_length = 1;
38,773,739✔
2543
        chunk_size = sizeof(unsigned long);
38,773,739✔
2544
        chunk_mismatch = 0;
38,773,739✔
2545
        if (chunk_size > 1) {
38,773,739!
2546
            remain = length - (index + run_length);
38,765,163✔
2547
            pattern = (~0UL / 0xffUL) * (unsigned long)value;
38,765,163✔
2548

2549
            while (remain >= (int)chunk_size) {
41,247,363✔
2550
                memcpy(&block,
26,519,280✔
2551
                       map + index + run_length,
24,766,737✔
2552
                       chunk_size);
1,752,543✔
2553
                block ^= pattern;
23,014,194✔
2554
                if (block != 0UL) {
23,014,194✔
2555
                    for (byte_index = 0;
11,557,910✔
2556
                         byte_index < (int)chunk_size;
29,066,723!
2557
                         byte_index++) {
8,534,729✔
2558
                        if ((block & 0xffUL) != 0UL) {
29,071,405✔
2559
                            chunk_mismatch = 1;
11,027,345✔
2560
                            break;
11,027,345✔
2561
                        }
2562
                        block >>= 8;
8,534,729✔
2563
                        run_length += 1;
8,534,729✔
2564
                    }
535,247✔
2565
                    break;
11,025,405✔
2566
                }
2567
                run_length += (int)chunk_size;
2,482,200✔
2568
                remain -= (int)chunk_size;
2,482,200✔
2569
            }
2570
        }
2,603,098✔
2571

2572
        if (!chunk_mismatch) {
38,776,481✔
2573
            while (index + run_length < length) {
23,187,618✔
2574
                unsigned char next;
6,796,014✔
2575

2576
                next = map[index + run_length];
14,234,945✔
2577
                if (next > '?') {
14,234,945!
2578
                    next = 0;
×
2579
                }
2580
                if (next != value) {
14,234,945✔
2581
                    break;
4,936,915✔
2582
                }
2583
                run_length += 1;
4,819,625✔
2584
            }
2585
        }
1,095,253✔
2586

2587
        status = sixel_emit_run(output,
41,367,909✔
2588
                                 (int)value + '?',
20,666,745✔
2589
                                 run_length);
2,599,208✔
2590
        if (SIXEL_FAILED(status)) {
38,756,400!
2591
            return status;
2592
        }
2593
    }
2,599,208✔
2594

2595
    return SIXEL_OK;
4,757,338✔
2596
}
605,101✔
2597

2598

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

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

2609
static SIXELSTATUS
2610
sixel_node_new(sixel_node_t **np, sixel_allocator_t *allocator)
6,888,190✔
2611
{
2612
    SIXELSTATUS status = SIXEL_FALSE;
6,888,190✔
2613

2614
    *np = (sixel_node_t *)sixel_allocator_malloc(allocator,
6,888,190✔
2615
                                                 sizeof(sixel_node_t));
2616
    if (np == NULL) {
6,889,649!
2617
        sixel_helper_set_additional_message(
71✔
2618
            "sixel_node_new: sixel_allocator_malloc() failed.");
2619
        status = SIXEL_BAD_ALLOCATION;
2620
        goto end;
2621
    }
2622

2623
    status = SIXEL_OK;
3,727,436✔
2624

2625
end:
6,297,641✔
2626
    return status;
6,889,578✔
2627
}
2628

2629
static void
2630
sixel_node_del(sixel_output_t *output, sixel_node_t *np)
8,959,878✔
2631
{
2632
    sixel_node_t *tp;
4,192,241✔
2633

2634
    if ((tp = output->node_top) == np) {
8,959,878✔
2635
        output->node_top = np->next;
2,184,047✔
2636
    } else {
145,796✔
2637
        while (tp->next != NULL) {
310,315,646!
2638
            if (tp->next == np) {
310,320,834✔
2639
                tp->next = np->next;
6,781,019✔
2640
                break;
6,781,019✔
2641
            }
2642
            tp = tp->next;
161,957,775✔
2643
        }
2644
    }
2645

2646
    np->next = output->node_free;
8,960,644✔
2647
    output->node_free = np;
8,960,644✔
2648
}
8,960,644✔
2649

2650

2651
static SIXELSTATUS
2652
sixel_put_node(
8,962,131✔
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;
8,962,131✔
2660
    int nwrite;
4,192,168✔
2661

2662
    if (ncolors != 2 || keycolor == (-1)) {
8,962,131✔
2663
        /* designate palette index */
2664
        if (output->active_palette != np->pal) {
8,959,564✔
2665
            sixel_putc(output->buffer + output->pos, '#');
8,826,608✔
2666
            sixel_advance(output, 1);
8,826,224✔
2667
            nwrite = sixel_putnum((char *)output->buffer + output->pos, np->pal);
8,826,347✔
2668
            sixel_advance(output, nwrite);
8,826,857✔
2669
            output->active_palette = np->pal;
8,826,457✔
2670
        }
596,719✔
2671
    }
605,086✔
2672

2673
    if (*x < np->sx) {
8,961,980✔
2674
        int span;
1,579,192✔
2675

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

2684
    if (*x < np->mx) {
8,961,850!
2685
        int span;
4,192,520✔
2686

2687
        span = np->mx - *x;
8,962,250✔
2688
        status = sixel_emit_span_from_map(output,
13,758,897✔
2689
                                          (unsigned char const *)np->map + *x,
8,962,250✔
2690
                                          span);
605,010✔
2691
        if (SIXEL_FAILED(status)) {
8,959,858!
2692
            goto end;
2693
        }
2694
        *x = np->mx;
8,959,858✔
2695
    }
605,010✔
2696

2697
    status = sixel_put_flash(output);
8,959,458✔
2698
    if (SIXEL_FAILED(status)) {
8,958,747!
2699
        goto end;
2700
    }
2701

2702
end:
8,353,603✔
2703
    return status;
8,958,479✔
2704
}
2705

2706

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

2715
    if (output->ormode) {
31,894✔
2716
        p[0] = 7;
75✔
2717
        p[1] = 5;
75✔
2718
    } else if (sixel_output_has_transparent_offset(output)) {
31,824✔
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;
52✔
2724
    } else if (keycolor >= 0) {
31,771✔
2725
        if (output->transparent_policy == SIXEL_TRANSPARENT_POLICY_KEEP) {
2,518✔
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;
195✔
2731
        } else if (output->transparent_policy ==
2,336✔
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,308✔
2739
        }
154✔
2740
    }
168✔
2741

2742
    output->pos = 0;
31,894✔
2743

2744
    if (pcount == 3 && p[2] == 0) {
31,894!
2745
        pcount--;
29,586✔
2746
        if (p[1] == 0) {
29,586✔
2747
            pcount--;
29,264✔
2748
            if (p[0] == 0) {
29,264!
2749
                pcount--;
29,264✔
2750
            }
1,967✔
2751
        }
1,967✔
2752
    }
1,989✔
2753

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

2763
    return status;
31,894✔
2764
}
2765

2766

2767
static int
2768
sixel_palette_float_pixelformat_for_colorspace(int colorspace)
48,768✔
2769
{
2770
    switch (colorspace) {
48,768✔
2771
    case SIXEL_COLORSPACE_LINEAR:
98✔
2772
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
112✔
2773
    case SIXEL_COLORSPACE_OKLAB:
273✔
2774
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
312✔
2775
    case SIXEL_COLORSPACE_CIELAB:
77✔
2776
        return SIXEL_PIXELFORMAT_CIELABFLOAT32;
88✔
2777
    case SIXEL_COLORSPACE_DIN99D:
70✔
2778
        return SIXEL_PIXELFORMAT_DIN99DFLOAT32;
80✔
2779
    default:
29,103✔
2780
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
33,278✔
2781
    }
2782
}
4,249✔
2783

2784
static int
2785
sixel_palette_float32_matches_u8(unsigned char const *palette,
3,452✔
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,452!
2796
        return 1;
2797
    }
2798
    if (count > SIZE_MAX / 3U) {
3,452!
2799
        return 0;
2800
    }
2801
    limit = count * 3U;
3,452✔
2802
    for (index = 0U; index < limit; ++index) {
697,580✔
2803
        channel = (int)(index % 3U);
694,128✔
2804
        expected = sixel_pixelformat_float_channel_to_byte(
1,040,028✔
2805
            float_pixelformat,
2,232✔
2806
            channel,
2,232✔
2807
            palette_float[index]);
694,128✔
2808
        if (palette[index] != expected) {
694,128!
2809
            return 0;
2810
        }
2811
    }
2,232✔
2812
    return 1;
1,784✔
2813
}
114✔
2814

2815
static void
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

2825
    if (palette == NULL || palette_float == NULL || count == 0U) {
×
2826
        return;
2827
    }
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);
×
2834
        palette_float[index] = sixel_pixelformat_byte_to_float(
×
2835
            float_pixelformat,
2836
            channel,
2837
            palette[index]);
×
2838
    }
2839
}
2840

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

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

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

2872
    return 0;
2873
}
502,566✔
2874

2875
static double
2876
sixel_output_palette_channel_to_float(unsigned char const *palette,
103,410✔
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;
103,410✔
2885
    value = 0.0;
103,410✔
2886
    if (palette_float != NULL) {
103,410✔
2887
        value = palette_float[index];
6,144✔
2888
    } else if (palette != NULL) {
97,266!
2889
        value = (double)palette[index] / 255.0;
97,266✔
2890
    }
6,894✔
2891
    if (value < 0.0) {
103,410✔
2892
        value = 0.0;
2893
    } else if (value > 1.0) {
103,410!
2894
        value = 1.0;
2895
    }
2896

2897
    return value;
103,410✔
2898
}
2899

2900
static SIXELSTATUS
2901
output_rgb_palette_definition(
2,483,428✔
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,483,428✔
2910
    int nwrite;
1,161,443✔
2911

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

2947
    status = SIXEL_OK;
2,483,428✔
2948

2949
    return status;
2,483,428✔
2950
}
2951

2952

2953
static SIXELSTATUS
2954
output_hls_palette_definition(
34,470✔
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;
34,470✔
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) {
34,470!
2978
        r = sixel_output_palette_channel_to_float(palette,
36,768✔
2979
                                                  palette_float,
2,298✔
2980
                                                  n,
2,298✔
2981
                                                  0);
2982
        g = sixel_output_palette_channel_to_float(palette,
36,768✔
2983
                                                  palette_float,
2,298✔
2984
                                                  n,
2,298✔
2985
                                                  1);
2986
        b = sixel_output_palette_channel_to_float(palette,
36,768✔
2987
                                                  palette_float,
2,298✔
2988
                                                  n,
2,298✔
2989
                                                  2);
2990
        maxc = r > g ? (r > b ? r : b) : (g > b ? g : b);
34,470✔
2991
        minc = r < g ? (r < b ? r : b) : (g < b ? g : b);
34,470✔
2992
        lightness = (maxc + minc) * 0.5;
34,470✔
2993
        saturation = 0.0;
34,470✔
2994
        hue = 0.0;
34,470✔
2995
        diff = maxc - minc;
34,470✔
2996
        if (diff <= 0.0) {
34,470✔
2997
            h = 0;
1,992✔
2998
            s = 0;
1,992✔
2999
        } else {
249✔
3000
            if (lightness < 0.5) {
30,735✔
3001
                saturation = diff / (maxc + minc);
26,135✔
3002
            } else {
1,741✔
3003
                saturation = diff / (2.0 - maxc - minc);
4,600✔
3004
            }
3005
            if (maxc == r) {
30,735✔
3006
                hue = (g - b) / diff;
20,981✔
3007
            } else if (maxc == g) {
11,153✔
3008
                hue = 2.0 + (b - r) / diff;
9,473✔
3009
            } else {
631✔
3010
                hue = 4.0 + (r - g) / diff;
281✔
3011
            }
3012
            hue *= 60.0;
30,735✔
3013
            if (hue < 0.0) {
30,735✔
3014
                hue += 360.0;
15✔
3015
            }
1✔
3016
            if (hue >= 360.0) {
30,735!
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;
30,735✔
3026
            while (hue >= 360.0) {
30,791✔
3027
                hue -= 360.0;
56✔
3028
            }
3029
            while (hue < 0.0) {
30,735!
3030
                hue += 360.0;
×
3031
            }
3032
            s = (int)(saturation * 100.0 + 0.5);
30,735✔
3033
            if (s < 0) {
30,735!
3034
                s = 0;
3035
            } else if (s > 100) {
30,735!
3036
                s = 100;
3037
            }
3038
            h = (int)(hue + 0.5);
30,735✔
3039
            if (h >= 360) {
30,735!
3040
                h -= 360;
×
3041
            } else if (h < 0) {
30,735!
3042
                h = 0;
3043
            }
3044
        }
3045
        l = (int)(lightness * 100.0 + 0.5);
34,470✔
3046
        if (l < 0) {
34,470!
3047
            l = 0;
3048
        } else if (l > 100) {
34,470!
3049
            l = 100;
3050
        }
3051
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
3052
        sixel_putc(output->buffer + output->pos, '#');
34,470✔
3053
        sixel_advance(output, 1);
34,470✔
3054
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
34,470✔
3055
        sixel_advance(output, nwrite);
34,470✔
3056
        sixel_puts(output->buffer + output->pos, ";1;", 3);
34,470✔
3057
        sixel_advance(output, 3);
34,470✔
3058
        nwrite = sixel_putnum((char *)output->buffer + output->pos, h);
34,470✔
3059
        sixel_advance(output, nwrite);
34,470✔
3060
        sixel_putc(output->buffer + output->pos, ';');
34,470✔
3061
        sixel_advance(output, 1);
34,470✔
3062
        nwrite = sixel_putnum((char *)output->buffer + output->pos, l);
34,470✔
3063
        sixel_advance(output, nwrite);
34,470✔
3064
        sixel_putc(output->buffer + output->pos, ';');
34,470✔
3065
        sixel_advance(output, 1);
34,470✔
3066
        nwrite = sixel_putnum((char *)output->buffer + output->pos, s);
34,470✔
3067
        sixel_advance(output, nwrite);
34,470✔
3068
    }
2,298✔
3069

3070
    status = SIXEL_OK;
34,470✔
3071
    return status;
34,470✔
3072
}
3073

3074

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

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

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

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

3131
    active_colors_size = (size_t)ncolors;
53,422✔
3132
    if (active_colors_size > 0) {
53,422!
3133
        work->active_colors =
77,759✔
3134
            (unsigned char *)sixel_allocator_malloc(allocator,
57,505✔
3135
                                                    active_colors_size);
4,085✔
3136
        if (work->active_colors == NULL) {
53,419!
3137
            sixel_helper_set_additional_message(
1✔
3138
                "sixel_encode_body: sixel_allocator_malloc() failed.");
3139
            status = SIXEL_BAD_ALLOCATION;
×
3140
            goto end;
×
3141
        }
3142
        memset(work->active_colors, 0, active_colors_size);
53,418✔
3143
        work->active_colors_size = active_colors_size;
53,418✔
3144
    } else {
4,085✔
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;
53,420✔
3150
    if (active_color_index_size > 0) {
53,420!
3151
        work->active_color_index = (int *)sixel_allocator_malloc(
53,417✔
3152
            allocator,
4,085✔
3153
            active_color_index_size);
4,085✔
3154
        if (work->active_color_index == NULL) {
53,422!
3155
            sixel_helper_set_additional_message(
×
3156
                "sixel_encode_body: sixel_allocator_malloc() failed.");
3157
            status = SIXEL_BAD_ALLOCATION;
×
3158
            goto end;
×
3159
        }
3160
        memset(work->active_color_index, 0, active_color_index_size);
53,422✔
3161
        work->active_color_index_size = active_color_index_size;
53,422✔
3162
    } else {
4,085✔
3163
        work->active_color_index = NULL;
3✔
3164
        work->active_color_index_size = 0;
3✔
3165
    }
3166

3167
    status = SIXEL_OK;
29,084✔
3168

3169
end:
24,999✔
3170
    if (SIXEL_FAILED(status)) {
43,399!
3171
        if (work->active_color_index != NULL) {
×
3172
            sixel_allocator_free(allocator, work->active_color_index);
×
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);
×
3178
            work->active_colors = NULL;
×
3179
        }
3180
        work->active_colors_size = 0;
×
3181
        if (work->columns != NULL) {
×
3182
            sixel_allocator_free(allocator, work->columns);
×
3183
            work->columns = NULL;
×
3184
        }
3185
        work->columns_size = 0;
×
3186
        if (work->map != NULL) {
×
3187
            sixel_allocator_free(allocator, work->map);
×
3188
            work->map = NULL;
×
3189
        }
3190
        work->map_size = 0;
×
3191
    }
3192

3193
    return status;
53,425✔
3194
}
3195

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

3222
static void
3223
sixel_band_state_reset(sixel_band_state_t *state)
260,942✔
3224
{
3225
    state->row_in_band = 0;
260,942✔
3226
    state->fillable = 0;
260,942✔
3227
    state->fill_mask = 0;
260,942✔
3228
    state->active_color_count = 0;
258,614!
3229
}
153,368✔
3230

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

3237
    if (work->active_colors == NULL
188,582!
3238
        || work->active_color_index == NULL) {
188,583!
3239
        state->active_color_count = 0;
3240
        return;
3241
    }
3242

3243
    for (color_index = 0;
507,154✔
3244
         color_index < state->active_color_count;
6,188,145✔
3245
         color_index++) {
5,999,562✔
3246
        c = work->active_color_index[color_index];
5,999,600✔
3247
        if (c >= 0
5,999,600!
3248
            && (size_t)c < work->active_colors_size) {
5,999,617!
3249
            work->active_colors[c] = 0;
5,999,609✔
3250
        }
406,652✔
3251
    }
406,676✔
3252
    state->active_color_count = 0;
188,545✔
3253
}
12,686✔
3254

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

3263
static SIXELSTATUS
3264
sixel_encode_emit_palette(int bodyonly,
33,439✔
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;
33,439✔
3272
    int n;
15,604✔
3273

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

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

3284
    if (output->palette_type == SIXEL_PALETTETYPE_HLS) {
32,689✔
3285
        for (n = 0; n < ncolors; n++) {
34,605✔
3286
            status = output_hls_palette_definition(output,
36,768✔
3287
                                                   palette,
2,298✔
3288
                                                   palette_float,
2,298✔
3289
                                                   n,
2,298✔
3290
                                                   keycolor);
2,298✔
3291
            if (SIXEL_FAILED(status)) {
34,470!
3292
                goto end;
3293
            }
3294
        }
2,298✔
3295
    } else {
9✔
3296
        for (n = 0; n < ncolors; n++) {
2,511,154✔
3297
            status = output_rgb_palette_definition(output,
2,645,916✔
3298
                                                   palette,
167,316✔
3299
                                                   palette_float,
167,316✔
3300
                                                   n,
167,316✔
3301
                                                   keycolor);
167,316✔
3302
            if (SIXEL_FAILED(status)) {
2,478,600!
3303
                goto end;
3304
            }
3305
        }
167,316✔
3306
    }
3307

3308
    status = SIXEL_OK;
17,435✔
3309

3310
end:
30,843✔
3311
    return status;
17,435✔
3312
}
2,246✔
3313

3314
static SIXELSTATUS
3315
sixel_band_classify_row(sixel_encode_work_t *work,
1,053,965✔
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,053,965✔
3330
    int row_bit;
492,181✔
3331
    int band_start;
492,181✔
3332
    int source_row;
492,181✔
3333
    int source_band_start;
492,181✔
3334
    int target_x;
492,181✔
3335
    int pix;
492,181✔
3336
    int x;
492,181✔
3337
    int check_integer_overflow;
492,181✔
3338
    int source_index_base;
492,181✔
3339
    char *map;
492,181✔
3340
    unsigned char *active_colors;
492,181✔
3341
    int *active_color_index;
492,181✔
3342

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

3349
    if (row_bit == 0) {
1,053,965✔
3350
        if (encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
188,564✔
3351
            state->fillable = 0;
188,355✔
3352
        } else if (palstate) {
12,916!
3353
            source_band_start = band_start - offset_top;
×
3354
            if (source_band_start < 0) {
×
3355
                source_band_start = 0;
3356
            }
3357
            if (width > 0) {
×
3358
                if (source_band_start >= height) {
×
3359
                    state->fillable = 0;
×
3360
                } else {
3361
                    pix = pixels[source_band_start * width];
×
3362
                    if (pix >= ncolors) {
×
3363
                        state->fillable = 0;
×
3364
                    } else {
3365
                        state->fillable = 1;
×
3366
                    }
3367
                }
3368
            } else {
3369
                state->fillable = 0;
×
3370
            }
3371
        } else {
3372
            state->fillable = 1;
209✔
3373
        }
3374
        state->active_color_count = 0;
188,564✔
3375
    }
12,721✔
3376

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

3392
    for (x = 0; x < width; x++) {
126,998,109✔
3393
        target_x = offset_left + x;
125,947,553✔
3394
        if (target_x < 0 || target_x >= map_width) {
125,947,553!
3395
            sixel_helper_set_additional_message(
81,720✔
3396
                "sixel_encode_body: transparent offset is out of range.");
3397
            status = SIXEL_BAD_INTEGER_OVERFLOW;
72,960✔
3398
            goto end;
72,960✔
3399
        }
3400
        check_integer_overflow = source_index_base;
125,944,334✔
3401
        if (check_integer_overflow > INT_MAX - x) {
125,944,334!
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;
×
3406
            goto end;
×
3407
        }
3408
        pix = pixels[check_integer_overflow + x];
125,944,334✔
3409
        if (pix >= 0 && pix < ncolors && pix != keycolor) {
125,944,334!
3410
            if (pix > INT_MAX / map_width) {
107,055,727!
3411
                sixel_helper_set_additional_message(
×
3412
                    "sixel_encode_body: integer overflow detected."
3413
                    " (pix > INT_MAX / width)");
3414
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3415
                goto end;
×
3416
            }
3417
            check_integer_overflow = pix * map_width;
107,055,727✔
3418
            if (check_integer_overflow > INT_MAX - target_x) {
107,055,727!
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;
×
3423
                goto end;
×
3424
            }
3425
            map[check_integer_overflow + target_x] |= (1 << row_bit);
107,055,727✔
3426
            if (active_colors != NULL && active_colors[pix] == 0) {
107,055,727!
3427
                active_colors[pix] = 1;
5,989,738✔
3428
                if (state->active_color_count < ncolors
5,989,738!
3429
                    && active_color_index != NULL) {
5,997,306!
3430
                    active_color_index[state->active_color_count] = pix;
5,997,294✔
3431
                    state->active_color_count += 1;
5,997,294✔
3432
                }
406,469✔
3433
            }
406,505✔
3434
        } else if (!palstate) {
25,966,636✔
3435
            state->fillable = 0;
14,490,374✔
3436
        }
963,558✔
3437
    }
8,339,426✔
3438

3439
    state->row_in_band += 1;
1,050,556✔
3440
    status = SIXEL_OK;
1,050,556✔
3441

3442
end:
771,536✔
3443
    return status;
558,461✔
3444
}
71,150✔
3445

3446
static SIXELSTATUS
3447
sixel_band_compose(sixel_encode_work_t *work,
188,580✔
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;
188,580✔
3456
    int color_index;
88,064✔
3457
    int c;
88,064✔
3458
    unsigned char *row;
88,064✔
3459
    int sx;
88,064✔
3460
    int mx;
88,064✔
3461
    int gap;
88,064✔
3462
    int gap_reached_end;
88,064✔
3463
    sixel_node_t *np;
88,064✔
3464
    sixel_node_t *column_head;
88,064✔
3465
    sixel_node_t *column_tail;
88,064✔
3466
    sixel_node_t top;
88,064✔
3467

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

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

3482
    for (color_index = 0;
594,998✔
3483
         color_index < state->active_color_count;
6,188,805✔
3484
         color_index++) {
6,000,227✔
3485
        c = work->active_color_index[color_index];
5,997,633✔
3486
        row = (unsigned char *)(work->map + c * width);
5,997,633✔
3487
        sx = 0;
5,997,633✔
3488
        while (sx < width) {
14,965,218✔
3489
            sx = sixel_compose_find_run_start(
14,541,376✔
3490
                row,
983,584✔
3491
                width,
983,584✔
3492
                sx);
983,584✔
3493
            if (sx >= width) {
14,539,677✔
3494
                break;
2,965,956✔
3495
            }
3496

3497
            mx = sx + 1;
8,963,292✔
3498
            while (mx < width) {
50,601,837✔
3499
                if (row[mx] != 0) {
50,181,988✔
3500
                    mx += 1;
34,463,416✔
3501
                    continue;
34,463,416✔
3502
                }
3503

3504
                gap = sixel_compose_measure_gap(
15,718,572✔
3505
                    row,
1,062,984✔
3506
                    width,
1,062,984✔
3507
                    mx + 1,
1,062,984✔
3508
                    &gap_reached_end);
3509
                if (gap >= 9 || gap_reached_end) {
15,715,500✔
3510
                    break;
576,778✔
3511
                }
3512
                mx += gap + 1;
7,175,129✔
3513
            }
3514

3515
            if ((np = output->node_free) != NULL) {
8,960,220✔
3516
                output->node_free = np->next;
2,075,535✔
3517
            } else {
13,729✔
3518
                status = sixel_node_new(&np, allocator);
6,884,685✔
3519
                if (SIXEL_FAILED(status)) {
6,889,188!
3520
                    goto end;
3521
                }
3522
            }
3523

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

3530
            if (work->columns != NULL) {
8,967,585!
3531
                column_head = work->columns[sx];
8,967,236✔
3532
                if (column_head == NULL
8,967,236✔
3533
                    || column_head->mx <= np->mx) {
4,343,007✔
3534
                    np->next = column_head;
7,039,474✔
3535
                    work->columns[sx] = np;
7,039,474✔
3536
                } else {
473,896✔
3537
                    column_tail = column_head;
1,025,333✔
3538
                    while (column_tail->next != NULL
2,381,915✔
3539
                           && column_tail->next->mx > np->mx) {
2,579,795✔
3540
                        column_tail = column_tail->next;
346,868✔
3541
                    }
3542
                    np->next = column_tail->next;
1,927,762✔
3543
                    column_tail->next = np;
1,927,762✔
3544
                }
3545
            } else {
605,671✔
3546
                top.next = output->node_top;
349✔
3547
                column_tail = &top;
349✔
3548

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

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

3564
            sx = mx;
7,206,898✔
3565
        }
3566
    }
406,418✔
3567

3568
    if (work->columns != NULL) {
191,172!
3569
        top.next = NULL;
188,580✔
3570
        column_tail = &top;
188,580✔
3571
        for (sx = 0; sx < width; sx++) {
21,893,281✔
3572
            column_head = work->columns[sx];
21,704,701✔
3573
            if (column_head == NULL) {
21,704,701✔
3574
                continue;
16,751,612✔
3575
            }
3576
            column_tail->next = column_head;
4,953,089✔
3577
            while (column_tail->next != NULL) {
13,915,528✔
3578
                column_tail = column_tail->next;
4,772,053✔
3579
            }
3580
            work->columns[sx] = NULL;
4,953,089✔
3581
        }
332,396✔
3582
        output->node_top = top.next;
188,580✔
3583
    }
12,724✔
3584

3585
    status = SIXEL_OK;
191,174✔
3586

3587
end:
178,449✔
3588
    return status;
191,174✔
3589
}
3590

3591
static SIXELSTATUS
3592
sixel_band_emit(sixel_encode_work_t *work,
188,573✔
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;
188,573✔
3600
    sixel_node_t *np;
88,063✔
3601
    sixel_node_t *next;
88,063✔
3602
    int x;
88,063✔
3603
    int emit_next_line;
88,063✔
3604

3605
    emit_next_line = (last_row_index >= 6);
188,573✔
3606
    if (emit_next_line) {
188,573✔
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] = '-';
155,207✔
3612
        sixel_advance(output, 1);
155,207✔
3613
    }
10,484✔
3614

3615
    for (x = 0; (np = output->node_top) != NULL;) {
1,398,706✔
3616
        if (x > np->sx) {
1,210,606✔
3617
            output->buffer[output->pos] = '$';
1,028,342✔
3618
            sixel_advance(output, 1);
1,028,342✔
3619
            x = 0;
1,028,325✔
3620
        }
70,026✔
3621

3622
        if (state->fillable) {
1,210,589✔
3623
            memset(np->map + np->sx,
238✔
3624
                   state->fill_mask,
14✔
3625
                   (size_t)(np->mx - np->sx));
210✔
3626
        }
14✔
3627
        status = sixel_put_node(output,
1,292,918✔
3628
                                &x,
3629
                                np,
82,329✔
3630
                                ncolors,
82,329✔
3631
                                keycolor);
82,329✔
3632
        if (SIXEL_FAILED(status)) {
1,210,530!
3633
            goto end;
3634
        }
3635
        next = np->next;
1,210,530✔
3636
        sixel_node_del(output, np);
1,210,530✔
3637
        np = next;
1,210,546✔
3638

3639
        while (np != NULL) {
47,631,569✔
3640
            if (np->sx < x) {
46,421,441✔
3641
                np = np->next;
38,668,537✔
3642
                continue;
38,668,537✔
3643
            }
3644

3645
            if (state->fillable) {
7,752,904!
3646
                memset(np->map + np->sx,
×
3647
                       state->fill_mask,
3648
                       (size_t)(np->mx - np->sx));
×
3649
            }
3650
            status = sixel_put_node(output,
8,275,566✔
3651
                                    &x,
3652
                                    np,
522,662✔
3653
                                    ncolors,
522,662✔
3654
                                    keycolor);
522,662✔
3655
            if (SIXEL_FAILED(status)) {
7,749,954!
3656
                goto end;
3657
            }
3658
            next = np->next;
7,749,954✔
3659
            sixel_node_del(output, np);
7,749,954✔
3660
            np = next;
7,751,300✔
3661
        }
3662

3663
        state->fillable = 0;
1,210,133✔
3664
    }
3665

3666
    status = SIXEL_OK;
100,047✔
3667

3668
end:
175,378✔
3669
    (void)work;
100,775✔
3670
    return status;
188,100✔
3671
}
3672

3673

3674
SIXELSTATUS
3675
sixel_encode_body(
16,037✔
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,037✔
3691
    int band_start;
7,689✔
3692
    int band_height;
7,689✔
3693
    int row_index;
7,689✔
3694
    int absolute_row;
7,689✔
3695
    int last_row_index;
7,689✔
3696
    int encoded_width;
7,689✔
3697
    int encoded_height;
7,689✔
3698
    int offset_left;
7,689✔
3699
    int offset_top;
7,689✔
3700
    sixel_node_t *np;
7,689✔
3701
    sixel_encode_work_t work;
7,689✔
3702
    sixel_band_state_t band;
7,689✔
3703
    int logging_active;
7,689✔
3704
    int job_index;
7,689✔
3705

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

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

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

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

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

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

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

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

3751
        nbands = sixel_count_sixel_bands(encoded_height);
7,221!
3752
        threads = work.requested_threads;
7,221✔
3753
        if (nbands > 1 && threads > 1) {
7,221✔
3754
            status = sixel_encode_body_parallel(pixels,
2,561✔
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,347!
3765
                goto cleanup;
3766
            }
3767
            goto finalize;
2,347✔
3768
        }
3769
    }
3770
#endif
3771

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

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

3788
    band_start = 0;
7,068✔
3789
    while (band_start < encoded_height) {
81,635✔
3790
        band_height = encoded_height - band_start;
67,945✔
3791
        if (band_height > 6) {
67,945✔
3792
            band_height = 6;
27,752✔
3793
        }
1,204✔
3794

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

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

3808
        for (row_index = 0; row_index < band_height; row_index++) {
438,714✔
3809
            absolute_row = band_start + row_index;
370,769✔
3810
            status = sixel_band_classify_row(&work,
370,769✔
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)) {
370,769!
3824
                goto cleanup;
×
3825
            }
3826
        }
8,374✔
3827

3828
        status = sixel_band_compose(&work,
67,945✔
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)) {
67,945!
3836
            goto cleanup;
3837
        }
3838

3839
        last_row_index = band_start + band_height - 1;
67,945✔
3840
        status = sixel_band_emit(&work,
67,945✔
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)) {
67,945!
3847
            goto cleanup;
3848
        }
3849

3850
        sixel_band_finish(&work, &band);
67,945✔
3851

3852
        sixel_band_clear_map(&work);
67,945✔
3853

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

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

3867
    status = SIXEL_OK;
13,690✔
3868
    goto finalize;
13,690✔
3869

3870
finalize:
15,386✔
3871
    if (palstate) {
16,165✔
3872
        output->buffer[output->pos] = '$';
1,920✔
3873
        sixel_advance(output, 1);
1,920✔
3874
    }
128✔
3875

3876
cleanup:
17,626✔
3877
    while ((np = output->node_free) != NULL) {
684,851✔
3878
        output->node_free = np->next;
668,814✔
3879
        sixel_allocator_free(allocator, np);
668,814✔
3880
    }
3881
    output->node_top = NULL;
16,037✔
3882

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

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

3892
    if (output->pos > 0) {
31,828✔
3893
        status = sixel_output_write_bytes(output,
48,680✔
3894
                                          (char *)output->buffer,
31,738✔
3895
                                          output->pos);
2,131✔
3896
        if (SIXEL_FAILED(status)) {
31,738✔
3897
            return status;
3898
        }
3899
        output->pos = 0;
31,738✔
3900
    }
2,131✔
3901

3902
    status = sixel_output_end_image(output);
31,828✔
3903

3904
    return status;
31,828✔
3905
}
2,137✔
3906

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

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

3918
    return nplanes;
694✔
3919
}
3920

3921
static SIXELSTATUS
3922
sixel_encode_body_ormode_emit_palette(unsigned char const *palette,
142✔
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) {
142!
3931
        return SIXEL_BAD_ARGUMENT;
3932
    }
3933
    for (n = 0; n < ncolors; n++) {
4,970✔
3934
        status = output_rgb_palette_definition(output,
5,152✔
3935
                                               palette,
324✔
3936
                                               NULL,
3937
                                               n,
324✔
3938
                                               keycolor);
324✔
3939
        if (SIXEL_FAILED(status)) {
4,828!
3940
            return status;
3941
        }
3942
    }
324✔
3943

3944
    return SIXEL_OK;
76✔
3945
}
10✔
3946

3947
static SIXELSTATUS
3948
sixel_encode_body_ormode_band(sixel_index_t const *pixels,
936✔
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 ||
936!
3979
            band_index < 0 || nplanes < 1) {
936!
3980
        return SIXEL_BAD_ARGUMENT;
3981
    }
3982

3983
    band_start = band_index * 6;
936✔
3984
    if (band_start >= height) {
936!
3985
        return SIXEL_OK;
3986
    }
3987
    band_height = height - band_start;
936✔
3988
    if (band_height > 6) {
936✔
3989
        band_height = 6;
424✔
3990
    }
54✔
3991
    band_pixels = pixels + (size_t)band_start * (size_t)width;
936✔
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) {
936✔
3999
        row0 = band_pixels;
824✔
4000
        row1 = row0 + width;
824✔
4001
        row2 = row1 + width;
824✔
4002
        row3 = row2 + width;
824✔
4003
        row4 = row3 + width;
824✔
4004
        row5 = row4 + width;
824✔
4005
        if (output->encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
824✔
4006
            for (plane = 0; plane < nplanes; plane++) {
4,226✔
4007
                sixel_putc(output->buffer + output->pos, '#');
3,418✔
4008
                sixel_advance(output, 1);
3,418✔
4009
                nwrite = sixel_putnum((char *)output->buffer + output->pos,
3,648✔
4010
                                      1 << plane);
230✔
4011
                sixel_advance(output, nwrite);
3,418✔
4012

4013
                for (x = 0; x < width; x++) {
215,730✔
4014
                    pix = (((row0[x] >> plane) & 0x1) << 0) |
238,952✔
4015
                          (((row1[x] >> plane) & 0x1) << 1) |
224,836✔
4016
                          (((row2[x] >> plane) & 0x1) << 2) |
224,836✔
4017
                          (((row3[x] >> plane) & 0x1) << 3) |
224,836✔
4018
                          (((row4[x] >> plane) & 0x1) << 4) |
224,836✔
4019
                          (((row5[x] >> plane) & 0x1) << 5);
210,720✔
4020
                    sixel_put_pixel(output, pix);
210,720✔
4021
                }
14,116✔
4022
                status = sixel_put_flash(output);
3,416✔
4023
                if (SIXEL_FAILED(status)) {
3,416!
4024
                    return status;
4025
                }
4026

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

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

4043
        if ((sample_mask & full_mask) == full_mask) {
15!
4044
            for (plane = 0; plane < nplanes; plane++) {
×
4045
                plane_bit = 1 << plane;
×
4046
                sixel_putc(output->buffer + output->pos, '#');
×
4047
                sixel_advance(output, 1);
×
4048
                nwrite = sixel_putnum((char *)output->buffer + output->pos,
×
4049
                                      plane_bit);
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);
×
4059
                    sixel_put_pixel(output, pix);
×
4060
                }
4061
                status = sixel_put_flash(output);
×
4062
                if (SIXEL_FAILED(status)) {
×
4063
                    return status;
4064
                }
4065

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

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

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

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

4119
            sixel_putc(output->buffer + output->pos, '$');
15✔
4120
            sixel_advance(output, 1);
15✔
4121
        }
1✔
4122
        sixel_putc(output->buffer + output->pos, '-');
15✔
4123
        sixel_advance(output, 1);
15✔
4124
        return SIXEL_OK;
15✔
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) {
112!
4136
        for (plane = 0; plane < nplanes; plane++) {
516✔
4137
            sixel_putc(output->buffer + output->pos, '#');
404✔
4138
            sixel_advance(output, 1);
404✔
4139
            nwrite = sixel_putnum((char *)output->buffer + output->pos,
432✔
4140
                                  1 << plane);
28✔
4141
            sixel_advance(output, nwrite);
404✔
4142

4143
            column_pixels = band_pixels;
404✔
4144
            for (x = 0; x < width; x++, column_pixels++) {
21,948✔
4145
                pix = 0;
11,392✔
4146
                for (y = 0; y < band_height; y++) {
106,192✔
4147
                    pix |= (((column_pixels[width * y] >> plane) & 0x1) << y);
84,836✔
4148
                }
5,660✔
4149
                sixel_put_pixel(output, pix);
21,356✔
4150
            }
1,428✔
4151
            status = sixel_put_flash(output);
404✔
4152
            if (SIXEL_FAILED(status)) {
404!
4153
                return status;
4154
            }
4155

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

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

UNCOV
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);
×
4176
            nwrite = sixel_putnum((char *)output->buffer + output->pos,
×
4177
                                  1 << plane);
4178
            sixel_advance(output, nwrite);
×
4179

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

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

UNCOV
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;
×
4204
        for (x = 0; x < width; x++, column_pixels++) {
×
4205
            pix = 0;
4206
            for (y = 0; y < band_height; y++) {
×
4207
                pix |= (((column_pixels[width * y] >> plane) & 0x1) << y);
×
4208
            }
4209
            if (pix != 0) {
×
4210
                first_x = x;
4211
                first_pix = pix;
4212
                break;
4213
            }
4214
        }
4215
        if (first_x < 0) {
×
4216
            continue;
×
4217
        }
4218

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

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

4243
        sixel_putc(output->buffer + output->pos, '$');
×
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(
91✔
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) {
91✔
4270
        return SIXEL_BAD_ARGUMENT;
8✔
4271
    }
4272

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

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

4295
    return SIXEL_OK;
40✔
4296
}
5✔
4297

4298

4299
static SIXELSTATUS
4300
sixel_encode_dither(
31,614✔
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;
31,614✔
4308
    sixel_index_t *paletted_pixels = NULL;
31,614✔
4309
    sixel_index_t *input_pixels;
14,753✔
4310
    size_t bufsize;
14,753✔
4311
    unsigned char *palette_entries = NULL;
31,614✔
4312
    float *palette_entries_float32 = NULL;
31,614✔
4313
    sixel_palette_t *palette_obj = NULL;
31,614✔
4314
    size_t palette_count = 0U;
31,614✔
4315
    size_t palette_float_count = 0U;
31,614✔
4316
    size_t palette_bytes = 0U;
31,614✔
4317
    size_t palette_float_bytes = 0U;
31,614✔
4318
    size_t palette_channels = 0U;
31,614✔
4319
    size_t palette_index = 0U;
31,614✔
4320
    size_t pixel_count = 0U;
31,614✔
4321
    int palette_source_colorspace;
14,753✔
4322
    int palette_float_pixelformat;
14,753✔
4323
    int output_float_pixelformat;
14,753✔
4324
    int palette_float_depth;
14,753✔
4325
    int pipeline_active;
14,753✔
4326
    int pipeline_threads = 0;  /* set to a deterministic default before use */
31,614✔
4327
    int pipeline_nbands;
14,753✔
4328
    int encoded_width;
14,753✔
4329
    int encoded_height;
14,753✔
4330
    sixel_parallel_dither_config_t dither_parallel;
14,753✔
4331
    sixel_palette_entries_view_t palette_view;
14,753✔
4332
    sixel_palette_float32_entries_view_t palette_float_view;
14,753✔
4333
#if SIXEL_ENABLE_THREADS
4334
    sixel_timeline_logger_t *serial_logger;
10,581✔
4335
    int logger_owned = 0;
23,270✔
4336
#endif  /* SIXEL_ENABLE_THREADS */
4337
    sixel_timeline_logger_t *logger = NULL;
31,614✔
4338
    SIXELSTATUS close_status;
14,753✔
4339
    int image_started;
14,753✔
4340

4341
    if (output == NULL || dither == NULL) {
31,614!
4342
        return SIXEL_BAD_ARGUMENT;
4343
    }
4344

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

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

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

4523
#if SIXEL_ENABLE_THREADS
4524
    if (!pipeline_active) {
12,689✔
4525
        logger = dither->pipeline_logger;
5,813✔
4526
        if (logger == NULL) {
5,813!
4527
            sixel_timeline_logger_prepare_default(dither->allocator,
5,813✔
4528
                                                  &serial_logger);
4529
            if (serial_logger != NULL) {
5,813✔
4530
                logger_owned = 1;
22✔
4531
                dither->pipeline_logger = serial_logger;
22✔
4532
                logger = serial_logger;
22✔
4533
            } else {
2✔
4534
                logger = NULL;
3,160✔
4535
            }
4536
        }
523✔
4537
        if (logger != NULL) {
3,182✔
4538
            sixel_timeline_logger_logf(logger,
24✔
4539
                              "controller",
4540
                              "pipeline",
4541
                              "configure",
4542
                              -1,
4543
                              -1,
4544
                              0,
4545
                              height,
2✔
4546
                              0,
4547
                              height,
2✔
4548
                              "serial path threads=1");
4549
        }
2✔
4550
    }
523✔
4551
#endif
4552

4553
    if (output != NULL) {
31,594!
4554
        palette_source_colorspace = output->source_colorspace;
31,594✔
4555
        palette_float_pixelformat =
16,866✔
4556
            sixel_palette_float_pixelformat_for_colorspace(
31,594✔
4557
                palette_source_colorspace);
2,123✔
4558
        palette_float_depth =
16,866✔
4559
            sixel_helper_compute_depth(palette_float_pixelformat);
31,594✔
4560
    }
2,123✔
4561

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

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

4653
                channel = (int)(palette_index % 3U);
206,229✔
4654
                palette_entries[palette_index] =
206,229✔
4655
                    sixel_pixelformat_float_channel_to_byte(
206,229✔
4656
                        output_float_pixelformat,
27✔
4657
                        channel,
27✔
4658
                        palette_entries_float32[palette_index]);
206,229✔
4659
            }
27✔
4660
        } else {
3✔
4661
            status = sixel_helper_convert_colorspace(palette_entries,
820✔
4662
                                                     palette_bytes,
68✔
4663
                                                     SIXEL_PIXELFORMAT_RGB888,
4664
                                                     output->source_colorspace,
68✔
4665
                                                     output->colorspace);
68✔
4666
            if (SIXEL_FAILED(status)) {
752!
4667
                sixel_helper_set_additional_message(
×
4668
                    "sixel_encode_dither: palette colorspace "
4669
                    "conversion failed.");
4670
                goto end;
×
4671
            }
4672
        }
4673
    }
71✔
4674
    if (SIXEL_FAILED(status) || palette_entries == NULL) {
16,851!
4675
        sixel_helper_set_additional_message(
4676
            "sixel_encode_dither: palette copy failed.");
4677
        goto end;
4678
    }
4679

4680
    if (input_pixels != NULL &&
31,594!
4681
            dither->pipeline_accumulation_result_enabled != 0) {
14,137✔
4682
        status = sixel_dither_set_pipeline_accumulation_result_rgb(
128✔
4683
            dither,
8✔
4684
            input_pixels,
8✔
4685
            pixel_count,
8✔
4686
            palette_entries,
8✔
4687
            palette_count);
8✔
4688
        if (SIXEL_FAILED(status)) {
128!
4689
            goto end;
×
4690
        }
4691
    }
8✔
4692

4693
    status = sixel_encode_header(encoded_width,
33,717✔
4694
                                 encoded_height,
2,123✔
4695
                                 dither->keycolor,
2,123✔
4696
                                 output);
2,123✔
4697
    if (SIXEL_FAILED(status)) {
31,594!
4698
        goto end;
×
4699
    }
4700
    image_started = 1;
31,594✔
4701

4702
    if (pipeline_active) {
31,594!
4703
        if (output->ormode) {
17,457!
4704
            status = sixel_encode_body_ormode_pipeline(pixels,
60✔
4705
                                                       width,
5✔
4706
                                                       height,
5✔
4707
                                                       palette_entries,
5✔
4708
                                                       dither,
5✔
4709
                                                       output,
5✔
4710
                                                       pipeline_threads);
5✔
4711
        } else {
5✔
4712
            status = sixel_encode_body_pipeline(pixels,
18,997✔
4713
                                                width,
1,595✔
4714
                                                height,
1,595✔
4715
                                                palette_entries,
1,595✔
4716
                                                palette_entries_float32,
1,595✔
4717
                                                dither,
1,595✔
4718
                                                output,
1,595✔
4719
                                                pipeline_threads);
1,595✔
4720
        }
4721
    } else if (output->ormode) {
15,737!
4722
        status = sixel_encode_body_ormode(input_pixels,
20✔
4723
                                          width,
4724
                                          height,
4725
                                          palette_entries,
4726
                                          dither->ncolors,
4727
                                          dither->keycolor,
4728
                                          output);
4729
    } else {
4730
        status = sixel_encode_body(input_pixels,
14,640✔
4731
                                   width,
523✔
4732
                                   height,
523✔
4733
                                   palette_entries,
523✔
4734
                                   palette_entries_float32,
523✔
4735
                                   dither->ncolors,
523✔
4736
                                   dither->keycolor,
523✔
4737
                                   dither->bodyonly,
523✔
4738
                                   output,
523✔
4739
                                   NULL,
4740
                                   dither->allocator,
523✔
4741
                                   dither->pipeline_pin_threads,
523✔
4742
                                   logger != NULL ?
523✔
4743
                                       logger :
2✔
4744
                                       NULL);
4745
    }
4746

4747
    if (SIXEL_FAILED(status)) {
31,594!
4748
        goto end;
66✔
4749
    }
4750

4751
    status = sixel_encode_footer(output);
31,528✔
4752
    if (SIXEL_FAILED(status)) {
31,528!
UNCOV
4753
        goto end;
×
4754
    }
4755
    image_started = 0;
16,815✔
4756

4757
end:
14,738✔
4758
    if (image_started != 0 && SIXEL_FAILED(status)) {
16,901!
4759
        /*
4760
         * Keep terminals from staying in DCS/SIXEL string state after a
4761
         * late body error.  The original failure remains authoritative; the
4762
         * best-effort close is only a stream recovery guard.
4763
         */
4764
        close_status = sixel_output_end_image(output);
66✔
4765
        (void)close_status;
14,759✔
4766
    }
6✔
4767
#if SIXEL_ENABLE_THREADS
4768
    if (logger_owned) {
23,270✔
4769
        dither->pipeline_logger = NULL;
22✔
4770
        sixel_timeline_logger_unref(serial_logger);
22✔
4771
    }
2✔
4772
#endif
4773
    if (palette_entries != NULL) {
31,614!
4774
        sixel_allocator_free(dither->allocator, palette_entries);
31,594✔
4775
    }
2,123✔
4776
    if (palette_entries_float32 != NULL) {
31,614!
4777
        sixel_allocator_free(dither->allocator, palette_entries_float32);
3,452✔
4778
    }
114✔
4779
    sixel_allocator_free(dither->allocator, paletted_pixels);
31,614✔
4780

4781
    return status;
31,614✔
4782
}
2,123✔
4783

4784
SIXEL_INTERNAL_API SIXELSTATUS
4785
sixel_encoder_core_encode_dispatch(
31,974✔
4786
    sixel_encoder_core_encode_request_t const *request)
4787
{
4788
    SIXELSTATUS status;
14,921✔
4789

4790
    if (request == NULL || request->pixels == NULL ||
31,974!
4791
        request->dither == NULL || request->output == NULL) {
31,914!
4792
        return SIXEL_BAD_ARGUMENT;
32✔
4793
    }
4794
    if (request->width < 1 || request->height < 1) {
31,914!
4795
        return SIXEL_BAD_INPUT;
4796
    }
4797
    if (sixel_output_has_transparent_offset(request->output) != 0 &&
31,914✔
4798
        request->dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
52!
4799
        sixel_helper_set_additional_message(
×
4800
            "transparent-offset cannot be used with high-color output.");
4801
        return SIXEL_BAD_ARGUMENT;
×
4802
    }
4803

4804
    (void)request->depth;
17,036✔
4805
    if (request->dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
31,914✔
4806
        status = sixel_encode_highcolor(request->pixels,
320✔
4807
                                        request->width,
160✔
4808
                                        request->height,
160✔
4809
                                        request->dither,
160✔
4810
                                        request->output);
160✔
4811
    } else {
20✔
4812
        status = sixel_encode_dither(request->pixels,
33,737✔
4813
                                     request->width,
16,861✔
4814
                                     request->height,
16,861✔
4815
                                     request->dither,
16,861✔
4816
                                     request->output);
16,861✔
4817
    }
4818

4819
    return status;
17,021✔
4820
}
2,147✔
4821

4822
SIXELAPI SIXELSTATUS
4823
sixel_encode(
31,899✔
4824
    unsigned char  /* in */ *pixels,   /* pixel bytes */
4825
    int            /* in */ width,     /* image width */
4826
    int            /* in */ height,    /* image height */
4827
    int const      /* in */ depth,     /* color depth */
4828
    sixel_dither_t /* in */ *dither,   /* dither context */
4829
    sixel_output_t /* in */ *output)   /* output context */
4830
{
4831
    SIXELSTATUS status = SIXEL_FALSE;
31,899✔
4832
    sixel_encoder_core_t *core;
14,886✔
4833
    sixel_encoder_core_encode_request_t request;
14,886✔
4834

4835
    if (pixels == NULL) {
31,899!
4836
        sixel_helper_set_additional_message(
×
4837
            "sixel_encode: bad pixels parameter."
4838
            " (pixels == NULL)");
4839
        return SIXEL_BAD_ARGUMENT;
×
4840
    }
4841

4842
    if (dither == NULL) {
31,899!
4843
        sixel_helper_set_additional_message(
×
4844
            "sixel_encode: bad dither parameter."
4845
            " (dither == NULL)");
4846
        return SIXEL_BAD_ARGUMENT;
×
4847
    }
4848

4849
    if (output == NULL) {
31,899!
4850
        sixel_helper_set_additional_message(
×
4851
            "sixel_encode: bad output parameter."
4852
            " (output == NULL)");
4853
        return SIXEL_BAD_ARGUMENT;
×
4854
    }
4855

4856
    /* TODO: reference counting should be thread-safe */
4857
    sixel_dither_ref(dither);
31,899✔
4858
    sixel_output_ref(output);
31,899✔
4859

4860
    if (width < 1) {
31,899!
4861
        sixel_helper_set_additional_message(
×
4862
            "sixel_encode: bad width parameter."
4863
            " (width < 1)");
4864
        status = SIXEL_BAD_INPUT;
×
4865
        goto end;
×
4866
    }
4867

4868
    if (height < 1) {
31,899!
4869
        sixel_helper_set_additional_message(
×
4870
            "sixel_encode: bad height parameter."
4871
            " (height < 1)");
4872
        status = SIXEL_BAD_INPUT;
×
4873
        goto end;
×
4874
    }
4875

4876
    core = sixel_output_as_encoder_core(output);
31,899✔
4877
    if (core == NULL || core->vtbl == NULL ||
31,899!
4878
        core->vtbl->encode == NULL) {
31,899!
4879
        status = SIXEL_BAD_ARGUMENT;
×
4880
        goto end;
×
4881
    }
4882
    request.pixels = pixels;
31,899✔
4883
    request.width = width;
31,899✔
4884
    request.height = height;
31,899✔
4885
    request.depth = depth;
31,899✔
4886
    request.dither = dither;
31,899✔
4887
    request.output = output;
31,899✔
4888
    status = core->vtbl->encode(core, &request);
31,899✔
4889

4890
end:
29,757✔
4891
    sixel_output_unref(output);
31,899✔
4892
    sixel_dither_unref(dither);
31,899✔
4893

4894
    return status;
31,899✔
4895
}
2,142✔
4896

4897

4898
/* emacs Local Variables:      */
4899
/* emacs mode: c               */
4900
/* emacs tab-width: 4          */
4901
/* emacs indent-tabs-mode: nil */
4902
/* emacs c-basic-offset: 4     */
4903
/* emacs End:                  */
4904
/* vim: set expandtab ts=4 sts=4 sw=4 : */
4905
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc