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

saitoha / libsixel / 25532151378

08 May 2026 01:45AM UTC coverage: 85.542% (+0.008%) from 85.534%
25532151378

push

github

saitoha
ci: fix experimental vcpkg checkout eol

126839 of 269435 branches covered (47.08%)

152560 of 178346 relevant lines covered (85.54%)

8894051.04 hits per line

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

94.66
/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

250
#if SIXEL_ENABLE_THREADS
251
static void
252
sixel_timeline_logger_prepare_default(sixel_allocator_t *allocator,
50,839✔
253
                                      sixel_timeline_logger_t **logger)
254
{
255
    if (logger == NULL) {
50,839!
256
        return;
257
    }
258

259
    *logger = NULL;
50,839✔
260
    (void)sixel_timeline_logger_prepare_env(allocator, logger);
50,839✔
261
}
26,589✔
262
#endif
263

264
static void
265
sixel_parallel_dither_configure(int height,
34,808✔
266
                                int ncolors,
267
                                int pipeline_threads,
268
                                int pin_threads,
269
                                sixel_parallel_dither_config_t *config)
270
{
271
    char const *text;
18,071✔
272
    long parsed;
18,071✔
273
    char *endptr;
18,071✔
274
    int band_height;
18,071✔
275
    int overlap;
18,071✔
276
    int dither_threads;
18,071✔
277
    int encode_threads;
18,071✔
278
    int dither_env_override;
18,071✔
279

280
    if (config == NULL) {
34,808✔
281
        return;
×
282
    }
283

284
    config->enabled = 0;
34,808✔
285
    config->band_height = 0;
34,808✔
286
    config->overlap = 0;
34,808✔
287
    config->dither_threads = 0;
34,808✔
288
    config->encode_threads = pipeline_threads;
34,808✔
289
    config->pin_threads = (pin_threads != 0) ? 1 : 0;
34,808!
290

291
    if (pipeline_threads <= 1 || height <= 0) {
34,808!
292
        return;
293
    }
294

295
    dither_env_override = 0;
34,808✔
296
    dither_threads = (pipeline_threads * 7 + 9) / 10;
34,808✔
297
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_THREADS_MAX");
34,808✔
298
    if (text != NULL && text[0] != '\0') {
34,808!
299
        errno = 0;
100✔
300
        parsed = strtol(text, &endptr, 10);
100✔
301
        if (endptr != text && errno != ERANGE && parsed > 0) {
100!
302
            if (parsed > INT_MAX) {
70!
303
                parsed = INT_MAX;
304
            }
305
            dither_threads = (int)parsed;
85✔
306
            dither_env_override = 1;
85✔
307
        }
45✔
308
    }
45✔
309
    if (dither_threads < 1) {
24,957!
310
        dither_threads = 1;
311
    }
312
    if (dither_threads > pipeline_threads) {
32,583!
313
        dither_threads = pipeline_threads;
314
    }
315

316
    if (!dither_env_override && pipeline_threads >= 4 && dither_threads < 2) {
34,803!
317
        /*
318
         * When the total budget is ample, keep at least two dither workers so
319
         * the banded producer can feed the encoder fast enough to pipeline.
320
         */
321
        dither_threads = pipeline_threads - 2;
×
322
    }
323

324
    encode_threads = pipeline_threads - dither_threads;
34,803✔
325
    if (encode_threads < 2 && pipeline_threads > 2) {
34,803!
326
        /*
327
         * Preserve a minimal pair of encoder workers to keep the pipeline
328
         * alive while leaving the rest to dithering. Small budgets fall back
329
         * to the serial encoder path later in the caller.
330
         */
331
        encode_threads = 2;
34,524✔
332
        dither_threads = pipeline_threads - encode_threads;
34,524✔
333
    }
17,943✔
334
    if (encode_threads < 1) {
36,996✔
335
        encode_threads = 1;
184✔
336
        dither_threads = pipeline_threads - encode_threads;
184✔
337
    }
85✔
338
    if (dither_threads < 1) {
28,704!
339
        return;
340
    }
341

342
    /*
343
     * Choose the band height from the environment when present. Otherwise
344
     * split the image across the initial dither workers so each thread starts
345
     * with a single band. The result is rounded to a six-line multiple to
346
     * stay aligned with the encoder's natural cadence.
347
     */
348
    band_height = 0;
34,808✔
349
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_WIDTH");
34,808✔
350
    if (text != NULL && text[0] != '\0') {
34,808!
351
        errno = 0;
100✔
352
        parsed = strtol(text, &endptr, 10);
100✔
353
        if (endptr != text && errno != ERANGE && parsed > 0) {
100!
354
            if (parsed > INT_MAX) {
70!
355
                parsed = INT_MAX;
356
            }
357
            band_height = (int)parsed;
85✔
358
        }
45✔
359
    }
45✔
360
    if (band_height <= 0) {
27,177✔
361
        band_height = (height + dither_threads - 1) / dither_threads;
34,708✔
362
    }
18,028✔
363
    if (band_height < 6) {
36,973✔
364
        band_height = 6;
1,691✔
365
    }
413✔
366
    if ((band_height % 6) != 0) {
33,463✔
367
        band_height = ((band_height + 5) / 6) * 6;
27,681✔
368
    }
15,594✔
369

370
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_OVERWRAP");
34,808✔
371
    /*
372
     * Default overlap favors quality for small palettes and speed when
373
     * colors are plentiful. The environment can override this policy.
374
     */
375
    if (ncolors <= 32) {
34,808✔
376
        overlap = 6;
14,928✔
377
    } else {
9,870✔
378
        overlap = 0;
15,655✔
379
    }
380
    if (text != NULL && text[0] != '\0') {
34,808!
381
        errno = 0;
100✔
382
        parsed = strtol(text, &endptr, 10);
100✔
383
        if (endptr != text && errno != ERANGE && parsed >= 0) {
100!
384
            if (parsed > INT_MAX) {
70!
385
                parsed = INT_MAX;
386
            }
387
            overlap = (int)parsed;
85✔
388
        }
45✔
389
    }
45✔
390
    if (overlap < 0) {
24,957!
391
        overlap = 0;
392
    }
393
    if (overlap > band_height / 2) {
32,636✔
394
        overlap = band_height / 2;
2,940✔
395
    }
612✔
396

397
    config->enabled = 1;
34,808✔
398
    config->band_height = band_height;
34,808✔
399
    config->overlap = overlap;
34,808✔
400
    config->dither_threads = dither_threads;
34,808✔
401
    config->encode_threads = encode_threads;
34,808✔
402
}
18,073!
403

404
#if SIXEL_ENABLE_THREADS
405
static int sixel_parallel_band_writer(char *data, int size, void *priv);
406
static int sixel_parallel_worker_main(sixel_thread_pool_job_t job,
407
                                      void *userdata,
408
                                      void *workspace);
409
static SIXELSTATUS
410
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
411
                             sixel_index_t *pixels,
412
                             int width,
413
                             int height,
414
                             int ncolors,
415
                             int keycolor,
416
                             unsigned char *palstate,
417
                             sixel_output_t *output,
418
                             sixel_allocator_t *allocator,
419
                             int requested_threads,
420
                             int worker_capacity,
421
                             int queue_capacity,
422
                             int pin_threads,
423
                             sixel_timeline_logger_t *logger);
424
static SIXELSTATUS sixel_parallel_context_grow(sixel_parallel_context_t *ctx,
425
                                              int target_threads);
426
static void sixel_parallel_submit_band(sixel_parallel_context_t *ctx,
427
                                       int band_index);
428
static SIXELSTATUS sixel_parallel_context_wait(sixel_parallel_context_t *ctx,
429
                                               int force_abort);
430
static void sixel_parallel_palette_row_ready(void *priv, int row_index);
431
static SIXELSTATUS sixel_encode_emit_palette(int bodyonly,
432
                          int ncolors,
433
                          int keycolor,
434
                          unsigned char const *palette,
435
                          float const *palette_float,
436
                          sixel_output_t *output);
437

438
static void
439
sixel_parallel_context_init(sixel_parallel_context_t *ctx)
39,499✔
440
{
441
    memset(ctx, 0, sizeof(*ctx));
39,499✔
442
    ctx->pixels = NULL;
39,499✔
443
    ctx->keycolor = (-1);
39,499✔
444
    ctx->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
39,499✔
445
    ctx->writer_error = SIXEL_OK;
39,499✔
446
}
32,617✔
447

448
static void
449
sixel_parallel_worker_release_nodes(sixel_parallel_worker_state_t *state,
313,021✔
450
                                    sixel_allocator_t *allocator)
451
{
452
    sixel_node_t *np;
161,835✔
453

454
    if (state == NULL || state->output == NULL) {
313,021!
455
        return;
456
    }
457

458
    while ((np = state->output->node_free) != NULL) {
14,197,488!
459
        state->output->node_free = np->next;
13,884,467✔
460
        sixel_allocator_free(allocator, np);
13,884,467✔
461
    }
462
    state->output->node_top = NULL;
313,021✔
463
}
159,064!
464

465
static void
466
sixel_parallel_worker_cleanup(sixel_parallel_worker_state_t *state,
114,227✔
467
                              sixel_allocator_t *allocator)
468
{
469
    if (state == NULL) {
114,227✔
470
        return;
35,078✔
471
    }
472
    sixel_parallel_worker_release_nodes(state, allocator);
69,876✔
473
    if (state->output != NULL) {
69,876!
474
        sixel_output_unref(state->output);
69,876✔
475
        state->output = NULL;
69,876✔
476
    }
31,883✔
477
    sixel_encode_work_cleanup(&state->work, allocator);
69,876✔
478
    sixel_band_state_reset(&state->band);
69,876✔
479
    state->initialized = 0;
69,876✔
480
    state->index = 0;
69,876✔
481
    state->writer_error = SIXEL_OK;
69,876✔
482
    state->band_buffer = NULL;
69,876✔
483
    state->context = NULL;
69,876✔
484
}
56,893✔
485

486
static void
487
sixel_parallel_context_cleanup(sixel_parallel_context_t *ctx)
39,499✔
488
{
489
    int i;
20,621✔
490

491
    if (ctx->workers != NULL) {
39,499!
492
        for (i = 0; i < ctx->worker_capacity; i++) {
153,726!
493
            sixel_parallel_worker_cleanup(ctx->workers[i], ctx->allocator);
114,227✔
494
        }
56,893✔
495
        free(ctx->workers);
39,499✔
496
        ctx->workers = NULL;
39,499✔
497
    }
20,728✔
498
    sixel_parallel_writer_stop(ctx, 1);
39,499✔
499
    if (ctx->bands != NULL) {
39,499!
500
        if (ctx->band_count < 0) {
39,499!
501
            ctx->band_count = 0;
502
        }
503
#if defined(_MSC_VER)
504
#pragma warning(push)
505
#pragma warning(disable : 6001)
506
#endif
507
        for (i = 0; i < ctx->band_count; i++) {
282,699!
508
            free(ctx->bands[i].data);
243,200✔
509
            ctx->bands[i].data = NULL;
243,200✔
510
        }
127,236✔
511
#if defined(_MSC_VER)
512
#pragma warning(pop)
513
#endif
514
        free(ctx->bands);
39,499✔
515
        ctx->bands = NULL;
39,499✔
516
    }
20,728✔
517
    ctx->band_count = 0;
39,499✔
518
    if (ctx->pool != NULL) {
39,499!
519
        ctx->pool->vtbl->unref(ctx->pool);
39,499✔
520
        ctx->pool = NULL;
39,499✔
521
    }
20,728✔
522
    if (ctx->cond_ready) {
39,499!
523
        sixel_cond_destroy(&ctx->cond_band_ready);
39,499✔
524
        ctx->cond_ready = 0;
39,499✔
525
    }
20,728✔
526
    if (ctx->mutex_ready) {
39,499!
527
        sixel_mutex_destroy(&ctx->mutex);
39,499✔
528
        ctx->mutex_ready = 0;
39,499✔
529
    }
20,728✔
530
}
39,499✔
531

532
/*
533
 * Abort the pipeline when either a worker or the writer encounters an error.
534
 * The helper normalizes the `writer_error` bookkeeping so later callers see a
535
 * consistent stop request regardless of whether the mutex has been
536
 * initialized.
537
 */
538
static void
539
sixel_parallel_context_abort_locked(sixel_parallel_context_t *ctx,
540
                                    SIXELSTATUS status)
541
{
542
    if (ctx == NULL) {
×
543
        return;
544
    }
545
    if (!ctx->mutex_ready) {
×
546
        if (ctx->writer_error == SIXEL_OK) {
×
547
            ctx->writer_error = status;
548
        }
549
        ctx->writer_should_stop = 1;
550
        return;
551
    }
552

553
    sixel_mutex_lock(&ctx->mutex);
554
    if (ctx->writer_error == SIXEL_OK) {
×
555
        ctx->writer_error = status;
556
    }
557
    ctx->writer_should_stop = 1;
558
    sixel_cond_broadcast(&ctx->cond_band_ready);
559
    sixel_mutex_unlock(&ctx->mutex);
560
}
561

562
/*
563
 * Determine whether additional bands should be queued or executed.  The
564
 * producer and workers call this guard to avoid redundant work once the writer
565
 * decides to shut the pipeline down.
566
 */
567
static int
568
sixel_parallel_jobs_allowed(sixel_parallel_context_t *ctx)
243,185✔
569
{
570
    int accept;
126,952✔
571

572
    if (ctx == NULL) {
243,185✔
573
        return 0;
574
    }
575
    if (!ctx->mutex_ready) {
243,185!
576
        if (ctx->writer_should_stop || ctx->writer_error != SIXEL_OK) {
×
577
            return 0;
578
        }
579
        return 1;
580
    }
581

582
    sixel_mutex_lock(&ctx->mutex);
243,185✔
583
    accept = (!ctx->writer_should_stop && ctx->writer_error == SIXEL_OK);
243,202!
584
    sixel_mutex_unlock(&ctx->mutex);
243,202✔
585
    return accept;
243,201✔
586
}
127,236✔
587

588
static void
589
sixel_parallel_worker_reset(sixel_parallel_worker_state_t *state)
243,198✔
590
{
591
    if (state == NULL || state->output == NULL) {
243,198!
592
        return;
4✔
593
    }
594

595
    sixel_band_state_reset(&state->band);
243,195✔
596
    sixel_band_clear_map(&state->work);
243,195✔
597
    /* Parallel workers reset band-local buffers and output. */
598
    state->writer_error = SIXEL_OK;
243,170✔
599
    state->output->pos = 0;
243,170✔
600
    state->output->save_count = 0;
243,170✔
601
    state->output->save_pixel = 0;
243,170✔
602
    state->output->active_palette = (-1);
243,170✔
603
    state->output->node_top = NULL;
243,170✔
604
    state->output->node_free = NULL;
243,170✔
605
}
127,233✔
606

607
static SIXELSTATUS
608
sixel_parallel_worker_prepare(sixel_parallel_worker_state_t *state,
243,197✔
609
                              sixel_parallel_context_t *ctx)
610
{
611
    SIXELSTATUS status;
126,957✔
612

613
    if (state->initialized) {
243,197✔
614
        return SIXEL_OK;
137,229✔
615
    }
616

617
    sixel_encode_work_init(&state->work);
69,874✔
618
    sixel_band_state_reset(&state->band);
69,865✔
619
    state->writer_error = SIXEL_OK;
69,863✔
620
    state->band_buffer = NULL;
69,863✔
621
    state->context = ctx;
69,863✔
622

623
    status = sixel_encode_work_allocate(&state->work,
118,653✔
624
                                        ctx->width,
31,883✔
625
                                        ctx->ncolors,
31,883✔
626
                                        ctx->allocator);
31,883✔
627
    if (SIXEL_FAILED(status)) {
69,873!
628
        return status;
629
    }
630

631
    status = sixel_encoder_core_create_output_from_factory(&state->output,
101,754✔
632
                                              sixel_parallel_band_writer,
633
                                              state,
31,882✔
634
                                              ctx->allocator);
31,882✔
635
    if (SIXEL_FAILED(status)) {
69,861!
636
        sixel_encode_work_cleanup(&state->work, ctx->allocator);
637
        return status;
638
    }
639

640
    state->output->has_8bit_control = ctx->output->has_8bit_control;
69,859✔
641
    state->output->has_sixel_scrolling = ctx->output->has_sixel_scrolling;
69,859✔
642
    state->output->has_sdm_glitch = ctx->output->has_sdm_glitch;
69,859✔
643
    state->output->has_gri_arg_limit = ctx->output->has_gri_arg_limit;
69,859✔
644
    state->output->skip_dcs_envelope = 1;
69,859✔
645
    state->output->skip_header = 1;
69,859✔
646
    state->output->palette_type = ctx->output->palette_type;
69,859✔
647
    state->output->colorspace = ctx->output->colorspace;
69,859✔
648
    state->output->source_colorspace = ctx->output->source_colorspace;
69,859✔
649
    state->output->pixelformat = ctx->output->pixelformat;
69,859✔
650
    state->output->penetrate_multiplexer =
69,859✔
651
        ctx->output->penetrate_multiplexer;
69,859✔
652
    state->output->encode_policy = ctx->output->encode_policy;
69,859✔
653
    state->output->ormode = ctx->output->ormode;
69,859✔
654

655
    state->initialized = 1;
69,859✔
656
    state->index = (-1);
69,859✔
657

658
    if (ctx->mutex_ready) {
69,859!
659
        sixel_mutex_lock(&ctx->mutex);
69,847✔
660
    }
31,870✔
661
    if (ctx->worker_registered < ctx->worker_capacity) {
69,878!
662
        state->index = ctx->worker_registered;
69,876✔
663
        ctx->workers[state->index] = state;
69,876✔
664
        ctx->worker_registered += 1;
69,876✔
665
    }
31,883✔
666
    if (ctx->mutex_ready) {
69,878!
667
        sixel_mutex_unlock(&ctx->mutex);
69,876✔
668
    }
31,883✔
669

670
    if (state->index < 0) {
69,878!
671
        sixel_parallel_worker_cleanup(state, ctx->allocator);
672
        return SIXEL_RUNTIME_ERROR;
673
    }
674

675
    return SIXEL_OK;
52,967✔
676
}
127,236✔
677

678
static SIXELSTATUS
679
sixel_parallel_context_grow(sixel_parallel_context_t *ctx, int target_threads)
34,624✔
680
{
681
    int capped_target;
17,978✔
682
    int delta;
17,978✔
683
    int status;
17,978✔
684

685
    if (ctx == NULL || ctx->pool == NULL) {
34,624!
686
        return SIXEL_BAD_ARGUMENT;
687
    }
688

689
    capped_target = target_threads;
34,624✔
690
    if (capped_target > ctx->worker_capacity) {
34,624✔
691
        capped_target = ctx->worker_capacity;
11,954✔
692
    }
5,696✔
693
    if (ctx->band_count > 0 && capped_target > ctx->band_count) {
34,624!
694
        capped_target = ctx->band_count;
7,591✔
695
    }
696
    if (capped_target <= ctx->thread_count) {
34,624✔
697
        return SIXEL_OK;
7,544✔
698
    }
699

700
    delta = capped_target - ctx->thread_count;
25,016✔
701
    status = ctx->pool->vtbl->grow(ctx->pool, delta);
25,016✔
702
    if (SIXEL_FAILED(status)) {
25,016!
703
        return status;
704
    }
705
    ctx->thread_count += delta;
25,016✔
706

707
    if (ctx->logger != NULL) {
25,016✔
708
        sixel_timeline_logger_logf(ctx->logger,
43✔
709
                          "controller",
710
                          "encode",
711
                          "grow_workers",
712
                          -1);
713
    }
21✔
714

715
    return SIXEL_OK;
19,489✔
716
}
17,988✔
717

