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

saitoha / libsixel / 28038478206

23 Jun 2026 03:50PM UTC coverage: 87.147% (+0.004%) from 87.143%
28038478206

push

github

saitoha
fix: detect ssize_t in meson config

131039 of 261754 branches covered (50.06%)

156440 of 179513 relevant lines covered (87.15%)

10363742.64 hits per line

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

92.54
/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
#if SIXEL_ENABLE_THREADS
73
# include "sixel_atomic.h"
74
# include <6cells.h>
75
#endif
76

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

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

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

111
typedef struct sixel_band_state {
112
    int row_in_band;
113
    int fillable;
114
    int active_color_count;
115
} sixel_band_state_t;
116

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

148
struct sixel_parallel_context;
149
typedef struct sixel_parallel_worker_state
150
    sixel_parallel_worker_state_t;
151

152
typedef struct sixel_parallel_context {
153
    sixel_index_t *pixels;
154
    int width;
155
    int height;
156
    int ncolors;
157
    int keycolor;
158
    unsigned char *palstate;
159
    int encode_policy;
160
    sixel_allocator_t *allocator;
161
    sixel_output_t *output;
162
    int thread_count;
163
    int band_count;
164
    sixel_parallel_band_buffer_t *bands;
165
    sixel_parallel_worker_state_t **workers;
166
    int worker_capacity;
167
    int worker_registered;
168
    sixel_thread_pool_t *pool;
169
    sixel_timeline_logger_t *logger;
170
    sixel_mutex_t mutex;
171
    int mutex_ready;
172
    sixel_cond_t cond_band_ready;
173
    int cond_ready;
174
    sixel_thread_t writer_thread;
175
    int writer_started;
176
    int next_band_to_flush;
177
    int writer_should_stop;
178
    SIXELSTATUS writer_error;
179
    int queue_capacity;
180
    int pin_threads;
181
} sixel_parallel_context_t;
182

183
typedef struct sixel_parallel_row_notifier {
184
    sixel_parallel_context_t *context;
185
    sixel_timeline_logger_t *logger;
186
    int band_height;
187
    int image_height;
188
} sixel_parallel_row_notifier_t;
189

190
static void sixel_parallel_writer_stop(sixel_parallel_context_t *ctx,
191
                                       int force_abort);
192
static int sixel_parallel_writer_main(void *arg);
193
#endif
194

195
#if SIXEL_ENABLE_THREADS
196
static int sixel_parallel_jobs_allowed(sixel_parallel_context_t *ctx);
197
static void sixel_parallel_context_abort_locked(sixel_parallel_context_t *ctx,
198
                                               SIXELSTATUS status);
199
#endif
200

201
#if SIXEL_ENABLE_THREADS
202
struct sixel_parallel_worker_state {
203
    int initialized;
204
    int index;
205
    SIXELSTATUS writer_error;
206
    sixel_parallel_band_buffer_t *band_buffer;
207
    sixel_parallel_context_t *context;
208
    sixel_output_t *output;
209
    sixel_encode_work_t work;
210
    sixel_band_state_t band;
211
};
212
#endif
213

214
static void sixel_encode_work_init(sixel_encode_work_t *work);
215
static SIXELSTATUS sixel_encode_work_allocate(sixel_encode_work_t *work,
216
                                              int width,
217
                                              int ncolors,
218
                                              sixel_allocator_t *allocator);
219
static void sixel_encode_work_cleanup(sixel_encode_work_t *work,
220
                                      sixel_allocator_t *allocator);
221
static void sixel_band_state_reset(sixel_band_state_t *state);
222
static void sixel_band_finish(sixel_encode_work_t *work,
223
                              sixel_band_state_t *state);
224
static void sixel_band_clear_map(sixel_encode_work_t *work);
225
static SIXELSTATUS sixel_band_classify_row(sixel_encode_work_t *work,
226
                                           sixel_band_state_t *state,
227
                                           sixel_index_t *pixels,
228
                                           int width,
229
                                           int absolute_row,
230
                                           int ncolors,
231
                                           int keycolor,
232
                                           unsigned char *palstate,
233
                                           int encode_policy);
234
static SIXELSTATUS sixel_band_compose(sixel_encode_work_t *work,
235
                                      sixel_band_state_t *state,
236
                                      sixel_output_t *output,
237
                                      int width,
238
                                      int ncolors,
239
                                      int keycolor,
240
                                      sixel_allocator_t *allocator);
241
static SIXELSTATUS sixel_band_emit(sixel_encode_work_t *work,
242
                                   sixel_band_state_t *state,
243
                                   sixel_output_t *output,
244
                                   int ncolors,
245
                                   int keycolor,
246
                                   int last_row_index);
247
static SIXELSTATUS sixel_put_flash(sixel_output_t *const output);
248
static void sixel_advance(sixel_output_t *output, int nwrite);
249
static int sixel_encode_body_ormode_nplanes(int ncolors);
250
static SIXELSTATUS
251
sixel_encode_body_ormode_emit_palette(unsigned char const *palette,
252
                                      int ncolors,
253
                                      int keycolor,
254
                                      sixel_output_t *output);
255
static SIXELSTATUS
256
sixel_encode_body_ormode_band(sixel_index_t const *pixels,
257
                              int width,
258
                              int height,
259
                              int band_index,
260
                              int nplanes,
261
                              sixel_output_t *output);
262

263
#if SIXEL_ENABLE_THREADS
264
static void
265
sixel_timeline_logger_prepare_default(sixel_allocator_t *allocator,
60,117✔
266
                                      sixel_timeline_logger_t **logger)
267
{
268
    if (logger == NULL) {
60,117!
269
        return;
270
    }
271

272
    *logger = NULL;
60,117✔
273
    (void)sixel_timeline_logger_prepare_env(allocator, logger);
60,117✔
274
}
34,993✔
275
#endif
276

277
static void
278
sixel_parallel_dither_configure(int height,
41,014✔
279
                                int ncolors,
280
                                int pipeline_threads,
281
                                int pin_threads,
282
                                sixel_parallel_dither_config_t *config)
283
{
284
    char const *text;
21,185✔
285
    long parsed;
21,185✔
286
    char *endptr;
21,185✔
287
    int band_height;
21,185✔
288
    int overlap;
21,185✔
289
    int dither_threads;
21,185✔
290
    int encode_threads;
21,185✔
291
    int dither_env_override;
21,185✔
292

293
    if (config == NULL) {
41,014✔
294
        return;
×
295
    }
296

297
    config->enabled = 0;
41,014✔
298
    config->band_height = 0;
41,014✔
299
    config->overlap = 0;
41,014✔
300
    config->dither_threads = 0;
41,014✔
301
    config->encode_threads = pipeline_threads;
41,014✔
302
    config->pin_threads = (pin_threads != 0) ? 1 : 0;
41,014✔
303

304
    if (pipeline_threads <= 1 || height <= 0) {
41,014!
305
        return;
306
    }
307

308
    dither_env_override = 0;
41,014✔
309
    dither_threads = (pipeline_threads * 7 + 9) / 10;
41,014✔
310
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_THREADS_MAX");
41,014✔
311
    if (text != NULL && text[0] != '\0') {
41,014!
312
        errno = 0;
110✔
313
        parsed = strtol(text, &endptr, 10);
110✔
314
        if (endptr != text && errno != ERANGE && parsed > 0) {
110!
315
            if (parsed > INT_MAX) {
80!
316
                parsed = INT_MAX;
317
            }
318
            dither_threads = (int)parsed;
95✔
319
            dither_env_override = 1;
95✔
320
        }
55✔
321
    }
55✔
322
    if (dither_threads < 1) {
30,847!
323
        dither_threads = 1;
324
    }
325
    if (dither_threads > pipeline_threads) {
38,734!
326
        dither_threads = pipeline_threads;
327
    }
328

329
    if (!dither_env_override && pipeline_threads >= 4 && dither_threads < 2) {
41,009!
330
        /*
331
         * When the total budget is ample, keep at least two dither workers so
332
         * the banded producer can feed the encoder fast enough to pipeline.
333
         */
334
        dither_threads = pipeline_threads - 2;
×
335
    }
336

337
    encode_threads = pipeline_threads - dither_threads;
41,009✔
338
    if (encode_threads < 2 && pipeline_threads > 2) {
41,009!
339
        /*
340
         * Preserve a minimal pair of encoder workers to keep the pipeline
341
         * alive while leaving the rest to dithering. Small budgets fall back
342
         * to the serial encoder path later in the caller.
343
         */
344
        encode_threads = 2;
40,700✔
345
        dither_threads = pipeline_threads - encode_threads;
40,700✔
346
    }
23,528✔
347
    if (encode_threads < 1) {
43,257✔
348
        encode_threads = 1;
204✔
349
        dither_threads = pipeline_threads - encode_threads;
204✔
350
    }
105✔
351
    if (dither_threads < 1) {
34,704!
352
        return;
353
    }
354

355
    /*
356
     * Choose the band height from the environment when present. Otherwise
357
     * split the image across the initial dither workers so each thread starts
358
     * with a single band. The result is rounded to a six-line multiple to
359
     * stay aligned with the encoder's natural cadence.
360
     */
361
    band_height = 0;
41,014✔
362
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_WIDTH");
41,014✔
363
    if (text != NULL && text[0] != '\0') {
41,014!
364
        errno = 0;
110✔
365
        parsed = strtol(text, &endptr, 10);
110✔
366
        if (endptr != text && errno != ERANGE && parsed > 0) {
110!
367
            if (parsed > INT_MAX) {
80!
368
                parsed = INT_MAX;
369
            }
370
            band_height = (int)parsed;
95✔
371
        }
55✔
372
    }
55✔
373
    if (band_height <= 0) {
33,122✔
374
        band_height = (height + dither_threads - 1) / dither_threads;
40,904✔
375
    }
23,633✔
376
    if (band_height < 6) {
43,234✔
377
        band_height = 6;
2,893✔
378
    }
1,519✔
379
    if ((band_height % 6) != 0) {
39,591✔
380
        band_height = ((band_height + 5) / 6) * 6;
32,101✔
381
    }
19,624✔
382

383
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_OVERWRAP");
41,014✔
384
    /*
385
     * Default overlap favors quality for small palettes and speed when
386
     * colors are plentiful. The environment can override this policy.
387
     */
388
    if (ncolors <= 32) {
41,014✔
389
        overlap = 6;
18,416✔
390
    } else {
13,070✔
391
        overlap = 0;
18,147✔
392
    }
393
    if (text != NULL && text[0] != '\0') {
41,014!
394
        errno = 0;
110✔
395
        parsed = strtol(text, &endptr, 10);
110✔
396
        if (endptr != text && errno != ERANGE && parsed >= 0) {
110!
397
            if (parsed > INT_MAX) {
80!
398
                parsed = INT_MAX;
399
            }
400
            overlap = (int)parsed;
95✔
401
        }
55✔
402
    }
55✔
403
    if (overlap < 0) {
30,847!
404
        overlap = 0;
405
    }
406
    if (overlap > band_height / 2) {
38,787✔
407
        overlap = band_height / 2;
4,454✔
408
    }
2,006✔
409

410
    config->enabled = 1;
41,014✔
411
    config->band_height = band_height;
41,014✔
412
    config->overlap = overlap;
41,014✔
413
    config->dither_threads = dither_threads;
41,014✔
414
    config->encode_threads = encode_threads;
41,014✔
415
}
23,688!
416

417
#if SIXEL_ENABLE_THREADS
418
static int sixel_parallel_band_writer(char *data, int size, void *priv);
419
static int sixel_parallel_worker_main(sixel_thread_pool_job_t job,
420
                                      void *userdata,
421
                                      void *workspace);
422
static SIXELSTATUS
423
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
424
                             sixel_index_t *pixels,
425
                             int width,
426
                             int height,
427
                             int ncolors,
428
                             int keycolor,
429
                             unsigned char *palstate,
430
                             sixel_output_t *output,
431
                             sixel_allocator_t *allocator,
432
                             int requested_threads,
433
                             int worker_capacity,
434
                             int queue_capacity,
435
                             int pin_threads,
436
                             sixel_timeline_logger_t *logger);
437
static SIXELSTATUS sixel_parallel_context_grow(sixel_parallel_context_t *ctx,
438
                                              int target_threads);
439
static void sixel_parallel_submit_band(sixel_parallel_context_t *ctx,
440
                                       int band_index);
441
static SIXELSTATUS sixel_parallel_context_wait(sixel_parallel_context_t *ctx,
442
                                               int force_abort);
443
static void sixel_parallel_palette_row_ready(void *priv, int row_index);
444
static int
445
sixel_parallel_context_is_ormode(sixel_parallel_context_t const *ctx);
446
static SIXELSTATUS
447
sixel_parallel_worker_flush_output(sixel_parallel_worker_state_t *state);
448
static SIXELSTATUS sixel_encode_emit_palette(int bodyonly,
449
                          int ncolors,
450
                          int keycolor,
451
                          unsigned char const *palette,
452
                          float const *palette_float,
453
                          sixel_output_t *output);
454

455
static void
456
sixel_parallel_context_init(sixel_parallel_context_t *ctx)
46,760✔
457
{
458
    memset(ctx, 0, sizeof(*ctx));
46,760✔
459
    ctx->pixels = NULL;
46,760✔
460
    ctx->keycolor = (-1);
46,760✔
461
    ctx->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
46,760✔
462
    ctx->writer_error = SIXEL_OK;
46,760✔
463
}
39,643✔
464

465
static int
466
sixel_parallel_context_is_ormode(sixel_parallel_context_t const *ctx)
600,130✔
467
{
468
    if (ctx == NULL || ctx->output == NULL) {
599,950!
469
        return 0;
12✔
470
    }
471
    return ctx->output->ormode != 0 ? 1 : 0;
582,473✔
472
}
375,677✔
473

474
static void
475
sixel_parallel_worker_release_nodes(sixel_parallel_worker_state_t *state,
368,426✔
476
                                    sixel_allocator_t *allocator)
477
{
478
    sixel_node_t *np;
189,826✔
479

480
    if (state == NULL || state->output == NULL) {
368,426!
481
        return;
4✔
482
    }
483

484
    while ((np = state->output->node_free) != NULL) {
16,142,464!
485
        state->output->node_free = np->next;
15,774,038✔
486
        sixel_allocator_free(allocator, np);
15,774,038✔
487
    }
488
    state->output->node_top = NULL;
368,426✔
489
}
209,288!
490

491
static void
492
sixel_parallel_worker_cleanup(sixel_parallel_worker_state_t *state,
136,239✔
493
                              sixel_allocator_t *allocator)
494
{
495
    if (state == NULL) {
136,239✔
496
        return;
44,482✔
497
    }
498
    sixel_parallel_worker_release_nodes(state, allocator);
82,077✔
499
    if (state->output != NULL) {
82,077!
500
        sixel_output_unref(state->output);
82,077✔
501
        state->output = NULL;
82,077✔
502
    }
42,918✔
503
    sixel_encode_work_cleanup(&state->work, allocator);
82,077✔
504
    sixel_band_state_reset(&state->band);
82,077✔
505
    state->initialized = 0;
82,077✔
506
    state->index = 0;
82,077✔
507
    state->writer_error = SIXEL_OK;
82,077✔
508
    state->band_buffer = NULL;
82,077✔
509
    state->context = NULL;
82,077✔
510
}
76,746✔
511

512
static void
513
sixel_parallel_context_cleanup(sixel_parallel_context_t *ctx)
46,760✔
514
{
515
    int i;
24,273✔
516

517
    if (ctx->workers != NULL) {
46,760!
518
        for (i = 0; i < ctx->worker_capacity; i++) {
182,999!
519
            sixel_parallel_worker_cleanup(ctx->workers[i], ctx->allocator);
136,239✔
520
        }
76,746✔
521
        free(ctx->workers);
46,760✔
522
        ctx->workers = NULL;
46,760✔
523
    }
27,320✔
524
    sixel_parallel_writer_stop(ctx, 1);
46,760✔
525
    if (ctx->bands != NULL) {
46,760!
526
        if (ctx->band_count < 0) {
46,760!
527
            ctx->band_count = 0;
528
        }
529
#if defined(_MSC_VER)
530
#pragma warning(push)
531
#pragma warning(disable : 6001)
532
#endif
533
        for (i = 0; i < ctx->band_count; i++) {
333,155!
534
            free(ctx->bands[i].data);
286,395✔
535
            ctx->bands[i].data = NULL;
286,395✔
536
        }
166,415✔
537
#if defined(_MSC_VER)
538
#pragma warning(pop)
539
#endif
540
        free(ctx->bands);
46,760✔
541
        ctx->bands = NULL;
46,760✔
542
    }
27,320✔
543
    ctx->band_count = 0;
46,760✔
544
    if (ctx->pool != NULL) {
46,760!
545
        ctx->pool->vtbl->unref(ctx->pool);
46,760✔
546
        ctx->pool = NULL;
46,760✔
547
    }
27,320✔
548
    if (ctx->cond_ready) {
46,760!
549
        sixel_cond_destroy(&ctx->cond_band_ready);
46,760✔
550
        ctx->cond_ready = 0;
46,760✔
551
    }
27,320✔
552
    if (ctx->mutex_ready) {
46,760!
553
        sixel_mutex_destroy(&ctx->mutex);
46,760✔
554
        ctx->mutex_ready = 0;
46,760✔
555
    }
27,320✔
556
}
46,760✔
557

558
/*
559
 * Abort the pipeline when either a worker or the writer encounters an error.
560
 * The helper normalizes the `writer_error` bookkeeping so later callers see a
561
 * consistent stop request regardless of whether the mutex has been
562
 * initialized.
563
 */
564
static void
565
sixel_parallel_context_abort_locked(sixel_parallel_context_t *ctx,
566
                                    SIXELSTATUS status)
567
{
568
    if (ctx == NULL) {
×
569
        return;
570
    }
571
    if (!ctx->mutex_ready) {
×
572
        if (ctx->writer_error == SIXEL_OK) {
×
573
            ctx->writer_error = status;
574
        }
575
        ctx->writer_should_stop = 1;
576
        return;
577
    }
578

579
    sixel_mutex_lock(&ctx->mutex);
580
    if (ctx->writer_error == SIXEL_OK) {
×
581
        ctx->writer_error = status;
582
    }
583
    ctx->writer_should_stop = 1;
584
    sixel_cond_broadcast(&ctx->cond_band_ready);
585
    sixel_mutex_unlock(&ctx->mutex);
586
}
587

588
/*
589
 * Determine whether additional bands should be queued or executed.  The
590
 * producer and workers call this guard to avoid redundant work once the writer
591
 * decides to shut the pipeline down.
592
 */
593
static int
594
sixel_parallel_jobs_allowed(sixel_parallel_context_t *ctx)
286,386✔
595
{
596
    int accept;
148,626✔
597

598
    if (ctx == NULL) {
286,386✔
599
        return 0;
600
    }
601
    if (!ctx->mutex_ready) {
286,386!
602
        if (ctx->writer_should_stop || ctx->writer_error != SIXEL_OK) {
×
603
            return 0;
604
        }
605
        return 1;
606
    }
607

608
    sixel_mutex_lock(&ctx->mutex);
286,385✔
609
    accept = (!ctx->writer_should_stop && ctx->writer_error == SIXEL_OK);
286,394!
610
    sixel_mutex_unlock(&ctx->mutex);
286,395✔
611
    return accept;
286,394✔
612
}
166,415✔
613

614
static void
615
sixel_parallel_worker_reset(sixel_parallel_worker_state_t *state)
286,396✔
616
{
617
    if (state == NULL || state->output == NULL) {
286,396!
618
        return;
16✔
619
    }
620

621
    sixel_band_state_reset(&state->band);
286,381✔
622
    if (!sixel_parallel_context_is_ormode(state->context)) {
286,380✔
623
        sixel_band_clear_map(&state->work);
285,111✔
624
    }
165,776✔
625
    /* Parallel workers reset band-local buffers and output. */
626
    state->writer_error = SIXEL_OK;
286,363✔
627
    state->output->pos = 0;
286,363✔
628
    state->output->save_count = 0;
286,363✔
629
    state->output->save_pixel = 0;
286,363✔
630
    state->output->active_palette = (-1);
286,363✔
631
    state->output->node_top = NULL;
286,363✔
632
    state->output->node_free = NULL;
286,363✔
633
}
166,404✔
634

635
static SIXELSTATUS
636
sixel_parallel_worker_prepare(sixel_parallel_worker_state_t *state,
286,389✔
637
                              sixel_parallel_context_t *ctx)
638
{
639
    SIXELSTATUS status;
148,627✔
640

641
    if (state->initialized) {
286,389✔
642
        return SIXEL_OK;
166,967✔
643
    }
644

645
    sixel_encode_work_init(&state->work);
82,072✔
646
    sixel_band_state_reset(&state->band);
82,066✔
647
    state->writer_error = SIXEL_OK;
82,064✔
648
    state->band_buffer = NULL;
82,064✔
649
    state->context = ctx;
82,064✔
650

651
    if (!sixel_parallel_context_is_ormode(ctx)) {
82,064✔
652
        status = sixel_encode_work_allocate(&state->work,
124,614✔
653
                                            ctx->width,
42,794✔
654
                                            ctx->ncolors,
42,794✔
655
                                            ctx->allocator);
42,794✔
656
        if (SIXEL_FAILED(status)) {
81,821!
657
            return status;
658
        }
659
    }
42,795✔
660

661
    status = sixel_encoder_core_create_output_from_factory(&state->output,
124,988✔
662
                                              sixel_parallel_band_writer,
663
                                              state,
42,917✔
664
                                              ctx->allocator);
42,917✔
665
    if (SIXEL_FAILED(status)) {
82,074!
666
        if (!sixel_parallel_context_is_ormode(ctx)) {
×
667
            sixel_encode_work_cleanup(&state->work, ctx->allocator);
668
        }
669
        return status;
670
    }
671

672
    state->output->has_8bit_control = ctx->output->has_8bit_control;
82,074✔
673
    state->output->has_sixel_scrolling = ctx->output->has_sixel_scrolling;
82,074✔
674
    state->output->has_sdm_glitch = ctx->output->has_sdm_glitch;
82,074✔
675
    state->output->has_gri_arg_limit = ctx->output->has_gri_arg_limit;
82,074✔
676
    state->output->skip_dcs_envelope = 1;
82,074✔
677
    state->output->skip_header = 1;
82,074✔
678
    state->output->palette_type = ctx->output->palette_type;
82,074✔
679
    state->output->colorspace = ctx->output->colorspace;
82,074✔
680
    state->output->source_colorspace = ctx->output->source_colorspace;
82,074✔
681
    state->output->pixelformat = ctx->output->pixelformat;
82,074✔
682
    state->output->penetrate_multiplexer =
82,074✔
683
        ctx->output->penetrate_multiplexer;
82,074✔
684
    state->output->encode_policy = ctx->output->encode_policy;
82,074✔
685
    state->output->ormode = ctx->output->ormode;
82,074✔
686

687
    state->initialized = 1;
82,074✔
688
    state->index = (-1);
82,074✔
689

690
    if (ctx->mutex_ready) {
82,074!
691
        sixel_mutex_lock(&ctx->mutex);
82,055✔
692
    }
42,904✔
693
    if (ctx->worker_registered < ctx->worker_capacity) {
82,084!
694
        state->index = ctx->worker_registered;
82,077✔
695
        ctx->workers[state->index] = state;
82,077✔
696
        ctx->worker_registered += 1;
82,077✔
697
    }
42,918✔
698
    if (ctx->mutex_ready) {
82,084!
699
        sixel_mutex_unlock(&ctx->mutex);
82,077✔
700
    }
42,918✔
701

702
    if (state->index < 0) {
82,081!
703
        sixel_parallel_worker_cleanup(state, ctx->allocator);
704
        return SIXEL_RUNTIME_ERROR;
705
    }
706

707
    return SIXEL_OK;
64,609✔
708
}
166,413✔
709

710
static SIXELSTATUS
711
sixel_parallel_context_grow(sixel_parallel_context_t *ctx, int target_threads)
40,832✔
712
{
713
    int capped_target;
21,093✔
714
    int delta;
21,093✔
715
    int status;
21,093✔
716

717
    if (ctx == NULL || ctx->pool == NULL) {
40,832!
718
        return SIXEL_BAD_ARGUMENT;
719
    }
720

721
    capped_target = target_threads;
40,832✔
722
    if (capped_target > ctx->worker_capacity) {
40,832✔
723
        capped_target = ctx->worker_capacity;
15,389✔
724
    }
9,005✔
725
    if (ctx->band_count > 0 && capped_target > ctx->band_count) {
40,832!
726
        capped_target = ctx->band_count;
7,857✔
727
    }
728
    if (capped_target <= ctx->thread_count) {
40,832✔
729
        return SIXEL_OK;
9,430✔
730
    }
731

732
    delta = capped_target - ctx->thread_count;
29,246✔
733
    status = ctx->pool->vtbl->grow(ctx->pool, delta);
29,246✔
734
    if (SIXEL_FAILED(status)) {
29,246!
735
        return status;
736
    }
737
    ctx->thread_count += delta;
29,246✔
738

739
    if (ctx->logger != NULL) {
29,246✔
740
        sixel_timeline_logger_logf(ctx->logger,
49✔
741
                          "controller",
742
                          "encode",
743
                          "grow_workers",
744
                          -1);
745
    }
27✔
746

747
    return SIXEL_OK;
23,545✔
748
}
23,594✔
749

750
static int
751
sixel_parallel_band_writer(char *data, int size, void *priv)
285,403✔
752
{
753
    sixel_parallel_worker_state_t *state;
148,130✔
754
    sixel_parallel_band_buffer_t *band;
148,130✔
755
    size_t required;
148,130✔
756
    size_t capacity;
148,130✔
757
    size_t new_capacity;
148,130✔
758
    unsigned char *tmp;
148,130✔
759

760
    state = (sixel_parallel_worker_state_t *)priv;
285,403✔
761
    if (state == NULL || data == NULL || size <= 0) {
285,403!
762
        return size;
168✔
763
    }
764
    band = state->band_buffer;
285,393✔
765
    if (band == NULL) {
285,393!
766
        state->writer_error = SIXEL_RUNTIME_ERROR;
767
        return size;
768
    }
769
    if (state->writer_error != SIXEL_OK) {
285,399!
770
        return size;
771
    }
772

773
    required = band->used + (size_t)size;
285,399✔
774
    if (required < band->used) {
285,399!
775
        state->writer_error = SIXEL_BAD_INTEGER_OVERFLOW;
776
        return size;
777
    }
778
    capacity = band->size;
285,400✔
779
    if (required > capacity) {
285,400!
780
        if (capacity == 0) {
285,407!
781
            new_capacity = (size_t)SIXEL_OUTPUT_PACKET_SIZE;
263,792✔
782
        } else {
165,836✔
783
            new_capacity = capacity;
67✔
784
        }
785
        while (new_capacity < required) {
285,404!
786
            if (new_capacity > SIZE_MAX / 2) {
×
787
                new_capacity = required;
788
                break;
789
            }
790
            new_capacity *= 2;
791
        }
792
        tmp = (unsigned char *)realloc(band->data, new_capacity);
285,293✔
793
        if (tmp == NULL) {
285,293!
794
            state->writer_error = SIXEL_BAD_ALLOCATION;
795
            return size;
796
        }
797
        band->data = tmp;
285,293✔
798
        band->size = new_capacity;
285,293✔
799
    }
165,788✔
800

801
    memcpy(band->data + band->used, data, (size_t)size);
285,290✔
802
    band->used += (size_t)size;
285,290✔
803

804
    return size;
285,290✔
805
}
165,786✔
806

807
static SIXELSTATUS
808
sixel_parallel_worker_flush_output(sixel_parallel_worker_state_t *state)
286,373✔
809
{
810
    if (state == NULL || state->output == NULL) {
286,373!
811
        return SIXEL_BAD_ARGUMENT;
130✔
812
    }
813
    if (state->output->pos > 0) {
286,296✔
814
        state->writer_error = sixel_output_write_bytes(
404,816✔
815
            state->output,
165,802✔
816
            (char *)state->output->buffer,
285,298✔
817
            state->output->pos);
230,703✔
818
        state->output->pos = 0;
285,320✔
819
    }
165,802✔
820
    if (state->writer_error != SIXEL_OK) {
286,332!
821
        return state->writer_error;
822
    }
823
    return SIXEL_OK;
231,509✔
824
}
166,345✔
825

826
static SIXELSTATUS
827
sixel_parallel_create_pool(sixel_thread_pool_t **pool,
46,760✔
828
                           int threads,
829
                           int queue_depth,
830
                           size_t workspace_size,
831
                           sixel_thread_pool_worker_function_t worker,
832
                           void *userdata)
833
{
834
    sixel_threadpool_service_t *service;
24,273✔
835
    sixel_thread_pool_create_request_t request;
24,273✔
836
    void *service_object;
24,273✔
837
    SIXELSTATUS status;
24,273✔
838

839
    if (pool != NULL) {
46,760!
840
        *pool = NULL;
46,760✔
841
    }
27,320✔
842
    if (pool == NULL) {
46,760!
843
        return SIXEL_BAD_ARGUMENT;
844
    }
845

846
    service = NULL;
46,760✔
847
    service_object = NULL;
46,760✔
848
    status = sixel_components_getservice("services/threadpool",
46,760✔
849
                                         &service_object);
850
    if (SIXEL_FAILED(status)) {
46,760!
851
        return status;
852
    }
853
    service = (sixel_threadpool_service_t *)service_object;
46,760✔
854
    if (service == NULL || service->vtbl == NULL ||
46,760!
855
        service->vtbl->create_pool == NULL) {
46,760!
856
        if (service != NULL && service->vtbl != NULL &&
×
857
            service->vtbl->unref != NULL) {
×
858
            service->vtbl->unref(service);
859
        }
860
        return SIXEL_BAD_ARGUMENT;
861
    }
862

863
    request.threads = threads;
46,760✔
864
    request.queue_size = queue_depth;
46,760✔
865
    request.workspace_size = workspace_size;
46,760✔
866
    request.worker = worker;
46,760✔
867
    request.userdata = userdata;
46,760✔
868
    request.workspace_cleanup = NULL;
46,760✔
869
    status = service->vtbl->create_pool(service, &request, pool);
46,760✔
870
    if (service->vtbl->unref != NULL) {
46,760!
871
        service->vtbl->unref(service);
46,760✔
872
    }
27,320✔
873

874
    return status;
37,901✔
875
}
27,320✔
876

877
static SIXELSTATUS
878
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
46,760✔
879
                             sixel_index_t *pixels,
880
                             int width,
881
                             int height,
882
                             int ncolors,
883
                             int keycolor,
884
                             unsigned char *palstate,
885
                             sixel_output_t *output,
886
                             sixel_allocator_t *allocator,
887
                             int requested_threads,
888
                             int worker_capacity,
889
                             int queue_capacity,
890
                             int pin_threads,
891
                             sixel_timeline_logger_t *logger)
892
{
893
    SIXELSTATUS status;
24,273✔
894
    int nbands;
24,273✔
895
    int threads;
24,273✔
896
    int i;
24,273✔
897

898
    if (ctx == NULL || pixels == NULL || output == NULL) {
46,760!
899
        return SIXEL_BAD_ARGUMENT;
900
    }
901

902
    ctx->pixels = pixels;
46,760✔
903
    ctx->width = width;
46,760✔
904
    ctx->height = height;
46,760✔
905
    ctx->ncolors = ncolors;
46,760✔
906
    ctx->keycolor = keycolor;
46,760✔
907
    ctx->palstate = palstate;
46,760✔
908
    ctx->encode_policy = output->encode_policy;
46,760✔
909
    ctx->allocator = allocator;
46,760✔
910
    ctx->output = output;
46,760✔
911
    ctx->logger = logger;
46,760✔
912
    ctx->pin_threads = (pin_threads != 0) ? 1 : 0;
46,760✔
913
    ctx->bands = NULL;
46,760✔
914
    ctx->band_count = 0;
46,760✔
915
    ctx->workers = NULL;
46,760✔
916
    ctx->worker_capacity = 0;
46,760✔
917

918
    nbands = (height + 5) / 6;
46,760✔
919
    if (nbands <= 0) {
46,760!
920
        return SIXEL_OK;
921
    }
922
    threads = requested_threads;
46,760✔
923
    if (threads > nbands) {
46,760!
924
        threads = nbands;
925
    }
926
    if (threads < 1) {
44,210!
927
        threads = 1;
928
    }
929
    ctx->thread_count = threads;
44,210✔
930
    if (worker_capacity < threads) {
44,210!
931
        worker_capacity = threads;
932
    }
933
    if (worker_capacity > nbands) {
44,210!
934
        worker_capacity = nbands;
935
    }
936
    ctx->worker_capacity = worker_capacity;
44,221✔
937

938
    if (logger != NULL) {
44,221✔
939
        sixel_timeline_logger_logf(logger,
225✔
940
                          "controller",
941
                          "encode",
942
                          "context_begin",
943
                          -1);
944
    }
115✔
945

946
    ctx->bands = (sixel_parallel_band_buffer_t *)calloc((size_t)nbands,
46,760✔
947
                                                        sizeof(*ctx->bands));
948
    if (ctx->bands == NULL) {
46,760!
949
        return SIXEL_BAD_ALLOCATION;
950
    }
951
    for (i = 0; i < nbands; ++i) {
333,155!
952
        ctx->bands[i].data = NULL;
286,395✔
953
        ctx->bands[i].size = 0;
286,395✔
954
        ctx->bands[i].used = 0;
286,395✔
955
        ctx->bands[i].status = SIXEL_OK;
286,395✔
956
        ctx->bands[i].ready = 0;
286,395✔
957
        ctx->bands[i].dispatched = 0;
286,395✔
958
    }
166,415✔
959
    ctx->band_count = nbands;
46,760✔
960

961
    ctx->workers = (sixel_parallel_worker_state_t **)
46,760✔
962
        calloc((size_t)ctx->worker_capacity, sizeof(*ctx->workers));
46,760✔
963
    if (ctx->workers == NULL) {
46,760!
964
        return SIXEL_BAD_ALLOCATION;
965
    }
966

967
    status = sixel_mutex_init(&ctx->mutex);
46,760✔
968
    if (status != SIXEL_OK) {
46,760!
969
        return status;
970
    }
971
    ctx->mutex_ready = 1;
46,760✔
972

973
    status = sixel_cond_init(&ctx->cond_band_ready);
46,760✔
974
    if (status != SIXEL_OK) {
46,760!
975
        return status;
976
    }
977
    ctx->cond_ready = 1;
46,760✔
978

979
    ctx->queue_capacity = queue_capacity;
46,760✔
980
    if (ctx->queue_capacity < 1) {
46,760!
981
        ctx->queue_capacity = nbands;
982
    }
983
    if (ctx->queue_capacity > nbands) {
44,210!
984
        ctx->queue_capacity = nbands;
985
    }
986

987
    status = sixel_parallel_create_pool(
46,760✔
988
        &ctx->pool,
27,320✔
989
        threads,
27,320✔
990
        ctx->queue_capacity,
27,320✔
991
        sizeof(sixel_parallel_worker_state_t),
992
        sixel_parallel_worker_main,
993
        ctx);
27,320✔
994
    if (SIXEL_FAILED(status)) {
46,760!
995
        return status;
996
    }
997

998
    ctx->pool->vtbl->set_affinity(ctx->pool, ctx->pin_threads);
46,760✔
999

1000
    /* Initialize writer-visible fields before the writer thread starts.
1001
     * Serialize initialization of writer-visible state so the writer thread
1002
     * cannot observe partially initialized fields on startup.
1003
     */
1004
    sixel_mutex_lock(&ctx->mutex);
46,760✔
1005
    ctx->next_band_to_flush = 0;
46,760✔
1006
    ctx->writer_should_stop = 0;
46,760✔
1007
    ctx->writer_error = SIXEL_OK;
46,760✔
1008

1009
    status = sixel_thread_create(&ctx->writer_thread,
74,080✔
1010
                                 sixel_parallel_writer_main,
1011
                                 ctx);
27,320✔
1012
    if (SIXEL_FAILED(status)) {
46,760!
1013
        sixel_mutex_unlock(&ctx->mutex);
1014
        return status;
1015
    }
1016
    ctx->writer_started = 1;
46,760✔
1017
    sixel_mutex_unlock(&ctx->mutex);
46,760✔
1018

1019
    return SIXEL_OK;
46,760✔
1020
}
27,320✔
1021

1022
static void
1023
sixel_parallel_submit_band(sixel_parallel_context_t *ctx, int band_index)
286,394✔
1024
{
1025
    sixel_thread_pool_job_t job;
148,631✔
1026
    SIXELSTATUS status;
148,631✔
1027
    int dispatch;
148,631✔
1028

1029
    if (ctx == NULL || ctx->pool == NULL) {
286,394!
1030
        return;
12✔
1031
    }
1032
    if (band_index < 0 || band_index >= ctx->band_count) {
286,393!
1033
        return;
13✔
1034
    }
1035

1036
    dispatch = 0;
286,392✔
1037
    /*
1038
     * Multiple producers may notify the same band when PaletteApply runs in
1039
     * parallel.  Guard the dispatched flag so only the first notifier pushes
1040
     * work into the encoder queue.
1041
     */
1042
    if (ctx->mutex_ready) {
286,392!
1043
        sixel_mutex_lock(&ctx->mutex);
286,394✔
1044
        if (!ctx->bands[band_index].dispatched
426,030!
1045
                && !ctx->writer_should_stop
275,734!
1046
                && ctx->writer_error == SIXEL_OK) {
286,395!
1047
            ctx->bands[band_index].dispatched = 1;
286,395✔
1048
            dispatch = 1;
286,395✔
1049
        }
166,415✔
1050
        sixel_mutex_unlock(&ctx->mutex);
286,395✔
1051
    } else {
166,415✔
1052
        if (!ctx->bands[band_index].dispatched
×
1053
                && sixel_parallel_jobs_allowed(ctx)) {
×
1054
            ctx->bands[band_index].dispatched = 1;
1055
            dispatch = 1;
1056
        }
1057
    }
1058

1059
    if (!dispatch) {
286,395!
1060
        return;
1061
    }
1062

1063
    sixel_fence_release();
286,395✔
1064
    if (ctx->logger != NULL) {
286,395✔
1065
        sixel_timeline_logger_logf(ctx->logger,
1,218✔
1066
                          "controller",
1067
                          "encode",
1068
                          "dispatch",
1069
                          band_index);
411✔
1070
    }
411✔
1071
    job.band_index = band_index;
270,753✔
1072
    status = ctx->pool->vtbl->push(ctx->pool, job);
270,753✔
1073
    if (SIXEL_FAILED(status)) {
270,753!
1074
        if (ctx->mutex_ready) {
×
1075
            sixel_mutex_lock(&ctx->mutex);
1076
            sixel_parallel_context_abort_locked(ctx, status);
1077
            sixel_mutex_unlock(&ctx->mutex);
1078
        } else {
1079
            ctx->writer_error = status;
1080
        }
1081
    }
1082
}
166,415!
1083

1084
static SIXELSTATUS
1085
sixel_parallel_context_wait(sixel_parallel_context_t *ctx, int force_abort)
46,760✔
1086
{
1087
    int pool_error;
24,273✔
1088

1089
    if (ctx == NULL || ctx->pool == NULL) {
46,760!
1090
        return SIXEL_BAD_ARGUMENT;
1091
    }
1092

1093
    ctx->pool->vtbl->finish(ctx->pool);
46,760✔
1094
    pool_error = ctx->pool->vtbl->get_error(ctx->pool);
46,760✔
1095
    sixel_parallel_writer_stop(ctx, force_abort || pool_error != SIXEL_OK);
46,760!
1096

1097
    if (pool_error != SIXEL_OK) {
46,760!
1098
        return pool_error;
1099
    }
1100
    if (ctx->writer_error != SIXEL_OK) {
46,760!
1101
        return ctx->writer_error;
1102
    }
1103

1104
    return SIXEL_OK;
37,901✔
1105
}
27,320✔
1106

1107
/*
1108
 * Producer callback invoked after PaletteApply finishes a scanline.  The
1109
 * helper promotes every sixth row (or the final partial band) into the job
1110
 * queue so workers can begin encoding while dithering continues.
1111
 */
1112
static void
1113
sixel_parallel_palette_row_ready(void *priv, int row_index)
1,404,121✔
1114
{
1115
    sixel_parallel_row_notifier_t *notifier;
728,655✔
1116
    sixel_parallel_context_t *ctx;
728,655✔
1117
    sixel_timeline_logger_t *logger;
728,655✔
1118
    int band_height;
728,655✔
1119
    int band_index;
728,655✔
1120

1121
    notifier = (sixel_parallel_row_notifier_t *)priv;
1,404,121✔
1122
    if (notifier == NULL) {
1,404,121✔
1123
        return;
1124
    }
1125
    ctx = notifier->context;
1,404,122✔
1126
    logger = notifier->logger;
1,404,122✔
1127
    if (ctx == NULL || ctx->band_count <= 0 || ctx->height <= 0) {
1,404,122!
1128
        return;
34✔
1129
    }
1130
    if (row_index < 0) {
1,404,131!
1131
        return;
1132
    }
1133
    band_height = notifier->band_height;
1,404,134✔
1134
    if (band_height < 1) {
1,404,134!
1135
        band_height = 6;
1136
    }
1137
    if ((row_index % band_height) != band_height - 1
1,392,275✔
1138
            && row_index != ctx->height - 1) {
1,302,814!
1139
        return;
933,391✔
1140
    }
1141

1142
    band_index = row_index / band_height;
235,502✔
1143
    if (band_index >= ctx->band_count) {
235,502!
1144
        band_index = ctx->band_count - 1;
1145
    }
1146
    if (band_index < 0) {
249,398!
1147
        return;
1148
    }
1149

1150
    if (logger != NULL) {
249,398✔
1151
        sixel_timeline_logger_logf(logger,
1,218✔
1152
                          "controller",
1153
                          "encode",
1154
                          "row_gate",
1155
                          band_index);
411✔
1156
    }
411✔
1157

1158
    sixel_parallel_submit_band(ctx, band_index);
249,398✔
1159
}
815,906!
1160

1161
static SIXELSTATUS
1162
sixel_parallel_flush_band(sixel_parallel_context_t *ctx, int band_index)
286,395✔
1163
{
1164
    sixel_parallel_band_buffer_t *band;
148,631✔
1165
    size_t offset;
148,631✔
1166
    size_t chunk;
148,631✔
1167

1168
    band = &ctx->bands[band_index];
286,395✔
1169
    if (ctx->logger != NULL) {
286,395✔
1170
        sixel_timeline_logger_logf(ctx->logger,
1,218✔
1171
                          "worker",
1172
                          "encode",
1173
                          "writer_flush",
1174
                          band_index);
411✔
1175
    }
411✔
1176
    offset = 0;
264,758✔
1177
    while (offset < band->used) {
575,606!
1178
        chunk = band->used - offset;
289,211✔
1179
        if (chunk > (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos)) {
289,211✔
1180
            chunk = (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos);
3,070✔
1181
        }
2,170✔
1182
        memcpy(ctx->output->buffer + ctx->output->pos,
311,547✔
1183
               band->data + offset,
132,336✔
1184
               chunk);
11,168✔
1185
        sixel_advance(ctx->output, (int)chunk);
289,211✔
1186
        offset += chunk;
289,211✔
1187
    }
1188
    return SIXEL_OK;
286,395✔
1189
}
93,814✔
1190

1191
static int
1192
sixel_parallel_worker_main(sixel_thread_pool_job_t job,
286,378✔
1193
                           void *userdata,
1194
                           void *workspace)