718
static int
719
sixel_parallel_band_writer(char *data, int size, void *priv)
242,340✔
720
{
721
    sixel_parallel_worker_state_t *state;
126,531✔
722
    sixel_parallel_band_buffer_t *band;
126,531✔
723
    size_t required;
126,531✔
724
    size_t capacity;
126,531✔
725
    size_t new_capacity;
126,531✔
726
    unsigned char *tmp;
126,531✔
727

728
    state = (sixel_parallel_worker_state_t *)priv;
242,340✔
729
    if (state == NULL || data == NULL || size <= 0) {
242,340!
730
        return size;
103✔
731
    }
732
    band = state->band_buffer;
242,335✔
733
    if (band == NULL) {
242,335!
734
        state->writer_error = SIXEL_RUNTIME_ERROR;
735
        return size;
736
    }
737
    if (state->writer_error != SIXEL_OK) {
242,332!
738
        return size;
739
    }
740

741
    required = band->used + (size_t)size;
242,330✔
742
    if (required < band->used) {
242,330!
743
        state->writer_error = SIXEL_BAD_INTEGER_OVERFLOW;
744
        return size;
745
    }
746
    capacity = band->size;
242,331✔
747
    if (required > capacity) {
242,331!
748
        if (capacity == 0) {
242,322!
749
            new_capacity = (size_t)SIXEL_OUTPUT_PACKET_SIZE;
189,482✔
750
        } else {
126,784✔
751
            new_capacity = capacity;
41✔
752
        }
753
        while (new_capacity < required) {
242,324!
754
            if (new_capacity > SIZE_MAX / 2) {
×
755
                new_capacity = required;
756
                break;
757
            }
758
            new_capacity *= 2;
759
        }
760
        tmp = (unsigned char *)realloc(band->data, new_capacity);
242,255✔
761
        if (tmp == NULL) {
242,255!
762
            state->writer_error = SIXEL_BAD_ALLOCATION;
763
            return size;
764
        }
765
        band->data = tmp;
242,255✔
766
        band->size = new_capacity;
242,255✔
767
    }
126,758✔
768

769
    memcpy(band->data + band->used, data, (size_t)size);
242,266✔
770
    band->used += (size_t)size;
242,266✔
771

772
    return size;
242,266✔
773
}
126,755✔
774

775
static SIXELSTATUS
776
sixel_parallel_create_pool(sixel_thread_pool_t **pool,
39,499✔
777
                           int threads,
778
                           int queue_depth,
779
                           size_t workspace_size,
780
                           sixel_thread_pool_worker_function_t worker,
781
                           void *userdata)
782
{
783
    sixel_threadpool_service_t *service;
20,621✔
784
    sixel_thread_pool_create_request_t request;
20,621✔
785
    void *service_object;
20,621✔
786
    SIXELSTATUS status;
20,621✔
787

788
    if (pool != NULL) {
39,499!
789
        *pool = NULL;
39,499✔
790
    }
20,728✔
791
    if (pool == NULL) {
39,499!
792
        return SIXEL_BAD_ARGUMENT;
793
    }
794

795
    service = NULL;
39,499✔
796
    service_object = NULL;
39,499✔
797
    status = sixel_components_getservice("services/threadpool",
39,499✔
798
                                         &service_object);
799
    if (SIXEL_FAILED(status)) {
39,499!
800
        return status;
801
    }
802
    service = (sixel_threadpool_service_t *)service_object;
39,499✔
803
    if (service == NULL || service->vtbl == NULL ||
39,499!
804
        service->vtbl->create_pool == NULL) {
39,499!
805
        if (service != NULL && service->vtbl != NULL &&
×
806
            service->vtbl->unref != NULL) {
×
807
            service->vtbl->unref(service);
808
        }
809
        return SIXEL_BAD_ARGUMENT;
810
    }
811

812
    request.threads = threads;
39,499✔
813
    request.queue_size = queue_depth;
39,499✔
814
    request.workspace_size = workspace_size;
39,499✔
815
    request.worker = worker;
39,499✔
816
    request.userdata = userdata;
39,499✔
817
    request.workspace_cleanup = NULL;
39,499✔
818
    status = service->vtbl->create_pool(service, &request, pool);
39,499✔
819
    if (service->vtbl->unref != NULL) {
39,499!
820
        service->vtbl->unref(service);
39,499✔
821
    }
20,728✔
822

823
    return status;
30,937✔
824
}
20,728✔
825

826
static SIXELSTATUS
827
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
39,499✔
828
                             sixel_index_t *pixels,
829
                             int width,
830
                             int height,
831
                             int ncolors,
832
                             int keycolor,
833
                             unsigned char *palstate,
834
                             sixel_output_t *output,
835
                             sixel_allocator_t *allocator,
836
                             int requested_threads,
837
                             int worker_capacity,
838
                             int queue_capacity,
839
                             int pin_threads,
840
                             sixel_timeline_logger_t *logger)
841
{
842
    SIXELSTATUS status;
20,621✔
843
    int nbands;
20,621✔
844
    int threads;
20,621✔
845
    int i;
20,621✔
846

847
    if (ctx == NULL || pixels == NULL || output == NULL) {
39,499!
848
        return SIXEL_BAD_ARGUMENT;
849
    }
850

851
    ctx->pixels = pixels;
39,499✔
852
    ctx->width = width;
39,499✔
853
    ctx->height = height;
39,499✔
854
    ctx->ncolors = ncolors;
39,499✔
855
    ctx->keycolor = keycolor;
39,499✔
856
    ctx->palstate = palstate;
39,499✔
857
    ctx->encode_policy = output->encode_policy;
39,499✔
858
    ctx->allocator = allocator;
39,499✔
859
    ctx->output = output;
39,499✔
860
    ctx->logger = logger;
39,499✔
861
    ctx->pin_threads = (pin_threads != 0) ? 1 : 0;
39,499!
862
    ctx->bands = NULL;
39,499✔
863
    ctx->band_count = 0;
39,499✔
864
    ctx->workers = NULL;
39,499✔
865
    ctx->worker_capacity = 0;
39,499✔
866

867
    nbands = (height + 5) / 6;
39,499✔
868
    if (nbands <= 0) {
39,499!
869
        return SIXEL_OK;
870
    }
871
    threads = requested_threads;
39,499✔
872
    if (threads > nbands) {
39,499!
873
        threads = nbands;
874
    }
875
    if (threads < 1) {
37,011!
876
        threads = 1;
877
    }
878
    ctx->thread_count = threads;
37,011✔
879
    if (worker_capacity < threads) {
37,011!
880
        worker_capacity = threads;
881
    }
882
    if (worker_capacity > nbands) {
37,011!
883
        worker_capacity = nbands;
884
    }
885
    ctx->worker_capacity = worker_capacity;
37,024✔
886

887
    if (logger != NULL) {
37,024✔
888
        sixel_timeline_logger_logf(logger,
243✔
889
                          "controller",
890
                          "encode",
891
                          "context_begin",
892
                          -1);
893
    }
111✔
894

895
    ctx->bands = (sixel_parallel_band_buffer_t *)calloc((size_t)nbands,
39,499✔
896
                                                        sizeof(*ctx->bands));
897
    if (ctx->bands == NULL) {
39,499!
898
        return SIXEL_BAD_ALLOCATION;
899
    }
900
    for (i = 0; i < nbands; ++i) {
282,701!
901
        ctx->bands[i].data = NULL;
243,202✔
902
        ctx->bands[i].size = 0;
243,202✔
903
        ctx->bands[i].used = 0;
243,202✔
904
        ctx->bands[i].status = SIXEL_OK;
243,202✔
905
        ctx->bands[i].ready = 0;
243,202✔
906
        ctx->bands[i].dispatched = 0;
243,202✔
907
    }
127,236✔
908
    ctx->band_count = nbands;
39,499✔
909

910
    ctx->workers = (sixel_parallel_worker_state_t **)
39,499✔
911
        calloc((size_t)ctx->worker_capacity, sizeof(*ctx->workers));
39,499✔
912
    if (ctx->workers == NULL) {
39,499!
913
        return SIXEL_BAD_ALLOCATION;
914
    }
915

916
    status = sixel_mutex_init(&ctx->mutex);
39,499✔
917
    if (status != SIXEL_OK) {
39,499!
918
        return status;
919
    }
920
    ctx->mutex_ready = 1;
39,499✔
921

922
    status = sixel_cond_init(&ctx->cond_band_ready);
39,499✔
923
    if (status != SIXEL_OK) {
39,499!
924
        return status;
925
    }
926
    ctx->cond_ready = 1;
39,499✔
927

928
    ctx->queue_capacity = queue_capacity;
39,499✔
929
    if (ctx->queue_capacity < 1) {
39,499!
930
        ctx->queue_capacity = nbands;
931
    }
932
    if (ctx->queue_capacity > nbands) {
37,011!
933
        ctx->queue_capacity = nbands;
934
    }
935

936
    status = sixel_parallel_create_pool(
39,499✔
937
        &ctx->pool,
20,728✔
938
        threads,
20,728✔
939
        ctx->queue_capacity,
20,728✔
940
        sizeof(sixel_parallel_worker_state_t),
941
        sixel_parallel_worker_main,
942
        ctx);
20,728✔
943
    if (SIXEL_FAILED(status)) {
39,499!
944
        return status;
945
    }
946

947
    ctx->pool->vtbl->set_affinity(ctx->pool, ctx->pin_threads);
39,499✔
948

949
    /* Initialize writer-visible fields before the writer thread starts.
950
     * Serialize initialization of writer-visible state so the writer thread
951
     * cannot observe partially initialized fields on startup.
952
     */
953
    sixel_mutex_lock(&ctx->mutex);
39,499✔
954
    ctx->next_band_to_flush = 0;
39,499✔
955
    ctx->writer_should_stop = 0;
39,499✔
956
    ctx->writer_error = SIXEL_OK;
39,499✔
957

958
    status = sixel_thread_create(&ctx->writer_thread,
60,227✔
959
                                 sixel_parallel_writer_main,
960
                                 ctx);
20,728✔
961
    if (SIXEL_FAILED(status)) {
39,499!
962
        sixel_mutex_unlock(&ctx->mutex);
963
        return status;
964
    }
965
    ctx->writer_started = 1;
39,499✔
966
    sixel_mutex_unlock(&ctx->mutex);
39,499✔
967

968
    return SIXEL_OK;
39,499✔
969
}
20,728✔
970

971
static void
972
sixel_parallel_submit_band(sixel_parallel_context_t *ctx, int band_index)
243,200✔
973
{
974
    sixel_thread_pool_job_t job;
126,962✔
975
    SIXELSTATUS status;
126,962✔
976
    int dispatch;
126,962✔
977

978
    if (ctx == NULL || ctx->pool == NULL) {
243,200!
979
        return;
1✔
980
    }
981
    if (band_index < 0 || band_index >= ctx->band_count) {
243,200!
982
        return;
1✔
983
    }
984

985
    dispatch = 0;
243,199✔
986
    /*
987
     * Multiple producers may notify the same band when PaletteApply runs in
988
     * parallel.  Guard the dispatched flag so only the first notifier pushes
989
     * work into the encoder queue.
990
     */
991
    if (ctx->mutex_ready) {
243,199!
992
        sixel_mutex_lock(&ctx->mutex);
243,199✔
993
        if (!ctx->bands[band_index].dispatched
359,745!
994
                && !ctx->writer_should_stop
232,908!
995
                && ctx->writer_error == SIXEL_OK) {
243,202!
996
            ctx->bands[band_index].dispatched = 1;
243,202✔
997
            dispatch = 1;
243,202✔
998
        }
127,236✔
999
        sixel_mutex_unlock(&ctx->mutex);
243,202✔
1000
    } else {
127,236✔
1001
        if (!ctx->bands[band_index].dispatched
1!
1002
                && sixel_parallel_jobs_allowed(ctx)) {
×
1003
            ctx->bands[band_index].dispatched = 1;
1004
            dispatch = 1;
1005
        }
1006
    }
1007

1008
    if (!dispatch) {
243,202!
1009
        return;
1010
    }
1011

1012
    sixel_fence_release();
243,202✔
1013
    if (ctx->logger != NULL) {
243,202✔
1014
        sixel_timeline_logger_logf(ctx->logger,
1,178✔
1015
                          "controller",
1016
                          "encode",
1017
                          "dispatch",
1018
                          band_index);
369✔
1019
    }
369✔
1020
    job.band_index = band_index;
227,935✔
1021
    status = ctx->pool->vtbl->push(ctx->pool, job);
227,935✔
1022
    if (SIXEL_FAILED(status)) {
227,935!
1023
        if (ctx->mutex_ready) {
×
1024
            sixel_mutex_lock(&ctx->mutex);
1025
            sixel_parallel_context_abort_locked(ctx, status);
1026
            sixel_mutex_unlock(&ctx->mutex);
1027
        } else {
1028
            ctx->writer_error = status;
1029
        }
1030
    }
1031
}
127,236!
1032

1033
static SIXELSTATUS
1034
sixel_parallel_context_wait(sixel_parallel_context_t *ctx, int force_abort)
39,499✔
1035
{
1036
    int pool_error;
20,621✔
1037

1038
    if (ctx == NULL || ctx->pool == NULL) {
39,499!
1039
        return SIXEL_BAD_ARGUMENT;
1040
    }
1041

1042
    ctx->pool->vtbl->finish(ctx->pool);
39,499✔
1043
    pool_error = ctx->pool->vtbl->get_error(ctx->pool);
39,499✔
1044
    sixel_parallel_writer_stop(ctx, force_abort || pool_error != SIXEL_OK);
39,499!
1045

1046
    if (pool_error != SIXEL_OK) {
39,499!
1047
        return pool_error;
1048
    }
1049
    if (ctx->writer_error != SIXEL_OK) {
39,499!
1050
        return ctx->writer_error;
1051
    }
1052

1053
    return SIXEL_OK;
30,937✔
1054
}
20,728✔
1055

1056
/*
1057
 * Producer callback invoked after PaletteApply finishes a scanline.  The
1058
 * helper promotes every sixth row (or the final partial band) into the job
1059
 * queue so workers can begin encoding while dithering continues.
1060
 */
1061
static void
1062
sixel_parallel_palette_row_ready(void *priv, int row_index)
1,187,156✔
1063
{
1064
    sixel_parallel_row_notifier_t *notifier;
619,985✔
1065
    sixel_parallel_context_t *ctx;
619,985✔
1066
    sixel_timeline_logger_t *logger;
619,985✔
1067
    int band_height;
619,985✔
1068
    int band_index;
619,985✔
1069

1070
    notifier = (sixel_parallel_row_notifier_t *)priv;
1,187,156✔
1071
    if (notifier == NULL) {
1,187,156✔
1072
        return;
1073
    }
1074
    ctx = notifier->context;
1,187,156✔
1075
    logger = notifier->logger;
1,187,156✔
1076
    if (ctx == NULL || ctx->band_count <= 0 || ctx->height <= 0) {
1,187,156!
1077
        return;
11✔
1078
    }
1079
    if (row_index < 0) {
1,187,156!
1080
        return;
1081
    }
1082
    band_height = notifier->band_height;
1,187,156✔
1083
    if (band_height < 1) {
1,187,156!
1084
        band_height = 6;
1085
    }
1086
    if ((row_index % band_height) != band_height - 1
1,175,614✔
1087
            && row_index != ctx->height - 1) {
1,089,652!
1088
        return;
763,654✔
1089
    }
1090

1091
    band_index = row_index / band_height;
197,148✔
1092
    if (band_index >= ctx->band_count) {
197,148!
1093
        band_index = ctx->band_count - 1;
1094
    }
1095
    if (band_index < 0) {
210,671!
1096
        return;
1097
    }
1098

1099
    if (logger != NULL) {
210,671✔
1100
        sixel_timeline_logger_logf(logger,
1,178✔
1101
                          "controller",
1102
                          "encode",
1103
                          "row_gate",
1104
                          band_index);
369✔
1105
    }
369✔
1106

1107
    sixel_parallel_submit_band(ctx, band_index);
210,671✔
1108
}
621,845!
1109

1110
static SIXELSTATUS
1111
sixel_parallel_flush_band(sixel_parallel_context_t *ctx, int band_index)
243,202✔
1112
{
1113
    sixel_parallel_band_buffer_t *band;
126,962✔
1114
    size_t offset;
126,962✔
1115
    size_t chunk;
126,962✔
1116

1117
    band = &ctx->bands[band_index];
243,202✔
1118
    if (ctx->logger != NULL) {
243,202✔
1119
        sixel_timeline_logger_logf(ctx->logger,
1,178✔
1120
                          "worker",
1121
                          "encode",
1122
                          "writer_flush",
1123
                          band_index);
369✔
1124
    }
369✔
1125
    offset = 0;
190,197✔
1126
    while (offset < band->used) {
489,530!
1127
        chunk = band->used - offset;
246,328✔
1128
        if (chunk > (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos)) {
246,328✔
1129
            chunk = (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos);
3,080✔
1130
        }
1,988✔
1131
        memcpy(ctx->output->buffer + ctx->output->pos,
257,162✔
1132
               band->data + offset,
128,351✔
1133
               chunk);
10,834✔
1134
        sixel_advance(ctx->output, (int)chunk);
246,328✔
1135
        offset += chunk;
246,328✔
1136
    }
1137
    return SIXEL_OK;
243,202✔
1138
}
73,957✔
1139

1140
static int
1141
sixel_parallel_worker_main(sixel_thread_pool_job_t job,
243,190✔
1142
                           void *userdata,
1143
                           void *workspace)