1195
{
1196
    sixel_parallel_context_t *ctx;
148,618✔
1197
    sixel_parallel_worker_state_t *state;
148,618✔
1198
    sixel_parallel_band_buffer_t *band;
148,618✔
1199
    SIXELSTATUS status;
148,618✔
1200
    int band_index;
148,618✔
1201
    int band_start;
148,618✔
1202
    int band_height;
148,618✔
1203
    int row_index;
148,618✔
1204
    int absolute_row;
148,618✔
1205
    int last_row_index;
148,618✔
1206
    int nplanes;
148,618✔
1207

1208
    ctx = (sixel_parallel_context_t *)userdata;
286,378✔
1209
    state = (sixel_parallel_worker_state_t *)workspace;
286,378✔
1210

1211
    if (ctx == NULL || state == NULL) {
286,378!
1212
        return SIXEL_BAD_ARGUMENT;
21✔
1213
    }
1214

1215
    band = NULL;
286,375✔
1216
    status = SIXEL_OK;
286,375✔
1217
    band_index = job.band_index;
286,375✔
1218
    band_start = 0;
286,375✔
1219
    band_height = 0;
286,375✔
1220
    last_row_index = -1;
286,375✔
1221
    if (band_index < 0 || band_index >= ctx->band_count) {
286,375!
1222
        status = SIXEL_BAD_ARGUMENT;
64✔
1223
        goto cleanup;
64✔
1224
    }
1225

1226
    band = &ctx->bands[band_index];
286,316✔
1227
    if (ctx->mutex_ready) {
286,316!
1228
        /* Synchronize band state reset with the writer thread. */
1229
        sixel_mutex_lock(&ctx->mutex);
286,316✔
1230
        band->used = 0;
286,332✔
1231
        band->status = SIXEL_OK;
286,332✔
1232
        band->ready = 0;
286,332✔
1233
        sixel_mutex_unlock(&ctx->mutex);
286,332✔
1234
    } else {
166,352✔
1235
        band->used = 0;
1✔
1236
        band->status = SIXEL_OK;
1✔
1237
        band->ready = 0;
1✔
1238
    }
1239

1240
    sixel_fence_acquire();
286,331✔
1241

1242
    status = sixel_parallel_worker_prepare(state, ctx);
286,331✔
1243
    if (SIXEL_FAILED(status)) {
286,330!
1244
        goto cleanup;
1245
    }
1246

1247
    if (!sixel_parallel_jobs_allowed(ctx)) {
286,330!
1248
        if (ctx->mutex_ready) {
×
1249
            sixel_mutex_lock(&ctx->mutex);
1250
            if (ctx->writer_error != SIXEL_OK) {
×
1251
                status = ctx->writer_error;
1252
            } else {
1253
                status = SIXEL_RUNTIME_ERROR;
1254
            }
1255
            sixel_mutex_unlock(&ctx->mutex);
1256
        } else if (ctx->writer_error != SIXEL_OK) {
×
1257
            status = ctx->writer_error;
1258
        } else {
1259
            status = SIXEL_RUNTIME_ERROR;
1260
        }
1261
        goto cleanup;
1262
    }
1263

1264
    state->band_buffer = band;
286,327✔
1265
    sixel_parallel_worker_reset(state);
286,327✔
1266

1267
    band_start = band_index * 6;
286,303✔
1268
    band_height = ctx->height - band_start;
286,303✔
1269
    if (band_height > 6) {
286,303✔
1270
        band_height = 6;
193,658✔
1271
    }
139,083✔
1272
    if (band_height <= 0) {
240,362!
1273
        goto cleanup;
1274
    }
1275

1276
    if (ctx->logger != NULL) {
286,302✔
1277
        sixel_timeline_logger_logf(ctx->logger,
1,218✔
1278
                          "worker",
1279
                          "encode",
1280
                          "worker_start",
1281
                          band_index);
411✔
1282
    }
411✔
1283

1284
    if (sixel_parallel_context_is_ormode(ctx)) {
341,100!
1285
        nplanes = sixel_encode_body_ormode_nplanes(ctx->ncolors);
1,564✔
1286
        status = sixel_encode_body_ormode_band(ctx->pixels,
2,203✔
1287
                                               ctx->width,
638✔
1288
                                               ctx->height,
638✔
1289
                                               band_index,
638✔
1290
                                               nplanes,
638✔
1291
                                               state->output);
638✔
1292
        if (SIXEL_FAILED(status)) {
1,276!
1293
            goto cleanup;
1294
        }
1295
        status = sixel_parallel_worker_flush_output(state);
1,276✔
1296
        if (SIXEL_FAILED(status)) {
1,276!
1297
            goto cleanup;
1298
        }
1299
        goto done;
1,276✔
1300
    }
1301

1302
    for (row_index = 0; row_index < band_height; row_index++) {
1,891,080!
1303
        absolute_row = band_start + row_index;
1,606,063✔
1304
        status = sixel_band_classify_row(&state->work,
2,536,337✔
1305
                                         &state->band,
930,274✔
1306
                                         ctx->pixels,
930,274✔
1307
                                         ctx->width,
930,274✔
1308
                                         absolute_row,
930,274✔
1309
                                         ctx->ncolors,
930,274✔
1310
                                         ctx->keycolor,
930,274✔
1311
                                         ctx->palstate,
930,274✔
1312
                                         ctx->encode_policy);
930,274✔
1313
        if (SIXEL_FAILED(status)) {
1,606,067!
1314
            goto cleanup;
1315
        }
1316
    }
930,258✔
1317

1318
    status = sixel_band_compose(&state->work,
450,744✔
1319
                                &state->band,
165,712✔
1320
                                state->output,
165,712✔
1321
                                ctx->width,
165,712✔
1322
                                ctx->ncolors,
165,712✔
1323
                                ctx->keycolor,
165,712✔
1324
                                ctx->allocator);
165,712✔
1325
    if (SIXEL_FAILED(status)) {
285,053!
1326
        goto cleanup;
1327
    }
1328

1329
    last_row_index = band_start + band_height - 1;
285,051✔
1330
    status = sixel_band_emit(&state->work,
450,761✔
1331
                             &state->band,
165,710✔
1332
                             state->output,
165,710✔
1333
                             ctx->ncolors,
165,710✔
1334
                             ctx->keycolor,
165,710✔
1335
                             last_row_index);
165,710✔
1336
    if (SIXEL_FAILED(status)) {
285,032!
1337
        goto cleanup;
1338
    }
1339

1340
    status = sixel_put_flash(state->output);
285,025✔
1341
    if (SIXEL_FAILED(status)) {
285,016!
1342
        goto cleanup;
1343
    }
1344

1345
    status = sixel_parallel_worker_flush_output(state);
285,015✔
1346
    if (SIXEL_FAILED(status)) {
285,044!
1347
        goto cleanup;
1348
    }
1349

1350
    sixel_band_finish(&state->work, &state->band);
285,056✔
1351
done:
98,341✔
1352
    status = SIXEL_OK;
231,514✔
1353

1354
cleanup:
119,975✔
1355
    sixel_parallel_worker_release_nodes(state, ctx->allocator);
286,386✔
1356
    if (band != NULL && ctx->mutex_ready && ctx->cond_ready) {
286,390!
1357
        sixel_fence_release();
286,360✔
1358
        sixel_mutex_lock(&ctx->mutex);
286,360✔
1359
        band->status = status;
286,363✔
1360
        band->ready = 1;
286,363✔
1361
        sixel_cond_broadcast(&ctx->cond_band_ready);
286,363✔
1362
        sixel_mutex_unlock(&ctx->mutex);
286,363✔
1363
    }
166,383✔
1364
    if (ctx->logger != NULL) {
302,075✔
1365
        sixel_timeline_logger_logf(ctx->logger,
1,218✔
1366
                          "worker",
1367
                          "encode",
1368
                          "worker_done",
1369
                          band_index);
411✔
1370
    }
411✔
1371
    if (SIXEL_FAILED(status)) {
286,394!
1372
        return status;
1373
    }
1374
    return SIXEL_OK;
231,577✔
1375
}
166,415✔
1376

1377
static void
1378
sixel_parallel_writer_stop(sixel_parallel_context_t *ctx, int force_abort)
93,520✔
1379
{
1380
    int should_signal;
48,546✔
1381

1382
    if (ctx == NULL || !ctx->writer_started) {
93,520!
1383
        return;
37,901✔
1384
    }
1385

1386
    should_signal = ctx->mutex_ready && ctx->cond_ready;
46,760!
1387
    if (should_signal) {
46,760!
1388
        sixel_mutex_lock(&ctx->mutex);
46,760✔
1389
        if (force_abort) {
46,760!
1390
            ctx->writer_should_stop = 1;
1391
        }
1392
        sixel_cond_broadcast(&ctx->cond_band_ready);
46,760✔
1393
        sixel_mutex_unlock(&ctx->mutex);
46,760✔
1394
    } else if (force_abort) {
27,320!
1395
        ctx->writer_should_stop = 1;
1396
    }
1397

1398
    sixel_thread_join(&ctx->writer_thread);
46,760✔
1399
    ctx->writer_started = 0;
46,760✔
1400
    ctx->writer_should_stop = 0;
46,760✔
1401
    if (ctx->logger != NULL) {
46,760✔
1402
        sixel_timeline_logger_logf(ctx->logger,
225✔
1403
                          "writer",
1404
                          "encode",
1405
                          "writer_stop",
1406
                          -1);
1407
    }
115✔
1408
}
54,640!
1409

1410
static int
1411
sixel_parallel_writer_main(void *arg)
46,760✔
1412
{
1413
    sixel_parallel_context_t *ctx;
24,273✔
1414
    sixel_parallel_band_buffer_t *band;
24,273✔
1415
    SIXELSTATUS status;
24,273✔
1416
    int band_index;
24,273✔
1417

1418
    ctx = (sixel_parallel_context_t *)arg;
46,760✔
1419
    if (ctx == NULL) {
46,760✔
1420
        return SIXEL_BAD_ARGUMENT;
1421
    }
1422

1423
    if (ctx->logger != NULL) {
46,760✔
1424
        sixel_timeline_logger_logf(ctx->logger,
225✔
1425
                                   "writer",
1426
                                   "encode",
1427
                                   "writer_start",
1428
                                   -1);
1429
    }
115✔
1430

1431
    for (;;) {
257,411✔
1432
        sixel_mutex_lock(&ctx->mutex);
333,155✔
1433
        while (!ctx->writer_should_stop &&
758,707!
1434
               ctx->next_band_to_flush < ctx->band_count) {
493,249✔
1435
    band_index = ctx->next_band_to_flush;
446,489✔
1436
    band = &ctx->bands[band_index];
446,489✔
1437
    if (band->ready) {
446,489✔
1438
        break;
231,578✔
1439
    }
1440
            sixel_cond_wait(&ctx->cond_band_ready, &ctx->mutex);
160,094✔
1441
        }
1442

1443
        if (ctx->writer_should_stop) {
333,155!
1444
            sixel_mutex_unlock(&ctx->mutex);
1445
            break;
1446
        }
1447

1448
        if (ctx->next_band_to_flush >= ctx->band_count) {
333,155✔
1449
            sixel_mutex_unlock(&ctx->mutex);
46,760✔
1450
            break;
46,760✔
1451
        }
1452

1453
        band_index = ctx->next_band_to_flush;
286,395✔
1454
        band = &ctx->bands[band_index];
286,395✔
1455
        if (!band->ready) {
286,395!
1456
            sixel_mutex_unlock(&ctx->mutex);
1457
            continue;
1458
        }
1459
        band->ready = 0;
286,395✔
1460
        ctx->next_band_to_flush += 1;
286,395✔
1461
        sixel_mutex_unlock(&ctx->mutex);
286,395✔
1462

1463
        sixel_fence_acquire();
286,395✔
1464
        status = band->status;
286,395✔
1465
        if (ctx->logger != NULL) {
286,395✔
1466
            sixel_timeline_logger_logf(ctx->logger,
1,218✔
1467
                              "writer",
1468
                              "encode",
1469
                              "writer_dequeue",
1470
                              band_index);
411✔
1471
        }
411✔
1472
        if (SIXEL_SUCCEEDED(status)) {
286,395!
1473
            status = sixel_parallel_flush_band(ctx, band_index);
286,395✔
1474
        }
166,415✔
1475
        if (SIXEL_FAILED(status)) {
286,395!
1476
            sixel_parallel_context_abort_locked(ctx, status);
1477
            break;
1478
        }
1479
    }
1480

1481
    return SIXEL_OK;
37,901✔
1482
}
27,320✔
1483

1484
static SIXELSTATUS
1485
sixel_encode_body_parallel(sixel_index_t *pixels,
5,928✔
1486
                           int width,
1487
                           int height,
1488
                           int ncolors,
1489
                           int keycolor,
1490
                           sixel_output_t *output,
1491
                           unsigned char *palstate,
1492
                           sixel_allocator_t *allocator,
1493
                           int requested_threads,
1494
                           int pin_threads)
1495
{
1496
    sixel_parallel_context_t ctx = {0};
5,928✔
1497
    SIXELSTATUS status;
3,180✔
1498
    int nbands;
3,180✔
1499
    int threads;
3,180✔
1500
    int i;
3,180✔
1501
    int queue_depth;
3,180✔
1502
    sixel_timeline_logger_t *logger;
3,180✔
1503

1504
    sixel_parallel_context_init(&ctx);
5,928✔
1505
    sixel_timeline_logger_prepare_default(allocator, &logger);
5,928✔
1506
    nbands = (height + 5) / 6;
5,928✔
1507
    if (nbands <= 0) {
5,928!
1508
        sixel_timeline_logger_unref(logger);
1509
        return SIXEL_OK;
1510
    }
1511

1512
    threads = requested_threads;
5,928✔
1513
    if (threads > nbands) {
5,928✔
1514
        threads = nbands;
2,268✔
1515
    }
1,728✔
1516
    if (threads < 1) {
5,832!
1517
        threads = 1;
1518
    }
1519
    ctx.thread_count = threads;
5,867✔
1520
    queue_depth = threads * 3;
5,867✔
1521
    if (queue_depth > nbands) {
5,867✔
1522
        queue_depth = nbands;
4,161✔
1523
    }
3,147✔
1524
    if (queue_depth < 1) {
6,084!
1525
        queue_depth = 1;
1526
    }
1527

1528
    status = sixel_parallel_context_begin(&ctx,
5,928✔
1529
                                          pixels,
3,726✔
1530
                                          width,
3,726✔
1531
                                          height,
3,726✔
1532
                                          ncolors,
3,726✔
1533
                                          keycolor,
3,726✔
1534
                                          palstate,
3,726✔
1535
                                          output,
3,726✔
1536
                                          allocator,
3,726✔
1537
                                          threads,
3,726✔
1538
                                          threads,
3,726✔
1539
                                          queue_depth,
3,726✔
1540
                                          pin_threads,
3,726✔
1541
                                          logger);
3,726✔
1542
    if (SIXEL_FAILED(status)) {
5,928!
1543
        sixel_parallel_context_cleanup(&ctx);
1544
        sixel_timeline_logger_unref(logger);
1545
        return status;
1546
    }
1547

1548
    for (i = 0; i < nbands; i++) {
42,924!
1549
        sixel_parallel_submit_band(&ctx, i);
36,996✔
1550
    }
21,216✔
1551

1552
    status = sixel_parallel_context_wait(&ctx, 0);
5,928✔
1553
    if (SIXEL_FAILED(status)) {
5,928!
1554
        sixel_parallel_context_cleanup(&ctx);
1555
        sixel_timeline_logger_unref(logger);
1556
        return status;
1557
    }
1558

1559
    sixel_parallel_context_cleanup(&ctx);
5,928✔
1560
    sixel_timeline_logger_unref(logger);
5,928✔
1561
    return SIXEL_OK;
5,928✔
1562
}
3,726✔
1563
#endif
1564

1565
#if SIXEL_ENABLE_THREADS
1566
/*
1567
 * Execute PaletteApply, band encoding, and output emission as a pipeline.
1568
 * The producer owns the dithered index buffer and enqueues bands once every
1569
 * six rows have been produced.  Worker threads encode in parallel while the
1570
 * writer emits completed bands in-order to preserve deterministic output.
1571
 */
1572
static SIXELSTATUS
1573
sixel_encode_body_pipeline(unsigned char *pixels,
40,700✔
1574
                           int width,
1575
                           int height,
1576
                           unsigned char const *palette,
1577
                           float const *palette_float,
1578
                           sixel_dither_t *dither,
1579
                           sixel_output_t *output,
1580
                           int encode_threads)
1581
{
1582
    SIXELSTATUS status;
21,027✔
1583
    SIXELSTATUS wait_status;
21,027✔
1584
    sixel_parallel_context_t ctx = {0};
40,700✔
1585
    sixel_index_t *indexes;
21,027✔
1586
    sixel_index_t *result;
21,027✔
1587
    sixel_allocator_t *allocator;
21,027✔
1588
    size_t pixel_count;
21,027✔
1589
    size_t buffer_size;
21,027✔
1590
    int threads;
21,027✔
1591
    int nbands;
21,027✔
1592
    int queue_depth;
21,027✔
1593
    int waited;
21,027✔
1594
    int dither_threads_budget;
21,027✔
1595
    int worker_capacity;
21,027✔
1596
    int boost_target;
21,027✔
1597
    sixel_timeline_logger_t *logger;
21,027✔
1598
    int owns_logger;
21,027✔
1599
    sixel_parallel_row_notifier_t notifier;
21,027✔
1600

1601
    if (pixels == NULL
60,384!
1602
            || (palette == NULL && palette_float == NULL)
40,700!
1603
            || dither == NULL
40,700!
1604
            || output == NULL) {
40,700!
1605
        return SIXEL_BAD_ARGUMENT;
1606
    }
1607

1608
    threads = encode_threads;
40,700✔
1609
    nbands = (height + 5) / 6;
40,700✔
1610
    if (threads <= 1 || nbands <= 1) {
40,700!
1611
        return SIXEL_RUNTIME_ERROR;
1612
    }
1613

1614
    pixel_count = (size_t)width * (size_t)height;
40,700✔
1615
    if (height != 0 && pixel_count / (size_t)height != (size_t)width) {
40,700!
1616
        return SIXEL_BAD_INTEGER_OVERFLOW;
1617
    }
1618
    buffer_size = pixel_count * sizeof(*indexes);
40,700✔
1619
    allocator = dither->allocator;
40,700✔
1620
    indexes = (sixel_index_t *)sixel_allocator_malloc(allocator, buffer_size);
40,700✔
1621
    if (indexes == NULL) {
40,700!
1622
        return SIXEL_BAD_ALLOCATION;
1623
    }
1624

1625
    sixel_parallel_context_init(&ctx);
40,700✔
1626
    logger = dither->pipeline_logger;
40,700✔
1627
    owns_logger = 0;
40,700✔
1628
    if (logger == NULL) {
40,700!
1629
        logger = NULL;
40,700✔
1630
        sixel_timeline_logger_prepare_default(allocator, &logger);
40,700✔
1631
        owns_logger = logger != NULL ? 1 : 0;
40,700✔
1632
    }
23,528✔
1633
    notifier.context = &ctx;
40,700✔
1634
    notifier.logger = logger;
40,700✔
1635
    notifier.band_height = 6;
40,700✔
1636
    notifier.image_height = height;
40,700✔
1637
    waited = 0;
40,700✔
1638
    status = SIXEL_OK;
40,700✔
1639

1640
    status = sixel_encode_emit_palette(dither->bodyonly,
64,228✔
1641
                                       dither->ncolors,
23,528✔
1642
                                       dither->keycolor,
23,528✔
1643
                                       palette,
23,528✔
1644
                                       palette_float,
23,528✔
1645
                                       output);
23,528✔
1646
    if (SIXEL_FAILED(status)) {
40,700!
1647
        goto cleanup;
1648
    }
1649

1650
    queue_depth = threads * 3;
40,700✔
1651
    if (queue_depth > nbands) {
40,700✔
1652
        queue_depth = nbands;
21,228✔
1653
    }
14,820✔
1654
    if (queue_depth < 1) {
41,222!
1655
        queue_depth = 1;
1656
    }
1657

1658
    dither_threads_budget = dither->pipeline_dither_threads;
38,434✔
1659
    worker_capacity = threads + dither_threads_budget;
38,434✔
1660
    if (worker_capacity < threads) {
38,434!
1661
        worker_capacity = threads;
1662
    }
1663
    if (worker_capacity > nbands) {
39,062✔
1664
        worker_capacity = nbands;
15,372✔
1665
    }
8,994✔
1666

1667
    dither->pipeline_index_buffer = indexes;
39,679✔
1668
    dither->pipeline_index_size = buffer_size;
39,679✔
1669
    dither->pipeline_row_callback = sixel_parallel_palette_row_ready;
39,679✔
1670
    dither->pipeline_row_priv = &notifier;
39,679✔
1671
    dither->pipeline_logger = logger;
39,679✔
1672
    dither->pipeline_image_width = width;
39,679✔
1673
    dither->pipeline_image_height = height;
39,679✔
1674

1675
    if (logger != NULL) {
39,679✔
1676
        /*
1677
         * Record the thread split and band geometry before spawning workers.
1678
         * This clarifies why only a subset of hardware threads might appear
1679
         * in the log when the encoder side is clamped to keep the pipeline
1680
         * draining.
1681
         */
1682
        sixel_timeline_logger_logf(logger,
225✔
1683
                          "controller",
1684
                          "pipeline",
1685
                          "configure",
1686
                          -1);
1687
    }
115✔
1688

1689
    status = sixel_parallel_context_begin(&ctx,
40,700✔
1690
                                          indexes,
23,528✔
1691
                                          width,
23,528✔
1692
                                          height,
23,528✔
1693
                                          dither->ncolors,
23,528✔
1694
                                          dither->keycolor,
23,528✔
1695
                                          NULL,
1696
                                          output,
23,528✔
1697
                                          allocator,
23,528✔
1698
                                          threads,
23,528✔
1699
                                          worker_capacity,
23,528✔
1700
                                          queue_depth,
23,528✔
1701
                                          dither->pipeline_pin_threads,
23,528✔
1702
                                          logger);
23,528✔
1703
    if (SIXEL_FAILED(status)) {
40,700!
1704
        goto cleanup;
1705
    }
1706

1707
    result = sixel_dither_apply_palette(dither, pixels, width, height);
40,700✔
1708
    if (result == NULL) {
40,700!
1709
        status = SIXEL_RUNTIME_ERROR;
1710
        goto cleanup;
1711
    }
1712
    if (result != indexes) {
40,700!
1713
        status = SIXEL_RUNTIME_ERROR;
1714
        goto cleanup;
1715
    }
1716

1717
    /*
1718
     * All dithering work has finished at this point.  Reclaim the idle dither
1719
     * workers for encoding so the tail of the pipeline drains with additional
1720
     * parallelism instead of leaving those CPU resources unused.
1721
     */
1722
    boost_target = threads + dither_threads_budget;
40,700✔
1723
    status = sixel_parallel_context_grow(&ctx, boost_target);
40,700✔
1724
    if (SIXEL_FAILED(status)) {
40,700!
1725
        goto cleanup;
1726
    }
1727

1728
    status = sixel_parallel_context_wait(&ctx, 0);
40,700✔
1729
    waited = 1;
40,700✔
1730
    if (SIXEL_FAILED(status)) {
40,700!
1731
        goto cleanup;
1732
    }
1733

1734
cleanup:
17,172✔
1735
    dither->pipeline_row_callback = NULL;
40,700✔
1736
    dither->pipeline_row_priv = NULL;
40,700✔
1737
    dither->pipeline_index_buffer = NULL;
40,700✔
1738
    dither->pipeline_index_size = 0;
40,700✔
1739
    dither->pipeline_image_width = 0;
40,700✔
1740
    dither->pipeline_image_height = 0;
40,700✔
1741
    dither->pipeline_transparent_mask = NULL;
40,700✔
1742
    dither->pipeline_transparent_mask_size = 0;
40,700✔
1743
    dither->pipeline_transparent_keycolor = (-1);
40,700✔
1744
    if (!waited && ctx.pool != NULL) {
40,700!
1745
        wait_status = sixel_parallel_context_wait(&ctx, status != SIXEL_OK);
1746
        if (status == SIXEL_OK) {
×
1747
            status = wait_status;
7,827✔
1748
        }
1749
    }
1750
    sixel_parallel_context_cleanup(&ctx);
38,445✔
1751
    if (owns_logger) {
38,445✔
1752
        sixel_timeline_logger_unref(logger);
225✔
1753
    }
115✔
1754
    if (indexes != NULL) {
40,700!
1755
        sixel_allocator_free(allocator, indexes);
40,700✔
1756
    }
23,528✔
1757
    return status;
35,966✔
1758
}
23,528✔
1759
#else
1760
static SIXELSTATUS
1761
sixel_encode_body_pipeline(unsigned char *pixels,
1762
                           int width,
1763
                           int height,
1764
                           unsigned char const *palette,
1765
                           float const *palette_float,
1766
                           sixel_dither_t *dither,
1767
                           sixel_output_t *output,
1768
                           int encode_threads)
1769
{
1770
    (void)pixels;
1771
    (void)width;
1772
    (void)height;
1773
    (void)palette;
1774
    (void)palette_float;
1775
    (void)dither;
1776
    (void)output;
1777
    (void)encode_threads;
1778
    return SIXEL_RUNTIME_ERROR;
1779
}
1780
#endif
1781

1782
#if SIXEL_ENABLE_THREADS
1783
/*
1784
 * OR mode shares the producer/writer pipeline with the normal encoder, but its
1785
 * worker body emits bit-planes from the finished palette-index rows directly.
1786
 * The palette is emitted before workers start so every band can stay
1787
 * independent and the writer only has to concatenate ordered body fragments.
1788
 */