1144
{
1145
    sixel_parallel_context_t *ctx;
126,952✔
1146
    sixel_parallel_worker_state_t *state;
126,952✔
1147
    sixel_parallel_band_buffer_t *band;
126,952✔
1148
    SIXELSTATUS status;
126,952✔
1149
    int band_index;
126,952✔
1150
    int band_start;
126,952✔
1151
    int band_height;
126,952✔
1152
    int row_index;
126,952✔
1153
    int absolute_row;
126,952✔
1154
    int last_row_index;
126,952✔
1155

1156
    ctx = (sixel_parallel_context_t *)userdata;
243,190✔
1157
    state = (sixel_parallel_worker_state_t *)workspace;
243,190✔
1158

1159
    if (ctx == NULL || state == NULL) {
243,190!
1160
        return SIXEL_BAD_ARGUMENT;
13✔
1161
    }
1162

1163
    band = NULL;
243,189✔
1164
    status = SIXEL_OK;
243,189✔
1165
    band_index = job.band_index;
243,189✔
1166
    band_start = 0;
243,189✔
1167
    band_height = 0;
243,189✔
1168
    last_row_index = -1;
243,189✔
1169
    if (band_index < 0 || band_index >= ctx->band_count) {
243,189!
1170
        status = SIXEL_BAD_ARGUMENT;
70✔
1171
        goto cleanup;
70✔
1172
    }
1173

1174
    band = &ctx->bands[band_index];
243,120✔
1175
    if (ctx->mutex_ready) {
243,120!
1176
        /* Synchronize band state reset with the writer thread. */
1177
        sixel_mutex_lock(&ctx->mutex);
243,120✔
1178
        band->used = 0;
243,130✔
1179
        band->status = SIXEL_OK;
243,130✔
1180
        band->ready = 0;
243,130✔
1181
        sixel_mutex_unlock(&ctx->mutex);
243,130✔
1182
    } else {
127,164✔
1183
        band->used = 0;
1184
        band->status = SIXEL_OK;
1185
        band->ready = 0;
1186
    }
1187

1188
    sixel_fence_acquire();
243,128✔
1189

1190
    status = sixel_parallel_worker_prepare(state, ctx);
243,128✔
1191
    if (SIXEL_FAILED(status)) {
243,127!
1192
        goto cleanup;
1193
    }
1194

1195
    if (!sixel_parallel_jobs_allowed(ctx)) {
243,131!
1196
        if (ctx->mutex_ready) {
×
1197
            sixel_mutex_lock(&ctx->mutex);
1198
            if (ctx->writer_error != SIXEL_OK) {
×
1199
                status = ctx->writer_error;
1200
            } else {
1201
                status = SIXEL_RUNTIME_ERROR;
1202
            }
1203
            sixel_mutex_unlock(&ctx->mutex);
1204
        } else if (ctx->writer_error != SIXEL_OK) {
×
1205
            status = ctx->writer_error;
1206
        } else {
1207
            status = SIXEL_RUNTIME_ERROR;
1208
        }
1209
        goto cleanup;
1210
    }
1211

1212
    state->band_buffer = band;
243,130✔
1213
    sixel_parallel_worker_reset(state);
243,130✔
1214

1215
    band_start = band_index * 6;
243,105✔
1216
    band_height = ctx->height - band_start;
243,105✔
1217
    if (band_height > 6) {
243,105✔
1218
        band_height = 6;
159,251✔
1219
    }
106,505✔
1220
    if (band_height <= 0) {
198,683!
1221
        goto cleanup;
1222
    }
1223

1224
    if (ctx->logger != NULL) {
243,105✔
1225
        sixel_timeline_logger_logf(ctx->logger,
1,178✔
1226
                          "worker",
1227
                          "encode",
1228
                          "worker_start",
1229
                          band_index);
369✔
1230
    }
369✔
1231

1232
    for (row_index = 0; row_index < band_height; row_index++) {
1,615,203!
1233
        absolute_row = band_start + row_index;
1,372,033✔
1234
        status = sixel_band_classify_row(&state->work,
2,087,160✔
1235
                                         &state->band,
715,127✔
1236
                                         ctx->pixels,
715,127✔
1237
                                         ctx->width,
715,127✔
1238
                                         absolute_row,
715,127✔
1239
                                         ctx->ncolors,
715,127✔
1240
                                         ctx->keycolor,
715,127✔
1241
                                         ctx->palstate,
715,127✔
1242
                                         ctx->encode_policy);
715,127✔
1243
        if (SIXEL_FAILED(status)) {
1,372,102!
1244
            goto cleanup;
1245
        }
1246
    }
715,121✔
1247

1248
    status = sixel_band_compose(&state->work,
370,340✔
1249
                                &state->band,
127,166✔
1250
                                state->output,
127,166✔
1251
                                ctx->width,
127,166✔
1252
                                ctx->ncolors,
127,166✔
1253
                                ctx->keycolor,
127,166✔
1254
                                ctx->allocator);
127,166✔
1255
    if (SIXEL_FAILED(status)) {
243,125!
1256
        goto cleanup;
1257
    }
1258

1259
    last_row_index = band_start + band_height - 1;
243,124✔
1260
    status = sixel_band_emit(&state->work,
370,289✔
1261
                             &state->band,
127,165✔
1262
                             state->output,
127,165✔
1263
                             ctx->ncolors,
127,165✔
1264
                             ctx->keycolor,
127,165✔
1265
                             last_row_index);
127,165✔
1266
    if (SIXEL_FAILED(status)) {
243,110!
1267
        goto cleanup;
1268
    }
1269

1270
    status = sixel_put_flash(state->output);
243,105✔
1271
    if (SIXEL_FAILED(status)) {
243,097!
1272
        goto cleanup;
1273
    }
1274

1275
    if (state->output->pos > 0) {
243,098✔
1276
        state->writer_error = sixel_output_write_bytes(
357,775✔
1277
            state->output,
126,787✔
1278
            (char *)state->output->buffer,
242,263✔
1279
            state->output->pos);
189,480✔
1280
        state->output->pos = 0;
242,299✔
1281
    }
126,787✔
1282
    if (state->writer_error != SIXEL_OK) {
243,134!
1283
        status = state->writer_error;
1284
        goto cleanup;
1285
    }
1286

1287
    sixel_band_finish(&state->work, &state->band);
243,134✔
1288
    status = SIXEL_OK;
243,125✔
1289

1290
cleanup:
115,964✔
1291
    sixel_parallel_worker_release_nodes(state, ctx->allocator);
243,194✔
1292
    if (band != NULL && ctx->mutex_ready && ctx->cond_ready) {
243,194!
1293
        sixel_fence_release();
243,154✔
1294
        sixel_mutex_lock(&ctx->mutex);
243,154✔
1295
        band->status = status;
243,156✔
1296
        band->ready = 1;
243,156✔
1297
        sixel_cond_broadcast(&ctx->cond_band_ready);
243,156✔
1298
        sixel_mutex_unlock(&ctx->mutex);
243,156✔
1299
    }
127,190✔
1300
    if (ctx->logger != NULL) {
258,502✔
1301
        sixel_timeline_logger_logf(ctx->logger,
1,178✔
1302
                          "worker",
1303
                          "encode",
1304
                          "worker_done",
1305
                          band_index);
369✔
1306
    }
369✔
1307
    if (SIXEL_FAILED(status)) {
243,188!
1308
        return status;
1309
    }
1310
    return SIXEL_OK;
190,183✔
1311
}
127,233✔
1312

1313
static void
1314
sixel_parallel_writer_stop(sixel_parallel_context_t *ctx, int force_abort)
78,998✔
1315
{
1316
    int should_signal;
41,242✔
1317

1318
    if (ctx == NULL || !ctx->writer_started) {
78,998!
1319
        return;
30,937✔
1320
    }
1321

1322
    should_signal = ctx->mutex_ready && ctx->cond_ready;
39,499!
1323
    if (should_signal) {
39,499!
1324
        sixel_mutex_lock(&ctx->mutex);
39,499✔
1325
        if (force_abort) {
39,499!
1326
            ctx->writer_should_stop = 1;
1327
        }
1328
        sixel_cond_broadcast(&ctx->cond_band_ready);
39,499✔
1329
        sixel_mutex_unlock(&ctx->mutex);
39,499✔
1330
    } else if (force_abort) {
20,728!
1331
        ctx->writer_should_stop = 1;
1332
    }
1333

1334
    sixel_thread_join(&ctx->writer_thread);
39,499✔
1335
    ctx->writer_started = 0;
39,499✔
1336
    ctx->writer_should_stop = 0;
39,499✔
1337
    if (ctx->logger != NULL) {
39,499✔
1338
        sixel_timeline_logger_logf(ctx->logger,
243✔
1339
                          "writer",
1340
                          "encode",
1341
                          "writer_stop",
1342
                          -1);
1343
    }
111✔
1344
}
41,456!
1345

1346
static int
1347
sixel_parallel_writer_main(void *arg)
39,499✔
1348
{
1349
    sixel_parallel_context_t *ctx;
20,621✔
1350
    sixel_parallel_band_buffer_t *band;
20,621✔
1351
    SIXELSTATUS status;
20,621✔
1352
    int band_index;
20,621✔
1353

1354
    ctx = (sixel_parallel_context_t *)arg;
39,499✔
1355
    if (ctx == NULL) {
39,499✔
1356
        return SIXEL_BAD_ARGUMENT;
1357
    }
1358

1359
    if (ctx->logger != NULL) {
39,499✔
1360
        sixel_timeline_logger_logf(ctx->logger,
243✔
1361
                                   "writer",
1362
                                   "encode",
1363
                                   "writer_start",
1364
                                   -1);
1365
    }
111✔
1366

1367
    for (;;) {
209,531✔
1368
        sixel_mutex_lock(&ctx->mutex);
282,701✔
1369
        while (!ctx->writer_should_stop &&
626,160!
1370
               ctx->next_band_to_flush < ctx->band_count) {
422,932!
1371
    band_index = ctx->next_band_to_flush;
383,433✔
1372
    band = &ctx->bands[band_index];
383,433✔
1373
    if (band->ready) {
383,433✔
1374
        break;
190,197✔
1375
    }
1376
            sixel_cond_wait(&ctx->cond_band_ready, &ctx->mutex);
140,231✔
1377
        }
1378

1379
        if (ctx->writer_should_stop) {
282,701!
1380
            sixel_mutex_unlock(&ctx->mutex);
1381
            break;
1382
        }
1383

1384
        if (ctx->next_band_to_flush >= ctx->band_count) {
282,701✔
1385
            sixel_mutex_unlock(&ctx->mutex);
39,499✔
1386
            break;
39,499✔
1387
        }
1388

1389
        band_index = ctx->next_band_to_flush;
243,202✔
1390
        band = &ctx->bands[band_index];
243,202✔
1391
        if (!band->ready) {
243,202!
1392
            sixel_mutex_unlock(&ctx->mutex);
1393
            continue;
1394
        }
1395
        band->ready = 0;
243,202✔
1396
        ctx->next_band_to_flush += 1;
243,202✔
1397
        sixel_mutex_unlock(&ctx->mutex);
243,202✔
1398

1399
        sixel_fence_acquire();
243,202✔
1400
        status = band->status;
243,202✔
1401
        if (ctx->logger != NULL) {
243,202✔
1402
            sixel_timeline_logger_logf(ctx->logger,
1,178✔
1403
                              "writer",
1404
                              "encode",
1405
                              "writer_dequeue",
1406
                              band_index);
369✔
1407
        }
369✔
1408
        if (SIXEL_SUCCEEDED(status)) {
243,202!
1409
            status = sixel_parallel_flush_band(ctx, band_index);
243,202✔
1410
        }
127,236✔
1411
        if (SIXEL_FAILED(status)) {
243,202!
1412
            sixel_parallel_context_abort_locked(ctx, status);
1413
            break;
1414
        }
1415
    }
1416

1417
    return SIXEL_OK;
30,937✔
1418
}
20,728✔
1419

1420
static SIXELSTATUS
1421
sixel_encode_body_parallel(sixel_index_t *pixels,
4,875✔
1422
                           int width,
1423
                           int height,
1424
                           int ncolors,
1425
                           int keycolor,
1426
                           sixel_output_t *output,
1427
                           unsigned char *palstate,
1428
                           sixel_allocator_t *allocator,
1429
                           int requested_threads,
1430
                           int pin_threads)
1431
{
1432
    sixel_parallel_context_t ctx = {0};
4,875✔
1433
    SIXELSTATUS status;
2,643✔
1434
    int nbands;
2,643✔
1435
    int threads;
2,643✔
1436
    int i;
2,643✔
1437
    int queue_depth;
2,643✔
1438
    sixel_timeline_logger_t *logger;
2,643✔
1439

1440
    sixel_parallel_context_init(&ctx);
4,875✔
1441
    sixel_timeline_logger_prepare_default(allocator, &logger);
4,875✔
1442
    nbands = (height + 5) / 6;
4,875✔
1443
    if (nbands <= 0) {
4,875!
1444
        sixel_timeline_logger_unref(logger);
1445
        return SIXEL_OK;
1446
    }
1447

1448
    threads = requested_threads;
4,875✔
1449
    if (threads > nbands) {
4,875✔
1450
        threads = nbands;
1,588✔
1451
    }
1,072✔
1452
    if (threads < 1) {
4,777!
1453
        threads = 1;
1454
    }
1455
    ctx.thread_count = threads;
4,814✔
1456
    queue_depth = threads * 3;
4,814✔
1457
    if (queue_depth > nbands) {
4,814✔
1458
        queue_depth = nbands;
3,194✔
1459
    }
2,222✔
1460
    if (queue_depth < 1) {
5,025!
1461
        queue_depth = 1;
1462
    }
1463

1464
    status = sixel_parallel_context_begin(&ctx,
4,875✔
1465
                                          pixels,
2,740✔
1466
                                          width,
2,740✔
1467
                                          height,
2,740✔
1468
                                          ncolors,
2,740✔
1469
                                          keycolor,
2,740✔
1470
                                          palstate,
2,740✔
1471
                                          output,
2,740✔
1472
                                          allocator,
2,740✔
1473
                                          threads,
2,740✔
1474
                                          threads,
2,740✔
1475
                                          queue_depth,
2,740✔
1476
                                          pin_threads,
2,740✔
1477
                                          logger);
2,740✔
1478
    if (SIXEL_FAILED(status)) {
4,875!
1479
        sixel_parallel_context_cleanup(&ctx);
1480
        sixel_timeline_logger_unref(logger);
1481
        return status;
1482
    }
1483

1484
    for (i = 0; i < nbands; i++) {
37,404!
1485
        sixel_parallel_submit_band(&ctx, i);
32,529✔
1486
    }
16,686✔
1487

1488
    status = sixel_parallel_context_wait(&ctx, 0);
4,875✔
1489
    if (SIXEL_FAILED(status)) {
4,875!
1490
        sixel_parallel_context_cleanup(&ctx);
1491
        sixel_timeline_logger_unref(logger);
1492
        return status;
1493
    }
1494

1495
    sixel_parallel_context_cleanup(&ctx);
4,875✔
1496
    sixel_timeline_logger_unref(logger);
4,875✔
1497
    return SIXEL_OK;
4,875✔
1498
}
2,740✔
1499
#endif
1500

1501
#if SIXEL_ENABLE_THREADS
1502
/*
1503
 * Execute PaletteApply, band encoding, and output emission as a pipeline.
1504
 * The producer owns the dithered index buffer and enqueues bands once every
1505
 * six rows have been produced.  Worker threads encode in parallel while the
1506
 * writer emits completed bands in-order to preserve deterministic output.
1507
 */
1508
static SIXELSTATUS
1509
sixel_encode_body_pipeline(unsigned char *pixels,
34,624✔
1510
                           int width,
1511
                           int height,
1512
                           unsigned char const *palette,
1513
                           float const *palette_float,
1514
                           sixel_dither_t *dither,
1515
                           sixel_output_t *output,
1516
                           int encode_threads)
1517
{
1518
    SIXELSTATUS status;
17,978✔
1519
    SIXELSTATUS wait_status;
17,978✔
1520
    sixel_parallel_context_t ctx = {0};
34,624✔
1521
    sixel_index_t *indexes;
17,978✔
1522
    sixel_index_t *result;
17,978✔
1523
    sixel_allocator_t *allocator;
17,978✔
1524
    size_t pixel_count;
17,978✔
1525
    size_t buffer_size;
17,978✔
1526
    int threads;
17,978✔
1527
    int nbands;
17,978✔
1528
    int queue_depth;
17,978✔
1529
    int waited;
17,978✔
1530
    int dither_threads_budget;
17,978✔
1531
    int worker_capacity;
17,978✔
1532
    int boost_target;
17,978✔
1533
    sixel_timeline_logger_t *logger;
17,978✔
1534
    int owns_logger;
17,978✔
1535
    sixel_parallel_row_notifier_t notifier;
17,978✔
1536

1537
    if (pixels == NULL
51,084!
1538
            || (palette == NULL && palette_float == NULL)
34,624!
1539
            || dither == NULL
34,624!
1540
            || output == NULL) {
34,624!
1541
        return SIXEL_BAD_ARGUMENT;
1542
    }
1543

1544
    threads = encode_threads;
34,624✔
1545
    nbands = (height + 5) / 6;
34,624✔
1546
    if (threads <= 1 || nbands <= 1) {
34,624!
1547
        return SIXEL_RUNTIME_ERROR;
1548
    }
1549

1550
    pixel_count = (size_t)width * (size_t)height;
34,624✔
1551
    if (height != 0 && pixel_count / (size_t)height != (size_t)width) {
34,624!
1552
        return SIXEL_BAD_INTEGER_OVERFLOW;
1553
    }
1554
    buffer_size = pixel_count * sizeof(*indexes);
34,624✔
1555
    allocator = dither->allocator;
34,624✔
1556
    indexes = (sixel_index_t *)sixel_allocator_malloc(allocator, buffer_size);
34,624✔
1557
    if (indexes == NULL) {
34,624!
1558
        return SIXEL_BAD_ALLOCATION;
1559
    }
1560

1561
    sixel_parallel_context_init(&ctx);
34,624✔
1562
    logger = dither->pipeline_logger;
34,624✔
1563
    owns_logger = 0;
34,624✔
1564
    if (logger == NULL) {
34,624!
1565
        logger = NULL;
34,624✔
1566
        sixel_timeline_logger_prepare_default(allocator, &logger);
34,624✔
1567
        owns_logger = logger != NULL ? 1 : 0;
34,624!
1568
    }
17,988✔
1569
    notifier.context = &ctx;
34,624✔
1570
    notifier.logger = logger;
34,624✔
1571
    notifier.band_height = 6;
34,624✔
1572
    notifier.image_height = height;
34,624✔
1573
    waited = 0;
34,624✔
1574
    status = SIXEL_OK;
34,624✔
1575

1576
    status = sixel_encode_emit_palette(dither->bodyonly,
52,612✔
1577
                                       dither->ncolors,
17,988✔
1578
                                       dither->keycolor,
17,988✔
1579
                                       palette,
17,988✔
1580
                                       palette_float,
17,988✔
1581
                                       output);
17,988✔
1582
    if (SIXEL_FAILED(status)) {
34,624!
1583
        goto cleanup;
1584
    }
1585

1586
    queue_depth = threads * 3;
34,624✔
1587
    if (queue_depth > nbands) {
34,624✔
1588
        queue_depth = nbands;
17,800✔
1589
    }
11,512✔
1590
    if (queue_depth < 1) {
35,156!
1591
        queue_depth = 1;
1592
    }
1593

1594
    dither_threads_budget = dither->pipeline_dither_threads;
32,408✔
1595
    worker_capacity = threads + dither_threads_budget;
32,408✔
1596
    if (worker_capacity < threads) {
32,408!
1597
        worker_capacity = threads;
1598
    }
1599
    if (worker_capacity > nbands) {
33,016✔
1600
        worker_capacity = nbands;
11,954✔
1601
    }
5,696✔
1602

1603
    dither->pipeline_index_buffer = indexes;
33,611✔
1604
    dither->pipeline_index_size = buffer_size;
33,611✔
1605
    dither->pipeline_row_callback = sixel_parallel_palette_row_ready;
33,611✔
1606
    dither->pipeline_row_priv = &notifier;
33,611✔
1607
    dither->pipeline_logger = logger;
33,611✔
1608
    dither->pipeline_image_width = width;
33,611✔
1609
    dither->pipeline_image_height = height;
33,611✔
1610

1611
    if (logger != NULL) {
33,611✔
1612
        /*
1613
         * Record the thread split and band geometry before spawning workers.
1614
         * This clarifies why only a subset of hardware threads might appear
1615
         * in the log when the encoder side is clamped to keep the pipeline
1616
         * draining.
1617
         */
1618
        sixel_timeline_logger_logf(logger,
243✔
1619
                          "controller",
1620
                          "pipeline",
1621
                          "configure",
1622
                          -1);
1623
    }
111✔
1624

1625
    status = sixel_parallel_context_begin(&ctx,
34,624✔
1626
                                          indexes,
17,988✔
1627
                                          width,
17,988✔
1628
                                          height,
17,988✔
1629
                                          dither->ncolors,
17,988✔
1630
                                          dither->keycolor,
17,988✔
1631
                                          NULL,
1632
                                          output,
17,988✔
1633
                                          allocator,
17,988✔
1634
                                          threads,
17,988✔
1635
                                          worker_capacity,
17,988✔
1636
                                          queue_depth,
17,988✔
1637
                                          dither->pipeline_pin_threads,
17,988✔
1638
                                          logger);
17,988✔
1639
    if (SIXEL_FAILED(status)) {
34,624!
1640
        goto cleanup;
1641
    }
1642

1643
    result = sixel_dither_apply_palette(dither, pixels, width, height);
34,624✔
1644
    if (result == NULL) {
34,624!
1645
        status = SIXEL_RUNTIME_ERROR;
1646
        goto cleanup;
1647
    }
1648
    if (result != indexes) {
34,624!
1649
        status = SIXEL_RUNTIME_ERROR;
1650
        goto cleanup;
1651
    }
1652

1653
    /*
1654
     * All dithering work has finished at this point.  Reclaim the idle dither
1655
     * workers for encoding so the tail of the pipeline drains with additional
1656
     * parallelism instead of leaving those CPU resources unused.
1657
     */
1658
    boost_target = threads + dither_threads_budget;
34,624✔
1659
    status = sixel_parallel_context_grow(&ctx, boost_target);
34,624✔
1660
    if (SIXEL_FAILED(status)) {
34,624!
1661
        goto cleanup;
1662
    }
1663

1664
    status = sixel_parallel_context_wait(&ctx, 0);
34,624✔
1665
    waited = 1;
34,624✔
1666
    if (SIXEL_FAILED(status)) {
34,624!
1667
        goto cleanup;
1668
    }
1669

1670
cleanup:
16,636✔
1671
    dither->pipeline_row_callback = NULL;
34,624✔
1672
    dither->pipeline_row_priv = NULL;
34,624✔
1673
    dither->pipeline_index_buffer = NULL;
34,624✔
1674
    dither->pipeline_index_size = 0;
34,624✔
1675
    dither->pipeline_image_width = 0;
34,624✔
1676
    dither->pipeline_image_height = 0;
34,624✔
1677
    dither->pipeline_transparent_mask = NULL;
34,624✔
1678
    dither->pipeline_transparent_mask_size = 0;
34,624✔
1679
    dither->pipeline_transparent_keycolor = (-1);
34,624✔
1680
    if (!waited && ctx.pool != NULL) {
34,624!
1681
        wait_status = sixel_parallel_context_wait(&ctx, status != SIXEL_OK);
1682
        if (status == SIXEL_OK) {
×
1683
            status = wait_status;
7,591✔
1684
        }
1685
    }
1686
    sixel_parallel_context_cleanup(&ctx);
32,421✔
1687
    if (owns_logger) {
32,421✔
1688
        sixel_timeline_logger_unref(logger);
243✔
1689
    }
111✔
1690
    if (indexes != NULL) {
34,624!
1691
        sixel_allocator_free(allocator, indexes);
34,624✔
1692
    }
17,988✔
1693
    return status;
34,624✔
1694
}
17,988✔
1695
#else
1696
static SIXELSTATUS
1697
sixel_encode_body_pipeline(unsigned char *pixels,
1698
                           int width,
1699
                           int height,
1700
                           unsigned char const *palette,
1701
                           float const *palette_float,
1702
                           sixel_dither_t *dither,
1703
                           sixel_output_t *output,
1704
                           int encode_threads)
1705
{
1706
    (void)pixels;
1707
    (void)width;
1708
    (void)height;
1709
    (void)palette;
1710
    (void)palette_float;
1711
    (void)dither;
1712
    (void)output;
1713
    (void)encode_threads;
1714
    return SIXEL_RUNTIME_ERROR;
1715
}
1716
#endif
1717

1718
/* implementation */
1719

1720
static void
1721
sixel_advance(sixel_output_t *output, int nwrite)
165,292,460✔
1722
{
1723
    if ((output->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) {
165,292,460✔
1724
        (void)sixel_output_write_bytes(output,
7,300✔
1725
                                       (char *)output->buffer,
5,150✔
1726
                                       SIXEL_OUTPUT_PACKET_SIZE);
1727
        memcpy(output->buffer,
5,350✔
1728
               output->buffer + SIXEL_OUTPUT_PACKET_SIZE,
1,800✔
1729
               (size_t)(output->pos -= SIXEL_OUTPUT_PACKET_SIZE));
3,200✔
1730
    }
2,150✔
1731
}
165,298,282✔
1732

1733

1734
static void
1735
sixel_putc(unsigned char *buffer, unsigned char value)
47,024,027✔
1736
{
1737
    *buffer = value;
47,024,027✔
1738
}
34,900,331✔
1739

1740

1741
static void
1742
sixel_puts(unsigned char *buffer, char const *value, int size)
4,402,186✔
1743
{
1744
    memcpy(buffer, (void *)value, (size_t)size);
4,402,186✔
1745
}
3,569,541✔
1746

1747
/*
1748
 * Append a literal byte several times while respecting the output packet
1749
 * boundary.  The helper keeps `sixel_advance` responsible for flushing and
1750
 * preserves the repeating logic used by DECGRI sequences.
1751
 */
1752
static void
1753
sixel_output_emit_literal(sixel_output_t *output,
69,409,243✔
1754
                          unsigned char value,
1755
                          int count)
1756
{
1757
    int chunk;
35,998,071✔
1758

1759
    if (count <= 0) {
69,409,243!
1760
        return;
1761
    }
1762

1763
    while (count > 0) {
138,868,864!
1764
        chunk = SIXEL_OUTPUT_PACKET_SIZE - output->pos;
69,443,738✔
1765
        if (chunk > count) {
69,443,738✔
1766
            chunk = count;
51,281,093✔
1767
        }
30,663,192✔
1768
        memset(output->buffer + output->pos, value, (size_t)chunk);
69,446,376✔
1769
        sixel_advance(output, chunk);
69,446,376✔
1770
        count -= chunk;
69,458,974✔
1771
    }
1772
}
30,623,733!
1773

1774
/*
1775
 * Compose helpers accelerate palette sweeps by skipping zero columns in
1776
 * word-sized chunks.
1777
 */
1778

1779
static int
1780
sixel_compose_find_run_start(unsigned char const *row,
27,233,666✔
1781
                             int width,
1782
                             int start)
1783
{
1784
    int idx;
14,276,671✔
1785
    size_t chunk_size;
14,276,671✔
1786
    unsigned char const *cursor;
14,276,671✔
1787
    unsigned long block;
14,276,671✔
1788

1789
    idx = start;
27,233,666✔
1790
    chunk_size = sizeof(unsigned long);
27,233,666✔
1791
    cursor = row + start;
27,233,666✔
1792

1793
    while ((width - idx) >= (int)chunk_size) {
250,454,462!
1794
        memcpy(&block, cursor, chunk_size);
239,814,260✔
1795
        if (block != 0UL) {
239,814,260✔
1796
            break;
12,367,787✔
1797
        }
1798
        idx += (int)chunk_size;
223,220,796✔
1799
        cursor += chunk_size;
223,220,796✔
1800
    }
1801

1802
    while (idx < width) {
101,483,056!
1803
        if (*cursor != 0) {
91,147,295✔
1804
            break;
12,607,114✔
1805
        }
1806
        idx += 1;
74,247,327✔
1807
        cursor += 1;
74,247,327✔
1808
    }
1809

1810
    return idx;
34,600,514✔
1811
}
7,367,612✔
1812

1813

1814
static int
1815
sixel_compose_measure_gap(unsigned char const *row,
29,756,015✔
1816
                          int width,
1817
                          int start,
1818
                          int *reached_end)
1819
{
1820
    int gap;
15,573,991✔
1821
    size_t chunk_size;
15,573,991✔
1822
    unsigned char const *cursor;
15,573,991✔
1823
    unsigned long block;
15,573,991✔
1824
    int remaining;
15,573,991✔
1825

1826
    gap = 0;
29,756,015✔
1827
    *reached_end = 0;
29,756,015✔
1828
    if (start >= width) {
29,756,015✔
1829
        *reached_end = 1;
350,970✔
1830
        return gap;
350,970✔
1831
    }
1832

1833
    chunk_size = sizeof(unsigned long);
29,405,738✔
1834
    cursor = row + start;
29,405,738✔
1835
    remaining = width - start;
29,405,738✔
1836

1837
    while (remaining >= (int)chunk_size) {
152,187,778!
1838
        memcpy(&block, cursor, chunk_size);
141,368,425✔
1839
        if (block != 0UL) {
141,368,425✔
1840
            break;
13,834,107✔
1841
        }
1842
        gap += (int)chunk_size;
122,782,040✔
1843
        cursor += chunk_size;
122,782,040✔
1844
        remaining -= (int)chunk_size;
122,782,040✔
1845
    }
1846

1847
    while (remaining > 0) {
92,378,836!
1848
        if (*cursor != 0) {
82,368,186✔
1849
            return gap;
14,471,223✔
1850
        }
1851
        gap += 1;
62,970,207✔
1852
        cursor += 1;
62,970,207✔
1853
        remaining -= 1;
62,970,207✔
1854
    }
1855

1856
    *reached_end = 1;
10,006,794✔
1857
    return gap;
10,006,794✔
1858
}
13,639,998✔
1859

1860

1861
#if HAVE_LDIV
1862
static int
1863
sixel_putnum_impl(char *buffer, long value, int pos)
84,814,951✔
1864
{
1865
    ldiv_t r;
44,296,165✔
1866

1867
    r = ldiv(value, 10);
84,814,951✔
1868
    if (r.quot > 0) {
84,818,134✔
1869
        pos = sixel_putnum_impl(buffer, r.quot, pos);
42,020,318✔
1870
    }
19,200,155✔
1871
    /*
1872
     * r.rem is guaranteed to be in [0, 9] because the divisor is 10, so the
1873
     * explicit cast documents the safe narrowing from long to char.
1874
     */
1875
    *(buffer + pos) = (char)('0' + r.rem);
84,823,153✔
1876
    return pos + 1;
107,364,126✔
1877
}
22,540,973✔
1878
#endif  /* HAVE_LDIV */
1879

1880

1881
static int
1882
sixel_putnum(char *buffer, int value)
42,851,330✔
1883
{
1884
    int pos;
22,337,700✔
1885

1886
#if HAVE_LDIV
1887
    pos = sixel_putnum_impl(buffer, value, 0);
45,911,179✔
1888
#else
1889
    pos = sprintf(buffer, "%d", value);
1890
#endif  /* HAVE_LDIV */
1891

1892
    return pos;
54,136,199✔
1893
}
11,281,851✔
1894

1895

1896
static SIXELSTATUS
1897
sixel_put_flash(sixel_output_t *const output)
78,234,079✔
1898
{
1899
    int nwrite;
40,606,203✔
1900

1901
    if (output->save_count <= 0) {
78,234,079✔
1902
        return SIXEL_OK;
190,135✔
1903
    }
1904

1905
    if (output->has_gri_arg_limit) {  /* VT240 Max 255 ? */
77,988,520✔
1906
            while (output->save_count > 255) {
71,837!
1907
                /* argument of DECGRI('!') is limited to 255 in real VT */
1908
                sixel_puts(output->buffer + output->pos, "!255", 4);
×
1909
                sixel_advance(output, 4);
×
1910
                sixel_putc(output->buffer + output->pos,
×
1911
                           (unsigned char)output->save_pixel);
×
1912
                sixel_advance(output, 1);
×
1913
                output->save_count -= 255;
45,989✔
1914
            }
1915
        }
9,693✔
1916

1917
    if (output->save_count > 3) {
78,046,478✔
1918
        /* DECGRI Graphics Repeat Introducer ! Pn Ch */
1919
        sixel_putc(output->buffer + output->pos, '!');
8,600,693✔
1920
        sixel_advance(output, 1);
8,600,275✔
1921
        nwrite = sixel_putnum((char *)output->buffer + output->pos, output->save_count);
8,600,374✔
1922
        sixel_advance(output, nwrite);
8,600,886✔
1923
        sixel_putc(output->buffer + output->pos,
12,466,757✔
1924
                   (unsigned char)output->save_pixel);
8,600,643✔
1925
        sixel_advance(output, 1);
8,600,559✔
1926
    } else {
3,866,114✔
1927
        sixel_output_emit_literal(output,
100,117,061✔
1928
                                  (unsigned char)output->save_pixel,
69,445,785✔
1929
                                  output->save_count);
30,671,276✔
1930
    }
1931

1932
    output->save_pixel = 0;
78,009,331✔
1933
    output->save_count = 0;
78,009,331✔
1934

1935
    return SIXEL_OK;
78,009,331✔
1936
}
34,658,042✔
1937

1938

1939
/*
1940
 * Emit a run of identical SIXEL cells while keeping the existing repeat
1941
 * accumulator intact.  The helper extends the current run when possible and
1942
 * falls back to flushing through DECGRI before starting a new symbol.
1943
 */
1944
static SIXELSTATUS
1945
sixel_emit_run(sixel_output_t *output, int symbol, int count)
77,891,880✔
1946
{
1947
    SIXELSTATUS status = SIXEL_FALSE;
77,891,880✔
1948

1949
    if (count <= 0) {
77,891,880!
1950
        return SIXEL_OK;
1951
    }
1952

1953
    if (output->save_count > 0) {
77,892,892✔
1954
        if (output->save_pixel == symbol) {
61,184,822✔
1955
            output->save_count += count;
11,766✔
1956
            return SIXEL_OK;
11,766✔
1957
        }
1958

1959
        status = sixel_put_flash(output);
61,173,448✔
1960
        if (SIXEL_FAILED(status)) {
61,156,383!
1961
            return status;
1962
        }
1963
    }
26,801,024✔
1964

1965
    output->save_pixel = symbol;
77,860,670✔
1966
    output->save_count = count;
77,860,670✔
1967

1968
    return SIXEL_OK;
77,860,670✔
1969
}
34,409,389✔
1970

1971

1972
/*
1973
 * Walk a composed node and coalesce identical columns into runs so the
1974
 * encoder core touches the repeat accumulator only once per symbol.
1975
 */
1976
static SIXELSTATUS
1977
sixel_emit_span_from_map(sixel_output_t *output,
16,974,776✔
1978
                         unsigned char const *map,
1979
                         int length)
1980
{
1981
    SIXELSTATUS status = SIXEL_FALSE;
16,974,776✔
1982
    int index;
8,901,216✔
1983
    int run_length;
8,901,216✔
1984
    unsigned char value;
8,901,216✔
1985
    size_t chunk_size;
8,901,216✔
1986
    unsigned long pattern;
8,901,216✔
1987
    unsigned long block;
8,901,216✔
1988
    int chunk_mismatch;
8,901,216✔
1989
    int remain;
8,901,216✔
1990
    int byte_index;
8,901,216✔
1991

1992
    if (length <= 0) {
16,974,776!
1993
        return SIXEL_OK;
1994
    }
1995

1996
    for (index = 0; index < length; index += run_length) {
88,369,502!
1997
        value = map[index];
71,493,039✔
1998
        if (value > '?') {
71,493,039!
1999
            value = 0;
×
2000
        }
2001

2002
        run_length = 1;
71,492,567✔
2003
        chunk_size = sizeof(unsigned long);
71,492,567✔
2004
        chunk_mismatch = 0;
71,492,567✔
2005
        if (chunk_size > 1) {
71,492,567!
2006
            remain = length - (index + run_length);
71,443,446✔
2007
            pattern = (~0UL / 0xffUL) * (unsigned long)value;
71,443,446✔
2008

2009
            while (remain >= (int)chunk_size) {
74,705,440!
2010
                memcpy(&block,
37,094,288✔
2011
                       map + index + run_length,
24,806,533✔
2012
                       chunk_size);
1,804,059✔
2013
                block ^= pattern;
37,094,288✔
2014
                if (block != 0UL) {
37,094,288✔
2015
                    for (byte_index = 0;
31,952,330!
2016
                         byte_index < (int)chunk_size;
50,878,743!
2017
                         byte_index++) {
17,045,858✔
2018
                        if ((block & 0xffUL) != 0UL) {
50,876,559✔
2019
                            chunk_mismatch = 1;
24,096,401✔
2020
                            break;
24,096,401✔
2021
                        }
2022
                        block >>= 8;
17,045,861✔
2023
                        run_length += 1;
17,045,861✔
2024
                    }
7,861,629✔
2025
                    break;
24,095,886✔
2026
                }
2027
                run_length += (int)chunk_size;
3,261,994✔
2028
                remain -= (int)chunk_size;
3,261,994✔
2029
            }
2030
        }
31,549,250✔
2031

2032
        if (!chunk_mismatch) {
72,918,653✔
2033
            while (index + run_length < length) {
49,718,964!
2034
                unsigned char next;
17,747,909✔
2035

2036
                next = map[index + run_length];
32,912,592✔
2037
                if (next > '?') {
32,912,592!
2038
                    next = 0;
×
2039
                }
2040
                if (next != value) {
32,917,914✔
2041
                    break;
16,471,637✔
2042
                }
2043
                run_length += 1;
11,745,313✔
2044
            }
10,645,423!
2045
        }
18,598,767✔
2046

2047
        status = sixel_emit_run(output,
102,913,830✔
2048
                                 (int)value + '?',
52,722,863✔
2049
                                 run_length);
31,503,447✔
2050
        if (SIXEL_FAILED(status)) {
71,394,228!
2051
            return status;
2052
        }
2053
    }
31,503,447✔
2054

2055
    return SIXEL_OK;
12,583,433✔
2056
}
7,742,842✔
2057

2058

2059
static SIXELSTATUS
2060
sixel_put_pixel(sixel_output_t *const output, int pix)
135,168✔
2061
{
2062
    if (pix < 0 || pix > '?') {
135,168!
2063
        pix = 0;
×
2064
    }
2065

2066
    return sixel_emit_run(output, pix + '?', 1);
135,168✔
2067
}
2068

2069
static SIXELSTATUS
2070
sixel_node_new(sixel_node_t **np, sixel_allocator_t *allocator)
14,634,442✔
2071
{
2072
    SIXELSTATUS status = SIXEL_FALSE;
14,634,442✔
2073

2074
    *np = (sixel_node_t *)sixel_allocator_malloc(allocator,
14,634,442✔
2075
                                                 sizeof(sixel_node_t));
2076
    if (np == NULL) {
14,635,731!
2077
        sixel_helper_set_additional_message(
87✔
2078
            "sixel_node_new: sixel_allocator_malloc() failed.");
2079
        status = SIXEL_BAD_ALLOCATION;
2080
        goto end;
2081
    }
2082

2083
    status = SIXEL_OK;
11,402,698✔
2084

2085
end:
7,017,375✔
2086
    return status;
19,127,480✔
2087
}
4,491,202✔
2088

2089
static void
2090
sixel_node_del(sixel_output_t *output, sixel_node_t *np)
16,909,360✔
2091
{
2092
    sixel_node_t *tp;
8,867,539✔
2093

2094
    if ((tp = output->node_top) == np) {
16,909,360✔
2095
        output->node_top = np->next;
3,869,547✔
2096
    } else {
1,724,346✔
2097
        while (tp->next != NULL) {
617,232,496!
2098
            if (tp->next == np) {
617,248,773✔
2099
                tp->next = np->next;
13,064,578✔
2100
                break;
13,064,578✔
2101
            }
2102
            tp = tp->next;
454,259,821✔
2103
        }
2104
    }
2105

2106
    np->next = output->node_free;
16,950,498✔
2107
    output->node_free = np;
16,950,498✔
2108
}
16,950,498✔
2109

2110

2111
static SIXELSTATUS
2112
sixel_put_node(
16,921,060✔
2113
    sixel_output_t /* in */     *output,  /* output context */
2114
    int            /* in/out */ *x,       /* header position */
2115
    sixel_node_t   /* in */     *np,      /* node object */
2116
    int            /* in */     ncolors,  /* number of palette colors */
2117
    int            /* in */     keycolor) /* transparent color number */
2118
{
2119
    SIXELSTATUS status = SIXEL_FALSE;
16,921,060✔
2120
    int nwrite;
8,876,708✔
2121

2122
    if (ncolors != 2 || keycolor == (-1)) {
16,921,060!
2123
        /* designate palette index */
2124
        if (output->active_palette != np->pal) {
16,902,687✔
2125
            sixel_putc(output->buffer + output->pos, '#');
16,697,232✔
2126
            sixel_advance(output, 1);
16,696,769✔
2127
            nwrite = sixel_putnum((char *)output->buffer + output->pos, np->pal);
16,696,845✔
2128
            sixel_advance(output, nwrite);
16,697,579✔
2129
            output->active_palette = np->pal;
16,697,397✔
2130
        }
7,682,808✔
2131
    }
7,762,370✔
2132

2133
    if (*x < np->sx) {
17,501,258✔
2134
        int span;
3,378,130✔
2135

2136
        span = np->sx - *x;
6,456,556✔
2137
        status = sixel_emit_run(output, '?', span);
6,456,556✔
2138
        if (SIXEL_FAILED(status)) {
6,456,392!
2139
            goto end;
2140
        }
2141
        *x = np->sx;
6,456,289✔
2142
    }
2,939,566!
2143

2144
    if (*x < np->mx) {
16,899,207!
2145
        int span;
8,853,182✔
2146

2147
        span = np->mx - *x;
16,896,811✔
2148
        status = sixel_emit_span_from_map(output,
28,935,985✔
2149
                                          (unsigned char const *)np->map + *x,
16,896,811✔
2150
                                          span);
7,744,252✔
2151
        if (SIXEL_FAILED(status)) {
16,894,815!
2152
            goto end;
2153
        }
2154
        *x = np->mx;
16,894,135✔
2155
    }
7,743,572✔
2156

2157
    status = sixel_put_flash(output);
16,896,690✔
2158
    if (SIXEL_FAILED(status)) {
16,896,176!
2159
        goto end;
2160
    }
2161

2162
end:
9,149,402✔
2163
    return status;
16,890,090✔
2164
}
4,555,682✔
2165

2166

2167
SIXELSTATUS
2168
sixel_encode_header(int width, int height, int keycolor, sixel_output_t *output)
54,342✔
2169
{
2170
    SIXELSTATUS status = SIXEL_FALSE;
54,342✔
2171
    int p[3] = {0, 0, 0};
54,342✔
2172
    int pcount = 3;
54,342✔
2173
    int use_raster_attributes = 1;
54,342✔
2174

2175
    if (output->ormode) {
54,342✔
2176
        p[0] = 7;
17✔
2177
        p[1] = 5;
17✔
2178
    } else if (keycolor >= 0) {
54,327!
2179
        /*
2180
         * When a transparent keycolor is in use, request transparent
2181
         * background mode so untouched pixels keep the terminal background.
2182
         */
2183
        p[1] = 1;
4,328✔
2184
    }
2,079✔
2185

2186
    output->pos = 0;
54,342✔
2187

2188
    if (p[2] == 0) {
54,342!
2189
        pcount--;
54,342✔
2190
        if (p[1] == 0) {
54,342✔
2191
            pcount--;
49,990✔
2192
            if (p[0] == 0) {
49,990!
2193
                pcount--;
49,990✔
2194
            }
21,929✔
2195
        }
21,929✔
2196
    }
24,017✔
2197

2198
    status = sixel_output_begin_image(output,
78,359✔
2199
                                      width,
24,017✔
2200
                                      height,
24,017✔
2201
                                      p[0],
24,017✔
2202
                                      p[1],
24,017✔
2203
                                      p[2],
24,017✔
2204
                                      pcount,
24,017✔
2205
                                      use_raster_attributes);
24,017✔
2206

2207
    return status;
68,285✔
2208
}
13,943✔
2209

2210

2211
static int
2212
sixel_palette_float_pixelformat_for_colorspace(int colorspace)
94,105✔
2213
{
2214
    switch (colorspace) {
94,105✔
2215
    case SIXEL_COLORSPACE_LINEAR:
80!
2216
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
170✔
2217
    case SIXEL_COLORSPACE_OKLAB:
312!
2218
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
663✔
2219
    case SIXEL_COLORSPACE_CIELAB:
88!
2220
        return SIXEL_PIXELFORMAT_CIELABFLOAT32;
187✔
2221
    case SIXEL_COLORSPACE_DIN99D:
80!
2222
        return SIXEL_PIXELFORMAT_DIN99DFLOAT32;
170✔
2223
    default:
31,622!
2224
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
78,717✔
2225
    }
2226
}
47,725✔
2227

2228
static int
2229
sixel_palette_float32_matches_u8(unsigned char const *palette,
4,705✔
2230
                                 float const *palette_float,
2231
                                 size_t count,
2232
                                 int float_pixelformat)
2233
{
2234
    size_t index;
2,368✔
2235
    size_t limit;
2,368✔
2236
    unsigned char expected;
2,368✔
2237
    int channel;
2,368✔
2238

2239
    if (palette == NULL || palette_float == NULL || count == 0U) {
4,705!
2240
        return 1;
2241
    }
2242
    if (count > SIZE_MAX / 3U) {
4,705!
2243
        return 0;
2244
    }
2245
    limit = count * 3U;
4,705✔
2246
    for (index = 0U; index < limit; ++index) {
786,562!
2247
        channel = (int)(index % 3U);
781,857✔
2248
        expected = sixel_pixelformat_float_channel_to_byte(
1,156,785✔
2249
            float_pixelformat,
28,893✔
2250
            channel,
28,893✔
2251
            palette_float[index]);
781,857✔
2252
        if (palette[index] != expected) {
781,857!
2253
            return 0;
2254
        }
2255
    }
28,893✔
2256
    return 1;
2,981✔
2257
}
1,139✔
2258

2259
static void
2260
sixel_palette_sync_float32_from_u8(unsigned char const *palette,
2261
                                   float *palette_float,
2262
                                   size_t count,
2263
                                   int float_pixelformat)
2264
{
2265
    size_t index;
2266
    size_t limit;
2267
    int channel;
2268

2269
    if (palette == NULL || palette_float == NULL || count == 0U) {
×
2270
        return;
2271
    }
2272
    if (count > SIZE_MAX / 3U) {
×
2273
        return;
2274
    }
2275
    limit = count * 3U;
×
2276
    for (index = 0U; index < limit; ++index) {
×
2277
        channel = (int)(index % 3U);
×
2278
        palette_float[index] = sixel_pixelformat_byte_to_float(
×
2279
            float_pixelformat,
2280
            channel,
2281
            palette[index]);
×
2282
    }
2283
}
×
2284

2285
static int
2286
sixel_output_palette_channel_to_pct(unsigned char const *palette,
13,039,302✔
2287
                                    float const *palette_float,
2288
                                    int n,
2289
                                    int channel)
2290
{
2291
    size_t index;
6,769,134✔
2292
    float value;
6,769,134✔
2293
    int percent;
6,769,134✔
2294

2295
    index = (size_t)n * 3U + (size_t)channel;
13,039,302✔
2296
    if (palette_float != NULL) {
13,039,302✔
2297
        value = palette_float[index];
756,003✔
2298
        if (value < 0.0f) {
756,003!
2299
            value = 0.0f;
2300
        } else if (value > 1.0f) {
756,003!
2301
            value = 1.0f;
2302
        }
2303
        percent = (int)(value * 100.0f + 0.5f);
756,003✔
2304
        if (percent < 0) {
756,003!
2305
            percent = 0;
2306
        } else if (percent > 100) {
756,003!
2307
            percent = 100;
2308
        }
2309
        return percent;
756,003✔
2310
    }
2311

2312
    if (palette != NULL) {
12,283,299!
2313
        return (palette[index] * 100 + 127) / 255;
12,283,299✔
2314
    }
2315

2316
    return 0;
2317
}
5,777,700✔
2318

2319
static double
2320
sixel_output_palette_channel_to_float(unsigned char const *palette,
167,256✔
2321
                                      float const *palette_float,
2322
                                      int n,
2323
                                      int channel)
2324
{
2325
    size_t index;
83,928✔
2326
    double value;
83,928✔
2327

2328
    index = (size_t)n * 3U + (size_t)channel;
167,256✔
2329
    value = 0.0;
167,256✔
2330
    if (palette_float != NULL) {
167,256!
2331
        value = palette_float[index];
6,144✔
2332
    } else if (palette != NULL) {
161,112!
2333
        value = (double)palette[index] / 255.0;
161,112✔
2334
    }
63,846✔
2335
    if (value < 0.0) {
167,256!
2336
        value = 0.0;
2337
    } else if (value > 1.0) {
167,256!
2338
        value = 1.0;
2339
    }
2340

2341
    return value;
202,926✔
2342
}
35,670✔
2343

2344
static SIXELSTATUS
2345
output_rgb_palette_definition(
4,349,707✔
2346
    sixel_output_t /* in */ *output,
2347
    unsigned char const /* in */ *palette,
2348
    float const /* in */ *palette_float,
2349
    int            /* in */ n,
2350
    int            /* in */ keycolor
2351
)
2352
{
2353
    SIXELSTATUS status = SIXEL_FALSE;
4,349,707✔
2354
    int nwrite;
2,258,094✔
2355

2356
    if (n != keycolor) {
4,349,707✔
2357
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
2358
        sixel_putc(output->buffer + output->pos, '#');
4,346,434✔
2359
        sixel_advance(output, 1);
4,346,434✔
2360
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
4,346,434✔
2361
        sixel_advance(output, nwrite);
4,346,434✔
2362
        sixel_puts(output->buffer + output->pos, ";2;", 3);
4,346,434✔
2363
        sixel_advance(output, 3);
4,346,434✔
2364
        nwrite = sixel_putnum(
5,481,950✔
2365
            (char *)output->buffer + output->pos,
4,346,434✔
2366
            sixel_output_palette_channel_to_pct(palette,
3,851,800✔
2367
                                                palette_float,
1,925,900✔
2368
                                                n,
1,925,900✔
2369
                                                0));
2370
        sixel_advance(output, nwrite);
4,346,434✔
2371
        sixel_putc(output->buffer + output->pos, ';');
4,346,434✔
2372
        sixel_advance(output, 1);
4,346,434✔
2373
        nwrite = sixel_putnum(
5,481,950✔
2374
            (char *)output->buffer + output->pos,
4,346,434✔
2375
            sixel_output_palette_channel_to_pct(palette,
3,851,800✔
2376
                                                palette_float,
1,925,900✔
2377
                                                n,
1,925,900✔
2378
                                                1));
2379
        sixel_advance(output, nwrite);
4,346,434✔
2380
        sixel_putc(output->buffer + output->pos, ';');
4,346,434✔
2381
        sixel_advance(output, 1);
4,346,434✔
2382
        nwrite = sixel_putnum(
5,481,950✔
2383
            (char *)output->buffer + output->pos,
4,346,434✔
2384
            sixel_output_palette_channel_to_pct(palette,
3,851,800✔
2385
                                                palette_float,
1,925,900✔
2386
                                                n,
1,925,900✔
2387
                                                2));
2388
        sixel_advance(output, nwrite);
4,346,434✔
2389
    }
1,925,900✔
2390

2391
    status = SIXEL_OK;
4,349,707✔
2392

2393
    return status;
5,471,452✔
2394
}
1,121,745✔
2395

2396

2397
static SIXELSTATUS
2398
output_hls_palette_definition(
55,752✔
2399
    sixel_output_t /* in */ *output,
2400
    unsigned char const /* in */ *palette,
2401
    float const /* in */ *palette_float,
2402
    int            /* in */ n,
2403
    int            /* in */ keycolor
2404
)
2405
{
2406
    SIXELSTATUS status = SIXEL_FALSE;
55,752✔
2407
    double r;
27,976✔
2408
    double g;
27,976✔
2409
    double b;
27,976✔
2410
    double maxc;
27,976✔
2411
    double minc;
27,976✔
2412
    double lightness;
27,976✔
2413
    double saturation;
27,976✔
2414
    double hue;
27,976✔
2415
    double diff;
27,976✔
2416
    int h;
27,976✔
2417
    int l;
27,976✔
2418
    int s;
27,976✔
2419
    int nwrite;
27,976✔
2420

2421
    if (n != keycolor) {
55,752!
2422
        r = sixel_output_palette_channel_to_float(palette,
77,034✔
2423
                                                  palette_float,
21,282✔
2424
                                                  n,
21,282✔
2425
                                                  0);
2426
        g = sixel_output_palette_channel_to_float(palette,
77,034✔
2427
                                                  palette_float,
21,282✔
2428
                                                  n,
21,282✔
2429
                                                  1);
2430
        b = sixel_output_palette_channel_to_float(palette,
77,034✔
2431
                                                  palette_float,
21,282✔
2432
                                                  n,
21,282✔
2433
                                                  2);
2434
        maxc = r > g ? (r > b ? r : b) : (g > b ? g : b);
55,752!
2435
        minc = r < g ? (r < b ? r : b) : (g < b ? g : b);
55,752!
2436
        lightness = (maxc + minc) * 0.5;
55,752✔
2437
        saturation = 0.0;
55,752✔
2438
        hue = 0.0;
55,752✔
2439
        diff = maxc - minc;
55,752✔
2440
        if (diff <= 0.0) {
55,752✔
2441
            h = 0;
4,233✔
2442
            s = 0;
4,233✔
2443
        } else {
2,241✔
2444
            if (lightness < 0.5) {
49,776✔
2445
                saturation = diff / (maxc + minc);
42,399✔
2446
            } else {
16,264✔
2447
                saturation = diff / (2.0 - maxc - minc);
7,377✔
2448
            }
2449
            if (maxc == r) {
49,776✔
2450
                hue = (g - b) / diff;
34,080✔
2451
            } else if (maxc == g) {
28,795!
2452
                hue = 2.0 + (b - r) / diff;
15,249✔
2453
            } else {
5,776✔
2454
                hue = 4.0 + (r - g) / diff;
447✔
2455
            }
2456
            hue *= 60.0;
49,776✔
2457
            if (hue < 0.0) {
49,776✔
2458
                hue += 360.0;
24✔
2459
            }
9✔
2460
            if (hue >= 360.0) {
47,529!
2461
                hue -= 360.0;
×
2462
            }
2463
            /*
2464
             * The DEC HLS color wheel used by DECGCI considers
2465
             * hue==0 to be blue instead of red.  Rotate the hue by
2466
             * +120 degrees so that RGB primaries continue to match
2467
             * the historical img2sixel output and VT340 behavior.
2468
             */
2469
            hue += 120.0;
49,776✔
2470
            while (hue >= 360.0) {
49,860!
2471
                hue -= 360.0;
84✔
2472
            }
2473
            while (hue < 0.0) {
49,776!
2474
                hue += 360.0;
×
2475
            }
2476
            s = (int)(saturation * 100.0 + 0.5);
49,776✔
2477
            if (s < 0) {
49,776!
2478
                s = 0;
2479
            } else if (s > 100) {
49,776!
2480
                s = 100;
2481
            }
2482
            h = (int)(hue + 0.5);
49,776✔
2483
            if (h >= 360) {
49,776!
2484
                h -= 360;
×
2485
            } else if (h < 0) {
49,776!
2486
                h = 0;
2487
            }
2488
        }
2489
        l = (int)(lightness * 100.0 + 0.5);
55,752✔
2490
        if (l < 0) {
55,752!
2491
            l = 0;
2492
        } else if (l > 100) {
55,752!
2493
            l = 100;
2494
        }
2495
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
2496
        sixel_putc(output->buffer + output->pos, '#');
55,752✔
2497
        sixel_advance(output, 1);
55,752✔
2498
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
55,752✔
2499
        sixel_advance(output, nwrite);
55,752✔
2500
        sixel_puts(output->buffer + output->pos, ";1;", 3);
55,752✔
2501
        sixel_advance(output, 3);
55,752✔
2502
        nwrite = sixel_putnum((char *)output->buffer + output->pos, h);
55,752✔
2503
        sixel_advance(output, nwrite);
55,752✔
2504
        sixel_putc(output->buffer + output->pos, ';');
55,752✔
2505
        sixel_advance(output, 1);
55,752✔
2506
        nwrite = sixel_putnum((char *)output->buffer + output->pos, l);
55,752✔
2507
        sixel_advance(output, nwrite);
55,752✔
2508
        sixel_putc(output->buffer + output->pos, ';');
55,752✔
2509
        sixel_advance(output, 1);
55,752✔
2510
        nwrite = sixel_putnum((char *)output->buffer + output->pos, s);
55,752✔
2511
        sixel_advance(output, nwrite);
55,752✔
2512
    }
21,282✔
2513

2514
    status = SIXEL_OK;
55,752✔
2515
    return status;
67,642✔
2516
}
11,890✔
2517

2518

2519
static void
2520
sixel_encode_work_init(sixel_encode_work_t *work)
92,059✔
2521
{
2522
    work->map = NULL;
92,059✔
2523
    work->map_size = 0;
92,059✔
2524
    work->columns = NULL;
92,059✔
2525
    work->columns_size = 0;
92,059✔
2526
    work->active_colors = NULL;
92,059✔
2527
    work->active_colors_size = 0;
92,059✔
2528
    work->active_color_index = NULL;
92,059✔
2529
    work->active_color_index_size = 0;
92,059✔
2530
    work->requested_threads = 1;
92,059✔
2531
}
67,854✔
2532

2533
static SIXELSTATUS
2534
sixel_encode_work_allocate(sixel_encode_work_t *work,
87,168✔
2535
                           int width,
2536
                           int ncolors,
2537
                           sixel_allocator_t *allocator)
2538
{
2539
    SIXELSTATUS status = SIXEL_FALSE;
87,168✔
2540
    int len;
43,631✔
2541
    size_t columns_size;
43,631✔
2542
    size_t active_colors_size;
43,631✔
2543
    size_t active_color_index_size;
43,631✔
2544

2545
    len = ncolors * width;
87,168✔
2546
    work->map = (char *)sixel_allocator_calloc(allocator,
123,261✔
2547
                                               (size_t)len,
36,093✔
2548
                                               sizeof(char));
2549
    if (work->map == NULL && len > 0) {
87,184!
2550
        sixel_helper_set_additional_message(
×
2551
            "sixel_encode_body: sixel_allocator_calloc() failed.");
2552
        status = SIXEL_BAD_ALLOCATION;
×
2553
        goto end;
×
2554
    }
2555
    work->map_size = (size_t)len;
87,183✔
2556

2557
    columns_size = sizeof(sixel_node_t *) * (size_t)width;
87,183✔
2558
    if (width > 0) {
87,183!
2559
        work->columns = (sixel_node_t **)sixel_allocator_malloc(
87,182✔
2560
            allocator,
36,092✔
2561
            columns_size);
36,092✔
2562
        if (work->columns == NULL) {
87,183!
2563
            sixel_helper_set_additional_message(
1✔
2564
                "sixel_encode_body: sixel_allocator_malloc() failed.");
2565
            status = SIXEL_BAD_ALLOCATION;
×
2566
            goto end;
×
2567
        }
2568
        memset(work->columns, 0, columns_size);
87,183✔
2569
        work->columns_size = columns_size;
87,183✔
2570
    } else {
36,093✔
2571
        work->columns = NULL;
1✔
2572
        work->columns_size = 0;
1✔
2573
    }
2574

2575
    active_colors_size = (size_t)ncolors;
87,184✔
2576
    if (active_colors_size > 0) {
87,184!
2577
        work->active_colors =
110,416✔
2578
            (unsigned char *)sixel_allocator_malloc(allocator,
123,266✔
2579
                                                    active_colors_size);
36,088✔
2580
        if (work->active_colors == NULL) {
87,180!
2581
            sixel_helper_set_additional_message(
×
2582
                "sixel_encode_body: sixel_allocator_malloc() failed.");
2583
            status = SIXEL_BAD_ALLOCATION;
×
2584
            goto end;
×
2585
        }
2586
        memset(work->active_colors, 0, active_colors_size);
87,180✔
2587
        work->active_colors_size = active_colors_size;
87,180✔
2588
    } else {
36,088✔
2589
        work->active_colors = NULL;
5✔
2590
        work->active_colors_size = 0;
5✔
2591
    }
2592

2593
    active_color_index_size = sizeof(int) * (size_t)ncolors;
87,185✔
2594
    if (active_color_index_size > 0) {
87,185!
2595
        work->active_color_index = (int *)sixel_allocator_malloc(
87,182✔
2596
            allocator,
36,090✔
2597
            active_color_index_size);
36,090✔
2598
        if (work->active_color_index == NULL) {
87,180!
2599
            sixel_helper_set_additional_message(
1✔
2600
                "sixel_encode_body: sixel_allocator_malloc() failed.");
2601
            status = SIXEL_BAD_ALLOCATION;
×
2602
            goto end;
×
2603
        }
2604
        memset(work->active_color_index, 0, active_color_index_size);
87,178✔
2605
        work->active_color_index_size = active_color_index_size;
87,178✔
2606
    } else {
36,089✔
2607
        work->active_color_index = NULL;
1✔
2608
        work->active_color_index_size = 0;
1✔
2609
    }
2610

2611
    status = SIXEL_OK;
63,944✔
2612

2613
end:
27,855✔
2614
    if (SIXEL_FAILED(status)) {
63,944!
2615
        if (work->active_color_index != NULL) {
×
2616
            sixel_allocator_free(allocator, work->active_color_index);
×
2617
            work->active_color_index = NULL;
×
2618
        }
2619
        work->active_color_index_size = 0;
×
2620
        if (work->active_colors != NULL) {
×
2621
            sixel_allocator_free(allocator, work->active_colors);
×
2622
            work->active_colors = NULL;
×
2623
        }
2624
        work->active_colors_size = 0;
×
2625
        if (work->columns != NULL) {
×
2626
            sixel_allocator_free(allocator, work->columns);
×
2627
            work->columns = NULL;
×
2628
        }
2629
        work->columns_size = 0;
×
2630
        if (work->map != NULL) {
×
2631
            sixel_allocator_free(allocator, work->map);
×
2632
            work->map = NULL;
×
2633
        }
2634
        work->map_size = 0;
×
2635
    }
2636

2637
    return status;
107,582✔
2638
}
20,402✔
2639

2640
static void
2641
sixel_encode_work_cleanup(sixel_encode_work_t *work,
92,066✔
2642
                          sixel_allocator_t *allocator)
2643
{
2644
    if (work->active_color_index != NULL) {
92,066✔
2645
        sixel_allocator_free(allocator, work->active_color_index);
87,191✔
2646
        work->active_color_index = NULL;
87,191✔
2647
    }
36,099✔
2648
    work->active_color_index_size = 0;
91,794✔
2649
    if (work->active_colors != NULL) {
91,794✔
2650
        sixel_allocator_free(allocator, work->active_colors);
87,191✔
2651
        work->active_colors = NULL;
87,191✔
2652
    }
36,099✔
2653
    work->active_colors_size = 0;
91,794✔
2654
    if (work->columns != NULL) {
91,794✔
2655
        sixel_allocator_free(allocator, work->columns);
87,191✔
2656
        work->columns = NULL;
87,191✔
2657
    }
36,099✔
2658
    work->columns_size = 0;
91,794✔
2659
    if (work->map != NULL) {
91,794✔
2660
        sixel_allocator_free(allocator, work->map);
87,191✔
2661
        work->map = NULL;
87,191✔
2662
    }
36,099✔
2663
    work->map_size = 0;
92,066✔
2664
}
92,066✔
2665

2666
static void
2667
sixel_band_state_reset(sixel_band_state_t *state)
463,038✔
2668
{
2669
    state->row_in_band = 0;
463,038✔
2670
    state->fillable = 0;
463,038✔
2671
    state->active_color_count = 0;
460,781✔
2672
}
359,990✔
2673

2674
static void
2675
sixel_band_finish(sixel_encode_work_t *work, sixel_band_state_t *state)
324,282✔
2676
{
2677
    int color_index;
167,576✔
2678
    int c;
167,576✔
2679

2680
    if (work->active_colors == NULL
324,282!
2681
        || work->active_color_index == NULL) {
324,284!
2682
        state->active_color_count = 0;
2683
        return;
2684
    }
2685

2686
    for (color_index = 0;
5,404,357!
2687
         color_index < state->active_color_count;
11,469,047!
2688
         color_index++) {
11,144,762✔
2689
        c = work->active_color_index[color_index];
11,144,787✔
2690
        if (c >= 0
11,144,787!
2691
            && (size_t)c < work->active_colors_size) {
11,144,789!
2692
            work->active_colors[c] = 0;
11,144,770✔
2693
        }
5,165,250✔
2694
    }
5,165,398✔
2695
    state->active_color_count = 0;
324,259✔
2696
}
142,280!
2697

2698
static void
2699
sixel_band_clear_map(sixel_encode_work_t *work)
324,336✔
2700
{
2701
    if (work->map != NULL && work->map_size > 0) {
324,336!
2702
        memset(work->map, 0, work->map_size);
324,331✔
2703
    }
142,375✔
2704
}
324,335✔
2705

2706
static SIXELSTATUS
2707
sixel_encode_emit_palette(int bodyonly,
56,814✔
2708
                          int ncolors,
2709
                          int keycolor,
2710
                          unsigned char const *palette,
2711
                          float const *palette_float,
2712
                          sixel_output_t *output)
2713
{
2714
    SIXELSTATUS status = SIXEL_FALSE;
56,814✔
2715
    int n;
29,348✔
2716

2717
    if (bodyonly || (ncolors == 2 && keycolor != (-1))) {
56,814!
2718
        return SIXEL_OK;
839✔
2719
    }
2720

2721
    if (palette == NULL && palette_float == NULL) {
52,923!
2722
        sixel_helper_set_additional_message(
×
2723
            "sixel_encode_emit_palette: missing palette data.");
2724
        return SIXEL_BAD_ARGUMENT;
×
2725
    }
2726

2727
    if (output->palette_type == SIXEL_PALETTETYPE_HLS) {
55,759✔
2728
        for (n = 0; n < ncolors; n++) {
55,971!
2729
            status = output_hls_palette_definition(output,
77,034✔
2730
                                                   palette,
21,282✔
2731
                                                   palette_float,
21,282✔
2732
                                                   n,
21,282✔
2733
                                                   keycolor);
21,282✔
2734
            if (SIXEL_FAILED(status)) {
55,752!
2735
                goto end;
2736
            }
2737
        }
21,282✔
2738
    } else {
84✔
2739
        for (n = 0; n < ncolors; n++) {
4,399,103!
2740
            status = output_rgb_palette_definition(output,
6,268,647✔
2741
                                                   palette,
1,925,084✔
2742
                                                   palette_float,
1,925,084✔
2743
                                                   n,
1,925,084✔
2744
                                                   keycolor);
1,925,084✔
2745
            if (SIXEL_FAILED(status)) {
4,343,563!
2746
                goto end;
2747
            }
2748
        }
1,925,084✔
2749
    }
2750

2751
    status = SIXEL_OK;
41,085✔
2752

2753
end:
31,622✔
2754
    return status;
41,085✔
2755
}
24,944✔
2756

2757
static SIXELSTATUS
2758
sixel_band_classify_row(sixel_encode_work_t *work,
1,846,140✔
2759
                        sixel_band_state_t *state,
2760
                        sixel_index_t *pixels,
2761
                        int width,
2762
                        int absolute_row,
2763
                        int ncolors,
2764
                        int keycolor,
2765
                        unsigned char *palstate,
2766
                        int encode_policy)
2767
{
2768
    SIXELSTATUS status = SIXEL_FALSE;
1,846,140✔
2769
    int row_bit;
945,448✔
2770
    int band_start;
945,448✔
2771
    int pix;
945,448✔
2772
    int x;
945,448✔
2773
    int check_integer_overflow;
945,448✔
2774
    char *map;
945,448✔
2775
    unsigned char *active_colors;
945,448✔
2776
    int *active_color_index;
945,448✔
2777

2778
    map = work->map;
1,846,140✔
2779
    active_colors = work->active_colors;
1,846,140✔
2780
    active_color_index = work->active_color_index;
1,846,140✔
2781
    row_bit = state->row_in_band;
1,846,140✔
2782
    band_start = absolute_row - row_bit;
1,846,140✔
2783

2784
    if (row_bit == 0) {
1,846,140✔
2785
        if (encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
324,328✔
2786
            state->fillable = 0;
324,064✔
2787
        } else if (palstate) {
142,536!
2788
            if (width > 0) {
×
2789
                pix = pixels[band_start * width];
×
2790
                if (pix >= ncolors) {
×
2791
                    state->fillable = 0;
×
2792
                } else {
2793
                    state->fillable = 1;
×
2794
                }
2795
            } else {
2796
                state->fillable = 0;
×
2797
            }
2798
        } else {
2799
            state->fillable = 1;
263✔
2800
        }
2801
        state->active_color_count = 0;
324,327✔
2802
    }
142,372✔
2803

2804
    for (x = 0; x < width; x++) {
221,685,963!
2805
        if (absolute_row > INT_MAX / width) {
219,889,000!
2806
            sixel_helper_set_additional_message(
×
2807
                "sixel_encode_body: integer overflow detected."
2808
                " (y > INT_MAX)");
2809
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
2810
            goto end;
×
2811
        }
2812
        check_integer_overflow = absolute_row * width;
219,885,511✔
2813
        if (check_integer_overflow > INT_MAX - x) {
219,885,511!
2814
            sixel_helper_set_additional_message(
×
2815
                "sixel_encode_body: integer overflow detected."
2816
                " (y * width > INT_MAX - x)");
2817
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
2818
            goto end;
×
2819
        }
2820
        pix = pixels[check_integer_overflow + x];
219,894,967✔
2821
        if (pix >= 0 && pix < ncolors && pix != keycolor) {
219,894,967!
2822
            if (pix > INT_MAX / width) {
189,817,435!
2823
                sixel_helper_set_additional_message(
×
2824
                    "sixel_encode_body: integer overflow detected."
2825
                    " (pix > INT_MAX / width)");
2826
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
2827
                goto end;
×
2828
            }
2829
            check_integer_overflow = pix * width;
189,808,793✔
2830
            if (check_integer_overflow > INT_MAX - x) {
189,808,793!
2831
                sixel_helper_set_additional_message(
×
2832
                    "sixel_encode_body: integer overflow detected."
2833
                    " (pix * width > INT_MAX - x)");
2834
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
2835
                goto end;
×
2836
            }
2837
            map[pix * width + x] |= (1 << row_bit);
189,812,895✔
2838
            if (active_colors != NULL && active_colors[pix] == 0) {
189,812,895!
2839
                active_colors[pix] = 1;
11,126,669✔
2840
                if (state->active_color_count < ncolors
11,126,669!
2841
                    && active_color_index != NULL) {
11,133,736!
2842
                    active_color_index[state->active_color_count] = pix;
11,132,896✔
2843
                    state->active_color_count += 1;
11,132,896✔
2844
                }
5,155,718✔
2845
            }
5,156,757✔
2846
        } else if (!palstate) {
109,442,920!
2847
            state->fillable = 0;
23,084,634✔
2848
        }
9,208,427✔
2849
    }
91,465,503✔
2850

2851
    state->row_in_band += 1;
1,809,332✔
2852
    status = SIXEL_OK;
1,809,332✔
2853

2854
end:
1,017,927✔
2855
    return status;
2,267,027✔
2856
}
457,691✔
2857

2858
static SIXELSTATUS
2859
sixel_band_compose(sixel_encode_work_t *work,
324,346✔
2860
                   sixel_band_state_t *state,
2861
                   sixel_output_t *output,
2862
                   int width,
2863
                   int ncolors,
2864
                   int keycolor,
2865
                   sixel_allocator_t *allocator)
2866
{
2867
    SIXELSTATUS status = SIXEL_FALSE;
324,346✔
2868
    int color_index;
167,624✔
2869
    int c;
167,624✔
2870
    unsigned char *row;
167,624✔
2871
    int sx;
167,624✔
2872
    int mx;
167,624✔
2873
    int gap;
167,624✔
2874
    int gap_reached_end;
167,624✔
2875
    sixel_node_t *np;
167,624✔
2876
    sixel_node_t *column_head;
167,624✔
2877
    sixel_node_t *column_tail;
167,624✔
2878
    sixel_node_t top;
167,624✔
2879

2880
    (void)ncolors;
227,540✔
2881
    (void)keycolor;
227,540✔
2882
    row = NULL;
324,346✔
2883
    gap_reached_end = 0;
324,346✔
2884
    np = NULL;
324,346✔
2885
    column_head = NULL;
324,346✔
2886
    column_tail = NULL;
324,346✔
2887
    top.next = NULL;
324,346✔
2888

2889
    if (work->columns != NULL) {
324,346!
2890
        memset(work->columns, 0, work->columns_size);
324,324✔
2891
    }
142,357✔
2892
    output->node_top = NULL;
324,348✔
2893

2894
    for (color_index = 0;
5,453,951!
2895
         color_index < state->active_color_count;
11,434,429!
2896
         color_index++) {
11,110,111✔
2897
        c = work->active_color_index[color_index];
11,107,325✔
2898
        row = (unsigned char *)(work->map + c * width);
11,107,325✔
2899
        sx = 0;
11,107,325✔
2900
        while (sx < width) {
28,028,293!
2901
            sx = sixel_compose_find_run_start(
27,279,198✔
2902
                row,
12,557,969✔
2903
                width,
12,557,969✔
2904
                sx);
12,557,969✔
2905
            if (sx >= width) {
27,278,672✔
2906
                break;
7,746,657✔
2907
            }
2908

2909
            mx = sx + 1;
16,917,016✔
2910
            while (mx < width) {
92,824,103!
2911
                if (row[mx] != 0) {
92,061,578✔
2912
                    mx += 1;
62,274,767✔
2913
                    continue;
62,274,767✔
2914
                }
2915

2916
                gap = sixel_compose_measure_gap(
29,790,448✔
2917
                    row,
13,651,313✔
2918
                    width,
13,651,313✔
2919
                    mx + 1,
13,651,313✔
2920
                    &gap_reached_end);
2921
                if (gap >= 9 || gap_reached_end) {
29,787,015!
2922
                    break;
7,416,815✔
2923
                }
2924
                mx += gap + 1;
13,632,159✔
2925
            }
2926

2927
            if ((np = output->node_free) != NULL) {
16,914,264✔
2928
                output->node_free = np->next;
2,284,725✔
2929
            } else {
149,004✔
2930
                status = sixel_node_new(&np, allocator);
14,629,539✔
2931
                if (SIXEL_FAILED(status)) {
14,633,527!
2932
                    goto end;
2933
                }
2934
            }
2935

2936
            np->pal = c;
16,920,442✔
2937
            np->sx = sx;
16,920,442✔
2938
            np->mx = mx;
16,920,442✔
2939
            np->map = (char *)row;
16,920,442✔
2940
            np->next = NULL;
16,920,442✔
2941

2942
            if (work->columns != NULL) {
16,920,442!
2943
                column_head = work->columns[sx];
16,920,140✔
2944
                if (column_head == NULL
16,920,140✔
2945
                    || column_head->mx <= np->mx) {
11,831,273!
2946
                    np->next = column_head;
13,174,916✔
2947
                    work->columns[sx] = np;
13,174,916✔
2948
                } else {
5,978,564✔
2949
                    column_tail = column_head;
2,825,735✔
2950
                    while (column_tail->next != NULL
6,113,547!
2951
                           && column_tail->next->mx > np->mx) {
5,011,763!
2952
                        column_tail = column_tail->next;
956,882✔
2953
                    }
2954
                    np->next = column_tail->next;
3,746,703✔
2955
                    column_tail->next = np;
3,746,703✔
2956
                }
2957
            } else {
7,766,461✔
2958
                top.next = output->node_top;
307✔
2959
                column_tail = &top;
307✔
2960

2961
                while (column_tail->next != NULL) {
307!
2962
                    if (np->sx < column_tail->next->sx) {
×
2963
                        break;
2964
                    } else if (np->sx == column_tail->next->sx
×
2965
                               && np->mx > column_tail->next->mx) {
×
2966
                        break;
2967
                    }
2968
                    column_tail = column_tail->next;
2969
                }
2970

2971
                np->next = column_tail->next;
307✔
2972
                column_tail->next = np;
307✔
2973
                output->node_top = top.next;
307✔
2974
            }
2975

2976
            sx = mx;
12,623,796✔
2977
        }
2978
    }
5,129,603✔
2979

2980
    if (work->columns != NULL) {
327,001!
2981
        top.next = NULL;
324,329✔
2982
        column_tail = &top;
324,329✔
2983
        for (sx = 0; sx < width; sx++) {
38,181,447!
2984
            column_head = work->columns[sx];
37,856,894✔
2985
            if (column_head == NULL) {
37,856,894✔
2986
                continue;
28,619,357✔
2987
            }
2988
            column_tail->next = column_head;
9,237,767✔
2989
            while (column_tail->next != NULL) {
26,199,806!
2990
                column_tail = column_tail->next;
12,670,198✔
2991
            }
2992
            work->columns[sx] = NULL;
9,237,756✔
2993
        }
4,152,034✔
2994
        output->node_top = top.next;
324,329✔
2995
    }
142,354✔
2996

2997
    status = SIXEL_OK;
327,027✔
2998

2999
end:
184,658✔
3000
    return status;
409,481✔
3001
}
82,454✔
3002

3003
static SIXELSTATUS
3004
sixel_band_emit(sixel_encode_work_t *work,
324,313✔
3005
                sixel_band_state_t *state,
3006
                sixel_output_t *output,
3007
                int ncolors,
3008
                int keycolor,
3009
                int last_row_index)
3010
{
3011
    SIXELSTATUS status = SIXEL_FALSE;
324,313✔
3012
    sixel_node_t *np;
167,596✔
3013
    sixel_node_t *next;
167,596✔
3014
    int x;
167,596✔
3015
    int emit_next_line;
167,596✔
3016

3017
    emit_next_line = (last_row_index >= 6);
324,313✔
3018
    if (emit_next_line) {
324,313✔
3019
        /*
3020
         * Emit DECGNL only after the first band. The first band starts at the
3021
         * origin, so leading DECGNL would shift short images down by 6 rows.
3022
         */
3023
        output->buffer[output->pos] = '-';
267,530✔
3024
        sixel_advance(output, 1);
267,530✔
3025
    }
117,424✔
3026

3027
    for (x = 0; (np = output->node_top) != NULL;) {
2,519,255!
3028
        if (x > np->sx) {
2,195,160✔
3029
            output->buffer[output->pos] = '$';
1,880,922✔
3030
            sixel_advance(output, 1);
1,880,922✔
3031
            x = 0;
1,880,917✔
3032
        }
866,628✔
3033

3034
        if (state->fillable) {
2,286,655✔
3035
            memset(np->map + np->sx,
286✔
3036
                   (1 << state->row_in_band) - 1,
176✔
3037
                   (size_t)(np->mx - np->sx));
176✔
3038
        }
99✔
3039
        status = sixel_put_node(output,
3,199,822✔
3040
                                &x,
3041
                                np,
1,004,712✔
3042
                                ncolors,
1,004,712✔
3043
                                keycolor);
1,004,712✔
3044
        if (SIXEL_FAILED(status)) {
2,195,072!
3045
            goto end;
3046
        }
3047
        next = np->next;
2,195,072✔
3048
        sixel_node_del(output, np);
2,195,072✔
3049
        np = next;
2,195,076✔
3050

3051
        while (np != NULL) {
92,397,404!
3052
            if (np->sx < x) {
90,199,994✔
3053
                np = np->next;
75,489,829✔
3054
                continue;
75,489,829✔
3055
            }
3056

3057
            if (state->fillable) {
14,713,633!
3058
                memset(np->map + np->sx,
×
3059
                       (1 << state->row_in_band) - 1,
3060
                       (size_t)(np->mx - np->sx));
3061
            }
3062
            status = sixel_put_node(output,
21,462,680✔
3063
                                    &x,
3064
                                    np,
6,749,984✔
3065
                                    ncolors,
6,749,984✔
3066
                                    keycolor);
6,749,984✔
3067
            if (SIXEL_FAILED(status)) {
14,710,485!
3068
                goto end;
3069
            }
3070
            next = np->next;
14,710,485✔
3071
            sixel_node_del(output, np);
14,710,485✔
3072
            np = next;
14,712,159✔
3073
        }
3074

3075
        state->fillable = 0;
2,194,945✔
3076
    }
3077

3078
    status = SIXEL_OK;
238,940✔
3079

3080
end:
181,753✔
3081
    (void)work;
227,491✔
3082
    return status;
406,527✔
3083
}
82,435✔
3084

3085

3086
SIXELSTATUS
3087
sixel_encode_body(
22,190✔
3088
    sixel_index_t       /* in */ *pixels,
3089
    int                 /* in */ width,
3090
    int                 /* in */ height,
3091
    unsigned char       /* in */ *palette,
3092
    float const         /* in */ *palette_float,
3093
    int                 /* in */ ncolors,
3094
    int                 /* in */ keycolor,
3095
    int                 /* in */ bodyonly,
3096
    sixel_output_t      /* in */ *output,
3097
    unsigned char       /* in */ *palstate,
3098
    sixel_allocator_t   /* in */ *allocator,
3099
    int                 /* in */ pin_threads,
3100
    sixel_timeline_logger_t      /* in */ *logger)
3101
{
3102
    SIXELSTATUS status = SIXEL_FALSE;
22,190✔
3103
    int band_start;
11,370✔
3104
    int band_height;
11,370✔
3105
    int row_index;
11,370✔
3106
    int absolute_row;
11,370✔
3107
    int last_row_index;
11,370✔
3108
    sixel_node_t *np;
11,370✔
3109
    sixel_encode_work_t work;
11,370✔
3110
    sixel_band_state_t band;
11,370✔
3111
    int logging_active;
11,370✔
3112
    int job_index;
11,370✔
3113

3114
#if !SIXEL_ENABLE_THREADS
3115
    (void) pin_threads;
4,212✔
3116
#endif
3117

3118
    sixel_encode_work_init(&work);
22,190✔
3119
    sixel_band_state_reset(&band);
22,190✔
3120

3121
    /* Record the caller/environment preference even before we fan out. */
3122
    work.requested_threads = sixel_threads_resolve();
22,190✔
3123

3124
    if (ncolors < 1) {
22,190!
3125
        status = SIXEL_BAD_ARGUMENT;
×
3126
        goto cleanup;
×
3127
    }
3128
    output->active_palette = (-1);
22,190✔
3129

3130
    logging_active = logger != NULL;
22,190✔
3131
    job_index = 0;
22,190✔
3132

3133
    status = sixel_encode_emit_palette(bodyonly,
29,146✔
3134
                                       ncolors,
6,956✔
3135
                                       keycolor,
6,956✔
3136
                                       palette,
6,956✔
3137
                                       palette_float,
6,956✔
3138
                                       output);
6,956✔
3139
    if (SIXEL_FAILED(status)) {
22,190!
3140
        goto cleanup;
×
3141
    }
3142

3143
#if SIXEL_ENABLE_THREADS
3144
    {
3145
        int nbands;
7,158✔
3146
        int threads;
7,158✔
3147

3148
        nbands = (height + 5) / 6;
13,766✔
3149
        threads = work.requested_threads;
13,766✔
3150
        if (nbands > 1 && threads > 1) {
13,766!
3151
            status = sixel_encode_body_parallel(pixels,
7,615✔
3152
                                                width,
2,740✔
3153
                                                height,
2,740✔
3154
                                                ncolors,
2,740✔
3155
                                                keycolor,
2,740✔
3156
                                                output,
2,740✔
3157
                                                palstate,
2,740✔
3158
                                                allocator,
2,740✔
3159
                                                threads,
2,740✔
3160
                                                pin_threads);
2,740✔
3161
            if (SIXEL_FAILED(status)) {
4,875!
3162
                goto cleanup;
3163
            }
3164
            goto finalize;
4,875✔
3165
        }
3166
    }
4,071!
3167
#endif
3168

3169
    if (logging_active) {
16,858!
3170
        sixel_timeline_logger_logf(logger,
12✔
3171
                          "controller",
3172
                          "encode",
3173
                          "configure",
3174
                          -1);
3175
    }
12✔
3176

3177
    status = sixel_encode_work_allocate(&work,
17,315✔
3178
                                        width,
4,216✔
3179
                                        ncolors,
4,216✔
3180
                                        allocator);
4,216✔
3181
    if (SIXEL_FAILED(status)) {
17,315!
3182
        goto cleanup;
×
3183
    }
3184

3185
    band_start = 0;
10,987✔
3186
    while (band_start < height) {
98,472!
3187
        band_height = height - band_start;
81,157✔
3188
        if (band_height > 6) {
81,157✔
3189
            band_height = 6;
38,003✔
3190
        }
10,927✔
3191

3192
        band.row_in_band = 0;
81,909✔
3193
        band.fillable = 0;
81,909✔
3194
        band.active_color_count = 0;
81,909✔
3195

3196
        if (logging_active) {
81,909!
3197
            sixel_timeline_logger_logf(logger,
24✔
3198
                              "worker",
3199
                              "encode",
3200
                              "start",
3201
                              job_index);
12✔
3202
        }
12✔
3203

3204
        for (row_index = 0; row_index < band_height; row_index++) {
518,404!
3205
            absolute_row = band_start + row_index;
437,247✔
3206
            status = sixel_band_classify_row(&work,
437,247✔
3207
                                             &band,
3208
                                             pixels,
76,275✔
3209
                                             width,
76,275✔
3210
                                             absolute_row,
76,275✔
3211
                                             ncolors,
76,275✔
3212
                                             keycolor,
76,275✔
3213
                                             palstate,
76,275✔
3214
                                             output->encode_policy);
76,275✔
3215
            if (SIXEL_FAILED(status)) {
437,247!
3216
                goto cleanup;
×
3217
            }
3218
        }
76,275✔
3219

3220
        status = sixel_band_compose(&work,
81,157✔
3221
                                    &band,
3222
                                    output,
15,143✔
3223
                                    width,
15,143✔
3224
                                    ncolors,
15,143✔
3225
                                    keycolor,
15,143✔
3226
                                    allocator);
15,143✔
3227
        if (SIXEL_FAILED(status)) {
81,157!
3228
            goto cleanup;
3229
        }
3230

3231
        last_row_index = band_start + band_height - 1;
81,157✔
3232
        status = sixel_band_emit(&work,
81,157✔
3233
                                 &band,
3234
                                 output,
15,143✔
3235
                                 ncolors,
15,143✔
3236
                                 keycolor,
15,143✔
3237
                                 last_row_index);
15,143✔
3238
        if (SIXEL_FAILED(status)) {
81,157!
3239
            goto cleanup;
3240
        }
3241

3242
        sixel_band_finish(&work, &band);
81,157✔
3243

3244
        sixel_band_clear_map(&work);
81,157✔
3245

3246
        if (logging_active) {
81,157!
3247
            sixel_timeline_logger_logf(logger,
24✔
3248
                              "worker",
3249
                              "encode",
3250
                              "finish",
3251
                              job_index);
12✔
3252
        }
12✔
3253

3254
        band_start += band_height;
81,157✔
3255
        sixel_band_state_reset(&band);
81,157✔
3256
        job_index += 1;
81,157✔
3257
    }
3258

3259
    status = SIXEL_OK;
17,315✔
3260
    goto finalize;
17,315✔
3261

3262
finalize:
15,234✔
3263
    if (palstate) {
23,294✔
3264
        output->buffer[output->pos] = '$';
2,934✔
3265
        sixel_advance(output, 1);
2,934✔
3266
    }
1,104✔
3267

3268
cleanup:
20,703✔
3269
    while ((np = output->node_free) != NULL) {
837,062!
3270
        output->node_free = np->next;
814,872✔
3271
        sixel_allocator_free(allocator, np);
814,872✔
3272
    }
3273
    output->node_top = NULL;
22,190✔
3274

3275
    sixel_encode_work_cleanup(&work, allocator);
22,190✔
3276

3277
    return status;
22,190✔
3278
}
4,071✔
3279
SIXELSTATUS
3280
sixel_encode_footer(sixel_output_t *output)
54,342✔
3281
{
3282
    SIXELSTATUS status = SIXEL_FALSE;
54,342✔
3283

3284
    if (output->pos > 0) {
54,342✔
3285
        status = sixel_output_write_bytes(output,
92,430✔
3286
                                          (char *)output->buffer,
54,285✔
3287
                                          output->pos);
23,990✔
3288
        if (SIXEL_FAILED(status)) {
54,285!
3289
            return status;
3290
        }
3291
        output->pos = 0;
54,285✔
3292
    }
23,990✔
3293

3294
    status = sixel_output_end_image(output);
54,342✔
3295

3296
    return status;
54,342✔
3297
}
24,017✔
3298

3299
static SIXELSTATUS
3300
sixel_encode_body_ormode(
24✔
3301
    uint8_t             /* in */ *pixels,
3302
    int                 /* in */ width,
3303
    int                 /* in */ height,
3304
    unsigned char       /* in */ *palette,
3305
    int                 /* in */ ncolors,
3306
    int                 /* in */ keycolor,
3307
    sixel_output_t      /* in */ *output)
3308
{
3309
    SIXELSTATUS status;
12✔
3310
    int n;
12✔
3311
    int nplanes;
12✔
3312
    uint8_t *buf;
12✔
3313
    uint8_t *buf_p;
12✔
3314
    int x;
12✔
3315
    int cur_h;
12✔
3316
    int nwrite;
12✔
3317
    int plane;
12✔
3318

3319
    if (pixels == NULL) {
24✔
3320
        return SIXEL_BAD_ARGUMENT;
3321
    }
3322
    buf = pixels;
1,816✔
3323

3324
    for (n = 0; n < ncolors; n++) {
6,168!
3325
        status = output_rgb_palette_definition(output, palette, NULL, n, keycolor);
6,144✔
3326
        if (SIXEL_FAILED(status)) {
6,144!
3327
            return status;
3328
        }
3329
    }
2,304✔
3330

3331
    for (nplanes = 8; nplanes > 1; nplanes--) {
24!
3332
        if (ncolors > (1 << (nplanes - 1))) {
24!
3333
            break;
17✔
3334
        }
3335
    }
3336

3337
    for (cur_h = 6; cur_h <= height; cur_h += 6) {
264!
3338
        for (plane = 0; plane < nplanes; plane++) {
2,160!
3339
            sixel_putc(output->buffer + output->pos, '#');
1,920✔
3340
            sixel_advance(output, 1);
1,920✔
3341
            nwrite = sixel_putnum((char *)output->buffer + output->pos, 1 << plane);
1,920✔
3342
            sixel_advance(output, nwrite);
1,920✔
3343

3344
            buf_p = buf;
1,920✔
3345
            for (x = 0; x < width; x++, buf_p++) {
125,360!
3346
                sixel_put_pixel(output,
168,960✔
3347
                                ((buf_p[0] >> plane) & 0x1) |
168,960✔
3348
                                (((buf_p[width] >> plane) << 1) & 0x2) |
168,960✔
3349
                                (((buf_p[width * 2] >> plane) << 2) & 0x4) |
168,960✔
3350
                                (((buf_p[width * 3] >> plane) << 3) & 0x8) |
168,960✔
3351
                                (((buf_p[width * 4] >> plane) << 4) & 0x10) |
168,960✔
3352
                                (((buf_p[width * 5] >> plane) << 5) & 0x20));
122,880✔
3353
            }
46,080✔
3354
            status = sixel_put_flash(output);
1,920✔
3355
            if (SIXEL_FAILED(status)) {
1,920!
3356
                return status;
3357
            }
3358
            sixel_putc(output->buffer + output->pos, '$');
1,920✔
3359
            sixel_advance(output, 1);
1,920✔
3360
        }
720✔
3361
        sixel_putc(output->buffer + output->pos, '-');
240✔
3362
        sixel_advance(output, 1);
240✔
3363
        buf += (width * 6);
240✔
3364
        }
90✔
3365

3366
    if (cur_h - height < 6) {
24!
3367
        for (plane = 0; plane < nplanes; plane++) {
216!
3368
            sixel_putc(output->buffer + output->pos, '#');
192✔
3369
            sixel_advance(output, 1);
192✔
3370
            nwrite = sixel_putnum((char *)output->buffer + output->pos, 1 << plane);
192✔
3371
            sixel_advance(output, nwrite);
192✔
3372

3373
            buf_p = buf;
192✔
3374
            for (x = 0; x < width; x++, buf_p++) {
12,536!
3375
                int pix = ((buf_p[0] >> plane) & 0x1);
12,288✔
3376

3377
                switch(cur_h - height) {
12,288!
3378
                case 1:
×
3379
                    pix |= (((buf_p[width * 4] >> plane) << 4) & 0x10);
×
3380
                    /* Fall through */
3381
                case 2:
7,680!
3382
                    pix |= (((buf_p[width * 3] >> plane) << 3) & 0x8);
12,288✔
3383
                    /* Fall through */
3384
                case 3:
7,680!
3385
                    pix |= (((buf_p[width * 2] >> plane) << 2) & 0x4);
12,288✔
3386
                    /* Fall through */
3387
                case 4:
7,680!
3388
                    pix |= (((buf_p[width] >> plane) << 1) & 0x2);
12,288✔
3389
                    /* Fall through */
3390
                default:
4,096!
3391
                    break;
12,288✔
3392
                }
3393

3394
                sixel_put_pixel(output, pix);
12,288✔
3395
            }
4,608✔
3396
            status = sixel_put_flash(output);
192✔
3397
            if (SIXEL_FAILED(status)) {
192!
3398
                return status;
3399
            }
3400

3401
            sixel_putc(output->buffer + output->pos, '$');
192✔
3402
            sixel_advance(output, 1);
192✔
3403
        }
72✔
3404
    }
9✔
3405

3406
    return 0;
17✔
3407
}
9✔
3408

3409

3410
static SIXELSTATUS
3411
sixel_encode_dither(
53,904✔
3412
    unsigned char   /* in */ *pixels,   /* pixel bytes to be encoded */
3413
    int             /* in */ width,     /* width of source image */
3414
    int             /* in */ height,    /* height of source image */
3415
    sixel_dither_t  /* in */ *dither,   /* dither context */
3416
    sixel_output_t  /* in */ *output)   /* output context */
3417
{
3418
    SIXELSTATUS status = SIXEL_FALSE;
53,904✔
3419
    sixel_index_t *paletted_pixels = NULL;
53,904✔
3420
    sixel_index_t *input_pixels;
27,892✔
3421
    size_t bufsize;
27,892✔
3422
    unsigned char *palette_entries = NULL;
53,904✔
3423
    float *palette_entries_float32 = NULL;
53,904✔
3424
    sixel_palette_t *palette_obj = NULL;
53,904✔
3425
    size_t palette_count = 0U;
53,904✔
3426
    size_t palette_float_count = 0U;
53,904✔
3427
    size_t palette_bytes = 0U;
53,904✔
3428
    size_t palette_float_bytes = 0U;
53,904✔
3429
    size_t palette_channels = 0U;
53,904✔
3430
    size_t palette_index = 0U;
53,904✔
3431
    int palette_source_colorspace;
27,892✔
3432
    int palette_float_pixelformat;
27,892✔
3433
    int output_float_pixelformat;
27,892✔
3434
    int palette_float_depth;
27,892✔
3435
    int pipeline_active;
27,892✔
3436
    int pipeline_threads = 0;  /* set to a deterministic default before use */
53,904✔
3437
    int pipeline_nbands;
27,892✔
3438
    sixel_parallel_dither_config_t dither_parallel;
27,892✔
3439
    char const *band_env_text;
27,892✔
3440
    int ormode_enabled;
27,892✔
3441
    sixel_palette_entries_view_t palette_view;
27,892✔
3442
    sixel_palette_float32_entries_view_t palette_float_view;
27,892✔
3443
#if SIXEL_ENABLE_THREADS
3444
    sixel_timeline_logger_t *serial_logger;
23,922✔
3445
    int logger_owned = 0;
45,964✔
3446
#endif  /* SIXEL_ENABLE_THREADS */
3447
    sixel_timeline_logger_t *logger = NULL;
53,904✔
3448

3449
    if (output == NULL || dither == NULL) {
53,904!
3450
        return SIXEL_BAD_ARGUMENT;
3451
    }
3452

3453
#if SIXEL_ENABLE_THREADS
3454
    serial_logger = NULL;
45,964✔
3455
#endif  /* SIXEL_ENABLE_THREADS */
3456
    palette_source_colorspace = SIXEL_COLORSPACE_GAMMA;
53,904✔
3457
    palette_float_pixelformat =
37,892✔
3458
        sixel_palette_float_pixelformat_for_colorspace(
53,904✔
3459
            palette_source_colorspace);
23,849✔
3460
    palette_float_depth =
37,892✔
3461
        sixel_helper_compute_depth(palette_float_pixelformat);
53,904✔
3462
    output_float_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
53,904✔
3463
    memset(&palette_view, 0, sizeof(palette_view));
53,904!
3464
    memset(&palette_float_view, 0, sizeof(palette_float_view));
53,904✔
3465
    palette_obj = dither->palette;
53,904✔
3466
    if (palette_obj == NULL || palette_obj->vtbl == NULL ||
75,734!
3467
            palette_obj->vtbl->get_entries == NULL ||
53,904!
3468
            palette_obj->vtbl->get_entries_float32 == NULL) {
53,904!
3469
        sixel_helper_set_additional_message(
×
3470
            "sixel_encode_dither: palette acquisition failed.");
3471
        status = SIXEL_BAD_ARGUMENT;
×
3472
        goto end;
×
3473
    }
3474

3475
    pipeline_active = 0;
53,904✔
3476
#if SIXEL_ENABLE_THREADS
3477
    #endif
3478
    dither_parallel.enabled = 0;
53,904✔
3479
    dither_parallel.band_height = 0;
53,904✔
3480
    dither_parallel.overlap = 0;
53,904✔
3481
    dither_parallel.dither_threads = 0;
53,904✔
3482
    dither_parallel.encode_threads = 0;
53,904✔
3483
    ormode_enabled = output != NULL && output->ormode != 0;
53,904!
3484
    /*
3485
     * Normalize the planner-provided pinning request so both palette and
3486
     * encode workers see the same 0/1 flag.
3487
     */
3488
    dither->pipeline_pin_threads =
54,177✔
3489
        dither->pipeline_pin_threads != 0 ? 1 : 0;
54,177!
3490
    dither_parallel.pin_threads = dither->pipeline_pin_threads;
54,177✔
3491
    switch (dither->pixelformat) {
54,177!
3492
    case SIXEL_PIXELFORMAT_PAL1:
×
3493
    case SIXEL_PIXELFORMAT_PAL2:
×
3494
    case SIXEL_PIXELFORMAT_PAL4:
×
3495
    case SIXEL_PIXELFORMAT_G1:
×
3496
    case SIXEL_PIXELFORMAT_G2:
×
3497
    case SIXEL_PIXELFORMAT_G4:
×
3498
        bufsize = (sizeof(sixel_index_t) * (size_t)width * (size_t)height * 3UL);
15✔
3499
        paletted_pixels = (sixel_index_t *)sixel_allocator_malloc(dither->allocator, bufsize);
15✔
3500
        if (paletted_pixels == NULL) {
15!
3501
            sixel_helper_set_additional_message(
×
3502
                "sixel_encode_dither: sixel_allocator_malloc() failed.");
3503
            status = SIXEL_BAD_ALLOCATION;
×
3504
            goto end;
×
3505
        }
3506
        status = sixel_helper_normalize_pixelformat(paletted_pixels,
30✔
3507
                                                    &dither->pixelformat,
15✔
3508
                                                    pixels,
15✔
3509
                                                    dither->pixelformat,
15✔
3510
                                                    width, height);
15✔
3511
        if (SIXEL_FAILED(status)) {
15!
3512
            goto end;
×
3513
        }
3514
        input_pixels = paletted_pixels;
15✔
3515
        break;
15✔
3516
    case SIXEL_PIXELFORMAT_PAL8:
1,536!
3517
    case SIXEL_PIXELFORMAT_G8:
×
3518
    case SIXEL_PIXELFORMAT_GA88:
×
3519
    case SIXEL_PIXELFORMAT_AG88:
×
3520
        input_pixels = pixels;
4,306✔
3521
        break;
4,306✔
3522
    default:
27,179!
3523
        /* apply palette */
3524
        pipeline_threads = sixel_threads_resolve();
48,243✔
3525
        band_env_text = sixel_compat_getenv(
48,243✔
3526
            "SIXEL_DITHER_PARALLEL_BAND_WIDTH");
3527
        if (pipeline_threads <= 1 && band_env_text != NULL
48,243!
3528
                && band_env_text[0] != '\0') {
1,781!
3529
            /*
3530
             * Parallel band dithering was explicitly requested via the
3531
             * environment.  When SIXEL_THREADS is absent, prefer hardware
3532
             * concurrency instead of silently running a single worker so
3533
             * that multiple dither jobs appear in the log.
3534
             */
3535
            pipeline_threads = sixel_threads_normalize(0);
20✔
3536
        }
3537
        pipeline_nbands = (height + 5) / 6;
48,242✔
3538
        /*
3539
         * Ormode encoding expects a palette-index buffer in a single,
3540
         * contiguous memory block. The palette-apply pipeline emits
3541
         * six-line jobs and leaves input_pixels unset until the encode
3542
         * stage pulls from the job queue, so forcing it on would leave a
3543
         * NULL input buffer and can crash when multiple encode threads
3544
         * run. Disable the pipeline when ormode is active to keep the
3545
         * data flow deterministic and thread-safe.
3546
         */
3547
        if (!ormode_enabled
65,904✔
3548
                && pipeline_threads > 1
48,234✔
3549
                && pipeline_nbands > 1) {
38,930!
3550
            pipeline_active = 1;
27,172✔
3551
            input_pixels = NULL;
27,172✔
3552
        } else {
18,073✔
3553
            paletted_pixels = sixel_dither_apply_palette(dither, pixels,
16,426✔
3554
                                                         width, height);
2,991✔
3555
            if (paletted_pixels == NULL) {
13,435!
3556
                status = SIXEL_RUNTIME_ERROR;
×
3557
                goto end;
×
3558
            }
3559
            input_pixels = paletted_pixels;
8,368✔
3560
        }
3561
        break;
35,540✔
3562
    }
3563

3564
    if (pipeline_active) {
47,497✔
3565
        sixel_parallel_dither_configure(height,
52,881✔
3566
                                        dither->ncolors,
18,073✔
3567
                                        pipeline_threads,
18,073✔
3568
                                        dither->pipeline_pin_threads,
18,073✔
3569
                                        &dither_parallel);
3570
        if (dither_parallel.enabled) {
34,808!
3571
            dither->pipeline_parallel_active = 1;
34,808✔
3572
            dither->pipeline_band_height = dither_parallel.band_height;
34,808✔
3573
            dither->pipeline_band_overlap = dither_parallel.overlap;
34,808✔
3574
            dither->pipeline_dither_threads =
34,808✔
3575
                dither_parallel.dither_threads;
34,808✔
3576
            pipeline_threads = dither_parallel.encode_threads;
34,808✔
3577
        }
18,073✔
3578
        if (pipeline_threads <= 1) {
37,024✔
3579
            /*
3580
             * Disable the pipeline when the encode side cannot spawn at
3581
             * least two workers.  A single encode thread cannot consume the
3582
             * six-line jobs that PaletteApply produces, so fall back to the
3583
             * serialized encoder path.
3584
             */
3585
            pipeline_active = 0;
184✔
3586
            dither->pipeline_parallel_active = 0;
184✔
3587
            if (paletted_pixels == NULL) {
184!
3588
                paletted_pixels = sixel_dither_apply_palette(dither, pixels,
269✔
3589
                                                             width, height);
85✔
3590
                if (paletted_pixels == NULL) {
184!
3591
                    status = SIXEL_RUNTIME_ERROR;
×
3592
                    goto end;
×
3593
                }
3594
            }
85✔
3595
            input_pixels = paletted_pixels;
139✔
3596
        }
85✔
3597
    }
18,073✔
3598

3599
#if SIXEL_ENABLE_THREADS
3600
    if (!pipeline_active) {
44,496!
3601
        logger = dither->pipeline_logger;
11,340✔
3602
        if (logger == NULL) {
11,340!
3603
            sixel_timeline_logger_prepare_default(dither->allocator,
11,340✔
3604
                                                  &serial_logger);
3605
            if (serial_logger != NULL) {
11,340!
3606
                logger_owned = 1;
12✔
3607
                dither->pipeline_logger = serial_logger;
12✔
3608
                logger = serial_logger;
12✔
3609
            } else {
12✔
3610
                logger = NULL;
8,846✔
3611
            }
3612
        }
5,861✔
3613
        if (logger != NULL) {
9,464!
3614
            sixel_timeline_logger_logf(logger,
24✔
3615
                              "controller",
3616
                              "pipeline",
3617
                              "configure",
3618
                              -1,
3619
                              -1,
3620
                              0,
3621
                              height,
12✔
3622
                              0,
3623
                              height,
12✔
3624
                              "serial path threads=1");
3625
        }
12✔
3626
    }
5,861✔
3627
#endif
3628

3629
    if (output != NULL) {
53,904!
3630
        palette_source_colorspace = output->source_colorspace;
53,904✔
3631
        palette_float_pixelformat =
37,892✔
3632
            sixel_palette_float_pixelformat_for_colorspace(
53,904✔
3633
                palette_source_colorspace);
23,849✔
3634
        palette_float_depth =
37,892✔
3635
            sixel_helper_compute_depth(palette_float_pixelformat);
53,904✔
3636
    }
23,849✔
3637

3638
    status = palette_obj->vtbl->get_entries(palette_obj, &palette_view);
53,904✔
3639
    if (SIXEL_FAILED(status) || palette_view.entries == NULL ||
75,734!
3640
            palette_view.depth != 3 || palette_view.entry_count == 0U) {
53,904!
3641
        sixel_helper_set_additional_message(
×
3642
            "sixel_encode_dither: palette view failed.");
3643
        status = SIXEL_RUNTIME_ERROR;
×
3644
        goto end;
×
3645
    }
3646
    palette_count = palette_view.entry_count;
53,904✔
3647
    palette_bytes = palette_count * 3U;
53,904✔
3648
    palette_entries = (unsigned char *)sixel_allocator_malloc(
53,904✔
3649
        dither->allocator,
23,849✔
3650
        palette_bytes);
23,849✔
3651
    if (palette_entries == NULL) {
53,904!
3652
        sixel_helper_set_additional_message(
×
3653
            "sixel_encode_dither: palette copy allocation failed.");
3654
        status = SIXEL_BAD_ALLOCATION;
×
3655
        goto end;
×
3656
    }
3657
    memcpy(palette_entries, palette_view.entries, palette_bytes);
53,904✔
3658

3659
    status = palette_obj->vtbl->get_entries_float32(palette_obj,
53,904✔
3660
                                                    &palette_float_view);
3661
    if (SIXEL_FAILED(status)) {
53,904!
3662
        goto end;
×
3663
    }
3664
    if (palette_float_view.entries != NULL &&
54,926!
3665
            palette_float_view.entry_count == palette_count &&
4,705!
3666
            palette_float_view.depth == palette_float_depth) {
4,705!
3667
        palette_float_count = palette_float_view.entry_count;
4,705✔
3668
        palette_float_bytes =
4,705✔
3669
            palette_float_count * (size_t)palette_float_view.depth;
4,705✔
3670
        palette_entries_float32 = (float *)sixel_allocator_malloc(
4,705✔
3671
            dither->allocator,
1,139✔
3672
            palette_float_bytes);
1,139✔
3673
        if (palette_entries_float32 == NULL) {
4,705!
3674
            sixel_helper_set_additional_message(
×
3675
                "sixel_encode_dither: float palette copy allocation failed.");
3676
            status = SIXEL_BAD_ALLOCATION;
×
3677
            goto end;
×
3678
        }
3679
        memcpy(palette_entries_float32,
4,822✔
3680
               palette_float_view.entries,
3,683✔
3681
               palette_float_bytes);
117✔
3682
    }
1,139✔
3683
    if (palette_entries != NULL && palette_entries_float32 != NULL
54,926!
3684
            && palette_count == palette_float_count
27,415!
3685
            && palette_count > 0U
4,705!
3686
            && !sixel_palette_float32_matches_u8(
4,705!
3687
                    palette_entries,
1,139✔
3688
                    palette_entries_float32,
1,139✔
3689
                    palette_count,
1,139✔
3690
                    palette_float_pixelformat)) {
1,139✔
3691
        sixel_palette_sync_float32_from_u8(palette_entries,
×
3692
                                           palette_entries_float32,
3693
                                           palette_count,
3694
                                           palette_float_pixelformat);
3695
    }
3696
    if (palette_entries != NULL && palette_count > 0U
75,734!
3697
            && output != NULL
39,861!
3698
            && output->source_colorspace != output->colorspace) {
53,904!
3699
        palette_bytes = palette_count * 3U;
1,704✔
3700
        if (palette_entries_float32 != NULL
1,704!
3701
                && palette_float_count == palette_count) {
1,294!
3702
            /*
3703
             * Use the higher-precision palette to change color spaces once and
3704
             * then quantize those float channels down to bytes.  The previous
3705
             * implementation converted the 8bit entries before overwriting
3706
             * them from float again, doubling the amount of work and rounding
3707
             * the palette twice.
3708
             */
3709
            palette_float_bytes = palette_bytes * sizeof(float);
340✔
3710
            status = sixel_helper_convert_colorspace(
340✔
3711
                (unsigned char *)palette_entries_float32,
27✔
3712
                palette_float_bytes,
27✔
3713
                palette_float_pixelformat,
27✔
3714
                output->source_colorspace,
27✔
3715
                output->colorspace);
27✔
3716
            if (SIXEL_FAILED(status)) {
340!
3717
                sixel_helper_set_additional_message(
×
3718
                    "sixel_encode_dither: float palette colorspace conversion failed.");
3719
                goto end;
×
3720
            }
3721
            output_float_pixelformat =
182✔
3722
                sixel_palette_float_pixelformat_for_colorspace(
340!
3723
                    output->colorspace);
27✔
3724
            palette_channels = palette_count * 3U;
340✔
3725
            for (palette_index = 0U; palette_index < palette_channels;
206,812!
3726
                    ++palette_index) {
206,472✔
3727
                int channel;
103,236✔
3728

3729
                channel = (int)(palette_index % 3U);
206,472✔
3730
                palette_entries[palette_index] =
206,472✔
3731
                    sixel_pixelformat_float_channel_to_byte(
206,472✔
3732
                        output_float_pixelformat,
243✔
3733
                        channel,
243✔
3734
                        palette_entries_float32[palette_index]);
206,472✔
3735
            }
243✔
3736
        } else {
27✔
3737
            status = sixel_helper_convert_colorspace(palette_entries,
1,976✔
3738
                                                     palette_bytes,
612✔
3739
                                                     SIXEL_PIXELFORMAT_RGB888,
3740
                                                     output->source_colorspace,
612✔
3741
                                                     output->colorspace);
612✔
3742
            if (SIXEL_FAILED(status)) {
1,364!
3743
                sixel_helper_set_additional_message(
×
3744
                    "sixel_encode_dither: palette colorspace "
3745
                    "conversion failed.");
3746
                goto end;
×
3747
            }
3748
        }
3749
    }
639✔
3750
    if (SIXEL_FAILED(status) || palette_entries == NULL) {
39,861!
3751
        sixel_helper_set_additional_message(
3752
            "sixel_encode_dither: palette copy failed.");
3753
        goto end;
3754
    }
3755

3756
    status = sixel_encode_header(width, height, dither->keycolor, output);
53,904✔
3757
    if (SIXEL_FAILED(status)) {
53,904!
3758
        goto end;
×
3759
    }
3760

3761
    if (output->ormode) {
53,904✔
3762
        status = sixel_encode_body_ormode(input_pixels,
33✔
3763
                                          width,
9✔
3764
                                          height,
9✔
3765
                                          palette_entries,
9✔
3766
                                          dither->ncolors,
9✔
3767
                                          dither->keycolor,
9✔
3768
                                          output);
9✔
3769
    } else if (pipeline_active) {
53,889!
3770
        status = sixel_encode_body_pipeline(pixels,
52,612✔
3771
                                            width,
17,988✔
3772
                                            height,
17,988✔
3773
                                            palette_entries,
17,988✔
3774
                                            palette_entries_float32,
17,988✔
3775
                                            dither,
17,988✔
3776
                                            output,
17,988✔
3777
                                            pipeline_threads);
17,988✔
3778
    } else {
17,988✔
3779
        status = sixel_encode_body(input_pixels,
25,108✔
3780
                                   width,
5,852✔
3781
                                   height,
5,852✔
3782
                                   palette_entries,
5,852✔
3783
                                   palette_entries_float32,
5,852✔
3784
                                   dither->ncolors,
5,852✔
3785
                                   dither->keycolor,
5,852✔
3786
                                   dither->bodyonly,
5,852✔
3787
                                   output,
5,852✔
3788
                                   NULL,
3789
                                   dither->allocator,
5,852✔
3790
                                   dither->pipeline_pin_threads,
5,852✔
3791
                                   logger != NULL ?
5,852✔
3792
                                       logger :
12✔
3793
                                       NULL);
3794
    }
3795

3796
    if (SIXEL_FAILED(status)) {
53,904!
3797
        goto end;
×
3798
    }
3799

3800
    status = sixel_encode_footer(output);
53,904✔
3801
    if (SIXEL_FAILED(status)) {
53,904!
3802
        goto end;
3803
    }
3804

3805
end:
30,055✔
3806
#if SIXEL_ENABLE_THREADS
3807
    if (logger_owned) {
45,964!
3808
        dither->pipeline_logger = NULL;
12✔
3809
        sixel_timeline_logger_unref(serial_logger);
12✔
3810
    }
12✔
3811
#endif
3812
    if (palette_entries != NULL) {
53,904!
3813
        sixel_allocator_free(dither->allocator, palette_entries);
53,904✔
3814
    }
23,849✔
3815
    if (palette_entries_float32 != NULL) {
56,609✔
3816
        sixel_allocator_free(dither->allocator, palette_entries_float32);
4,705✔
3817
    }
1,139✔
3818
    sixel_allocator_free(dither->allocator, paletted_pixels);
53,904✔
3819

3820
    return status;
53,904✔
3821
}
23,849✔
3822

3823
SIXEL_INTERNAL_API SIXELSTATUS
3824
sixel_encoder_core_encode_dispatch(
54,438✔
3825
    sixel_encoder_core_encode_request_t const *request)
3826
{
3827
    SIXELSTATUS status;
28,160✔
3828

3829
    if (request == NULL || request->pixels == NULL ||
76,418!
3830
        request->dither == NULL || request->output == NULL) {
54,342!
3831
        return SIXEL_BAD_ARGUMENT;
68✔
3832
    }
3833
    if (request->width < 1 || request->height < 1) {
54,342!
3834
        return SIXEL_BAD_INPUT;
3835
    }
3836

3837
    (void)request->depth;
38,186✔
3838
    if (request->dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
54,342✔
3839
        status = sixel_encode_highcolor(request->pixels,
606✔
3840
                                        request->width,
312✔
3841
                                        request->height,
312✔
3842
                                        request->dither,
312✔
3843
                                        request->output);
312✔
3844
    } else {
168✔
3845
        status = sixel_encode_dither(request->pixels,
77,753✔
3846
                                     request->width,
39,861✔
3847
                                     request->height,
39,861✔
3848
                                     request->dither,
39,861✔
3849
                                     request->output);
39,861✔
3850
    }
3851

3852
    return status;
40,173✔
3853
}
24,053✔
3854

3855
SIXELAPI SIXELSTATUS
3856
sixel_encode(
54,318✔
3857
    unsigned char  /* in */ *pixels,   /* pixel bytes */
3858
    int            /* in */ width,     /* image width */
3859
    int            /* in */ height,    /* image height */
3860
    int const      /* in */ depth,     /* color depth */
3861
    sixel_dither_t /* in */ *dither,   /* dither context */
3862
    sixel_output_t /* in */ *output)   /* output context */
3863
{
3864
    SIXELSTATUS status = SIXEL_FALSE;
54,318✔
3865
    sixel_encoder_core_t *core;
28,100✔
3866
    sixel_encoder_core_encode_request_t request;
28,100✔
3867

3868
    if (pixels == NULL) {
54,318!
3869
        sixel_helper_set_additional_message(
×
3870
            "sixel_encode: bad pixels parameter."
3871
            " (pixels == NULL)");
3872
        return SIXEL_BAD_ARGUMENT;
×
3873
    }
3874

3875
    if (dither == NULL) {
54,318!
3876
        sixel_helper_set_additional_message(
×
3877
            "sixel_encode: bad dither parameter."
3878
            " (dither == NULL)");
3879
        return SIXEL_BAD_ARGUMENT;
×
3880
    }
3881

3882
    if (output == NULL) {
54,318!
3883
        sixel_helper_set_additional_message(
×
3884
            "sixel_encode: bad output parameter."
3885
            " (output == NULL)");
3886
        return SIXEL_BAD_ARGUMENT;
×
3887
    }
3888

3889
    /* TODO: reference counting should be thread-safe */
3890
    sixel_dither_ref(dither);
54,318✔
3891
    sixel_output_ref(output);
54,318✔
3892

3893
    if (width < 1) {
54,318!
3894
        sixel_helper_set_additional_message(
×
3895
            "sixel_encode: bad width parameter."
3896
            " (width < 1)");
3897
        status = SIXEL_BAD_INPUT;
×
3898
        goto end;
×
3899
    }
3900

3901
    if (height < 1) {
54,318!
3902
        sixel_helper_set_additional_message(
×
3903
            "sixel_encode: bad height parameter."
3904
            " (height < 1)");
3905
        status = SIXEL_BAD_INPUT;
×
3906
        goto end;
×
3907
    }
3908

3909
    core = sixel_output_as_encoder_core(output);
54,318✔
3910
    if (core == NULL || core->vtbl == NULL ||
54,318!
3911
        core->vtbl->encode == NULL) {
54,318!
3912
        status = SIXEL_BAD_ARGUMENT;
×
3913
        goto end;
×
3914
    }
3915
    request.pixels = pixels;
54,318✔
3916
    request.width = width;
54,318✔
3917
    request.height = height;
54,318✔
3918
    request.depth = depth;
54,318✔
3919
    request.dither = dither;
54,318✔
3920
    request.output = output;
54,318✔
3921
    status = core->vtbl->encode(core, &request);
54,318✔
3922

3923
end:
30,310✔
3924
    sixel_output_unref(output);
54,318✔
3925
    sixel_dither_unref(dither);
54,318✔
3926

3927
    return status;
54,318✔
3928
}
24,008✔
3929

3930

3931
/* emacs Local Variables:      */
3932
/* emacs mode: c               */
3933
/* emacs tab-width: 4          */
3934
/* emacs indent-tabs-mode: nil */
3935
/* emacs c-basic-offset: 4     */
3936
/* emacs End:                  */
3937
/* vim: set expandtab ts=4 sts=4 sw=4 : */
3938
/* 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