1789
SIXEL_INTERNAL_API SIXELSTATUS
1790
sixel_encode_body_ormode_pipeline(unsigned char *pixels,
132✔
1791
                                  int width,
1792
                                  int height,
1793
                                  unsigned char const *palette,
1794
                                  sixel_dither_t *dither,
1795
                                  sixel_output_t *output,
1796
                                  int encode_threads)
1797
{
1798
    SIXELSTATUS status;
66✔
1799
    SIXELSTATUS wait_status;
66✔
1800
    sixel_parallel_context_t ctx = {0};
132✔
1801
    sixel_index_t *indexes;
66✔
1802
    sixel_index_t *result;
66✔
1803
    sixel_allocator_t *allocator;
66✔
1804
    size_t pixel_count;
66✔
1805
    size_t buffer_size;
66✔
1806
    int threads;
66✔
1807
    int nbands;
66✔
1808
    int queue_depth;
66✔
1809
    int waited;
66✔
1810
    int dither_threads_budget;
66✔
1811
    int worker_capacity;
66✔
1812
    int boost_target;
66✔
1813
    sixel_timeline_logger_t *logger;
66✔
1814
    int owns_logger;
66✔
1815
    sixel_parallel_row_notifier_t notifier;
66✔
1816

1817
    if (pixels == NULL || palette == NULL || dither == NULL ||
132!
1818
            output == NULL) {
96!
1819
        return SIXEL_BAD_ARGUMENT;
1820
    }
1821

1822
    threads = encode_threads;
132✔
1823
    nbands = (height + 5) / 6;
132✔
1824
    if (threads <= 1 || nbands <= 1) {
132!
1825
        return SIXEL_RUNTIME_ERROR;
1826
    }
1827

1828
    pixel_count = (size_t)width * (size_t)height;
132✔
1829
    if (height != 0 && pixel_count / (size_t)height != (size_t)width) {
132!
1830
        return SIXEL_BAD_INTEGER_OVERFLOW;
1831
    }
1832
    buffer_size = pixel_count * sizeof(*indexes);
132✔
1833
    allocator = dither->allocator;
132✔
1834
    indexes = (sixel_index_t *)sixel_allocator_malloc(allocator, buffer_size);
132✔
1835
    if (indexes == NULL) {
132!
1836
        return SIXEL_BAD_ALLOCATION;
1837
    }
1838

1839
    sixel_parallel_context_init(&ctx);
132✔
1840
    logger = dither->pipeline_logger;
132✔
1841
    owns_logger = 0;
132✔
1842
    if (logger == NULL) {
132!
1843
        logger = NULL;
132✔
1844
        sixel_timeline_logger_prepare_default(allocator, &logger);
132✔
1845
        owns_logger = logger != NULL ? 1 : 0;
132✔
1846
    }
66✔
1847
    notifier.context = &ctx;
132✔
1848
    notifier.logger = logger;
132✔
1849
    notifier.band_height = 6;
132✔
1850
    notifier.image_height = height;
132✔
1851
    waited = 0;
132✔
1852
    status = SIXEL_OK;
132✔
1853

1854
    status = sixel_encode_body_ormode_emit_palette(palette,
198✔
1855
                                                   dither->ncolors,
66✔
1856
                                                   dither->keycolor,
66✔
1857
                                                   output);
66✔
1858
    if (SIXEL_FAILED(status)) {
132!
1859
        goto cleanup;
1860
    }
1861

1862
    queue_depth = threads * 3;
132✔
1863
    if (queue_depth > nbands) {
132✔
1864
        queue_depth = nbands;
17✔
1865
    }
11✔
1866
    if (queue_depth < 1) {
128!
1867
        queue_depth = 1;
1868
    }
1869

1870
    dither_threads_budget = dither->pipeline_dither_threads;
126✔
1871
    worker_capacity = threads + dither_threads_budget;
126✔
1872
    if (worker_capacity < threads) {
126!
1873
        worker_capacity = threads;
1874
    }
1875
    if (worker_capacity > nbands) {
127✔
1876
        worker_capacity = nbands;
17✔
1877
    }
11✔
1878

1879
    dither->pipeline_index_buffer = indexes;
128✔
1880
    dither->pipeline_index_size = buffer_size;
128✔
1881
    dither->pipeline_row_callback = sixel_parallel_palette_row_ready;
128✔
1882
    dither->pipeline_row_priv = &notifier;
128✔
1883
    dither->pipeline_logger = logger;
128✔
1884
    dither->pipeline_image_width = width;
128✔
1885
    dither->pipeline_image_height = height;
128✔
1886

1887
    if (logger != NULL) {
128!
1888
        sixel_timeline_logger_logf(logger,
1889
                          "controller",
1890
                          "pipeline",
1891
                          "configure",
1892
                          -1);
1893
    }
1894

1895
    status = sixel_parallel_context_begin(&ctx,
132✔
1896
                                          indexes,
66✔
1897
                                          width,
66✔
1898
                                          height,
66✔
1899
                                          dither->ncolors,
66✔
1900
                                          dither->keycolor,
66✔
1901
                                          NULL,
1902
                                          output,
66✔
1903
                                          allocator,
66✔
1904
                                          threads,
66✔
1905
                                          worker_capacity,
66✔
1906
                                          queue_depth,
66✔
1907
                                          dither->pipeline_pin_threads,
66✔
1908
                                          logger);
66✔
1909
    if (SIXEL_FAILED(status)) {
132!
1910
        goto cleanup;
1911
    }
1912

1913
    result = sixel_dither_apply_palette(dither, pixels, width, height);
132✔
1914
    if (result == NULL) {
132!
1915
        status = SIXEL_RUNTIME_ERROR;
1916
        goto cleanup;
1917
    }
1918
    if (result != indexes) {
132!
1919
        status = SIXEL_RUNTIME_ERROR;
1920
        goto cleanup;
1921
    }
1922

1923
    /*
1924
     * PaletteApply is complete, so the encode queue can borrow the dither-side
1925
     * thread budget and finish any remaining bands with a wider worker set.
1926
     */
1927
    boost_target = threads + dither_threads_budget;
132✔
1928
    status = sixel_parallel_context_grow(&ctx, boost_target);
132✔
1929
    if (SIXEL_FAILED(status)) {
132!
1930
        goto cleanup;
1931
    }
1932

1933
    status = sixel_parallel_context_wait(&ctx, 0);
132✔
1934
    waited = 1;
132✔
1935
    if (SIXEL_FAILED(status)) {
132!
1936
        goto cleanup;
1937
    }
1938

1939
cleanup:
66✔
1940
    dither->pipeline_row_callback = NULL;
132✔
1941
    dither->pipeline_row_priv = NULL;
132✔
1942
    dither->pipeline_index_buffer = NULL;
132✔
1943
    dither->pipeline_index_size = 0;
132✔
1944
    dither->pipeline_image_width = 0;
132✔
1945
    dither->pipeline_image_height = 0;
132✔
1946
    dither->pipeline_transparent_mask = NULL;
132✔
1947
    dither->pipeline_transparent_mask_size = 0;
132✔
1948
    dither->pipeline_transparent_keycolor = (-1);
132✔
1949
    if (!waited && ctx.pool != NULL) {
132!
1950
        wait_status = sixel_parallel_context_wait(&ctx, status != SIXEL_OK);
1951
        if (status == SIXEL_OK) {
×
1952
            status = wait_status;
30✔
1953
        }
1954
    }
1955
    sixel_parallel_context_cleanup(&ctx);
126✔
1956
    if (owns_logger) {
126!
1957
        sixel_timeline_logger_unref(logger);
1958
    }
1959
    if (indexes != NULL) {
132!
1960
        sixel_allocator_free(allocator, indexes);
132✔
1961
    }
66✔
1962
    return status;
114✔
1963
}
66✔
1964
#else
1965
SIXEL_INTERNAL_API SIXELSTATUS
1966
sixel_encode_body_ormode_pipeline(unsigned char *pixels,
1967
                                  int width,
1968
                                  int height,
1969
                                  unsigned char const *palette,
1970
                                  sixel_dither_t *dither,
1971
                                  sixel_output_t *output,
1972
                                  int encode_threads)
1973
{
1974
    (void)pixels;
1975
    (void)width;
1976
    (void)height;
1977
    (void)palette;
1978
    (void)dither;
1979
    (void)output;
1980
    (void)encode_threads;
1981
    return SIXEL_RUNTIME_ERROR;
1982
}
1983
#endif
1984

1985
/* implementation */
1986

1987
static void
1988
sixel_advance(sixel_output_t *output, int nwrite)
183,283,700✔
1989
{
1990
    if ((output->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) {
183,283,700✔
1991
        (void)sixel_output_write_bytes(output,
7,316✔
1992
                                       (char *)output->buffer,
4,948✔
1993
                                       SIXEL_OUTPUT_PACKET_SIZE);
1994
        memcpy(output->buffer,
5,284✔
1995
               output->buffer + SIXEL_OUTPUT_PACKET_SIZE,
1,542✔
1996
               (size_t)(output->pos -= SIXEL_OUTPUT_PACKET_SIZE));
2,748✔
1997
    }
2,368✔
1998
}
183,287,383✔
1999

2000

2001
static void
2002
sixel_putc(unsigned char *buffer, unsigned char value)
52,161,717✔
2003
{
2004
    *buffer = value;
52,161,717✔
2005
}
40,303,438✔
2006

2007

2008
static void
2009
sixel_puts(unsigned char *buffer, char const *value, int size)
5,042,748✔
2010
{
2011
    memcpy(buffer, (void *)value, (size_t)size);
5,042,748✔
2012
}
4,195,193✔
2013

2014
/*
2015
 * Append a literal byte several times while respecting the output packet
2016
 * boundary.  The helper keeps `sixel_advance` responsible for flushing and
2017
 * preserves the repeating logic used by DECGRI sequences.
2018
 */
2019
static void
2020
sixel_output_emit_literal(sixel_output_t *output,
76,270,491✔
2021
                          unsigned char value,
2022
                          int count)
2023
{
2024
    int chunk;
39,435,600✔
2025

2026
    if (count <= 0) {
76,270,491!
2027
        return;
2028
    }
2029

2030
    while (count > 0) {
152,582,793!
2031
        chunk = SIXEL_OUTPUT_PACKET_SIZE - output->pos;
76,294,064✔
2032
        if (chunk > count) {
76,294,064✔
2033
            chunk = count;
58,606,642✔
2034
        }
38,504,336✔
2035
        memset(output->buffer + output->pos, value, (size_t)chunk);
76,298,622✔
2036
        sixel_advance(output, chunk);
76,298,622✔
2037
        count -= chunk;
76,312,325✔
2038
    }
2039
}
38,484,973!
2040

2041
/*
2042
 * Compose helpers accelerate palette sweeps by skipping zero columns in
2043
 * word-sized chunks.
2044
 */
2045

2046
static int
2047
sixel_compose_find_run_start(unsigned char const *row,
30,525,666✔
2048
                             int width,
2049
                             int start)
2050
{
2051
    int idx;
15,944,028✔
2052
    size_t chunk_size;
15,944,028✔
2053
    unsigned char const *cursor;
15,944,028✔
2054
    unsigned long block;
15,944,028✔
2055

2056
    idx = start;
30,525,666✔
2057
    chunk_size = sizeof(unsigned long);
30,525,666✔
2058
    cursor = row + start;
30,525,666✔
2059

2060
    while ((width - idx) >= (int)chunk_size) {
264,245,681!
2061
        memcpy(&block, cursor, chunk_size);
252,154,659✔
2062
        if (block != 0UL) {
252,154,659✔
2063
            break;
14,320,972✔
2064
        }
2065
        idx += (int)chunk_size;
233,720,015✔
2066
        cursor += chunk_size;
233,720,015✔
2067
    }
2068

2069
    while (idx < width) {
116,014,410!
2070
        if (*cursor != 0) {
104,285,058✔
2071
            break;
14,611,870✔
2072
        }
2073
        idx += 1;
85,488,408✔
2074
        cursor += 1;
85,488,408✔
2075
    }
2076

2077
    return idx;
39,672,898✔
2078
}
9,149,144✔
2079

2080

2081
static int
2082
sixel_compose_measure_gap(unsigned char const *row,
32,920,629✔
2083
                          int width,
2084
                          int start,
2085
                          int *reached_end)
2086
{
2087
    int gap;
17,171,175✔
2088
    size_t chunk_size;
17,171,175✔
2089
    unsigned char const *cursor;
17,171,175✔
2090
    unsigned long block;
17,171,175✔
2091
    int remaining;
17,171,175✔
2092

2093
    gap = 0;
32,920,629✔
2094
    *reached_end = 0;
32,920,629✔
2095
    if (start >= width) {
32,920,629✔
2096
        *reached_end = 1;
404,044✔
2097
        return gap;
404,044✔
2098
    }
2099

2100
    chunk_size = sizeof(unsigned long);
32,516,547✔
2101
    cursor = row + start;
32,516,547✔
2102
    remaining = width - start;
32,516,547✔
2103

2104
    while (remaining >= (int)chunk_size) {
160,608,074!
2105
        memcpy(&block, cursor, chunk_size);
148,290,145✔
2106
        if (block != 0UL) {
148,290,145✔
2107
            break;
15,679,030✔
2108
        }
2109
        gap += (int)chunk_size;
128,091,527✔
2110
        cursor += chunk_size;
128,091,527✔
2111
        remaining -= (int)chunk_size;
128,091,527✔
2112
    }
2113

2114
    while (remaining > 0) {
104,095,623!
2115
        if (*cursor != 0) {
92,743,203✔
2116
            return gap;
16,462,072✔
2117
        }
2118
        gap += 1;
71,575,868✔
2119
        cursor += 1;
71,575,868✔
2120
        remaining -= 1;
71,575,868✔
2121
    }
2122

2123
    *reached_end = 1;
11,350,607✔
2124
    return gap;
11,350,607✔
2125
}
17,300,116✔
2126

2127

2128
#if HAVE_LDIV
2129
static int
2130
sixel_putnum_impl(char *buffer, long value, int pos)
95,219,817✔
2131
{
2132
    ldiv_t r;
49,528,281✔
2133

2134
    r = ldiv(value, 10);
95,219,817✔
2135
    if (r.quot > 0) {
95,222,172✔
2136
        pos = sixel_putnum_impl(buffer, r.quot, pos);
47,350,850✔
2137
    }
24,706,341✔
2138
    /*
2139
     * r.rem is guaranteed to be in [0, 9] because the divisor is 10, so the
2140
     * explicit cast documents the safe narrowing from long to char.
2141
     */
2142
    *(buffer + pos) = (char)('0' + r.rem);
95,225,729✔
2143
    return pos + 1;
123,208,241✔
2144
}
27,982,512✔
2145
#endif  /* HAVE_LDIV */
2146

2147

2148
static int
2149
sixel_putnum(char *buffer, int value)
47,949,840✔
2150
{
2151
    int pos;
24,899,960✔
2152

2153
#if HAVE_LDIV
2154
    pos = sixel_putnum_impl(buffer, value, 0);
50,971,641✔
2155
#else
2156
    pos = sprintf(buffer, "%d", value);
2157
#endif  /* HAVE_LDIV */
2158

2159
    return pos;
61,926,606✔
2160
}
13,975,728✔
2161

2162

2163
static SIXELSTATUS
2164
sixel_put_flash(sixel_output_t *const output)
85,844,038✔
2165
{
2166
    int nwrite;
44,421,926✔
2167

2168
    if (output->save_count <= 0) {
85,844,038✔
2169
        return SIXEL_OK;
230,515✔
2170
    }
2171

2172
    if (output->has_gri_arg_limit) {  /* VT240 Max 255 ? */
85,559,739✔
2173
            while (output->save_count > 255) {
71,623!
2174
                /* argument of DECGRI('!') is limited to 255 in real VT */
2175
                sixel_puts(output->buffer + output->pos, "!255", 4);
×
2176
                sixel_advance(output, 4);
×
2177
                sixel_putc(output->buffer + output->pos,
×
2178
                           (unsigned char)output->save_pixel);
×
2179
                sixel_advance(output, 1);
×
2180
                output->save_count -= 255;
42,419✔
2181
            }
2182
        }
12,430✔
2183

2184
    if (output->save_count > 3) {
85,619,382✔
2185
        /* DECGRI Graphics Repeat Introducer ! Pn Ch */
2186
        sixel_putc(output->buffer + output->pos, '!');
9,292,533✔
2187
        sixel_advance(output, 1);
9,292,400✔
2188
        nwrite = sixel_putnum((char *)output->buffer + output->pos, output->save_count);
9,292,731✔
2189
        sixel_advance(output, nwrite);
9,292,973✔
2190
        sixel_putc(output->buffer + output->pos,
14,093,997✔
2191
                   (unsigned char)output->save_pixel);
9,292,516✔
2192
        sixel_advance(output, 1);
9,292,438✔
2193
    } else {
4,801,481✔
2194
        sixel_output_emit_literal(output,
114,861,006✔
2195
                                  (unsigned char)output->save_pixel,
76,326,849✔
2196
                                  output->save_count);
38,534,157✔
2197
    }
2198

2199
    output->save_pixel = 0;
85,596,829✔
2200
    output->save_count = 0;
85,596,829✔
2201

2202
    return SIXEL_OK;
85,596,829✔
2203
}
43,487,068✔
2204

2205

2206
/*
2207
 * Emit a run of identical SIXEL cells while keeping the existing repeat
2208
 * accumulator intact.  The helper extends the current run when possible and
2209
 * falls back to flushing through DECGRI before starting a new symbol.
2210
 */
2211
static SIXELSTATUS
2212
sixel_emit_run(sixel_output_t *output, int symbol, int count)
85,503,195✔
2213
{
2214
    SIXELSTATUS status = SIXEL_FALSE;
85,503,195✔
2215

2216
    if (count <= 0) {
85,503,195✔
2217
        return SIXEL_OK;
19✔
2218
    }
2219

2220
    if (output->save_count > 0) {
85,503,304✔
2221
        if (output->save_pixel == symbol) {
66,961,226✔
2222
            output->save_count += count;
74,135✔
2223
            return SIXEL_OK;
74,135✔
2224
        }
2225

2226
        status = sixel_put_flash(output);
66,887,316✔
2227
        if (SIXEL_FAILED(status)) {
66,870,596!
2228
            return status;
2229
        }
2230
    }
33,511,490✔
2231

2232
    output->save_pixel = symbol;
85,408,900✔
2233
    output->save_count = count;
85,408,900✔
2234

2235
    return SIXEL_OK;
85,408,900✔
2236
}
43,198,565✔
2237

2238

2239
/*
2240
 * Walk a composed node and coalesce identical columns into runs so the
2241
 * encoder core touches the repeat accumulator only once per symbol.
2242
 */
2243
static SIXELSTATUS
2244
sixel_emit_span_from_map(sixel_output_t *output,
18,879,764✔
2245
                         unsigned char const *map,
2246
                         int length)
2247
{
2248
    SIXELSTATUS status = SIXEL_FALSE;
18,879,764✔
2249
    int index;
9,855,782✔
2250
    int run_length;
9,855,782✔
2251
    unsigned char value;
9,855,782✔
2252
    size_t chunk_size;
9,855,782✔
2253
    unsigned long pattern;
9,855,782✔
2254
    unsigned long block;
9,855,782✔
2255
    int chunk_mismatch;
9,855,782✔
2256
    int remain;
9,855,782✔
2257
    int byte_index;
9,855,782✔
2258

2259
    if (length <= 0) {
18,879,764!
2260
        return SIXEL_OK;
2261
    }
2262

2263
    for (index = 0; index < length; index += run_length) {
97,055,111!
2264
        value = map[index];
78,306,219✔
2265
        if (value > '?') {
78,306,219!
2266
            value = 0;
×
2267
        }
2268

2269
        run_length = 1;
78,304,181✔
2270
        chunk_size = sizeof(unsigned long);
78,304,181✔
2271
        chunk_mismatch = 0;
78,304,181✔
2272
        if (chunk_size > 1) {
78,304,181!
2273
            remain = length - (index + run_length);
78,239,516✔
2274
            pattern = (~0UL / 0xffUL) * (unsigned long)value;
78,239,516✔
2275

2276
            while (remain >= (int)chunk_size) {
81,582,226!
2277
                memcpy(&block,
43,306,601✔
2278
                       map + index + run_length,
25,939,220✔
2279
                       chunk_size);
1,741,559✔
2280
                block ^= pattern;
39,823,483✔
2281
                if (block != 0UL) {
39,823,483✔
2282
                    for (byte_index = 0;
36,610,765!
2283
                         byte_index < (int)chunk_size;
54,733,704!
2284
                         byte_index++) {
18,252,577✔
2285
                        if ((block & 0xffUL) != 0UL) {
54,732,910✔
2286
                            chunk_mismatch = 1;
26,984,271✔
2287
                            break;
26,984,271✔
2288
                        }
2289
                        block >>= 8;
18,252,572✔
2290
                        run_length += 1;
18,252,572✔
2291
                    }
9,637,621✔
2292
                    break;
26,980,261✔
2293
                }
2294
                run_length += (int)chunk_size;
3,342,710✔
2295
                remain -= (int)chunk_size;
3,342,710✔
2296
            }
2297
        }
39,524,196✔
2298

2299
        if (!chunk_mismatch) {
79,693,135✔
2300
            while (index + run_length < length) {
55,524,470!
2301
                unsigned char next;
19,715,154✔
2302

2303
                next = map[index + run_length];
36,862,651✔
2304
                if (next > '?') {
36,862,651!
2305
                    next = 0;
×
2306
                }
2307
                if (next != value) {
36,870,763✔
2308
                    break;
19,055,048✔
2309
                }
2310
                run_length += 1;
13,338,589✔
2311
            }
12,920,460!
2312
        }
23,530,129✔
2313

2314
        status = sixel_emit_run(output,
117,648,532✔
2315
                                 (int)value + '?',
60,060,264✔
2316
                                 run_length);
39,459,054✔
2317
        if (SIXEL_FAILED(status)) {
78,175,345!
2318
            return status;
2319
        }
2320
    }
39,459,054✔
2321

2322
    return SIXEL_OK;
14,564,807✔
2323
}
9,846,356✔
2324

2325

2326
static SIXELSTATUS
2327
sixel_put_pixel(sixel_output_t *const output, int pix)
403,417✔
2328
{
2329
    if (pix < 0 || pix > '?') {
403,417!
2330
        pix = 0;
4✔
2331
    }
4✔
2332

2333
    return sixel_emit_run(output, pix + '?', 1);
403,413✔
2334
}
2335

2336
static SIXELSTATUS
2337
sixel_node_new(sixel_node_t **np, sixel_allocator_t *allocator)
16,568,104✔
2338
{
2339
    SIXELSTATUS status = SIXEL_FALSE;
16,568,104✔
2340

2341
    *np = (sixel_node_t *)sixel_allocator_malloc(allocator,
16,568,104✔
2342
                                                 sizeof(sixel_node_t));
2343
    if (np == NULL) {
16,569,315!
2344
        sixel_helper_set_additional_message(
44✔
2345
            "sixel_node_new: sixel_allocator_malloc() failed.");
2346
        status = SIXEL_BAD_ALLOCATION;
2347
        goto end;
2348
    }
2349

2350
    status = SIXEL_OK;
13,413,876✔
2351

2352
end:
6,850,436✔
2353
    return status;
22,114,152✔
2354
}
5,540,556✔
2355

2356
static void
2357
sixel_node_del(sixel_output_t *output, sixel_node_t *np)
18,789,381✔
2358
{
2359
    sixel_node_t *tp;
9,810,231✔
2360

2361
    if ((tp = output->node_top) == np) {
18,789,381✔
2362
        output->node_top = np->next;
4,408,613✔
2363
    } else {
2,235,937✔
2364
        while (tp->next != NULL) {
670,804,557!
2365
            if (tp->next == np) {
670,826,302✔
2366
                tp->next = np->next;
14,412,801✔
2367
                break;
14,412,801✔
2368
            }
2369
            tp = tp->next;
514,940,187✔
2370
        }
2371
    }
2372

2373
    np->next = output->node_free;
18,845,862✔
2374
    output->node_free = np;
18,845,862✔
2375
}
18,845,862✔
2376

2377

2378
static SIXELSTATUS
2379
sixel_put_node(
18,804,497✔
2380
    sixel_output_t /* in */     *output,  /* output context */
2381
    int            /* in/out */ *x,       /* header position */
2382
    sixel_node_t   /* in */     *np,      /* node object */
2383
    int            /* in */     ncolors,  /* number of palette colors */
2384
    int            /* in */     keycolor) /* transparent color number */
2385
{
2386
    SIXELSTATUS status = SIXEL_FALSE;
18,804,497✔
2387
    int nwrite;
9,821,767✔
2388

2389
    if (ncolors != 2 || keycolor == (-1)) {
18,804,497!
2390
        /* designate palette index */
2391
        if (output->active_palette != np->pal) {
18,779,335✔
2392
            sixel_putc(output->buffer + output->pos, '#');
18,562,528✔
2393
            sixel_advance(output, 1);
18,562,146✔
2394
            nwrite = sixel_putnum((char *)output->buffer + output->pos, np->pal);
18,562,657✔
2395
            sixel_advance(output, nwrite);
18,562,430✔
2396
            output->active_palette = np->pal;
18,562,189✔
2397
        }
9,775,292✔
2398
    }
9,868,009✔
2399

2400
    if (*x < np->sx) {
19,371,272✔
2401
        int span;
3,671,677✔
2402

2403
        span = np->sx - *x;
7,042,018✔
2404
        status = sixel_emit_run(output, '?', span);
7,042,018✔
2405
        if (SIXEL_FAILED(status)) {
7,041,782!
2406
            goto end;
2407
        }
2408
        *x = np->sx;
7,041,704✔
2409
    }
3,678,857!
2410

2411
    if (*x < np->mx) {
18,777,475!
2412
        int span;
9,791,060✔
2413

2414
        span = np->mx - *x;
18,771,538✔
2415
        status = sixel_emit_span_from_map(output,
32,809,712✔
2416
                                          (unsigned char const *)np->map + *x,
18,771,538✔
2417
                                          span);
9,848,349✔
2418
        if (SIXEL_FAILED(status)) {
18,770,663!
2419
            goto end;
2420
        }
2421
        *x = np->mx;
18,769,185✔
2422
    }
9,846,871✔
2423

2424
    status = sixel_put_flash(output);
18,777,463✔
2425
    if (SIXEL_FAILED(status)) {
18,776,445!
2426
        goto end;
2427
    }
2428

2429
end:
8,920,549✔
2430
    return status;
18,761,321✔
2431
}
5,599,726✔
2432

2433

2434
SIXELSTATUS
2435
sixel_encode_header(int width, int height, int keycolor, sixel_output_t *output)
62,941✔
2436
{
2437
    SIXELSTATUS status = SIXEL_FALSE;
62,941✔
2438
    int p[3] = {0, 0, 0};
62,941✔
2439
    int pcount = 3;
62,941✔
2440
    int use_raster_attributes = 1;
62,941✔
2441

2442
    if (output->ormode) {
62,941✔
2443
        p[0] = 7;
95✔
2444
        p[1] = 5;
95✔
2445
    } else if (keycolor >= 0) {
62,866!
2446
        /*
2447
         * When a transparent keycolor is in use, request transparent
2448
         * background mode so untouched pixels keep the terminal background.
2449
         */
2450
        p[1] = 1;
5,311✔
2451
    }
2,896✔
2452

2453
    output->pos = 0;
62,941✔
2454

2455
    if (p[2] == 0) {
62,941!
2456
        pcount--;
62,941✔
2457
        if (p[1] == 0) {
62,941✔
2458
            pcount--;
57,500✔
2459
            if (p[0] == 0) {
57,500!
2460
                pcount--;
57,500✔
2461
            }
28,547✔
2462
        }
28,547✔
2463
    }
31,498✔
2464

2465
    status = sixel_output_begin_image(output,
94,439✔
2466
                                      width,
31,498✔
2467
                                      height,
31,498✔
2468
                                      p[0],
31,498✔
2469
                                      p[1],
31,498✔
2470
                                      p[2],
31,498✔
2471
                                      pcount,
31,498✔
2472
                                      use_raster_attributes);
31,498✔
2473

2474
    return status;
80,682✔
2475
}
17,741✔
2476

2477

2478
static int
2479
sixel_palette_float_pixelformat_for_colorspace(int colorspace)
110,597✔
2480
{
2481
    switch (colorspace) {
110,597✔
2482
    case SIXEL_COLORSPACE_LINEAR:
112✔
2483
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
266✔
2484
    case SIXEL_COLORSPACE_OKLAB:
312✔
2485
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
741✔
2486
    case SIXEL_COLORSPACE_CIELAB:
88✔
2487
        return SIXEL_PIXELFORMAT_CIELABFLOAT32;
209✔
2488
    case SIXEL_COLORSPACE_DIN99D:
80✔
2489
        return SIXEL_PIXELFORMAT_DIN99DFLOAT32;
190✔
2490
    default:
32,758✔
2491
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
94,489✔
2492
    }
2493
}
62,545✔
2494

2495
static int
2496
sixel_palette_float32_matches_u8(unsigned char const *palette,
4,839✔
2497
                                 float const *palette_float,
2498
                                 size_t count,
2499
                                 int float_pixelformat)
2500
{
2501
    size_t index;
2,436✔
2502
    size_t limit;
2,436✔
2503
    unsigned char expected;
2,436✔
2504
    int channel;
2,436✔
2505

2506
    if (palette == NULL || palette_float == NULL || count == 0U) {
4,839!
2507
        return 1;
2508
    }
2509
    if (count > SIZE_MAX / 3U) {
4,839!
2510
        return 0;
2511
    }
2512
    limit = count * 3U;
4,839✔
2513
    for (index = 0U; index < limit; ++index) {
725,619!
2514
        channel = (int)(index % 3U);
720,780✔
2515
        expected = sixel_pixelformat_float_channel_to_byte(
1,066,791✔
2516
            float_pixelformat,
26,517✔
2517
            channel,
26,517✔
2518
            palette_float[index]);
720,780✔
2519
        if (palette[index] != expected) {
720,780!
2520
            return 0;
2521
        }
2522
    }
26,517✔
2523
    return 1;
3,170✔
2524
}
1,386✔
2525

2526
static void
2527
sixel_palette_sync_float32_from_u8(unsigned char const *palette,
×
2528
                                   float *palette_float,
2529
                                   size_t count,
2530
                                   int float_pixelformat)
2531
{
2532
    size_t index;
2533
    size_t limit;
2534
    int channel;
2535

2536
    if (palette == NULL || palette_float == NULL || count == 0U) {
×
2537
        return;
2538
    }
2539
    if (count > SIZE_MAX / 3U) {
×
2540
        return;
2541
    }
2542
    limit = count * 3U;
×
2543
    for (index = 0U; index < limit; ++index) {
×
2544
        channel = (int)(index % 3U);
×
2545
        palette_float[index] = sixel_pixelformat_byte_to_float(
×
2546
            float_pixelformat,
2547
            channel,
2548
            palette[index]);
×
2549
    }
2550
}
×
2551

2552
static int
2553
sixel_output_palette_channel_to_pct(unsigned char const *palette,
14,946,000✔
2554
                                    float const *palette_float,
2555
                                    int n,
2556
                                    int channel)
2557
{
2558
    size_t index;
7,732,512✔
2559
    float value;
7,732,512✔
2560
    int percent;
7,732,512✔
2561

2562
    index = (size_t)n * 3U + (size_t)channel;
14,946,000✔
2563
    if (palette_float != NULL) {
14,946,000✔
2564
        value = palette_float[index];
713,304✔
2565
        if (value < 0.0f) {
713,304!
2566
            value = 0.0f;
2567
        } else if (value > 1.0f) {
713,304!
2568
            value = 1.0f;
2569
        }
2570
        percent = (int)(value * 100.0f + 0.5f);
713,304✔
2571
        if (percent < 0) {
713,304!
2572
            percent = 0;
2573
        } else if (percent > 100) {
713,304!
2574
            percent = 100;
2575
        }
2576
        return percent;
713,304✔
2577
    }
2578

2579
    if (palette != NULL) {
14,232,696!
2580
        return (palette[index] * 100 + 127) / 255;
14,232,696✔
2581
    }
2582

2583
    return 0;
2584
}
7,550,769✔
2585

2586
static double
2587
sixel_output_palette_channel_to_float(unsigned char const *palette,
182,244✔
2588
                                      float const *palette_float,
2589
                                      int n,
2590
                                      int channel)
2591
{
2592
    size_t index;
91,422✔
2593
    double value;
91,422✔
2594

2595
    index = (size_t)n * 3U + (size_t)channel;
182,244✔
2596
    value = 0.0;
182,244✔
2597
    if (palette_float != NULL) {
182,244!
2598
        value = palette_float[index];
6,144✔
2599
    } else if (palette != NULL) {
176,100!
2600
        value = (double)palette[index] / 255.0;
176,100✔
2601
    }
78,834✔
2602
    if (value < 0.0) {
182,244!
2603
        value = 0.0;
2604
    } else if (value > 1.0) {
182,244!
2605
        value = 1.0;
2606
    }
2607

2608
    return value;
225,408✔
2609
}
43,164✔
2610

2611
static SIXELSTATUS
2612
output_rgb_palette_definition(
4,985,719✔
2613
    sixel_output_t /* in */ *output,
2614
    unsigned char const /* in */ *palette,
2615
    float const /* in */ *palette_float,
2616
    int            /* in */ n,
2617
    int            /* in */ keycolor
2618
)
2619
{
2620
    SIXELSTATUS status = SIXEL_FALSE;
4,985,719✔
2621
    int nwrite;
2,579,443✔
2622

2623
    if (n != keycolor) {
4,985,719✔
2624
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
2625
        sixel_putc(output->buffer + output->pos, '#');
4,982,000✔
2626
        sixel_advance(output, 1);
4,982,000✔
2627
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
4,982,000✔
2628
        sixel_advance(output, nwrite);
4,982,000✔
2629
        sixel_puts(output->buffer + output->pos, ";2;", 3);
4,982,000✔
2630
        sixel_advance(output, 3);
4,982,000✔
2631
        nwrite = sixel_putnum(
6,138,561✔
2632
            (char *)output->buffer + output->pos,
4,982,000✔
2633
            sixel_output_palette_channel_to_pct(palette,
5,033,846✔
2634
                                                palette_float,
2,516,923✔
2635
                                                n,
2,516,923✔
2636
                                                0));
2637
        sixel_advance(output, nwrite);
4,982,000✔
2638
        sixel_putc(output->buffer + output->pos, ';');
4,982,000✔
2639
        sixel_advance(output, 1);
4,982,000✔
2640
        nwrite = sixel_putnum(
6,138,561✔
2641
            (char *)output->buffer + output->pos,
4,982,000✔
2642
            sixel_output_palette_channel_to_pct(palette,
5,033,846✔
2643
                                                palette_float,
2,516,923✔
2644
                                                n,
2,516,923✔
2645
                                                1));
2646
        sixel_advance(output, nwrite);
4,982,000✔
2647
        sixel_putc(output->buffer + output->pos, ';');
4,982,000✔
2648
        sixel_advance(output, 1);
4,982,000✔
2649
        nwrite = sixel_putnum(
6,138,561✔
2650
            (char *)output->buffer + output->pos,
4,982,000✔
2651
            sixel_output_palette_channel_to_pct(palette,
5,033,846✔
2652
                                                palette_float,
2,516,923✔
2653
                                                n,
2,516,923✔
2654
                                                2));
2655
        sixel_advance(output, nwrite);
4,982,000✔
2656
    }
2,516,923✔
2657

2658
    status = SIXEL_OK;
4,985,719✔
2659

2660
    return status;
6,407,761✔
2661
}
1,422,042✔
2662

2663

2664
static SIXELSTATUS
2665
output_hls_palette_definition(
60,748✔
2666
    sixel_output_t /* in */ *output,
2667
    unsigned char const /* in */ *palette,
2668
    float const /* in */ *palette_float,
2669
    int            /* in */ n,
2670
    int            /* in */ keycolor
2671
)
2672
{
2673
    SIXELSTATUS status = SIXEL_FALSE;
60,748✔
2674
    double r;
30,474✔
2675
    double g;
30,474✔
2676
    double b;
30,474✔
2677
    double maxc;
30,474✔
2678
    double minc;
30,474✔
2679
    double lightness;
30,474✔
2680
    double saturation;
30,474✔
2681
    double hue;
30,474✔
2682
    double diff;
30,474✔
2683
    int h;
30,474✔
2684
    int l;
30,474✔
2685
    int s;
30,474✔
2686
    int nwrite;
30,474✔
2687

2688
    if (n != keycolor) {
60,748!
2689
        r = sixel_output_palette_channel_to_float(palette,
87,026✔
2690
                                                  palette_float,
26,278✔
2691
                                                  n,
26,278✔
2692
                                                  0);
2693
        g = sixel_output_palette_channel_to_float(palette,
87,026✔
2694
                                                  palette_float,
26,278✔
2695
                                                  n,
26,278✔
2696
                                                  1);
2697
        b = sixel_output_palette_channel_to_float(palette,
87,026✔
2698
                                                  palette_float,
26,278✔
2699
                                                  n,
26,278✔
2700
                                                  2);
2701
        maxc = r > g ? (r > b ? r : b) : (g > b ? g : b);
60,748!
2702
        minc = r < g ? (r < b ? r : b) : (g < b ? g : b);
60,748!
2703
        lightness = (maxc + minc) * 0.5;
60,748✔
2704
        saturation = 0.0;
60,748✔
2705
        hue = 0.0;
60,748✔
2706
        diff = maxc - minc;
60,748✔
2707
        if (diff <= 0.0) {
60,748✔
2708
            h = 0;
4,731✔
2709
            s = 0;
4,731✔
2710
        } else {
2,739✔
2711
            if (lightness < 0.5) {
54,274✔
2712
                saturation = diff / (maxc + minc);
46,251✔
2713
            } else {
20,116✔
2714
                saturation = diff / (2.0 - maxc - minc);
8,023✔
2715
            }
2716
            if (maxc == r) {
54,274✔
2717
                hue = (g - b) / diff;
37,226✔
2718
            } else if (maxc == g) {
33,293!
2719
                hue = 2.0 + (b - r) / diff;
16,559✔
2720
            } else {
7,086✔
2721
                hue = 4.0 + (r - g) / diff;
489✔
2722
            }
2723
            hue *= 60.0;
54,274✔
2724
            if (hue < 0.0) {
54,274✔
2725
                hue += 360.0;
26✔
2726
            }
11✔
2727
            if (hue >= 360.0) {
52,027!
2728
                hue -= 360.0;
×
2729
            }
2730
            /*
2731
             * The DEC HLS color wheel used by DECGCI considers
2732
             * hue==0 to be blue instead of red.  Rotate the hue by
2733
             * +120 degrees so that RGB primaries continue to match
2734
             * the historical img2sixel output and VT340 behavior.
2735
             */
2736
            hue += 120.0;
54,274✔
2737
            while (hue >= 360.0) {
54,370!
2738
                hue -= 360.0;
96✔
2739
            }
2740
            while (hue < 0.0) {
54,274!
2741
                hue += 360.0;
×
2742
            }
2743
            s = (int)(saturation * 100.0 + 0.5);
54,274✔
2744
            if (s < 0) {
54,274!
2745
                s = 0;
2746
            } else if (s > 100) {
54,274!
2747
                s = 100;
2748
            }
2749
            h = (int)(hue + 0.5);
54,274✔
2750
            if (h >= 360) {
54,274!
2751
                h -= 360;
×
2752
            } else if (h < 0) {
54,274!
2753
                h = 0;
2754
            }
2755
        }
2756
        l = (int)(lightness * 100.0 + 0.5);
60,748✔
2757
        if (l < 0) {
60,748!
2758
            l = 0;
2759
        } else if (l > 100) {
60,748!
2760
            l = 100;
2761
        }
2762
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
2763
        sixel_putc(output->buffer + output->pos, '#');
60,748✔
2764
        sixel_advance(output, 1);
60,748✔
2765
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
60,748✔
2766
        sixel_advance(output, nwrite);
60,748✔
2767
        sixel_puts(output->buffer + output->pos, ";1;", 3);
60,748✔
2768
        sixel_advance(output, 3);
60,748✔
2769
        nwrite = sixel_putnum((char *)output->buffer + output->pos, h);
60,748✔
2770
        sixel_advance(output, nwrite);
60,748✔
2771
        sixel_putc(output->buffer + output->pos, ';');
60,748✔
2772
        sixel_advance(output, 1);
60,748✔
2773
        nwrite = sixel_putnum((char *)output->buffer + output->pos, l);
60,748✔
2774
        sixel_advance(output, nwrite);
60,748✔
2775
        sixel_putc(output->buffer + output->pos, ';');
60,748✔
2776
        sixel_advance(output, 1);
60,748✔
2777
        nwrite = sixel_putnum((char *)output->buffer + output->pos, s);
60,748✔
2778
        sixel_advance(output, nwrite);
60,748✔
2779
    }
26,278✔
2780

2781
    status = SIXEL_OK;
60,748✔
2782
    return status;
75,136✔
2783
}
14,388✔
2784

2785

2786
static void
2787
sixel_encode_work_init(sixel_encode_work_t *work)
107,031✔
2788
{
2789
    work->map = NULL;
107,031✔
2790
    work->map_size = 0;
107,031✔
2791
    work->columns = NULL;
107,031✔
2792
    work->columns_size = 0;
107,031✔
2793
    work->active_colors = NULL;
107,031✔
2794
    work->active_colors_size = 0;
107,031✔
2795
    work->active_color_index = NULL;
107,031✔
2796
    work->active_color_index_size = 0;
107,031✔
2797
    work->requested_threads = 1;
107,031✔
2798
}
81,987✔
2799

2800
static SIXELSTATUS
2801
sixel_encode_work_allocate(sixel_encode_work_t *work,
100,865✔
2802
                           int width,
2803
                           int ncolors,
2804
                           sixel_allocator_t *allocator)
2805
{
2806
    SIXELSTATUS status = SIXEL_FALSE;
100,865✔
2807
    int len;
50,699✔
2808
    size_t columns_size;
50,699✔
2809
    size_t active_colors_size;
50,699✔
2810
    size_t active_color_index_size;
50,699✔
2811

2812
    len = ncolors * width;
100,865✔
2813
    work->map = (char *)sixel_allocator_calloc(allocator,
149,097✔
2814
                                               (size_t)len,
48,232✔
2815
                                               sizeof(char));
2816
    if (work->map == NULL && len > 0) {
100,871!
2817
        sixel_helper_set_additional_message(
×
2818
            "sixel_encode_body: sixel_allocator_calloc() failed.");
2819
        status = SIXEL_BAD_ALLOCATION;
×
2820
        goto end;
×
2821
    }
2822
    work->map_size = (size_t)len;
100,873✔
2823

2824
    columns_size = sizeof(sixel_node_t *) * (size_t)width;
100,873✔
2825
    if (width > 0) {
100,873!
2826
        work->columns = (sixel_node_t **)sixel_allocator_malloc(
100,874✔
2827
            allocator,
48,234✔
2828
            columns_size);
48,234✔
2829
        if (work->columns == NULL) {
100,877!
2830
            sixel_helper_set_additional_message(
×
2831
                "sixel_encode_body: sixel_allocator_malloc() failed.");
2832
            status = SIXEL_BAD_ALLOCATION;
×
2833
            goto end;
×
2834
        }
2835
        memset(work->columns, 0, columns_size);
100,877✔
2836
        work->columns_size = columns_size;
100,877✔
2837
    } else {
48,234✔
2838
        work->columns = NULL;
2839
        work->columns_size = 0;
2840
    }
2841

2842
    active_colors_size = (size_t)ncolors;
100,876✔
2843
    if (active_colors_size > 0) {
100,876!
2844
        work->active_colors =
124,854✔
2845
            (unsigned char *)sixel_allocator_malloc(allocator,
149,090✔
2846
                                                    active_colors_size);
48,223✔
2847
        if (work->active_colors == NULL) {
100,864!
2848
            sixel_helper_set_additional_message(
×
2849
                "sixel_encode_body: sixel_allocator_malloc() failed.");
2850
            status = SIXEL_BAD_ALLOCATION;
×
2851
            goto end;
×
2852
        }
2853
        memset(work->active_colors, 0, active_colors_size);
100,866✔
2854
        work->active_colors_size = active_colors_size;
100,866✔
2855
    } else {
48,225✔
2856
        work->active_colors = NULL;
9✔
2857
        work->active_colors_size = 0;
9✔
2858
    }
2859

2860
    active_color_index_size = sizeof(int) * (size_t)ncolors;
100,873✔
2861
    if (active_color_index_size > 0) {
100,873!
2862
        work->active_color_index = (int *)sixel_allocator_malloc(
100,865✔
2863
            allocator,
48,224✔
2864
            active_color_index_size);
48,224✔
2865
        if (work->active_color_index == NULL) {
100,864!
2866
            sixel_helper_set_additional_message(
×
2867
                "sixel_encode_body: sixel_allocator_malloc() failed.");
2868
            status = SIXEL_BAD_ALLOCATION;
×
2869
            goto end;
×
2870
        }
2871
        memset(work->active_color_index, 0, active_color_index_size);
100,864✔
2872
        work->active_color_index_size = active_color_index_size;
100,864✔
2873
    } else {
48,224✔
2874
        work->active_color_index = NULL;
1✔
2875
        work->active_color_index_size = 0;
1✔
2876
    }
2877

2878
    status = SIXEL_OK;
76,876✔
2879

2880
end:
28,652✔
2881
    if (SIXEL_FAILED(status)) {
90,975!
2882
        if (work->active_color_index != NULL) {
×
2883
            sixel_allocator_free(allocator, work->active_color_index);
×
2884
            work->active_color_index = NULL;
×
2885
        }
2886
        work->active_color_index_size = 0;
×
2887
        if (work->active_colors != NULL) {
×
2888
            sixel_allocator_free(allocator, work->active_colors);
×
2889
            work->active_colors = NULL;
×
2890
        }
2891
        work->active_colors_size = 0;
×
2892
        if (work->columns != NULL) {
×
2893
            sixel_allocator_free(allocator, work->columns);
×
2894
            work->columns = NULL;
×
2895
        }
2896
        work->columns_size = 0;
×
2897
        if (work->map != NULL) {
×
2898
            sixel_allocator_free(allocator, work->map);
×
2899
            work->map = NULL;
×
2900
        }
2901
        work->map_size = 0;
×
2902
    }
2903

2904
    return status;
127,569✔
2905
}
26,707✔
2906

2907
static void
2908
sixel_encode_work_cleanup(sixel_encode_work_t *work,
107,044✔
2909
                          sixel_allocator_t *allocator)
2910
{
2911
    if (work->active_color_index != NULL) {
107,044✔
2912
        sixel_allocator_free(allocator, work->active_color_index);
100,878✔
2913
        work->active_color_index = NULL;
100,878✔
2914
    }
48,234✔
2915
    work->active_color_index_size = 0;
106,757✔
2916
    if (work->active_colors != NULL) {
106,757✔
2917
        sixel_allocator_free(allocator, work->active_colors);
100,878✔
2918
        work->active_colors = NULL;
100,878✔
2919
    }
48,234✔
2920
    work->active_colors_size = 0;
106,757✔
2921
    if (work->columns != NULL) {
106,757✔
2922
        sixel_allocator_free(allocator, work->columns);
100,878✔
2923
        work->columns = NULL;
100,878✔
2924
    }
48,234✔
2925
    work->columns_size = 0;
106,757✔
2926
    if (work->map != NULL) {
106,757✔
2927
        sixel_allocator_free(allocator, work->map);
100,878✔
2928
        work->map = NULL;
100,878✔
2929
    }
48,234✔
2930
    work->map_size = 0;
107,044✔
2931
}
107,044✔
2932

2933
static void
2934
sixel_band_state_reset(sixel_band_state_t *state)
538,089✔
2935
{
2936
    state->row_in_band = 0;
538,089✔
2937
    state->fillable = 0;
538,089✔
2938
    state->active_color_count = 0;
535,786!
2939
}
431,667✔
2940

2941
static void
2942
sixel_band_finish(sixel_encode_work_t *work, sixel_band_state_t *state)
371,530✔
2943
{
2944
    int color_index;
191,297✔
2945
    int c;
191,297✔
2946

2947
    if (work->active_colors == NULL
371,530!
2948
        || work->active_color_index == NULL) {
371,530!
2949
        state->active_color_count = 0;
4✔
2950
        return;
4✔
2951
    }
2952

2953
    for (color_index = 0;
6,973,320!
2954
         color_index < state->active_color_count;
13,026,877✔
2955
         color_index++) {
12,655,347✔
2956
        c = work->active_color_index[color_index];
12,655,387✔
2957
        if (c >= 0
12,655,387!
2958
            && (size_t)c < work->active_colors_size) {
12,655,399!
2959
            work->active_colors[c] = 0;
12,655,358✔
2960
        }
6,689,302✔
2961
    }
6,689,440✔
2962
    state->active_color_count = 0;
371,490✔
2963
}
184,544!
2964

2965
static void
2966
sixel_band_clear_map(sixel_encode_work_t *work)
371,577✔
2967
{
2968
    if (work->map != NULL && work->map_size > 0) {
371,577!
2969
        memset(work->map, 0, work->map_size);
371,578✔
2970
    }
184,644✔
2971
}
371,586✔
2972

2973
static SIXELSTATUS
2974
sixel_encode_emit_palette(int bodyonly,
65,667✔
2975
                          int ncolors,
2976
                          int keycolor,
2977
                          unsigned char const *palette,
2978
                          float const *palette_float,
2979
                          sixel_output_t *output)
2980
{
2981
    SIXELSTATUS status = SIXEL_FALSE;
65,667✔
2982
    int n;
33,799✔
2983

2984
    if (bodyonly || (ncolors == 2 && keycolor != (-1))) {
65,667!
2985
        return SIXEL_OK;
1,305✔
2986
    }
2987

2988
    if (palette == NULL && palette_float == NULL) {
61,174!
2989
        sixel_helper_set_additional_message(
×
2990
            "sixel_encode_emit_palette: missing palette data.");
2991
        return SIXEL_BAD_ARGUMENT;
×
2992
    }
2993

2994
    if (output->palette_type == SIXEL_PALETTETYPE_HLS) {
64,075✔
2995
        for (n = 0; n < ncolors; n++) {
60,987!
2996
            status = output_hls_palette_definition(output,
87,026✔
2997
                                                   palette,
26,278✔
2998
                                                   palette_float,
26,278✔
2999
                                                   n,
26,278✔
3000
                                                   keycolor);
26,278✔
3001
            if (SIXEL_FAILED(status)) {
60,748!
3002
                goto end;
3003
            }
3004
        }
26,278✔
3005
    } else {
104✔
3006
        for (n = 0; n < ncolors; n++) {
5,041,163!
3007
            status = output_rgb_palette_definition(output,
7,492,605✔
3008
                                                   palette,
2,515,278✔
3009
                                                   palette_float,
2,515,278✔
3010
                                                   n,
2,515,278✔
3011
                                                   keycolor);
2,515,278✔
3012
            if (SIXEL_FAILED(status)) {
4,977,327!
3013
                goto end;
3014
            }
3015
        }
2,515,278✔
3016
    }
3017

3018
    status = SIXEL_OK;
48,954✔
3019

3020
end:
32,660✔
3021
    return status;
48,954✔
3022
}
32,679✔
3023

3024
static SIXELSTATUS
3025
sixel_band_classify_row(sixel_encode_work_t *work,
2,094,675✔
3026
                        sixel_band_state_t *state,
3027
                        sixel_index_t *pixels,
3028
                        int width,
3029
                        int absolute_row,
3030
                        int ncolors,
3031
                        int keycolor,
3032
                        unsigned char *palstate,
3033
                        int encode_policy)
3034
{
3035
    SIXELSTATUS status = SIXEL_FALSE;
2,094,675✔
3036
    int row_bit;
1,073,268✔
3037
    int band_start;
1,073,268✔
3038
    int pix;
1,073,268✔
3039
    int x;
1,073,268✔
3040
    int check_integer_overflow;
1,073,268✔
3041
    char *map;
1,073,268✔
3042
    unsigned char *active_colors;
1,073,268✔
3043
    int *active_color_index;
1,073,268✔
3044

3045
    map = work->map;
2,094,675✔
3046
    active_colors = work->active_colors;
2,094,675✔
3047
    active_color_index = work->active_color_index;
2,094,675✔
3048
    row_bit = state->row_in_band;
2,094,675✔
3049
    band_start = absolute_row - row_bit;
2,094,675✔
3050

3051
    if (row_bit == 0) {
2,094,675✔
3052
        if (encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
371,577✔
3053
            state->fillable = 0;
371,291✔
3054
        } else if (palstate) {
184,806!
3055
            if (width > 0) {
×
3056
                pix = pixels[band_start * width];
×
3057
                if (pix >= ncolors) {
×
3058
                    state->fillable = 0;
×
3059
                } else {
3060
                    state->fillable = 1;
×
3061
                }
3062
            } else {
3063
                state->fillable = 0;
×
3064
            }
3065
        } else {
3066
            state->fillable = 1;
286✔
3067
        }
3068
        state->active_color_count = 0;
371,577✔
3069
    }
184,641✔
3070

3071
    for (x = 0; x < width; x++) {
241,511,723!
3072
        if (absolute_row > INT_MAX / width) {
239,442,961!
3073
            sixel_helper_set_additional_message(
×
3074
                "sixel_encode_body: integer overflow detected."
3075
                " (y > INT_MAX)");
3076
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3077
            goto end;
×
3078
        }
3079
        check_integer_overflow = absolute_row * width;
239,442,748✔
3080
        if (check_integer_overflow > INT_MAX - x) {
239,442,748!
3081
            sixel_helper_set_additional_message(
×
3082
                "sixel_encode_body: integer overflow detected."
3083
                " (y * width > INT_MAX - x)");
3084
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3085
            goto end;
×
3086
        }
3087
        pix = pixels[check_integer_overflow + x];
239,443,017✔
3088
        if (pix >= 0 && pix < ncolors && pix != keycolor) {
239,443,017!
3089
            if (pix > INT_MAX / width) {
205,551,806!
3090
                sixel_helper_set_additional_message(
×
3091
                    "sixel_encode_body: integer overflow detected."
3092
                    " (pix > INT_MAX / width)");
3093
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3094
                goto end;
×
3095
            }
3096
            check_integer_overflow = pix * width;
205,551,742✔
3097
            if (check_integer_overflow > INT_MAX - x) {
205,551,742!
3098
                sixel_helper_set_additional_message(
×
3099
                    "sixel_encode_body: integer overflow detected."
3100
                    " (pix * width > INT_MAX - x)");
3101
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3102
                goto end;
×
3103
            }
3104
            map[pix * width + x] |= (1 << row_bit);
205,551,821✔
3105
            if (active_colors != NULL && active_colors[pix] == 0) {
205,551,821!
3106
                active_colors[pix] = 1;
12,635,214✔
3107
                if (state->active_color_count < ncolors
12,635,214!
3108
                    && active_color_index != NULL) {
12,640,769!
3109
                    active_color_index[state->active_color_count] = pix;
12,640,227✔
3110
                    state->active_color_count += 1;
12,640,227✔
3111
                }
6,676,834✔
3112
            }
6,677,547✔
3113
        } else if (!palstate) {
131,677,725!
3114
            state->fillable = 0;
26,264,398✔
3115
        }
11,773,823✔
3116
    }
113,077,477✔
3117

3118
    state->row_in_band += 1;
2,068,937✔
3119
    status = SIXEL_OK;
2,068,937✔
3120

3121
end:
1,044,481✔
3122
    return status;
2,644,503✔
3123
}
575,565✔
3124

3125
static SIXELSTATUS
3126
sixel_band_compose(sixel_encode_work_t *work,
371,578✔
3127
                   sixel_band_state_t *state,
3128
                   sixel_output_t *output,
3129
                   int width,
3130
                   int ncolors,
3131
                   int keycolor,
3132
                   sixel_allocator_t *allocator)
3133
{
3134
    SIXELSTATUS status = SIXEL_FALSE;
371,578✔
3135
    int color_index;
191,330✔
3136
    int c;
191,330✔
3137
    unsigned char *row;
191,330✔
3138
    int sx;
191,330✔
3139
    int mx;
191,330✔
3140
    int gap;
191,330✔
3141
    int gap_reached_end;
191,330✔
3142
    sixel_node_t *np;
191,330✔
3143
    sixel_node_t *column_head;
191,330✔
3144
    sixel_node_t *column_tail;
191,330✔
3145
    sixel_node_t top;
191,330✔
3146

3147
    (void)ncolors;
272,131✔
3148
    (void)keycolor;
272,131✔
3149
    row = NULL;
371,578✔
3150
    gap_reached_end = 0;
371,578✔
3151
    np = NULL;
371,578✔
3152
    column_head = NULL;
371,578✔
3153
    column_tail = NULL;
371,578✔
3154
    top.next = NULL;
371,578✔
3155

3156
    if (work->columns != NULL) {
371,578!
3157
        memset(work->columns, 0, work->columns_size);
371,554✔
3158
    }
184,617✔
3159
    output->node_top = NULL;
371,578✔
3160

3161
    for (color_index = 0;
7,025,388!
3162
         color_index < state->active_color_count;
12,993,073✔
3163
         color_index++) {
12,621,528✔
3164
        c = work->active_color_index[color_index];
12,618,092✔
3165
        row = (unsigned char *)(work->map + c * width);
12,618,092✔
3166
        sx = 0;
12,618,092✔
3167
        while (sx < width) {
31,444,102!
3168
            sx = sixel_compose_find_run_start(
30,582,499✔
3169
                row,
16,113,348✔
3170
                width,
16,113,348✔
3171
                sx);
16,113,348✔
3172
            if (sx >= width) {
30,582,357✔
3173
                break;
9,156,068✔
3174
            }
3175

3176
            mx = sx + 1;
18,819,083✔
3177
            while (mx < width) {
101,012,134!
3178
                if (row[mx] != 0) {
100,131,456✔
3179
                    mx += 1;
67,176,864✔
3180
                    continue;
67,176,864✔
3181
                }
3182

3183
                gap = sixel_compose_measure_gap(
32,959,160✔
3184
                    row,
17,313,976✔
3185
                    width,
17,313,976✔
3186
                    mx + 1,
17,313,976✔
3187
                    &gap_reached_end);
3188
                if (gap >= 9 || gap_reached_end) {
32,956,359!
3189
                    break;
9,439,166✔
3190
                }
3191
                mx += gap + 1;
15,016,128✔
3192
            }
3193

3194
            if ((np = output->node_free) != NULL) {
18,817,095✔
3195
                output->node_free = np->next;
2,252,035✔
3196
            } else {
178,595✔
3197
                status = sixel_node_new(&np, allocator);
16,565,060✔
3198
                if (SIXEL_FAILED(status)) {
16,569,131!
3199
                    goto end;
3200
                }
3201
            }
3202

3203
            np->pal = c;
18,825,615✔
3204
            np->sx = sx;
18,825,615✔
3205
            np->mx = mx;
18,825,615✔
3206
            np->map = (char *)row;
18,825,615✔
3207
            np->next = NULL;
18,825,615✔
3208

3209
            if (work->columns != NULL) {
18,825,615!
3210
                column_head = work->columns[sx];
18,825,313✔
3211
                if (column_head == NULL
18,825,313✔
3212
                    || column_head->mx <= np->mx) {
13,890,276!
3213
                    np->next = column_head;
14,603,878✔
3214
                    work->columns[sx] = np;
14,603,878✔
3215
                } else {
7,595,266✔
3216
                    column_tail = column_head;
3,320,548✔
3217
                    while (column_tail->next != NULL
7,196,181!
3218
                           && column_tail->next->mx > np->mx) {
5,665,840!
3219
                        column_tail = column_tail->next;
1,138,511✔
3220
                    }
3221
                    np->next = column_tail->next;
4,222,337✔
3222
                    column_tail->next = np;
4,222,337✔
3223
                }
3224
            } else {
9,900,732✔
3225
                top.next = output->node_top;
273✔
3226
                column_tail = &top;
273✔
3227

3228
                while (column_tail->next != NULL) {
273!
3229
                    if (np->sx < column_tail->next->sx) {
×
3230
                        break;
3231
                    } else if (np->sx == column_tail->next->sx
×
3232
                               && np->mx > column_tail->next->mx) {
×
3233
                        break;
3234
                    }
3235
                    column_tail = column_tail->next;
3236
                }
3237

3238
                np->next = column_tail->next;
273✔
3239
                column_tail->next = np;
273✔
3240
                output->node_top = top.next;
273✔
3241
            }
3242

3243
            sx = mx;
17,067,307✔
3244
        }
3245
    }
6,653,810✔
3246

3247
    if (work->columns != NULL) {
374,787!
3248
        top.next = NULL;
371,567✔
3249
        column_tail = &top;
371,567✔
3250
        for (sx = 0; sx < width; sx++) {
41,618,703!
3251
            column_head = work->columns[sx];
41,246,601✔
3252
            if (column_head == NULL) {
41,246,601✔
3253
                continue;
31,087,819✔
3254
            }
3255
            column_tail->next = column_head;
10,159,245✔
3256
            while (column_tail->next != NULL) {
29,019,722!
3257
                column_tail = column_tail->next;
14,675,085✔
3258
            }
3259
            work->columns[sx] = NULL;
10,159,270✔
3260
        }
5,228,185✔
3261
        output->node_top = top.next;
371,564✔
3262
    }
184,621✔
3263

3264
    status = SIXEL_OK;
374,824✔
3265

3266
end:
190,176✔
3267
    return status;
478,672✔
3268
}
103,848✔
3269

3270
static SIXELSTATUS
3271
sixel_band_emit(sixel_encode_work_t *work,
371,521✔
3272
                sixel_band_state_t *state,
3273
                sixel_output_t *output,
3274
                int ncolors,
3275
                int keycolor,
3276
                int last_row_index)
3277
{
3278
    SIXELSTATUS status = SIXEL_FALSE;
371,521✔
3279
    sixel_node_t *np;
191,288✔
3280
    sixel_node_t *next;
191,288✔
3281
    int x;
191,288✔
3282
    int emit_next_line;
191,288✔
3283

3284
    emit_next_line = (last_row_index >= 6);
371,521✔
3285
    if (emit_next_line) {
371,521✔
3286
        /*
3287
         * Emit DECGNL only after the first band. The first band starts at the
3288
         * origin, so leading DECGNL would shift short images down by 6 rows.
3289
         */
3290
        output->buffer[output->pos] = '-';
305,909✔
3291
        sixel_advance(output, 1);
305,909✔
3292
    }
151,954✔
3293

3294
    for (x = 0; (np = output->node_top) != NULL;) {
2,880,997!
3295
        if (x > np->sx) {
2,509,621✔
3296
            output->buffer[output->pos] = '$';
2,149,499✔
3297
            sixel_advance(output, 1);
2,149,499✔
3298
            x = 0;
2,149,483✔
3299
        }
1,127,211✔
3300

3301
        if (state->fillable) {
2,601,286✔
3302
            memset(np->map + np->sx,
308✔
3303
                   (1 << state->row_in_band) - 1,
176✔
3304
                   (size_t)(np->mx - np->sx));
176✔
3305
        }
121✔
3306
        status = sixel_put_node(output,
3,815,879✔
3307
                                &x,
3308
                                np,
1,306,344✔
3309
                                ncolors,
1,306,344✔
3310
                                keycolor);
1,306,344✔
3311
        if (SIXEL_FAILED(status)) {
2,509,469!
3312
            goto end;
3313
        }
3314
        next = np->next;
2,509,469✔
3315
        sixel_node_del(output, np);
2,509,469✔
3316
        np = next;
2,509,475✔
3317

3318
        while (np != NULL) {
103,545,573!
3319
            if (np->sx < x) {
101,031,680✔
3320
                np = np->next;
84,760,450✔
3321
                continue;
84,760,450✔
3322
            }
3323

3324
            if (state->fillable) {
16,277,034!
3325
                memset(np->map + np->sx,
×
3326
                       (1 << state->row_in_band) - 1,
3327
                       (size_t)(np->mx - np->sx));
3328
            }
3329
            status = sixel_put_node(output,
24,830,198✔
3330
                                    &x,
3331
                                    np,
8,554,582✔
3332
                                    ncolors,
8,554,582✔
3333
                                    keycolor);
8,554,582✔
3334
            if (SIXEL_FAILED(status)) {
16,273,846!
3335
                goto end;
3336
            }
3337
            next = np->next;
16,273,846✔
3338
            sixel_node_del(output, np);
16,273,846✔
3339
            np = next;
16,275,106✔
3340
        }
3341

3342
        state->fillable = 0;
2,509,475✔
3343
    }
3344

3345
    status = SIXEL_OK;
283,925✔
3346

3347
end:
186,820✔
3348
    (void)work;
272,059✔
3349
    return status;
475,202✔
3350
}
103,799✔
3351

3352

3353
SIXELSTATUS
3354
sixel_encode_body(
24,967✔
3355
    sixel_index_t       /* in */ *pixels,
3356
    int                 /* in */ width,
3357
    int                 /* in */ height,
3358
    unsigned char       /* in */ *palette,
3359
    float const         /* in */ *palette_float,
3360
    int                 /* in */ ncolors,
3361
    int                 /* in */ keycolor,
3362
    int                 /* in */ bodyonly,
3363
    sixel_output_t      /* in */ *output,
3364
    unsigned char       /* in */ *palstate,
3365
    sixel_allocator_t   /* in */ *allocator,
3366
    int                 /* in */ pin_threads,
3367
    sixel_timeline_logger_t      /* in */ *logger)
3368
{
3369
    SIXELSTATUS status = SIXEL_FALSE;
24,967✔
3370
    int band_start;
12,772✔
3371
    int band_height;
12,772✔
3372
    int row_index;
12,772✔
3373
    int absolute_row;
12,772✔
3374
    int last_row_index;
12,772✔
3375
    sixel_node_t *np;
12,772✔
3376
    sixel_encode_work_t work;
12,772✔
3377
    sixel_band_state_t band;
12,772✔
3378
    int logging_active;
12,772✔
3379
    int job_index;
12,772✔
3380

3381
#if !SIXEL_ENABLE_THREADS
3382
    (void) pin_threads;
4,362✔
3383
#endif
3384

3385
    sixel_encode_work_init(&work);
24,967✔
3386
    sixel_band_state_reset(&band);
24,967✔
3387

3388
    /* Record the caller/environment preference even before we fan out. */
3389
    work.requested_threads = sixel_threads_resolve();
24,967✔
3390

3391
    if (ncolors < 1) {
24,967!
3392
        status = SIXEL_BAD_ARGUMENT;
×
3393
        goto cleanup;
×
3394
    }
3395
    output->active_palette = (-1);
24,967✔
3396

3397
    logging_active = logger != NULL;
24,967✔
3398
    job_index = 0;
24,967✔
3399

3400
    status = sixel_encode_emit_palette(bodyonly,
34,118✔
3401
                                       ncolors,
9,151✔
3402
                                       keycolor,
9,151✔
3403
                                       palette,
9,151✔
3404
                                       palette_float,
9,151✔
3405
                                       output);
9,151✔
3406
    if (SIXEL_FAILED(status)) {
24,967!
3407
        goto cleanup;
×
3408
    }
3409

3410
#if SIXEL_ENABLE_THREADS
3411
    {
3412
        int nbands;
8,410✔
3413
        int threads;
8,410✔
3414

3415
        nbands = (height + 5) / 6;
16,243✔
3416
        threads = work.requested_threads;
16,243✔
3417
        if (nbands > 1 && threads > 1) {
16,243!
3418
            status = sixel_encode_body_parallel(pixels,
9,654✔
3419
                                                width,
3,726✔
3420
                                                height,
3,726✔
3421
                                                ncolors,
3,726✔
3422
                                                keycolor,
3,726✔
3423
                                                output,
3,726✔
3424
                                                palstate,
3,726✔
3425
                                                allocator,
3,726✔
3426
                                                threads,
3,726✔
3427
                                                pin_threads);
3,726✔
3428
            if (SIXEL_FAILED(status)) {
5,928!
3429
                goto cleanup;
3430
            }
3431
            goto finalize;
5,928✔
3432
        }
3433
    }
5,191!
3434
#endif
3435

3436
    if (logging_active) {
18,565!
3437
        sixel_timeline_logger_logf(logger,
64✔
3438
                          "controller",
3439
                          "encode",
3440
                          "configure",
3441
                          -1);
3442
    }
42✔
3443

3444
    status = sixel_encode_work_allocate(&work,
19,039✔
3445
                                        width,
5,425✔
3446
                                        ncolors,
5,425✔
3447
                                        allocator);
5,425✔
3448
    if (SIXEL_FAILED(status)) {
19,039!
3449
        goto cleanup;
×
3450
    }
3451

3452
    band_start = 0;
12,460✔
3453
    while (band_start < height) {
105,514!
3454
        band_height = height - band_start;
86,475✔
3455
        if (band_height > 6) {
86,475✔
3456
            band_height = 6;
41,043✔
3457
        }
13,445✔
3458

3459
        band.row_in_band = 0;
87,206✔
3460
        band.fillable = 0;
87,206✔
3461
        band.active_color_count = 0;
87,206✔
3462

3463
        if (logging_active) {
87,206!
3464
            sixel_timeline_logger_logf(logger,
172✔
3465
                              "worker",
3466
                              "encode",
3467
                              "start",
3468
                              job_index);
64✔
3469
        }
64✔
3470

3471
        for (row_index = 0; row_index < band_height; row_index++) {
549,371!
3472
            absolute_row = band_start + row_index;
462,896✔
3473
            status = sixel_band_classify_row(&work,
462,896✔
3474
                                             &band,
3475
                                             pixels,
94,189✔
3476
                                             width,
94,189✔
3477
                                             absolute_row,
94,189✔
3478
                                             ncolors,
94,189✔
3479
                                             keycolor,
94,189✔
3480
                                             palstate,
94,189✔
3481
                                             output->encode_policy);
94,189✔
3482
            if (SIXEL_FAILED(status)) {
462,896!
3483
                goto cleanup;
×
3484
            }
3485
        }
94,189✔
3486

3487
        status = sixel_band_compose(&work,
86,475✔
3488
                                    &band,
3489
                                    output,
18,870✔
3490
                                    width,
18,870✔
3491
                                    ncolors,
18,870✔
3492
                                    keycolor,
18,870✔
3493
                                    allocator);
18,870✔
3494
        if (SIXEL_FAILED(status)) {
86,475!
3495
            goto cleanup;
3496
        }
3497

3498
        last_row_index = band_start + band_height - 1;
86,475✔
3499
        status = sixel_band_emit(&work,
86,475✔
3500
                                 &band,
3501
                                 output,
18,870✔
3502
                                 ncolors,
18,870✔
3503
                                 keycolor,
18,870✔
3504
                                 last_row_index);
18,870✔
3505
        if (SIXEL_FAILED(status)) {
86,475!
3506
            goto cleanup;
3507
        }
3508

3509
        sixel_band_finish(&work, &band);
86,475✔
3510

3511
        sixel_band_clear_map(&work);
86,475✔
3512

3513
        if (logging_active) {
86,475!
3514
            sixel_timeline_logger_logf(logger,
172✔
3515
                              "worker",
3516
                              "encode",
3517
                              "finish",
3518
                              job_index);
64✔
3519
        }
64✔
3520

3521
        band_start += band_height;
86,475✔
3522
        sixel_band_state_reset(&band);
86,475✔
3523
        job_index += 1;
86,475✔
3524
    }
3525

3526
    status = SIXEL_OK;
19,039✔
3527
    goto finalize;
19,039✔
3528

3529
finalize:
15,816✔
3530
    if (palstate) {
26,445✔
3531
        output->buffer[output->pos] = '$';
3,398✔
3532
        sixel_advance(output, 1);
3,398✔
3533
    }
1,478✔
3534

3535
cleanup:
17,885✔
3536
    while ((np = output->node_free) != NULL) {
880,983!
3537
        output->node_free = np->next;
856,016✔
3538
        sixel_allocator_free(allocator, np);
856,016✔
3539
    }
3540
    output->node_top = NULL;
24,967✔
3541

3542
    sixel_encode_work_cleanup(&work, allocator);
24,967✔
3543

3544
    return status;
24,967✔
3545
}
5,191✔
3546
SIXELSTATUS
3547
sixel_encode_footer(sixel_output_t *output)
62,941✔
3548
{
3549
    SIXELSTATUS status = SIXEL_FALSE;
62,941✔
3550

3551
    if (output->pos > 0) {
62,941✔
3552
        status = sixel_output_write_bytes(output,
108,832✔
3553
                                          (char *)output->buffer,
62,770✔
3554
                                          output->pos);
31,417✔
3555
        if (SIXEL_FAILED(status)) {
62,770!
3556
            return status;
3557
        }
3558
        output->pos = 0;
62,770✔
3559
    }
31,417✔
3560

3561
    status = sixel_output_end_image(output);
62,941✔
3562

3563
    return status;
62,941✔
3564
}
31,498✔
3565

3566
static int
3567
sixel_encode_body_ormode_nplanes(int ncolors)
1,358✔
3568
{
3569
    int nplanes;
661✔
3570

3571
    for (nplanes = 8; nplanes > 1; nplanes--) {
6,806!
3572
        if (ncolors > (1 << (nplanes - 1))) {
6,808✔
3573
            break;
1,070✔
3574
        }
3575
    }
2,640✔
3576

3577
    return nplanes;
1,746✔
3578
}
372✔
3579

3580
static SIXELSTATUS
3581
sixel_encode_body_ormode_emit_palette(unsigned char const *palette,
252✔
3582
                                      int ncolors,
3583
                                      int keycolor,
3584
                                      sixel_output_t *output)
3585
{
3586
    SIXELSTATUS status;
126✔
3587
    int n;
126✔
3588

3589
    if (palette == NULL || output == NULL) {
252!
3590
        return SIXEL_BAD_ARGUMENT;
3591
    }
3592
    for (n = 0; n < ncolors; n++) {
8,644!
3593
        status = output_rgb_palette_definition(output,
11,956✔
3594
                                               palette,
3,564✔
3595
                                               NULL,
3596
                                               n,
3,564✔
3597
                                               keycolor);
3,564✔
3598
        if (SIXEL_FAILED(status)) {
8,392!
3599
            return status;
3600
        }
3601
    }
3,564✔
3602

3603
    return SIXEL_OK;
186✔
3604
}
110✔
3605

3606
static SIXELSTATUS
3607
sixel_encode_body_ormode_band(sixel_index_t const *pixels,
1,640✔
3608
                              int width,
3609
                              int height,
3610
                              int band_index,
3611
                              int nplanes,
3612
                              sixel_output_t *output)
3613
{
3614
    SIXELSTATUS status;
820✔
3615
    sixel_index_t const *band_pixels;
820✔
3616
    sixel_index_t const *column_pixels;
820✔
3617
    sixel_index_t const *row0;
820✔
3618
    sixel_index_t const *row1;
820✔
3619
    sixel_index_t const *row2;
820✔
3620
    sixel_index_t const *row3;
820✔
3621
    sixel_index_t const *row4;
820✔
3622
    sixel_index_t const *row5;
820✔
3623
    int band_start;
820✔
3624
    int band_height;
820✔
3625
    int nwrite;
820✔
3626
    int plane;
820✔
3627
    int plane_bit;
820✔
3628
    int full_mask;
820✔
3629
    int sample_mask;
820✔
3630
    int sample_width;
820✔
3631
    int first_x;
820✔
3632
    int first_pix;
820✔
3633
    int x;
820✔
3634
    int y;
820✔
3635
    int pix;
820✔
3636

3637
    if (pixels == NULL || output == NULL || width < 1 || height < 0 ||
2,216!
3638
            band_index < 0 || nplanes < 1) {
1,640!
3639
        return SIXEL_BAD_ARGUMENT;
3640
    }
3641

3642
    band_start = band_index * 6;
1,640✔
3643
    if (band_start >= height) {
1,640!
3644
        return SIXEL_OK;
3645
    }
3646
    band_height = height - band_start;
1,640✔
3647
    if (band_height > 6) {
1,640✔
3648
        band_height = 6;
1,018✔
3649
    }
594✔
3650
    band_pixels = pixels + (size_t)band_start * (size_t)width;
1,640✔
3651

3652
    /*
3653
     * Full six-row bands are the hot path for real images.  Keep the partial
3654
     * tail on the generic loop below, but avoid the inner row loop and repeated
3655
     * width multiplication for complete bands.
3656
     */
3657
    if (band_height == 6) {
1,640✔
3658
        row0 = band_pixels;
1,440✔
3659
        row1 = row0 + width;
1,440✔
3660
        row2 = row1 + width;
1,440✔
3661
        row3 = row2 + width;
1,440✔
3662
        row4 = row3 + width;
1,440✔
3663
        row5 = row4 + width;
1,440✔
3664
        if (output->encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
1,440✔
3665
            for (plane = 0; plane < nplanes; plane++) {
7,362!
3666
                sixel_putc(output->buffer + output->pos, '#');
5,948✔
3667
                sixel_advance(output, 1);
5,948✔
3668
                nwrite = sixel_putnum((char *)output->buffer + output->pos,
8,478✔
3669
                                      1 << plane);
2,530✔
3670
                sixel_advance(output, nwrite);
5,948✔
3671

3672
                for (x = 0; x < width; x++) {
373,734!
3673
                    pix = (((row0[x] >> plane) & 0x1) << 0) |
675,933✔
3674
                          (((row1[x] >> plane) & 0x1) << 1) |
521,063✔
3675
                          (((row2[x] >> plane) & 0x1) << 2) |
521,063✔
3676
                          (((row3[x] >> plane) & 0x1) << 3) |
521,063✔
3677
                          (((row4[x] >> plane) & 0x1) << 4) |
521,063✔
3678
                          (((row5[x] >> plane) & 0x1) << 5);
366,193✔
3679
                    sixel_put_pixel(output, pix);
366,193✔
3680
                }
154,870✔
3681
                status = sixel_put_flash(output);
5,947✔
3682
                if (SIXEL_FAILED(status)) {
5,948!
3683
                    return status;
3684
                }
3685

3686
                sixel_putc(output->buffer + output->pos, '$');
5,948✔
3687
                sixel_advance(output, 1);
5,948✔
3688
            }
2,530✔
3689
            sixel_putc(output->buffer + output->pos, '-');
1,414✔
3690
            sixel_advance(output, 1);
1,414✔
3691
            return SIXEL_OK;
1,414✔
3692
        }
3693

3694
        full_mask = (1 << nplanes) - 1;
27✔
3695
        sample_mask = 0;
27✔
3696
        sample_width = width < 64 ? width : 64;
27!
3697
        for (x = 0; x < sample_width; x++) {
157!
3698
            sample_mask |= row0[x] | row1[x] | row2[x] |
240✔
3699
                           row3[x] | row4[x] | row5[x];
185✔
3700
        }
55✔
3701

3702
        if ((sample_mask & full_mask) == full_mask) {
27!
3703
            for (plane = 0; plane < nplanes; plane++) {
×
3704
                plane_bit = 1 << plane;
×
3705
                sixel_putc(output->buffer + output->pos, '#');
×
3706
                sixel_advance(output, 1);
×
3707
                nwrite = sixel_putnum((char *)output->buffer + output->pos,
×
3708
                                      plane_bit);
3709
                sixel_advance(output, nwrite);
×
3710

3711
                for (x = 0; x < width; x++) {
×
3712
                    pix = ((row0[x] & plane_bit) ? 0x01 : 0) |
×
3713
                          ((row1[x] & plane_bit) ? 0x02 : 0) |
×
3714
                          ((row2[x] & plane_bit) ? 0x04 : 0) |
×
3715
                          ((row3[x] & plane_bit) ? 0x08 : 0) |
×
3716
                          ((row4[x] & plane_bit) ? 0x10 : 0) |
×
3717
                          ((row5[x] & plane_bit) ? 0x20 : 0);
×
3718
                    sixel_put_pixel(output, pix);
×
3719
                }
3720
                status = sixel_put_flash(output);
×
3721
                if (SIXEL_FAILED(status)) {
×
3722
                    return status;
3723
                }
3724

3725
                sixel_putc(output->buffer + output->pos, '$');
×
3726
                sixel_advance(output, 1);
×
3727
            }
3728
            sixel_putc(output->buffer + output->pos, '-');
×
3729
            sixel_advance(output, 1);
×
3730
            return SIXEL_OK;
×
3731
        }
3732

3733
        for (plane = 0; plane < nplanes; plane++) {
79!
3734
            plane_bit = 1 << plane;
52✔
3735
            first_x = (-1);
52✔
3736
            pix = 0;
52✔
3737
            for (x = 0; x < width; x++) {
182!
3738
                pix = ((row0[x] & plane_bit) ? 0x01 : 0) |
468✔
3739
                      ((row1[x] & plane_bit) ? 0x02 : 0) |
222✔
3740
                      ((row2[x] & plane_bit) ? 0x04 : 0) |
222✔
3741
                      ((row3[x] & plane_bit) ? 0x08 : 0) |
222✔
3742
                      ((row4[x] & plane_bit) ? 0x10 : 0) |
222✔
3743
                      ((row5[x] & plane_bit) ? 0x20 : 0);
156✔
3744
                if (pix != 0) {
156!
3745
                    first_x = x;
19✔
3746
                    break;
19✔
3747
                }
3748
            }
55✔
3749
            if (first_x < 0) {
52✔
3750
                continue;
26✔
3751
            }
3752

3753
            sixel_putc(output->buffer + output->pos, '#');
26✔
3754
            sixel_advance(output, 1);
26✔
3755
            nwrite = sixel_putnum((char *)output->buffer + output->pos,
37✔
3756
                                  plane_bit);
11✔
3757
            sixel_advance(output, nwrite);
26✔
3758

3759
            status = sixel_emit_run(output, '?', first_x);
26✔
3760
            if (SIXEL_FAILED(status)) {
26!
3761
                return status;
3762
            }
3763
            sixel_put_pixel(output, pix);
26✔
3764
            for (x = first_x + 1; x < width; x++) {
130!
3765
                pix = ((row0[x] & plane_bit) ? 0x01 : 0) |
312✔
3766
                      ((row1[x] & plane_bit) ? 0x02 : 0) |
148!
3767
                      ((row2[x] & plane_bit) ? 0x04 : 0) |
148!
3768
                      ((row3[x] & plane_bit) ? 0x08 : 0) |
148!
3769
                      ((row4[x] & plane_bit) ? 0x10 : 0) |
148!
3770
                      ((row5[x] & plane_bit) ? 0x20 : 0);
104!
3771
                sixel_put_pixel(output, pix);
104✔
3772
            }
44✔
3773
            status = sixel_put_flash(output);
26✔
3774
            if (SIXEL_FAILED(status)) {
26!
3775
                return status;
3776
            }
3777

3778
            sixel_putc(output->buffer + output->pos, '$');
26✔
3779
            sixel_advance(output, 1);
26✔
3780
        }
11✔
3781
        sixel_putc(output->buffer + output->pos, '-');
27✔
3782
        sixel_advance(output, 1);
26✔
3783
        return SIXEL_OK;
26✔
3784
    }
3785

3786
    /*
3787
     * OR mode composes the final color by drawing powers-of-two bit-planes.
3788
     * Size policy may spend a little more branch work to avoid empty planes.
3789
     * Delay the plane header until the first non-zero cell.  A plane that never
3790
     * draws any cell cannot affect the OR-composited result, while a non-empty
3791
     * plane keeps its horizontal position by emitting the leading zero run
3792
     * before the first drawn cell.
3793
     */
3794
    if (output->encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
200!
3795
        for (plane = 0; plane < nplanes; plane++) {
912!
3796
            sixel_putc(output->buffer + output->pos, '#');
712✔
3797
            sixel_advance(output, 1);
712✔
3798
            nwrite = sixel_putnum((char *)output->buffer + output->pos,
1,020✔
3799
                                  1 << plane);
308✔
3800
            sixel_advance(output, nwrite);
712✔
3801

3802
            column_pixels = band_pixels;
712✔
3803
            for (x = 0; x < width; x++, column_pixels++) {
37,964!
3804
                pix = 0;
27,100✔
3805
                for (y = 0; y < band_height; y++) {
184,160!
3806
                    pix |= (((column_pixels[width * y] >> plane) & 0x1) << y);
147,096✔
3807
                }
62,260✔
3808
                sixel_put_pixel(output, pix);
37,064✔
3809
            }
15,708✔
3810
            status = sixel_put_flash(output);
712✔
3811
            if (SIXEL_FAILED(status)) {
712!
3812
                return status;
3813
            }
3814

3815
            sixel_putc(output->buffer + output->pos, '$');
712✔
3816
            sixel_advance(output, 1);
712✔
3817
        }
308✔
3818
        return SIXEL_OK;
148✔
3819
    }
3820

3821
    full_mask = (1 << nplanes) - 1;
×
3822
    sample_mask = 0;
×
3823
    sample_width = width < 64 ? width : 64;
×
3824
    column_pixels = band_pixels;
×
3825
    for (x = 0; x < sample_width; x++, column_pixels++) {
×
3826
        for (y = 0; y < band_height; y++) {
×
3827
            sample_mask |= column_pixels[width * y];
×
3828
        }
3829
    }
3830

3831
    if ((sample_mask & full_mask) == full_mask) {
×
3832
        for (plane = 0; plane < nplanes; plane++) {
×
3833
            sixel_putc(output->buffer + output->pos, '#');
×
3834
            sixel_advance(output, 1);
×
3835
            nwrite = sixel_putnum((char *)output->buffer + output->pos,
×
3836
                                  1 << plane);
3837
            sixel_advance(output, nwrite);
×
3838

3839
            column_pixels = band_pixels;
×
3840
            for (x = 0; x < width; x++, column_pixels++) {
×
3841
                pix = 0;
3842
                for (y = 0; y < band_height; y++) {
×
3843
                    pix |= (((column_pixels[width * y] >> plane) & 0x1) << y);
×
3844
                }
3845
                sixel_put_pixel(output, pix);
×
3846
            }
3847
            status = sixel_put_flash(output);
×
3848
            if (SIXEL_FAILED(status)) {
×
3849
                return status;
3850
            }
3851

3852
            sixel_putc(output->buffer + output->pos, '$');
×
3853
            sixel_advance(output, 1);
×
3854
        }
3855
        return SIXEL_OK;
3856
    }
3857

3858
    for (plane = 0; plane < nplanes; plane++) {
×
3859
        plane_bit = 1 << plane;
×
3860
        first_x = (-1);
×
3861
        first_pix = 0;
×
3862
        column_pixels = band_pixels;
×
3863
        for (x = 0; x < width; x++, column_pixels++) {
×
3864
            pix = 0;
3865
            for (y = 0; y < band_height; y++) {
×
3866
                pix |= (((column_pixels[width * y] >> plane) & 0x1) << y);
×
3867
            }
3868
            if (pix != 0) {
×
3869
                first_x = x;
3870
                first_pix = pix;
3871
                break;
3872
            }
3873
        }
3874
        if (first_x < 0) {
×
3875
            continue;
×
3876
        }
3877

3878
        sixel_putc(output->buffer + output->pos, '#');
×
3879
        sixel_advance(output, 1);
×
3880
        nwrite = sixel_putnum((char *)output->buffer + output->pos,
×
3881
                              plane_bit);
3882
        sixel_advance(output, nwrite);
×
3883

3884
        status = sixel_emit_run(output, '?', first_x);
×
3885
        if (SIXEL_FAILED(status)) {
×
3886
            return status;
3887
        }
3888
        sixel_put_pixel(output, first_pix);
×
3889
        column_pixels = band_pixels + first_x + 1;
×
3890
        for (x = first_x + 1; x < width; x++, column_pixels++) {
×
3891
            pix = 0;
3892
            for (y = 0; y < band_height; y++) {
×
3893
                pix |= (((column_pixels[width * y] >> plane) & 0x1) << y);
×
3894
            }
3895
            sixel_put_pixel(output, pix);
×
3896
        }
3897
        status = sixel_put_flash(output);
×
3898
        if (SIXEL_FAILED(status)) {
×
3899
            return status;
3900
        }
3901

3902
        sixel_putc(output->buffer + output->pos, '$');
×
3903
        sixel_advance(output, 1);
×
3904
    }
3905
    if (band_height == 6) {
×
3906
        sixel_putc(output->buffer + output->pos, '-');
3907
        sixel_advance(output, 1);
3908
    }
3909

3910
    return SIXEL_OK;
3911
}
704✔
3912

3913
SIXEL_INTERNAL_API SIXELSTATUS
3914
sixel_encode_body_ormode(
146✔
3915
    sixel_index_t       /* in */ *pixels,
3916
    int                 /* in */ width,
3917
    int                 /* in */ height,
3918
    unsigned char       /* in */ *palette,
3919
    int                 /* in */ ncolors,
3920
    int                 /* in */ keycolor,
3921
    sixel_output_t      /* in */ *output)
3922
{
3923
    SIXELSTATUS status;
73✔
3924
    int nplanes;
73✔
3925
    int nbands;
73✔
3926
    int band_index;
73✔
3927

3928
    if (pixels == NULL) {
146✔
3929
        return SIXEL_BAD_ARGUMENT;
19✔
3930
    }
3931

3932
    status = sixel_encode_body_ormode_emit_palette(palette,
164✔
3933
                                                   ncolors,
44✔
3934
                                                   keycolor,
44✔
3935
                                                   output);
44✔
3936
    if (SIXEL_FAILED(status)) {
120!
3937
        return status;
3938
    }
3939

3940
    nplanes = sixel_encode_body_ormode_nplanes(ncolors);
120✔
3941
    nbands = (height + 5) / 6;
120✔
3942
    for (band_index = 0; band_index < nbands; band_index++) {
484!
3943
        status = sixel_encode_body_ormode_band(pixels,
430✔
3944
                                               width,
66✔
3945
                                               height,
66✔
3946
                                               band_index,
66✔
3947
                                               nplanes,
66✔
3948
                                               output);
66✔
3949
        if (SIXEL_FAILED(status)) {
364!
3950
            return status;
3951
        }
3952
    }
66✔
3953

3954
    return SIXEL_OK;
84✔
3955
}
55✔
3956

3957

3958
static SIXELSTATUS
3959
sixel_encode_dither(
62,399✔
3960
    unsigned char   /* in */ *pixels,   /* pixel bytes to be encoded */
3961
    int             /* in */ width,     /* width of source image */
3962
    int             /* in */ height,    /* height of source image */
3963
    sixel_dither_t  /* in */ *dither,   /* dither context */
3964
    sixel_output_t  /* in */ *output)   /* output context */
3965
{
3966
    SIXELSTATUS status = SIXEL_FALSE;
62,399✔
3967
    sixel_index_t *paletted_pixels = NULL;
62,399✔
3968
    sixel_index_t *input_pixels;
32,154✔
3969
    size_t bufsize;
32,154✔
3970
    unsigned char *palette_entries = NULL;
62,399✔
3971
    float *palette_entries_float32 = NULL;
62,399✔
3972
    sixel_palette_t *palette_obj = NULL;
62,399✔
3973
    size_t palette_count = 0U;
62,399✔
3974
    size_t palette_float_count = 0U;
62,399✔
3975
    size_t palette_bytes = 0U;
62,399✔
3976
    size_t palette_float_bytes = 0U;
62,399✔
3977
    size_t palette_channels = 0U;
62,399✔
3978
    size_t palette_index = 0U;
62,399✔
3979
    int palette_source_colorspace;
32,154✔
3980
    int palette_float_pixelformat;
32,154✔
3981
    int output_float_pixelformat;
32,154✔
3982
    int palette_float_depth;
32,154✔
3983
    int pipeline_active;
32,154✔
3984
    int pipeline_threads = 0;  /* set to a deterministic default before use */
62,399✔
3985
    int pipeline_nbands;
32,154✔
3986
    sixel_parallel_dither_config_t dither_parallel;
32,154✔
3987
    char const *band_env_text;
32,154✔
3988
    sixel_palette_entries_view_t palette_view;
32,154✔
3989
    sixel_palette_float32_entries_view_t palette_float_view;
32,154✔
3990
#if SIXEL_ENABLE_THREADS
3991
    sixel_timeline_logger_t *serial_logger;
28,038✔
3992
    int logger_owned = 0;
54,167✔
3993
#endif  /* SIXEL_ENABLE_THREADS */
3994
    sixel_timeline_logger_t *logger = NULL;
62,399✔
3995

3996
    if (output == NULL || dither == NULL) {
62,399!
3997
        return SIXEL_BAD_ARGUMENT;
3998
    }
3999

4000
#if SIXEL_ENABLE_THREADS
4001
    serial_logger = NULL;
54,167✔
4002
#endif  /* SIXEL_ENABLE_THREADS */
4003
    palette_source_colorspace = SIXEL_COLORSPACE_GAMMA;
62,399✔
4004
    palette_float_pixelformat =
45,803✔
4005
        sixel_palette_float_pixelformat_for_colorspace(
62,399✔
4006
            palette_source_colorspace);
31,256✔
4007
    palette_float_depth =
45,803✔
4008
        sixel_helper_compute_depth(palette_float_pixelformat);
62,399✔
4009
    output_float_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
62,399✔
4010
    memset(&palette_view, 0, sizeof(palette_view));
62,399!
4011
    memset(&palette_float_view, 0, sizeof(palette_float_view));
62,399✔
4012
    palette_obj = dither->palette;
62,399✔
4013
    if (palette_obj == NULL || palette_obj->vtbl == NULL ||
88,664!
4014
            palette_obj->vtbl->get_entries == NULL ||
62,399!
4015
            palette_obj->vtbl->get_entries_float32 == NULL) {
62,399!
4016
        sixel_helper_set_additional_message(
×
4017
            "sixel_encode_dither: palette acquisition failed.");
4018
        status = SIXEL_BAD_ARGUMENT;
×
4019
        goto end;
×
4020
    }
4021

4022
    pipeline_active = 0;
62,399✔
4023
#if SIXEL_ENABLE_THREADS
4024
    #endif
4025
    dither_parallel.enabled = 0;
62,399✔
4026
    dither_parallel.band_height = 0;
62,399✔
4027
    dither_parallel.overlap = 0;
62,399✔
4028
    dither_parallel.dither_threads = 0;
62,399✔
4029
    dither_parallel.encode_threads = 0;
62,399✔
4030
    /*
4031
     * Normalize the planner-provided pinning request so both palette and
4032
     * encode workers see the same 0/1 flag.
4033
     */
4034
    dither->pipeline_pin_threads =
62,399✔
4035
        dither->pipeline_pin_threads != 0 ? 1 : 0;
62,399✔
4036
    dither_parallel.pin_threads = dither->pipeline_pin_threads;
62,399✔
4037
    switch (dither->pixelformat) {
62,399!
4038
    case SIXEL_PIXELFORMAT_PAL1:
4039
    case SIXEL_PIXELFORMAT_PAL2:
4040
    case SIXEL_PIXELFORMAT_PAL4:
4041
    case SIXEL_PIXELFORMAT_G1:
4042
    case SIXEL_PIXELFORMAT_G2:
4043
    case SIXEL_PIXELFORMAT_G4:
4044
        bufsize = (sizeof(sixel_index_t) * (size_t)width * (size_t)height * 3UL);
25✔
4045
        paletted_pixels = (sixel_index_t *)sixel_allocator_malloc(dither->allocator, bufsize);
25✔
4046
        if (paletted_pixels == NULL) {
25!
4047
            sixel_helper_set_additional_message(
×
4048
                "sixel_encode_dither: sixel_allocator_malloc() failed.");
4049
            status = SIXEL_BAD_ALLOCATION;
×
4050
            goto end;
×
4051
        }
4052
        status = sixel_helper_normalize_pixelformat(paletted_pixels,
50✔
4053
                                                    &dither->pixelformat,
25✔
4054
                                                    pixels,
25✔
4055
                                                    dither->pixelformat,
25✔
4056
                                                    width, height);
25✔
4057
        if (SIXEL_FAILED(status)) {
25!
4058
            goto end;
×
4059
        }
4060
        input_pixels = paletted_pixels;
25✔
4061
        break;
25✔
4062
    case SIXEL_PIXELFORMAT_PAL8:
1,536✔
4063
    case SIXEL_PIXELFORMAT_G8:
4064
    case SIXEL_PIXELFORMAT_GA88:
4065
    case SIXEL_PIXELFORMAT_AG88:
4066
        input_pixels = pixels;
5,184✔
4067
        break;
5,184✔
4068
    default:
28,261✔
4069
        /* apply palette */
4070
        pipeline_threads = sixel_threads_resolve();
55,844✔
4071
        band_env_text = sixel_compat_getenv(
55,844✔
4072
            "SIXEL_DITHER_PARALLEL_BAND_WIDTH");
4073
        if (pipeline_threads <= 1 && band_env_text != NULL
55,844!
4074
                && band_env_text[0] != '\0') {
2,203!
4075
            /*
4076
             * Parallel band dithering was explicitly requested via the
4077
             * environment.  When SIXEL_THREADS is absent, prefer hardware
4078
             * concurrency instead of silently running a single worker so
4079
             * that multiple dither jobs appear in the log.
4080
             */
4081
            pipeline_threads = sixel_threads_normalize(0);
20✔
4082
        }
4083
        pipeline_nbands = (height + 5) / 6;
55,645✔
4084
        /*
4085
         * Pipeline mode lets PaletteApply produce contiguous index rows while
4086
         * band workers consume completed six-line slices. OR mode now has its
4087
         * own band encoder, so it can share this producer/writer path without
4088
         * dereferencing input_pixels on the caller side.
4089
         */
4090
        if (pipeline_threads > 1 && pipeline_nbands > 1) {
55,645!
4091
            pipeline_active = 1;
33,117✔
4092
            input_pixels = NULL;
33,117✔
4093
        } else {
23,688✔
4094
            paletted_pixels = sixel_dither_apply_palette(dither, pixels,
18,725✔
4095
                                                         width, height);
3,895✔
4096
            if (paletted_pixels == NULL) {
14,830!
4097
                status = SIXEL_RUNTIME_ERROR;
×
4098
                goto end;
×
4099
            }
4100
            input_pixels = paletted_pixels;
9,526✔
4101
        }
4102
        break;
42,643✔
4103
    }
4104

4105
    if (pipeline_active) {
55,749✔
4106
        sixel_parallel_dither_configure(height,
64,702✔
4107
                                        dither->ncolors,
23,688✔
4108
                                        pipeline_threads,
23,688✔
4109
                                        dither->pipeline_pin_threads,
23,688✔
4110
                                        &dither_parallel);
4111
        if (dither_parallel.enabled) {
41,014!
4112
            dither->pipeline_parallel_active = 1;
41,014✔
4113
            dither->pipeline_band_height = dither_parallel.band_height;
41,014✔
4114
            dither->pipeline_band_overlap = dither_parallel.overlap;
41,014✔
4115
            dither->pipeline_dither_threads =
41,014✔
4116
                dither_parallel.dither_threads;
41,014✔
4117
            pipeline_threads = dither_parallel.encode_threads;
41,014✔
4118
        }
23,688✔
4119
        if (pipeline_threads <= 1) {
43,285✔
4120
            /*
4121
             * Disable the pipeline when the encode side cannot spawn at
4122
             * least two workers.  A single encode thread cannot consume the
4123
             * six-line jobs that PaletteApply produces, so fall back to the
4124
             * serialized encoder path.
4125
             */
4126
            pipeline_active = 0;
204✔
4127
            dither->pipeline_parallel_active = 0;
204✔
4128
            if (paletted_pixels == NULL) {
204!
4129
                paletted_pixels = sixel_dither_apply_palette(dither, pixels,
309✔
4130
                                                             width, height);
105✔
4131
                if (paletted_pixels == NULL) {
204!
4132
                    status = SIXEL_RUNTIME_ERROR;
×
4133
                    goto end;
×
4134
                }
4135
            }
105✔
4136
            input_pixels = paletted_pixels;
159✔
4137
        }
105✔
4138
    }
23,688✔
4139

4140
#if SIXEL_ENABLE_THREADS
4141
    if (!pipeline_active) {
52,621!
4142
        logger = dither->pipeline_logger;
13,357✔
4143
        if (logger == NULL) {
13,357!
4144
            sixel_timeline_logger_prepare_default(dither->allocator,
13,357✔
4145
                                                  &serial_logger);
4146
            if (serial_logger != NULL) {
13,357✔
4147
                logger_owned = 1;
64✔
4148
                dither->pipeline_logger = serial_logger;
64✔
4149
                logger = serial_logger;
64✔
4150
            } else {
42✔
4151
                logger = NULL;
10,724✔
4152
            }
4153
        }
7,673✔
4154
        if (logger != NULL) {
11,410✔
4155
            sixel_timeline_logger_logf(logger,
106✔
4156
                              "controller",
4157
                              "pipeline",
4158
                              "configure",
4159
                              -1,
4160
                              -1,
4161
                              0,
4162
                              height,
42✔
4163
                              0,
4164
                              height,
42✔
4165
                              "serial path threads=1");
4166
        }
42✔
4167
    }
7,673✔
4168
#endif
4169

4170
    if (output != NULL) {
62,399!
4171
        palette_source_colorspace = output->source_colorspace;
62,399✔
4172
        palette_float_pixelformat =
45,803✔
4173
            sixel_palette_float_pixelformat_for_colorspace(
62,399✔
4174
                palette_source_colorspace);
31,256✔
4175
        palette_float_depth =
45,803✔
4176
            sixel_helper_compute_depth(palette_float_pixelformat);
62,399✔
4177
    }
31,256✔
4178

4179
    status = palette_obj->vtbl->get_entries(palette_obj, &palette_view);
62,399✔
4180
    if (SIXEL_FAILED(status) || palette_view.entries == NULL ||
88,664!
4181
            palette_view.depth != 3 || palette_view.entry_count == 0U) {
62,399!
4182
        sixel_helper_set_additional_message(
×
4183
            "sixel_encode_dither: palette view failed.");
4184
        status = SIXEL_RUNTIME_ERROR;
×
4185
        goto end;
×
4186
    }
4187
    palette_count = palette_view.entry_count;
62,399✔
4188
    palette_bytes = palette_count * 3U;
62,399✔
4189
    palette_entries = (unsigned char *)sixel_allocator_malloc(
62,399✔
4190
        dither->allocator,
31,256✔
4191
        palette_bytes);
31,256✔
4192
    if (palette_entries == NULL) {
62,399!
4193
        sixel_helper_set_additional_message(
×
4194
            "sixel_encode_dither: palette copy allocation failed.");
4195
        status = SIXEL_BAD_ALLOCATION;
×
4196
        goto end;
×
4197
    }
4198
    memcpy(palette_entries, palette_view.entries, palette_bytes);
62,399✔
4199

4200
    status = palette_obj->vtbl->get_entries_float32(palette_obj,
62,399✔
4201
                                                    &palette_float_view);
4202
    if (SIXEL_FAILED(status)) {
62,399!
4203
        goto end;
×
4204
    }
4205
    if (palette_float_view.entries != NULL &&
63,551!
4206
            palette_float_view.entry_count == palette_count &&
4,839!
4207
            palette_float_view.depth == palette_float_depth) {
4,839!
4208
        palette_float_count = palette_float_view.entry_count;
4,839✔
4209
        palette_float_bytes =
4,839✔
4210
            palette_float_count * (size_t)palette_float_view.depth;
4,839✔
4211
        palette_entries_float32 = (float *)sixel_allocator_malloc(
4,839✔
4212
            dither->allocator,
1,386✔
4213
            palette_float_bytes);
1,386✔
4214
        if (palette_entries_float32 == NULL) {
4,839!
4215
            sixel_helper_set_additional_message(
×
4216
                "sixel_encode_dither: float palette copy allocation failed.");
4217
            status = SIXEL_BAD_ALLOCATION;
×
4218
            goto end;
×
4219
        }
4220
        memcpy(palette_entries_float32,
4,953✔
4221
               palette_float_view.entries,
3,567✔
4222
               palette_float_bytes);
114✔
4223
    }
1,386✔
4224
    if (palette_entries != NULL && palette_entries_float32 != NULL
63,551!
4225
            && palette_count == palette_float_count
34,709!
4226
            && palette_count > 0U
4,839!
4227
            && !sixel_palette_float32_matches_u8(
4,839!
4228
                    palette_entries,
1,386✔
4229
                    palette_entries_float32,
1,386✔
4230
                    palette_count,
1,386✔
4231
                    palette_float_pixelformat)) {
1,386✔
4232
        sixel_palette_sync_float32_from_u8(palette_entries,
×
4233
                                           palette_entries_float32,
4234
                                           palette_count,
4235
                                           palette_float_pixelformat);
4236
    }
4237
    if (palette_entries != NULL && palette_count > 0U
88,664!
4238
            && output != NULL
47,852!
4239
            && output->source_colorspace != output->colorspace) {
62,399!
4240
        palette_bytes = palette_count * 3U;
1,846✔
4241
        if (palette_entries_float32 != NULL
1,846!
4242
                && palette_float_count == palette_count) {
1,436!
4243
            /*
4244
             * Use the higher-precision palette to change color spaces once and
4245
             * then quantize those float channels down to bytes.  The previous
4246
             * implementation converted the 8bit entries before overwriting
4247
             * them from float again, doubling the amount of work and rounding
4248
             * the palette twice.
4249
             */
4250
            palette_float_bytes = palette_bytes * sizeof(float);
346✔
4251
            status = sixel_helper_convert_colorspace(
346✔
4252
                (unsigned char *)palette_entries_float32,
33✔
4253
                palette_float_bytes,
33✔
4254
                palette_float_pixelformat,
33✔
4255
                output->source_colorspace,
33✔
4256
                output->colorspace);
33✔
4257
            if (SIXEL_FAILED(status)) {
346!
4258
                sixel_helper_set_additional_message(
×
4259
                    "sixel_encode_dither: float palette colorspace conversion failed.");
4260
                goto end;
×
4261
            }
4262
            output_float_pixelformat =
188✔
4263
                sixel_palette_float_pixelformat_for_colorspace(
346!
4264
                    output->colorspace);
33✔
4265
            palette_channels = palette_count * 3U;
346✔
4266
            for (palette_index = 0U; palette_index < palette_channels;
206,872!
4267
                    ++palette_index) {
206,526✔
4268
                int channel;
103,263✔
4269

4270
                channel = (int)(palette_index % 3U);
206,526✔
4271
                palette_entries[palette_index] =
206,526✔
4272
                    sixel_pixelformat_float_channel_to_byte(
206,526✔
4273
                        output_float_pixelformat,
297✔
4274
                        channel,
297✔
4275
                        palette_entries_float32[palette_index]);
206,526✔
4276
            }
297✔
4277
        } else {
33✔
4278
            status = sixel_helper_convert_colorspace(palette_entries,
2,248✔
4279
                                                     palette_bytes,
748✔
4280
                                                     SIXEL_PIXELFORMAT_RGB888,
4281
                                                     output->source_colorspace,
748✔
4282
                                                     output->colorspace);
748✔
4283
            if (SIXEL_FAILED(status)) {
1,500!
4284
                sixel_helper_set_additional_message(
×
4285
                    "sixel_encode_dither: palette colorspace "
4286
                    "conversion failed.");
4287
                goto end;
×
4288
            }
4289
        }
4290
    }
781✔
4291
    if (SIXEL_FAILED(status) || palette_entries == NULL) {
47,852!
4292
        sixel_helper_set_additional_message(
4293
            "sixel_encode_dither: palette copy failed.");
4294
        goto end;
4295
    }
4296

4297
    status = sixel_encode_header(width, height, dither->keycolor, output);
62,399✔
4298
    if (SIXEL_FAILED(status)) {
62,399!
4299
        goto end;
×
4300
    }
4301

4302
    if (pipeline_active) {
62,399!
4303
        if (output->ormode) {
40,810!
4304
            status = sixel_encode_body_ormode_pipeline(pixels,
165✔
4305
                                                       width,
55✔
4306
                                                       height,
55✔
4307
                                                       palette_entries,
55✔
4308
                                                       dither,
55✔
4309
                                                       output,
55✔
4310
                                                       pipeline_threads);
55✔
4311
        } else {
55✔
4312
            status = sixel_encode_body_pipeline(pixels,
64,228✔
4313
                                                width,
23,528✔
4314
                                                height,
23,528✔
4315
                                                palette_entries,
23,528✔
4316
                                                palette_entries_float32,
23,528✔
4317
                                                dither,
23,528✔
4318
                                                output,
23,528✔
4319
                                                pipeline_threads);
23,528✔
4320
        }
4321
    } else if (output->ormode) {
45,172!
4322
        status = sixel_encode_body_ormode(input_pixels,
20✔
4323
                                          width,
4324
                                          height,
4325
                                          palette_entries,
4326
                                          dither->ncolors,
4327
                                          dither->keycolor,
4328
                                          output);
4329
    } else {
4330
        status = sixel_encode_body(input_pixels,
29,242✔
4331
                                   width,
7,673✔
4332
                                   height,
7,673✔
4333
                                   palette_entries,
7,673✔
4334
                                   palette_entries_float32,
7,673✔
4335
                                   dither->ncolors,
7,673✔
4336
                                   dither->keycolor,
7,673✔
4337
                                   dither->bodyonly,
7,673✔
4338
                                   output,
7,673✔
4339
                                   NULL,
4340
                                   dither->allocator,
7,673✔
4341
                                   dither->pipeline_pin_threads,
7,673✔
4342
                                   logger != NULL ?
7,673✔
4343
                                       logger :
42✔
4344
                                       NULL);
4345
    }
4346

4347
    if (SIXEL_FAILED(status)) {
62,399!
4348
        goto end;
×
4349
    }
4350

4351
    status = sixel_encode_footer(output);
62,399✔
4352
    if (SIXEL_FAILED(status)) {
62,399!
4353
        goto end;
4354
    }
4355

4356
end:
31,143✔
4357
#if SIXEL_ENABLE_THREADS
4358
    if (logger_owned) {
54,167✔
4359
        dither->pipeline_logger = NULL;
64✔
4360
        sixel_timeline_logger_unref(serial_logger);
64✔
4361
    }
42✔
4362
#endif
4363
    if (palette_entries != NULL) {
62,399!
4364
        sixel_allocator_free(dither->allocator, palette_entries);
62,399✔
4365
    }
31,256✔
4366
    if (palette_entries_float32 != NULL) {
65,178!
4367
        sixel_allocator_free(dither->allocator, palette_entries_float32);
4,839✔
4368
    }
1,386✔
4369
    sixel_allocator_free(dither->allocator, paletted_pixels);
62,399✔
4370

4371
    return status;
62,399✔
4372
}
31,256✔
4373

4374
SIXEL_INTERNAL_API SIXELSTATUS
4375
sixel_encoder_core_encode_dispatch(
63,045✔
4376
    sixel_encoder_core_encode_request_t const *request)
4377
{
4378
    SIXELSTATUS status;
32,480✔
4379

4380
    if (request == NULL || request->pixels == NULL ||
89,510!
4381
        request->dither == NULL || request->output == NULL) {
62,941!
4382
        return SIXEL_BAD_ARGUMENT;
76✔
4383
    }
4384
    if (request->width < 1 || request->height < 1) {
62,941!
4385
        return SIXEL_BAD_INPUT;
4386
    }
4387

4388
    (void)request->depth;
46,185✔
4389
    if (request->dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
62,941✔
4390
        status = sixel_encode_highcolor(request->pixels,
784✔
4391
                                        request->width,
402✔
4392
                                        request->height,
402✔
4393
                                        request->dither,
402✔
4394
                                        request->output);
402✔
4395
    } else {
242✔
4396
        status = sixel_encode_dither(request->pixels,
93,655✔
4397
                                     request->width,
47,852✔
4398
                                     request->height,
47,852✔
4399
                                     request->dither,
47,852✔
4400
                                     request->output);
47,852✔
4401
    }
4402

4403
    return status;
48,254✔
4404
}
31,542✔
4405

4406
SIXELAPI SIXELSTATUS
4407
sixel_encode(
62,915✔
4408
    unsigned char  /* in */ *pixels,   /* pixel bytes */
4409
    int            /* in */ width,     /* image width */
4410
    int            /* in */ height,    /* image height */
4411
    int const      /* in */ depth,     /* color depth */
4412
    sixel_dither_t /* in */ *dither,   /* dither context */
4413
    sixel_output_t /* in */ *output)   /* output context */
4414
{
4415
    SIXELSTATUS status = SIXEL_FALSE;
62,915✔
4416
    sixel_encoder_core_t *core;
32,415✔
4417
    sixel_encoder_core_encode_request_t request;
32,415✔
4418

4419
    if (pixels == NULL) {
62,915!
4420
        sixel_helper_set_additional_message(
×
4421
            "sixel_encode: bad pixels parameter."
4422
            " (pixels == NULL)");
4423
        return SIXEL_BAD_ARGUMENT;
×
4424
    }
4425

4426
    if (dither == NULL) {
62,915!
4427
        sixel_helper_set_additional_message(
×
4428
            "sixel_encode: bad dither parameter."
4429
            " (dither == NULL)");
4430
        return SIXEL_BAD_ARGUMENT;
×
4431
    }
4432

4433
    if (output == NULL) {
62,915!
4434
        sixel_helper_set_additional_message(
×
4435
            "sixel_encode: bad output parameter."
4436
            " (output == NULL)");
4437
        return SIXEL_BAD_ARGUMENT;
×
4438
    }
4439

4440
    /* TODO: reference counting should be thread-safe */
4441
    sixel_dither_ref(dither);
62,915✔
4442
    sixel_output_ref(output);
62,915✔
4443

4444
    if (width < 1) {
62,915!
4445
        sixel_helper_set_additional_message(
×
4446
            "sixel_encode: bad width parameter."
4447
            " (width < 1)");
4448
        status = SIXEL_BAD_INPUT;
×
4449
        goto end;
×
4450
    }
4451

4452
    if (height < 1) {
62,915!
4453
        sixel_helper_set_additional_message(
×
4454
            "sixel_encode: bad height parameter."
4455
            " (height < 1)");
4456
        status = SIXEL_BAD_INPUT;
×
4457
        goto end;
×
4458
    }
4459

4460
    core = sixel_output_as_encoder_core(output);
62,915✔
4461
    if (core == NULL || core->vtbl == NULL ||
62,915!
4462
        core->vtbl->encode == NULL) {
62,915!
4463
        status = SIXEL_BAD_ARGUMENT;
×
4464
        goto end;
×
4465
    }
4466
    request.pixels = pixels;
62,915✔
4467
    request.width = width;
62,915✔
4468
    request.height = height;
62,915✔
4469
    request.depth = depth;
62,915✔
4470
    request.dither = dither;
62,915✔
4471
    request.output = output;
62,915✔
4472
    status = core->vtbl->encode(core, &request);
62,915✔
4473

4474
end:
31,428✔
4475
    sixel_output_unref(output);
62,915✔
4476
    sixel_dither_unref(dither);
62,915✔
4477

4478
    return status;
62,915✔
4479
}
31,487✔
4480

4481

4482
/* emacs Local Variables:      */
4483
/* emacs mode: c               */
4484
/* emacs tab-width: 4          */
4485
/* emacs indent-tabs-mode: nil */
4486
/* emacs c-basic-offset: 4     */
4487
/* emacs End:                  */
4488
/* vim: set expandtab ts=4 sts=4 sw=4 : */
4489
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc