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

saitoha / libsixel / 25641692718

10 May 2026 06:43PM UTC coverage: 86.759% (+0.01%) from 86.748%
25641692718

push

github

saitoha
fix: export loader CMS helpers for Windows tests

127365 of 257198 branches covered (49.52%)

153075 of 176437 relevant lines covered (86.76%)

9515946.94 hits per line

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

94.49
/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,
52,424✔
253
                                      sixel_timeline_logger_t **logger)
254
{
255
    if (logger == NULL) {
52,424!
256
        return;
257
    }
258

259
    *logger = NULL;
52,424✔
260
    (void)sixel_timeline_logger_prepare_env(allocator, logger);
52,424✔
261
}
27,345✔
262
#endif
263

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

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

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

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

295
    dither_env_override = 0;
35,875✔
296
    dither_threads = (pipeline_threads * 7 + 9) / 10;
35,875✔
297
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_THREADS_MAX");
35,875✔
298
    if (text != NULL && text[0] != '\0') {
35,875!
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) {
25,728!
310
        dither_threads = 1;
311
    }
312
    if (dither_threads > pipeline_threads) {
33,600!
313
        dither_threads = pipeline_threads;
314
    }
315

316
    if (!dither_env_override && pipeline_threads >= 4 && dither_threads < 2) {
35,870!
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;
35,870✔
325
    if (encode_threads < 2 && pipeline_threads > 2) {
35,870!
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;
35,591✔
332
        dither_threads = pipeline_threads - encode_threads;
35,591✔
333
    }
18,465✔
334
    if (encode_threads < 1) {
38,113✔
335
        encode_threads = 1;
184✔
336
        dither_threads = pipeline_threads - encode_threads;
184✔
337
    }
85✔
338
    if (dither_threads < 1) {
29,575!
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;
35,875✔
349
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_WIDTH");
35,875✔
350
    if (text != NULL && text[0] != '\0') {
35,875!
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,998✔
361
        band_height = (height + dither_threads - 1) / dither_threads;
35,775✔
362
    }
18,550✔
363
    if (band_height < 6) {
38,090✔
364
        band_height = 6;
1,803✔
365
    }
429✔
366
    if ((band_height % 6) != 0) {
34,452✔
367
        band_height = ((band_height + 5) / 6) * 6;
28,501✔
368
    }
16,073✔
369

370
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_OVERWRAP");
35,875✔
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) {
35,875✔
376
        overlap = 6;
15,637✔
377
    } else {
10,321✔
378
        overlap = 0;
15,804✔
379
    }
380
    if (text != NULL && text[0] != '\0') {
35,875!
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) {
25,728!
391
        overlap = 0;
392
    }
393
    if (overlap > band_height / 2) {
33,653✔
394
        overlap = band_height / 2;
3,073✔
395
    }
631✔
396

397
    config->enabled = 1;
35,875✔
398
    config->band_height = band_height;
35,875✔
399
    config->overlap = overlap;
35,875✔
400
    config->dither_threads = dither_threads;
35,875✔
401
    config->encode_threads = encode_threads;
35,875✔
402
}
18,595!
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)
40,686✔
440
{
441
    memset(ctx, 0, sizeof(*ctx));
40,686✔
442
    ctx->pixels = NULL;
40,686✔
443
    ctx->keycolor = (-1);
40,686✔
444
    ctx->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
40,686✔
445
    ctx->writer_error = SIXEL_OK;
40,686✔
446
}
33,584✔
447

448
static void
449
sixel_parallel_worker_release_nodes(sixel_parallel_worker_state_t *state,
323,847✔
450
                                    sixel_allocator_t *allocator)
451
{
452
    sixel_node_t *np;
167,099✔
453

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

458
    while ((np = state->output->node_free) != NULL) {
14,395,165!
459
        state->output->node_free = np->next;
14,071,315✔
460
        sixel_allocator_free(allocator, np);
14,071,315✔
461
    }
462
    state->output->node_top = NULL;
323,850✔
463
}
163,977!
464

465
static void
466
sixel_parallel_worker_cleanup(sixel_parallel_worker_state_t *state,
117,666✔
467
                              sixel_allocator_t *allocator)
468
{
469
    if (state == NULL) {
117,666✔
470
        return;
36,155✔
471
    }
472
    sixel_parallel_worker_release_nodes(state, allocator);
71,922✔
473
    if (state->output != NULL) {
71,922!
474
        sixel_output_unref(state->output);
71,922✔
475
        state->output = NULL;
71,922✔
476
    }
32,719✔
477
    sixel_encode_work_cleanup(&state->work, allocator);
71,922✔
478
    sixel_band_state_reset(&state->band);
71,922✔
479
    state->initialized = 0;
71,922✔
480
    state->index = 0;
71,922✔
481
    state->writer_error = SIXEL_OK;
71,922✔
482
    state->band_buffer = NULL;
71,922✔
483
    state->context = NULL;
71,922✔
484
}
58,383✔
485

486
static void
487
sixel_parallel_context_cleanup(sixel_parallel_context_t *ctx)
40,686✔
488
{
489
    int i;
21,231✔
490

491
    if (ctx->workers != NULL) {
40,686!
492
        for (i = 0; i < ctx->worker_capacity; i++) {
158,352!
493
            sixel_parallel_worker_cleanup(ctx->workers[i], ctx->allocator);
117,666✔
494
        }
58,383✔
495
        free(ctx->workers);
40,686✔
496
        ctx->workers = NULL;
40,686✔
497
    }
21,304✔
498
    sixel_parallel_writer_stop(ctx, 1);
40,686✔
499
    if (ctx->bands != NULL) {
40,686!
500
        if (ctx->band_count < 0) {
40,686!
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++) {
292,662!
508
            free(ctx->bands[i].data);
251,976✔
509
            ctx->bands[i].data = NULL;
251,976✔
510
        }
131,304✔
511
#if defined(_MSC_VER)
512
#pragma warning(pop)
513
#endif
514
        free(ctx->bands);
40,686✔
515
        ctx->bands = NULL;
40,686✔
516
    }
21,304✔
517
    ctx->band_count = 0;
40,686✔
518
    if (ctx->pool != NULL) {
40,686!
519
        ctx->pool->vtbl->unref(ctx->pool);
40,686✔
520
        ctx->pool = NULL;
40,686✔
521
    }
21,304✔
522
    if (ctx->cond_ready) {
40,686!
523
        sixel_cond_destroy(&ctx->cond_band_ready);
40,686✔
524
        ctx->cond_ready = 0;
40,686✔
525
    }
21,304✔
526
    if (ctx->mutex_ready) {
40,686!
527
        sixel_mutex_destroy(&ctx->mutex);
40,686✔
528
        ctx->mutex_ready = 0;
40,686✔
529
    }
21,304✔
530
}
40,686✔
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)
251,971✔
569
{
570
    int accept;
131,403✔
571

572
    if (ctx == NULL) {
251,971✔
573
        return 0;
574
    }
575
    if (!ctx->mutex_ready) {
251,971!
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);
251,971✔
583
    accept = (!ctx->writer_should_stop && ctx->writer_error == SIXEL_OK);
251,976!
584
    sixel_mutex_unlock(&ctx->mutex);
251,976✔
585
    return accept;
251,974✔
586
}
131,304✔
587

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

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

607
static SIXELSTATUS
608
sixel_parallel_worker_prepare(sixel_parallel_worker_state_t *state,
251,965✔
609
                              sixel_parallel_context_t *ctx)
610
{
611
    SIXELSTATUS status;
131,398✔
612

613
    if (state->initialized) {
251,965✔
614
        return SIXEL_OK;
142,383✔
615
    }
616

617
    sixel_encode_work_init(&state->work);
71,916✔
618
    sixel_band_state_reset(&state->band);
71,910✔
619
    state->writer_error = SIXEL_OK;
71,909✔
620
    state->band_buffer = NULL;
71,909✔
621
    state->context = ctx;
71,909✔
622

623
    status = sixel_encode_work_allocate(&state->work,
122,105✔
624
                                        ctx->width,
32,719✔
625
                                        ctx->ncolors,
32,719✔
626
                                        ctx->allocator);
32,719✔
627
    if (SIXEL_FAILED(status)) {
71,921!
628
        return status;
629
    }
630

631
    status = sixel_encoder_core_create_output_from_factory(&state->output,
104,634✔
632
                                              sixel_parallel_band_writer,
633
                                              state,
32,716✔
634
                                              ctx->allocator);
32,716✔
635
    if (SIXEL_FAILED(status)) {
71,915!
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;
71,917✔
641
    state->output->has_sixel_scrolling = ctx->output->has_sixel_scrolling;
71,917✔
642
    state->output->has_sdm_glitch = ctx->output->has_sdm_glitch;
71,917✔
643
    state->output->has_gri_arg_limit = ctx->output->has_gri_arg_limit;
71,917✔
644
    state->output->skip_dcs_envelope = 1;
71,917✔
645
    state->output->skip_header = 1;
71,917✔
646
    state->output->palette_type = ctx->output->palette_type;
71,917✔
647
    state->output->colorspace = ctx->output->colorspace;
71,917✔
648
    state->output->source_colorspace = ctx->output->source_colorspace;
71,917✔
649
    state->output->pixelformat = ctx->output->pixelformat;
71,917✔
650
    state->output->penetrate_multiplexer =
71,917✔
651
        ctx->output->penetrate_multiplexer;
71,917✔
652
    state->output->encode_policy = ctx->output->encode_policy;
71,917✔
653
    state->output->ormode = ctx->output->ormode;
71,917✔
654

655
    state->initialized = 1;
71,917✔
656
    state->index = (-1);
71,917✔
657

658
    if (ctx->mutex_ready) {
71,917!
659
        sixel_mutex_lock(&ctx->mutex);
71,903✔
660
    }
32,708✔
661
    if (ctx->worker_registered < ctx->worker_capacity) {
71,926!
662
        state->index = ctx->worker_registered;
71,922✔
663
        ctx->workers[state->index] = state;
71,922✔
664
        ctx->worker_registered += 1;
71,922✔
665
    }
32,719✔
666
    if (ctx->mutex_ready) {
71,926!
667
        sixel_mutex_unlock(&ctx->mutex);
71,922✔
668
    }
32,719✔
669

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

675
    return SIXEL_OK;
54,445✔
676
}
131,304✔
677

678
static SIXELSTATUS
679
sixel_parallel_context_grow(sixel_parallel_context_t *ctx, int target_threads)
35,691✔
680
{
681
    int capped_target;
18,528✔
682
    int delta;
18,528✔
683
    int status;
18,528✔
684

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

689
    capped_target = target_threads;
35,691✔
690
    if (capped_target > ctx->worker_capacity) {
35,691✔
691
        capped_target = ctx->worker_capacity;
12,302✔
692
    }
5,931✔
693
    if (ctx->band_count > 0 && capped_target > ctx->band_count) {
35,691!
694
        capped_target = ctx->band_count;
7,837✔
695
    }
696
    if (capped_target <= ctx->thread_count) {
35,691✔
697
        return SIXEL_OK;
7,893✔
698
    }
699

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

707
    if (ctx->logger != NULL) {
25,644✔
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,961✔
716
}
18,510✔
717

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

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

741
    required = band->used + (size_t)size;
251,049✔
742
    if (required < band->used) {
251,049!
743
        state->writer_error = SIXEL_BAD_INTEGER_OVERFLOW;
744
        return size;
745
    }
746
    capacity = band->size;
251,049✔
747
    if (required > capacity) {
251,049!
748
        if (capacity == 0) {
251,046!
749
            new_capacity = (size_t)SIXEL_OUTPUT_PACKET_SIZE;
196,070✔
750
        } else {
130,813✔
751
            new_capacity = capacity;
41✔
752
        }
753
        while (new_capacity < required) {
251,047!
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);
250,971✔
761
        if (tmp == NULL) {
250,971!
762
            state->writer_error = SIXEL_BAD_ALLOCATION;
763
            return size;
764
        }
765
        band->data = tmp;
250,971✔
766
        band->size = new_capacity;
250,971✔
767
    }
130,775✔
768

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

772
    return size;
250,977✔
773
}
130,775✔
774

775
static SIXELSTATUS
776
sixel_parallel_create_pool(sixel_thread_pool_t **pool,
40,686✔
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;
21,231✔
784
    sixel_thread_pool_create_request_t request;
21,231✔
785
    void *service_object;
21,231✔
786
    SIXELSTATUS status;
21,231✔
787

788
    if (pool != NULL) {
40,686!
789
        *pool = NULL;
40,686✔
790
    }
21,304✔
791
    if (pool == NULL) {
40,686!
792
        return SIXEL_BAD_ARGUMENT;
793
    }
794

795
    service = NULL;
40,686✔
796
    service_object = NULL;
40,686✔
797
    status = sixel_components_getservice("services/threadpool",
40,686✔
798
                                         &service_object);
799
    if (SIXEL_FAILED(status)) {
40,686!
800
        return status;
801
    }
802
    service = (sixel_threadpool_service_t *)service_object;
40,686✔
803
    if (service == NULL || service->vtbl == NULL ||
40,686!
804
        service->vtbl->create_pool == NULL) {
40,686!
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;
40,686✔
813
    request.queue_size = queue_depth;
40,686✔
814
    request.workspace_size = workspace_size;
40,686✔
815
    request.worker = worker;
40,686✔
816
    request.userdata = userdata;
40,686✔
817
    request.workspace_cleanup = NULL;
40,686✔
818
    status = service->vtbl->create_pool(service, &request, pool);
40,686✔
819
    if (service->vtbl->unref != NULL) {
40,686!
820
        service->vtbl->unref(service);
40,686✔
821
    }
21,304✔
822

823
    return status;
31,848✔
824
}
21,304✔
825

826
static SIXELSTATUS
827
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
40,685✔
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;
21,231✔
843
    int nbands;
21,231✔
844
    int threads;
21,231✔
845
    int i;
21,231✔
846

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

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

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

887
    if (logger != NULL) {
38,155✔
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,
40,686✔
896
                                                        sizeof(*ctx->bands));
897
    if (ctx->bands == NULL) {
40,686!
898
        return SIXEL_BAD_ALLOCATION;
899
    }
900
    for (i = 0; i < nbands; ++i) {
292,662!
901
        ctx->bands[i].data = NULL;
251,976✔
902
        ctx->bands[i].size = 0;
251,976✔
903
        ctx->bands[i].used = 0;
251,976✔
904
        ctx->bands[i].status = SIXEL_OK;
251,976✔
905
        ctx->bands[i].ready = 0;
251,976✔
906
        ctx->bands[i].dispatched = 0;
251,976✔
907
    }
131,304✔
908
    ctx->band_count = nbands;
40,686✔
909

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

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

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

928
    ctx->queue_capacity = queue_capacity;
40,686✔
929
    if (ctx->queue_capacity < 1) {
40,686!
930
        ctx->queue_capacity = nbands;
931
    }
932
    if (ctx->queue_capacity > nbands) {
38,142!
933
        ctx->queue_capacity = nbands;
934
    }
935

936
    status = sixel_parallel_create_pool(
40,686✔
937
        &ctx->pool,
21,304✔
938
        threads,
21,304✔
939
        ctx->queue_capacity,
21,304✔
940
        sizeof(sixel_parallel_worker_state_t),
941
        sixel_parallel_worker_main,
942
        ctx);
21,304✔
943
    if (SIXEL_FAILED(status)) {
40,685!
944
        return status;
945
    }
946

947
    ctx->pool->vtbl->set_affinity(ctx->pool, ctx->pin_threads);
40,685✔
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);
40,686✔
954
    ctx->next_band_to_flush = 0;
40,686✔
955
    ctx->writer_should_stop = 0;
40,686✔
956
    ctx->writer_error = SIXEL_OK;
40,686✔
957

958
    status = sixel_thread_create(&ctx->writer_thread,
61,990✔
959
                                 sixel_parallel_writer_main,
960
                                 ctx);
21,304✔
961
    if (SIXEL_FAILED(status)) {
40,686!
962
        sixel_mutex_unlock(&ctx->mutex);
963
        return status;
964
    }
965
    ctx->writer_started = 1;
40,686✔
966
    sixel_mutex_unlock(&ctx->mutex);
40,686✔
967

968
    return SIXEL_OK;
40,686✔
969
}
21,304✔
970

971
static void
972
sixel_parallel_submit_band(sixel_parallel_context_t *ctx, int band_index)
251,972✔
973
{
974
    sixel_thread_pool_job_t job;
131,403✔
975
    SIXELSTATUS status;
131,403✔
976
    int dispatch;
131,403✔
977

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

985
    dispatch = 0;
251,974✔
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) {
251,974!
992
        sixel_mutex_lock(&ctx->mutex);
251,973✔
993
        if (!ctx->bands[band_index].dispatched
372,159!
994
                && !ctx->writer_should_stop
241,254!
995
                && ctx->writer_error == SIXEL_OK) {
251,976!
996
            ctx->bands[band_index].dispatched = 1;
251,976✔
997
            dispatch = 1;
251,976✔
998
        }
131,304✔
999
        sixel_mutex_unlock(&ctx->mutex);
251,976✔
1000
    } else {
131,304✔
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) {
251,975!
1009
        return;
1010
    }
1011

1012
    sixel_fence_release();
251,975✔
1013
    if (ctx->logger != NULL) {
251,975✔
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;
236,280✔
1021
    status = ctx->pool->vtbl->push(ctx->pool, job);
236,280✔
1022
    if (SIXEL_FAILED(status)) {
236,281!
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
}
131,304!
1032

1033
static SIXELSTATUS
1034
sixel_parallel_context_wait(sixel_parallel_context_t *ctx, int force_abort)
40,686✔
1035
{
1036
    int pool_error;
21,231✔
1037

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

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

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

1053
    return SIXEL_OK;
31,848✔
1054
}
21,304✔
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,234,946✔
1063
{
1064
    sixel_parallel_row_notifier_t *notifier;
644,144✔
1065
    sixel_parallel_context_t *ctx;
644,144✔
1066
    sixel_timeline_logger_t *logger;
644,144✔
1067
    int band_height;
644,144✔
1068
    int band_index;
644,144✔
1069

1070
    notifier = (sixel_parallel_row_notifier_t *)priv;
1,234,946✔
1071
    if (notifier == NULL) {
1,234,946✔
1072
        return;
1073
    }
1074
    ctx = notifier->context;
1,234,946✔
1075
    logger = notifier->logger;
1,234,946✔
1076
    if (ctx == NULL || ctx->band_count <= 0 || ctx->height <= 0) {
1,234,946!
1077
        return;
12✔
1078
    }
1079
    if (row_index < 0) {
1,234,959!
1080
        return;
1081
    }
1082
    band_height = notifier->band_height;
1,234,959✔
1083
    if (band_height < 1) {
1,234,959!
1084
        band_height = 6;
1085
    }
1086
    if ((row_index % band_height) != band_height - 1
1,223,053✔
1087
            && row_index != ctx->height - 1) {
1,133,082!
1088
        return;
793,387✔
1089
    }
1090

1091
    band_index = row_index / band_height;
205,167✔
1092
    if (band_index >= ctx->band_count) {
205,167!
1093
        band_index = ctx->band_count - 1;
1094
    }
1095
    if (band_index < 0) {
219,101!
1096
        return;
1097
    }
1098

1099
    if (logger != NULL) {
219,101✔
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);
219,101✔
1108
}
643,928!
1109

1110
static SIXELSTATUS
1111
sixel_parallel_flush_band(sixel_parallel_context_t *ctx, int band_index)
251,976✔
1112
{
1113
    sixel_parallel_band_buffer_t *band;
131,405✔
1114
    size_t offset;
131,405✔
1115
    size_t chunk;
131,405✔
1116

1117
    band = &ctx->bands[band_index];
251,976✔
1118
    if (ctx->logger != NULL) {
251,976✔
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;
196,830✔
1126
    while (offset < band->used) {
506,782!
1127
        chunk = band->used - offset;
254,806✔
1128
        if (chunk > (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos)) {
254,806✔
1129
            chunk = (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos);
2,885✔
1130
        }
1,871✔
1131
        memcpy(ctx->output->buffer + ctx->output->pos,
266,054✔
1132
               band->data + offset,
133,317✔
1133
               chunk);
11,248✔
1134
        sixel_advance(ctx->output, (int)chunk);
254,806✔
1135
        offset += chunk;
254,806✔
1136
    }
1137
    return SIXEL_OK;
251,976✔
1138
}
76,259✔
1139

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

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

1159
    if (ctx == NULL || state == NULL) {
251,954!
1160
        return SIXEL_BAD_ARGUMENT;
14✔
1161
    }
1162

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

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

1188
    sixel_fence_acquire();
251,903✔
1189

1190
    status = sixel_parallel_worker_prepare(state, ctx);
251,903✔
1191
    if (SIXEL_FAILED(status)) {
251,902!
1192
        goto cleanup;
1193
    }
1194

1195
    if (!sixel_parallel_jobs_allowed(ctx)) {
251,903!
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;
251,902✔
1213
    sixel_parallel_worker_reset(state);
251,902✔
1214

1215
    band_start = band_index * 6;
251,883✔
1216
    band_height = ctx->height - band_start;
251,883✔
1217
    if (band_height > 6) {
251,883✔
1218
        band_height = 6;
164,969✔
1219
    }
109,992✔
1220
    if (band_height <= 0) {
205,587!
1221
        goto cleanup;
1222
    }
1223

1224
    if (ctx->logger != NULL) {
251,882✔
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,673,792!
1233
        absolute_row = band_start + row_index;
1,421,910✔
1234
        status = sixel_band_classify_row(&state->work,
2,159,998✔
1235
                                         &state->band,
738,088✔
1236
                                         ctx->pixels,
738,088✔
1237
                                         ctx->width,
738,088✔
1238
                                         absolute_row,
738,088✔
1239
                                         ctx->ncolors,
738,088✔
1240
                                         ctx->keycolor,
738,088✔
1241
                                         ctx->palstate,
738,088✔
1242
                                         ctx->encode_policy);
738,088✔
1243
        if (SIXEL_FAILED(status)) {
1,421,922!
1244
            goto cleanup;
1245
        }
1246
    }
738,074✔
1247

1248
    status = sixel_band_compose(&state->work,
383,127✔
1249
                                &state->band,
131,231✔
1250
                                state->output,
131,231✔
1251
                                ctx->width,
131,231✔
1252
                                ctx->ncolors,
131,231✔
1253
                                ctx->keycolor,
131,231✔
1254
                                ctx->allocator);
131,231✔
1255
    if (SIXEL_FAILED(status)) {
251,880!
1256
        goto cleanup;
1257
    }
1258

1259
    last_row_index = band_start + band_height - 1;
251,878✔
1260
    status = sixel_band_emit(&state->work,
383,107✔
1261
                             &state->band,
131,229✔
1262
                             state->output,
131,229✔
1263
                             ctx->ncolors,
131,229✔
1264
                             ctx->keycolor,
131,229✔
1265
                             last_row_index);
131,229✔
1266
    if (SIXEL_FAILED(status)) {
251,877!
1267
        goto cleanup;
1268
    }
1269

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

1275
    if (state->output->pos > 0) {
251,871✔
1276
        state->writer_error = sixel_output_write_bytes(
371,190✔
1277
            state->output,
130,802✔
1278
            (char *)state->output->buffer,
250,985✔
1279
            state->output->pos);
196,065✔
1280
        state->output->pos = 0;
251,007✔
1281
    }
130,802✔
1282
    if (state->writer_error != SIXEL_OK) {
251,890!
1283
        status = state->writer_error;
1284
        goto cleanup;
1285
    }
1286

1287
    sixel_band_finish(&state->work, &state->band);
251,890✔
1288
    status = SIXEL_OK;
251,892✔
1289

1290
cleanup:
120,668✔
1291
    sixel_parallel_worker_release_nodes(state, ctx->allocator);
251,958✔
1292
    if (band != NULL && ctx->mutex_ready && ctx->cond_ready) {
251,959!
1293
        sixel_fence_release();
251,922✔
1294
        sixel_mutex_lock(&ctx->mutex);
251,922✔
1295
        band->status = status;
251,926✔
1296
        band->ready = 1;
251,926✔
1297
        sixel_cond_broadcast(&ctx->cond_band_ready);
251,926✔
1298
        sixel_mutex_unlock(&ctx->mutex);
251,926✔
1299
    }
131,254✔
1300
    if (ctx->logger != NULL) {
267,712✔
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)) {
251,972!
1308
        return status;
1309
    }
1310
    return SIXEL_OK;
196,824✔
1311
}
131,304✔
1312

1313
static void
1314
sixel_parallel_writer_stop(sixel_parallel_context_t *ctx, int force_abort)
81,372✔
1315
{
1316
    int should_signal;
42,462✔
1317

1318
    if (ctx == NULL || !ctx->writer_started) {
81,372!
1319
        return;
31,848✔
1320
    }
1321

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

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

1346
static int
1347
sixel_parallel_writer_main(void *arg)
40,686✔
1348
{
1349
    sixel_parallel_context_t *ctx;
21,231✔
1350
    sixel_parallel_band_buffer_t *band;
21,231✔
1351
    SIXELSTATUS status;
21,231✔
1352
    int band_index;
21,231✔
1353

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

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

1367
    for (;;) {
216,592✔
1368
        sixel_mutex_lock(&ctx->mutex);
292,662✔
1369
        while (!ctx->writer_should_stop &&
649,802!
1370
               ctx->next_band_to_flush < ctx->band_count) {
439,877✔
1371
    band_index = ctx->next_band_to_flush;
399,191✔
1372
    band = &ctx->bands[band_index];
399,191✔
1373
    if (band->ready) {
399,191✔
1374
        break;
196,830✔
1375
    }
1376
            sixel_cond_wait(&ctx->cond_band_ready, &ctx->mutex);
147,215✔
1377
        }
1378

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

1384
        if (ctx->next_band_to_flush >= ctx->band_count) {
292,662✔
1385
            sixel_mutex_unlock(&ctx->mutex);
40,686✔
1386
            break;
40,686✔
1387
        }
1388

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

1399
        sixel_fence_acquire();
251,976✔
1400
        status = band->status;
251,976✔
1401
        if (ctx->logger != NULL) {
251,976✔
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)) {
251,976!
1409
            status = sixel_parallel_flush_band(ctx, band_index);
251,976✔
1410
        }
131,304✔
1411
        if (SIXEL_FAILED(status)) {
251,976!
1412
            sixel_parallel_context_abort_locked(ctx, status);
1413
            break;
1414
        }
1415
    }
1416

1417
    return SIXEL_OK;
31,848✔
1418
}
21,304✔
1419

1420
static SIXELSTATUS
1421
sixel_encode_body_parallel(sixel_index_t *pixels,
4,995✔
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,995✔
1433
    SIXELSTATUS status;
2,703✔
1434
    int nbands;
2,703✔
1435
    int threads;
2,703✔
1436
    int i;
2,703✔
1437
    int queue_depth;
2,703✔
1438
    sixel_timeline_logger_t *logger;
2,703✔
1439

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

1448
    threads = requested_threads;
4,995✔
1449
    if (threads > nbands) {
4,995✔
1450
        threads = nbands;
1,648✔
1451
    }
1,108✔
1452
    if (threads < 1) {
4,899!
1453
        threads = 1;
1454
    }
1455
    ctx.thread_count = threads;
4,934✔
1456
    queue_depth = threads * 3;
4,934✔
1457
    if (queue_depth > nbands) {
4,934✔
1458
        queue_depth = nbands;
3,284✔
1459
    }
2,276✔
1460
    if (queue_depth < 1) {
5,151!
1461
        queue_depth = 1;
1462
    }
1463

1464
    status = sixel_parallel_context_begin(&ctx,
4,995✔
1465
                                          pixels,
2,794✔
1466
                                          width,
2,794✔
1467
                                          height,
2,794✔
1468
                                          ncolors,
2,794✔
1469
                                          keycolor,
2,794✔
1470
                                          palstate,
2,794✔
1471
                                          output,
2,794✔
1472
                                          allocator,
2,794✔
1473
                                          threads,
2,794✔
1474
                                          threads,
2,794✔
1475
                                          queue_depth,
2,794✔
1476
                                          pin_threads,
2,794✔
1477
                                          logger);
2,794✔
1478
    if (SIXEL_FAILED(status)) {
4,995!
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,864!
1485
        sixel_parallel_submit_band(&ctx, i);
32,869✔
1486
    }
16,839✔
1487

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

1495
    sixel_parallel_context_cleanup(&ctx);
4,995✔
1496
    sixel_timeline_logger_unref(logger);
4,995✔
1497
    return SIXEL_OK;
4,995✔
1498
}
2,794✔
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,
35,691✔
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;
18,528✔
1519
    SIXELSTATUS wait_status;
18,528✔
1520
    sixel_parallel_context_t ctx = {0};
35,691✔
1521
    sixel_index_t *indexes;
18,528✔
1522
    sixel_index_t *result;
18,528✔
1523
    sixel_allocator_t *allocator;
18,528✔
1524
    size_t pixel_count;
18,528✔
1525
    size_t buffer_size;
18,528✔
1526
    int threads;
18,528✔
1527
    int nbands;
18,528✔
1528
    int queue_depth;
18,528✔
1529
    int waited;
18,528✔
1530
    int dither_threads_budget;
18,528✔
1531
    int worker_capacity;
18,528✔
1532
    int boost_target;
18,528✔
1533
    sixel_timeline_logger_t *logger;
18,528✔
1534
    int owns_logger;
18,528✔
1535
    sixel_parallel_row_notifier_t notifier;
18,528✔
1536

1537
    if (pixels == NULL
52,623!
1538
            || (palette == NULL && palette_float == NULL)
35,691!
1539
            || dither == NULL
35,691!
1540
            || output == NULL) {
35,691!
1541
        return SIXEL_BAD_ARGUMENT;
1542
    }
1543

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

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

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

1576
    status = sixel_encode_emit_palette(dither->bodyonly,
54,201✔
1577
                                       dither->ncolors,
18,510✔
1578
                                       dither->keycolor,
18,510✔
1579
                                       palette,
18,510✔
1580
                                       palette_float,
18,510✔
1581
                                       output);
18,510✔
1582
    if (SIXEL_FAILED(status)) {
35,691!
1583
        goto cleanup;
1584
    }
1585

1586
    queue_depth = threads * 3;
35,691✔
1587
    if (queue_depth > nbands) {
35,691✔
1588
        queue_depth = nbands;
18,148✔
1589
    }
11,747✔
1590
    if (queue_depth < 1) {
36,211!
1591
        queue_depth = 1;
1592
    }
1593

1594
    dither_threads_budget = dither->pipeline_dither_threads;
33,425✔
1595
    worker_capacity = threads + dither_threads_budget;
33,425✔
1596
    if (worker_capacity < threads) {
33,425!
1597
        worker_capacity = threads;
1598
    }
1599
    if (worker_capacity > nbands) {
34,052✔
1600
        worker_capacity = nbands;
12,302✔
1601
    }
5,931✔
1602

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

1611
    if (logger != NULL) {
34,666✔
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,
35,691✔
1626
                                          indexes,
18,510✔
1627
                                          width,
18,510✔
1628
                                          height,
18,510✔
1629
                                          dither->ncolors,
18,510✔
1630
                                          dither->keycolor,
18,510✔
1631
                                          NULL,
1632
                                          output,
18,510✔
1633
                                          allocator,
18,510✔
1634
                                          threads,
18,510✔
1635
                                          worker_capacity,
18,510✔
1636
                                          queue_depth,
18,510✔
1637
                                          dither->pipeline_pin_threads,
18,510✔
1638
                                          logger);
18,510✔
1639
    if (SIXEL_FAILED(status)) {
35,691!
1640
        goto cleanup;
1641
    }
1642

1643
    result = sixel_dither_apply_palette(dither, pixels, width, height);
35,691✔
1644
    if (result == NULL) {
35,691!
1645
        status = SIXEL_RUNTIME_ERROR;
1646
        goto cleanup;
1647
    }
1648
    if (result != indexes) {
35,691!
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;
35,691✔
1659
    status = sixel_parallel_context_grow(&ctx, boost_target);
35,691✔
1660
    if (SIXEL_FAILED(status)) {
35,691!
1661
        goto cleanup;
1662
    }
1663

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

1670
cleanup:
17,181✔
1671
    dither->pipeline_row_callback = NULL;
35,691✔
1672
    dither->pipeline_row_priv = NULL;
35,691✔
1673
    dither->pipeline_index_buffer = NULL;
35,691✔
1674
    dither->pipeline_index_size = 0;
35,691✔
1675
    dither->pipeline_image_width = 0;
35,691✔
1676
    dither->pipeline_image_height = 0;
35,691✔
1677
    dither->pipeline_transparent_mask = NULL;
35,691✔
1678
    dither->pipeline_transparent_mask_size = 0;
35,691✔
1679
    dither->pipeline_transparent_keycolor = (-1);
35,691✔
1680
    if (!waited && ctx.pool != NULL) {
35,691!
1681
        wait_status = sixel_parallel_context_wait(&ctx, status != SIXEL_OK);
1682
        if (status == SIXEL_OK) {
×
1683
            status = wait_status;
7,837✔
1684
        }
1685
    }
1686
    sixel_parallel_context_cleanup(&ctx);
33,438✔
1687
    if (owns_logger) {
33,438✔
1688
        sixel_timeline_logger_unref(logger);
243✔
1689
    }
111✔
1690
    if (indexes != NULL) {
35,691!
1691
        sixel_allocator_free(allocator, indexes);
35,691✔
1692
    }
18,510✔
1693
    return status;
35,691✔
1694
}
18,510✔
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)
168,258,466✔
1722
{
1723
    if ((output->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) {
168,258,466✔
1724
        (void)sixel_output_write_bytes(output,
6,931✔
1725
                                       (char *)output->buffer,
4,898✔
1726
                                       SIXEL_OUTPUT_PACKET_SIZE);
1727
        memcpy(output->buffer,
5,085✔
1728
               output->buffer + SIXEL_OUTPUT_PACKET_SIZE,
1,713✔
1729
               (size_t)(output->pos -= SIXEL_OUTPUT_PACKET_SIZE));
3,052✔
1730
    }
2,033✔
1731
}
168,267,888✔
1732

1733

1734
static void
1735
sixel_putc(unsigned char *buffer, unsigned char value)
47,701,353✔
1736
{
1737
    *buffer = value;
47,701,353✔
1738
}
35,370,934✔
1739

1740

1741
static void
1742
sixel_puts(unsigned char *buffer, char const *value, int size)
4,480,097✔
1743
{
1744
    memcpy(buffer, (void *)value, (size_t)size);
4,480,097✔
1745
}
3,631,077✔
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,
71,011,778✔
1754
                          unsigned char value,
1755
                          int count)
1756
{
1757
    int chunk;
36,791,671✔
1758

1759
    if (count <= 0) {
71,011,778!
1760
        return;
1761
    }
1762

1763
    while (count > 0) {
142,051,182!
1764
        chunk = SIXEL_OUTPUT_PACKET_SIZE - output->pos;
71,029,651✔
1765
        if (chunk > count) {
71,029,651✔
1766
            chunk = count;
52,415,005✔
1767
        }
31,224,571✔
1768
        memset(output->buffer + output->pos, value, (size_t)chunk);
71,029,860✔
1769
        sixel_advance(output, chunk);
71,029,860✔
1770
        count -= chunk;
71,039,350✔
1771
    }
1772
}
31,210,545!
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,602,289✔
1781
                             int width,
1782
                             int start)
1783
{
1784
    int idx;
14,470,796✔
1785
    size_t chunk_size;
14,470,796✔
1786
    unsigned char const *cursor;
14,470,796✔
1787
    unsigned long block;
14,470,796✔
1788

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

1793
    while ((width - idx) >= (int)chunk_size) {
252,174,966!
1794
        memcpy(&block, cursor, chunk_size);
241,388,195✔
1795
        if (block != 0UL) {
241,388,195✔
1796
            break;
12,525,571✔
1797
        }
1798
        idx += (int)chunk_size;
224,572,677✔
1799
        cursor += chunk_size;
224,572,677✔
1800
    }
1801

1802
    while (idx < width) {
102,797,597!
1803
        if (*cursor != 0) {
92,319,691✔
1804
            break;
12,767,980✔
1805
        }
1806
        idx += 1;
75,192,637✔
1807
        cursor += 1;
75,192,637✔
1808
    }
1809

1810
    return idx;
35,053,022✔
1811
}
7,449,483✔
1812

1813

1814
static int
1815
sixel_compose_measure_gap(unsigned char const *row,
30,234,520✔
1816
                          int width,
1817
                          int start,
1818
                          int *reached_end)
1819
{
1820
    int gap;
15,822,213✔
1821
    size_t chunk_size;
15,822,213✔
1822
    unsigned char const *cursor;
15,822,213✔
1823
    unsigned long block;
15,822,213✔
1824
    int remaining;
15,822,213✔
1825

1826
    gap = 0;
30,234,520✔
1827
    *reached_end = 0;
30,234,520✔
1828
    if (start >= width) {
30,234,520✔
1829
        *reached_end = 1;
358,843✔
1830
        return gap;
358,843✔
1831
    }
1832

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

1837
    while (remaining >= (int)chunk_size) {
153,679,615!
1838
        memcpy(&block, cursor, chunk_size);
142,702,704✔
1839
        if (block != 0UL) {
142,702,704✔
1840
            break;
14,058,307✔
1841
        }
1842
        gap += (int)chunk_size;
123,802,124✔
1843
        cursor += chunk_size;
123,802,124✔
1844
        remaining -= (int)chunk_size;
123,802,124✔
1845
    }
1846

1847
    while (remaining > 0) {
93,682,000!
1848
        if (*cursor != 0) {
83,540,267✔
1849
            return gap;
14,710,551✔
1850
        }
1851
        gap += 1;
63,803,255✔
1852
        cursor += 1;
63,803,255✔
1853
        remaining -= 1;
63,803,255✔
1854
    }
1855

1856
    *reached_end = 1;
10,137,211✔
1857
    return gap;
10,137,211✔
1858
}
13,820,554✔
1859

1860

1861
#if HAVE_LDIV
1862
static int
1863
sixel_putnum_impl(char *buffer, long value, int pos)
85,955,465✔
1864
{
1865
    ldiv_t r;
44,867,699✔
1866

1867
    r = ldiv(value, 10);
85,955,465✔
1868
    if (r.quot > 0) {
85,960,654✔
1869
        pos = sixel_putnum_impl(buffer, r.quot, pos);
42,536,404✔
1870
    }
19,387,290✔
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);
85,964,451✔
1876
    return pos + 1;
108,729,163✔
1877
}
22,764,712✔
1878
#endif  /* HAVE_LDIV */
1879

1880

1881
static int
1882
sixel_putnum(char *buffer, int value)
43,487,369✔
1883
{
1884
    int pos;
22,655,052✔
1885

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

1892
    return pos;
54,898,618✔
1893
}
11,405,968✔
1894

1895

1896
static SIXELSTATUS
1897
sixel_put_flash(sixel_output_t *const output)
79,925,496✔
1898
{
1899
    int nwrite;
41,437,703✔
1900

1901
    if (output->save_count <= 0) {
79,925,496✔
1902
        return SIXEL_OK;
196,756✔
1903
    }
1904

1905
    if (output->has_gri_arg_limit) {  /* VT240 Max 255 ? */
79,674,593✔
1906
            while (output->save_count > 255) {
83,982!
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;
58,134✔
1914
            }
1915
        }
9,693✔
1916

1917
    if (output->save_count > 3) {
79,750,603✔
1918
        /* DECGRI Graphics Repeat Introducer ! Pn Ch */
1919
        sixel_putc(output->buffer + output->pos, '!');
8,717,491✔
1920
        sixel_advance(output, 1);
8,717,110✔
1921
        nwrite = sixel_putnum((char *)output->buffer + output->pos, output->save_count);
8,716,998✔
1922
        sixel_advance(output, nwrite);
8,718,036✔
1923
        sixel_putc(output->buffer + output->pos,
12,625,615✔
1924
                   (unsigned char)output->save_pixel);
8,717,144✔
1925
        sixel_advance(output, 1);
8,716,981✔
1926
    } else {
3,908,471✔
1927
        sixel_output_emit_literal(output,
102,262,565✔
1928
                                  (unsigned char)output->save_pixel,
71,033,112✔
1929
                                  output->save_count);
31,229,453✔
1930
    }
1931

1932
    output->save_pixel = 0;
79,697,161✔
1933
    output->save_count = 0;
79,697,161✔
1934

1935
    return SIXEL_OK;
79,697,161✔
1936
}
35,252,392✔
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)
79,656,757✔
1946
{
1947
    SIXELSTATUS status = SIXEL_FALSE;
79,656,757✔
1948

1949
    if (count <= 0) {
79,656,757!
1950
        return SIXEL_OK;
1951
    }
1952

1953
    if (output->save_count > 0) {
79,656,689✔
1954
        if (output->save_pixel == symbol) {
62,757,801✔
1955
            output->save_count += count;
62,310✔
1956
            return SIXEL_OK;
62,310✔
1957
        }
1958

1959
        status = sixel_put_flash(output);
62,696,069✔
1960
        if (SIXEL_FAILED(status)) {
62,678,022!
1961
            return status;
1962
        }
1963
    }
27,354,064✔
1964

1965
    output->save_pixel = symbol;
79,565,861✔
1966
    output->save_count = count;
79,565,861✔
1967

1968
    return SIXEL_OK;
79,565,861✔
1969
}
35,034,703✔
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,
17,220,849✔
1978
                         unsigned char const *map,
1979
                         int length)
1980
{
1981
    SIXELSTATUS status = SIXEL_FALSE;
17,220,849✔
1982
    int index;
9,029,214✔
1983
    int run_length;
9,029,214✔
1984
    unsigned char value;
9,029,214✔
1985
    size_t chunk_size;
9,029,214✔
1986
    unsigned long pattern;
9,029,214✔
1987
    unsigned long block;
9,029,214✔
1988
    int chunk_mismatch;
9,029,214✔
1989
    int remain;
9,029,214✔
1990
    int byte_index;
9,029,214✔
1991

1992
    if (length <= 0) {
17,220,849!
1993
        return SIXEL_OK;
1994
    }
1995

1996
    for (index = 0; index < length; index += run_length) {
90,056,784!
1997
        value = map[index];
72,964,884✔
1998
        if (value > '?') {
72,964,884!
1999
            value = 0;
×
2000
        }
2001

2002
        run_length = 1;
72,964,673✔
2003
        chunk_size = sizeof(unsigned long);
72,964,673✔
2004
        chunk_mismatch = 0;
72,964,673✔
2005
        if (chunk_size > 1) {
72,964,673!
2006
            remain = length - (index + run_length);
72,900,601✔
2007
            pattern = (~0UL / 0xffUL) * (unsigned long)value;
72,900,601✔
2008

2009
            while (remain >= (int)chunk_size) {
76,200,959!
2010
                memcpy(&block,
38,134,077✔
2011
                       map + index + run_length,
25,513,150✔
2012
                       chunk_size);
1,842,994✔
2013
                block ^= pattern;
38,134,077✔
2014
                if (block != 0UL) {
38,134,077✔
2015
                    for (byte_index = 0;
32,757,512!
2016
                         byte_index < (int)chunk_size;
52,125,907!
2017
                         byte_index++) {
17,291,176✔
2018
                        if ((block & 0xffUL) != 0UL) {
52,118,038✔
2019
                            chunk_mismatch = 1;
24,808,771✔
2020
                            break;
24,808,771✔
2021
                        }
2022
                        block >>= 8;
17,291,071✔
2023
                        run_length += 1;
17,291,071✔
2024
                    }
7,953,416✔
2025
                    break;
24,809,155✔
2026
                }
2027
                run_length += (int)chunk_size;
3,300,358✔
2028
                remain -= (int)chunk_size;
3,300,358✔
2029
            }
2030
        }
32,073,715✔
2031

2032
        if (!chunk_mismatch) {
74,416,399✔
2033
            while (index + run_length < length) {
50,379,365!
2034
                unsigned char next;
17,982,600✔
2035

2036
                next = map[index + run_length];
33,363,823✔
2037
                if (next > '?') {
33,363,823!
2038
                    next = 0;
×
2039
                }
2040
                if (next != value) {
33,372,246✔
2041
                    break;
16,700,960✔
2042
                }
2043
                run_length += 1;
11,889,603✔
2044
            }
10,765,301!
2045
        }
18,794,255✔
2046

2047
        status = sixel_emit_run(output,
104,859,595✔
2048
                                 (int)value + '?',
53,744,998✔
2049
                                 run_length);
32,007,695✔
2050
        if (SIXEL_FAILED(status)) {
72,835,975!
2051
            return status;
2052
        }
2053
    }
32,007,695✔
2054

2055
    return SIXEL_OK;
12,731,923✔
2056
}
7,817,111✔
2057

2058

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

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

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

2074
    *np = (sixel_node_t *)sixel_allocator_malloc(allocator,
14,827,984✔
2075
                                                 sizeof(sixel_node_t));
2076
    if (np == NULL) {
14,829,418!
2077
        sixel_helper_set_additional_message(
90✔
2078
            "sixel_node_new: sixel_allocator_malloc() failed.");
2079
        status = SIXEL_BAD_ALLOCATION;
2080
        goto end;
2081
    }
2082

2083
    status = SIXEL_OK;
11,542,963✔
2084

2085
end:
7,125,779✔
2086
    return status;
19,367,757✔
2087
}
4,539,708✔
2088

2089
static void
2090
sixel_node_del(sixel_output_t *output, sixel_node_t *np)
17,129,103✔
2091
{
2092
    sixel_node_t *tp;
8,979,924✔
2093

2094
    if ((tp = output->node_top) == np) {
17,129,103✔
2095
        output->node_top = np->next;
3,963,412✔
2096
    } else {
1,757,122✔
2097
        while (tp->next != NULL) {
621,875,189!
2098
            if (tp->next == np) {
621,889,257✔
2099
                tp->next = np->next;
13,192,276✔
2100
                break;
13,192,276✔
2101
            }
2102
            tp = tp->next;
457,133,235✔
2103
        }
2104
    }
2105

2106
    np->next = output->node_free;
17,172,045✔
2107
    output->node_free = np;
17,172,045✔
2108
}
17,172,045✔
2109

2110

2111
static SIXELSTATUS
2112
sixel_put_node(
17,138,063✔
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;
17,138,063✔
2120
    int nwrite;
8,983,586✔
2121

2122
    if (ncolors != 2 || keycolor == (-1)) {
17,138,063!
2123
        /* designate palette index */
2124
        if (output->active_palette != np->pal) {
17,119,191✔
2125
            sixel_putc(output->buffer + output->pos, '#');
16,909,791✔
2126
            sixel_advance(output, 1);
16,909,279✔
2127
            nwrite = sixel_putnum((char *)output->buffer + output->pos, np->pal);
16,908,886✔
2128
            sixel_advance(output, nwrite);
16,911,015✔
2129
            output->active_palette = np->pal;
16,910,580✔
2130
        }
7,757,260✔
2131
    }
7,836,992✔
2132

2133
    if (*x < np->sx) {
17,722,968✔
2134
        int span;
3,424,587✔
2135

2136
        span = np->sx - *x;
6,549,173✔
2137
        status = sixel_emit_run(output, '?', span);
6,549,173✔
2138
        if (SIXEL_FAILED(status)) {
6,548,921!
2139
            goto end;
2140
        }
2141
        *x = np->sx;
6,548,909✔
2142
    }
2,974,967!
2143

2144
    if (*x < np->mx) {
17,114,910!
2145
        int span;
8,961,003✔
2146

2147
        span = np->mx - *x;
17,113,299✔
2148
        status = sixel_emit_span_from_map(output,
29,294,114✔
2149
                                          (unsigned char const *)np->map + *x,
17,113,299✔
2150
                                          span);
7,818,885✔
2151
        if (SIXEL_FAILED(status)) {
17,110,287!
2152
            goto end;
2153
        }
2154
        *x = np->mx;
17,107,250✔
2155
    }
7,815,848✔
2156

2157
    status = sixel_put_flash(output);
17,108,641✔
2158
    if (SIXEL_FAILED(status)) {
17,108,217!
2159
        goto end;
2160
    }
2161

2162
end:
9,290,487✔
2163
    return status;
17,105,374✔
2164
}
4,597,051✔
2165

2166

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

2175
    if (output->ormode) {
56,135✔
2176
        p[0] = 7;
85✔
2177
        p[1] = 5;
85✔
2178
    } else if (keycolor >= 0) {
56,060!
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,624✔
2184
    }
2,210✔
2185

2186
    output->pos = 0;
56,135✔
2187

2188
    if (p[2] == 0) {
56,135!
2189
        pcount--;
56,135✔
2190
        if (p[1] == 0) {
56,135✔
2191
            pcount--;
51,391✔
2192
            if (p[0] == 0) {
51,391!
2193
                pcount--;
51,391✔
2194
            }
22,482✔
2195
        }
22,482✔
2196
    }
24,737✔
2197

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

2207
    return status;
70,492✔
2208
}
14,357✔
2209

2210

2211
static int
2212
sixel_palette_float_pixelformat_for_colorspace(int colorspace)
97,111✔
2213
{
2214
    switch (colorspace) {
97,111✔
2215
    case SIXEL_COLORSPACE_LINEAR:
112✔
2216
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
238✔
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:
32,708✔
2224
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
81,171✔
2225
    }
2226
}
49,129✔
2227

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

2239
    if (palette == NULL || palette_float == NULL || count == 0U) {
4,721!
2240
        return 1;
2241
    }
2242
    if (count > SIZE_MAX / 3U) {
4,721!
2243
        return 0;
2244
    }
2245
    limit = count * 3U;
4,721✔
2246
    for (index = 0U; index < limit; ++index) {
787,061!
2247
        channel = (int)(index % 3U);
782,340✔
2248
        expected = sixel_pixelformat_float_channel_to_byte(
1,157,511✔
2249
            float_pixelformat,
28,893✔
2250
            channel,
28,893✔
2251
            palette_float[index]);
782,340✔
2252
        if (palette[index] != expected) {
782,340!
2253
            return 0;
2254
        }
2255
    }
28,893✔
2256
    return 1;
2,989✔
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,273,035✔
2287
                                    float const *palette_float,
2288
                                    int n,
2289
                                    int channel)
2290
{
2291
    size_t index;
6,888,978✔
2292
    float value;
6,888,978✔
2293
    int percent;
6,888,978✔
2294

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

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

2316
    return 0;
2317
}
5,866,833✔
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,427,641✔
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,427,641✔
2354
    int nwrite;
2,298,053✔
2355

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

2391
    status = SIXEL_OK;
4,427,641✔
2392

2393
    return status;
5,566,370✔
2394
}
1,138,729✔
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)
94,827✔
2521
{
2522
    work->map = NULL;
94,827✔
2523
    work->map_size = 0;
94,827✔
2524
    work->columns = NULL;
94,827✔
2525
    work->columns_size = 0;
94,827✔
2526
    work->active_colors = NULL;
94,827✔
2527
    work->active_colors_size = 0;
94,827✔
2528
    work->active_color_index = NULL;
94,827✔
2529
    work->active_color_index_size = 0;
94,827✔
2530
    work->requested_threads = 1;
94,827✔
2531
}
69,803✔
2532

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

2545
    len = ncolors * width;
89,820✔
2546
    work->map = (char *)sixel_allocator_calloc(allocator,
126,893✔
2547
                                               (size_t)len,
37,073✔
2548
                                               sizeof(char));
2549
    if (work->map == NULL && len > 0) {
89,837!
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;
89,836✔
2556

2557
    columns_size = sizeof(sixel_node_t *) * (size_t)width;
89,836✔
2558
    if (width > 0) {
89,836!
2559
        work->columns = (sixel_node_t **)sixel_allocator_malloc(
89,836✔
2560
            allocator,
37,072✔
2561
            columns_size);
37,072✔
2562
        if (work->columns == NULL) {
89,831!
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);
89,830✔
2569
        work->columns_size = columns_size;
89,830✔
2570
    } else {
37,072✔
2571
        work->columns = NULL;
×
2572
        work->columns_size = 0;
×
2573
    }
2574

2575
    active_colors_size = (size_t)ncolors;
89,830✔
2576
    if (active_colors_size > 0) {
89,830!
2577
        work->active_colors =
113,851✔
2578
            (unsigned char *)sixel_allocator_malloc(allocator,
126,890✔
2579
                                                    active_colors_size);
37,065✔
2580
        if (work->active_colors == NULL) {
89,828!
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);
89,827✔
2587
        work->active_colors_size = active_colors_size;
89,827✔
2588
    } else {
37,064✔
2589
        work->active_colors = NULL;
7✔
2590
        work->active_colors_size = 0;
7✔
2591
    }
2592

2593
    active_color_index_size = sizeof(int) * (size_t)ncolors;
89,832✔
2594
    if (active_color_index_size > 0) {
89,832!
2595
        work->active_color_index = (int *)sixel_allocator_malloc(
89,832✔
2596
            allocator,
37,069✔
2597
            active_color_index_size);
37,069✔
2598
        if (work->active_color_index == NULL) {
89,832!
2599
            sixel_helper_set_additional_message(
×
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);
89,830✔
2605
        work->active_color_index_size = active_color_index_size;
89,830✔
2606
    } else {
37,067✔
2607
        work->active_color_index = NULL;
2608
        work->active_color_index_size = 0;
2609
    }
2610

2611
    status = SIXEL_OK;
65,804✔
2612

2613
end:
28,736✔
2614
    if (SIXEL_FAILED(status)) {
65,804!
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;
110,561✔
2638
}
20,731✔
2639

2640
static void
2641
sixel_encode_work_cleanup(sixel_encode_work_t *work,
94,838✔
2642
                          sixel_allocator_t *allocator)
2643
{
2644
    if (work->active_color_index != NULL) {
94,838✔
2645
        sixel_allocator_free(allocator, work->active_color_index);
89,843✔
2646
        work->active_color_index = NULL;
89,843✔
2647
    }
37,079✔
2648
    work->active_color_index_size = 0;
94,560✔
2649
    if (work->active_colors != NULL) {
94,560✔
2650
        sixel_allocator_free(allocator, work->active_colors);
89,843✔
2651
        work->active_colors = NULL;
89,843✔
2652
    }
37,079✔
2653
    work->active_colors_size = 0;
94,560✔
2654
    if (work->columns != NULL) {
94,560✔
2655
        sixel_allocator_free(allocator, work->columns);
89,843✔
2656
        work->columns = NULL;
89,843✔
2657
    }
37,079✔
2658
    work->columns_size = 0;
94,560✔
2659
    if (work->map != NULL) {
94,560✔
2660
        sixel_allocator_free(allocator, work->map);
89,843✔
2661
        work->map = NULL;
89,843✔
2662
    }
37,079✔
2663
    work->map_size = 0;
94,838✔
2664
}
94,838✔
2665

2666
static void
2667
sixel_band_state_reset(sixel_band_state_t *state)
478,055✔
2668
{
2669
    state->row_in_band = 0;
478,055✔
2670
    state->fillable = 0;
478,055✔
2671
    state->active_color_count = 0;
475,760✔
2672
}
371,177✔
2673

2674
static void
2675
sixel_band_finish(sixel_encode_work_t *work, sixel_band_state_t *state)
335,164✔
2676
{
2677
    int color_index;
173,078✔
2678
    int c;
173,078✔
2679

2680
    if (work->active_colors == NULL
335,164!
2681
        || work->active_color_index == NULL) {
335,165!
2682
        state->active_color_count = 0;
2✔
2683
        return;
2✔
2684
    }
2685

2686
    for (color_index = 0;
5,475,284!
2687
         color_index < state->active_color_count;
11,643,551✔
2688
         color_index++) {
11,308,380✔
2689
        c = work->active_color_index[color_index];
11,308,434✔
2690
        if (c >= 0
11,308,434!
2691
            && (size_t)c < work->active_colors_size) {
11,308,442!
2692
            work->active_colors[c] = 0;
11,308,413✔
2693
        }
5,228,437✔
2694
    }
5,228,563✔
2695
    state->active_color_count = 0;
335,121✔
2696
}
146,470!
2697

2698
static void
2699
sixel_band_clear_map(sixel_encode_work_t *work)
335,229✔
2700
{
2701
    if (work->map != NULL && work->map_size > 0) {
335,229!
2702
        memset(work->map, 0, work->map_size);
335,228✔
2703
    }
146,587✔
2704
}
335,229✔
2705

2706
static SIXELSTATUS
2707
sixel_encode_emit_palette(int bodyonly,
58,607✔
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;
58,607✔
2715
    int n;
30,260✔
2716

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

2721
    if (palette == NULL && palette_float == NULL) {
54,381!
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) {
57,279✔
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,477,405!
2740
            status = output_rgb_palette_definition(output,
6,374,717✔
2741
                                                   palette,
1,954,372✔
2742
                                                   palette_float,
1,954,372✔
2743
                                                   n,
1,954,372✔
2744
                                                   keycolor);
1,954,372✔
2745
            if (SIXEL_FAILED(status)) {
4,420,345!
2746
                goto end;
2747
            }
2748
        }
1,954,372✔
2749
    }
2750

2751
    status = SIXEL_OK;
42,178✔
2752

2753
end:
32,615✔
2754
    return status;
42,178✔
2755
}
25,664✔
2756

2757
static SIXELSTATUS
2758
sixel_band_classify_row(sixel_encode_work_t *work,
2,036,902✔
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;
2,036,902✔
2769
    int row_bit;
1,007,578✔
2770
    int band_start;
1,007,578✔
2771
    int pix;
1,007,578✔
2772
    int x;
1,007,578✔
2773
    int check_integer_overflow;
1,007,578✔
2774
    char *map;
1,007,578✔
2775
    unsigned char *active_colors;
1,007,578✔
2776
    int *active_color_index;
1,007,578✔
2777

2778
    map = work->map;
2,036,902✔
2779
    active_colors = work->active_colors;
2,036,902✔
2780
    active_color_index = work->active_color_index;
2,036,902✔
2781
    row_bit = state->row_in_band;
2,036,902✔
2782
    band_start = absolute_row - row_bit;
2,036,902✔
2783

2784
    if (row_bit == 0) {
2,036,902✔
2785
        if (encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
335,227✔
2786
            state->fillable = 0;
334,964✔
2787
        } else if (palstate) {
146,749!
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;
335,227✔
2802
    }
146,585✔
2803

2804
    for (x = 0; x < width; x++) {
226,474,450!
2805
        if (absolute_row > INT_MAX / width) {
224,607,782!
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;
224,607,795✔
2813
        if (check_integer_overflow > INT_MAX - x) {
224,607,795!
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];
224,608,370✔
2821
        if (pix >= 0 && pix < ncolors && pix != keycolor) {
224,608,370!
2822
            if (pix > INT_MAX / width) {
193,527,844!
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;
193,526,254✔
2830
            if (check_integer_overflow > INT_MAX - x) {
193,526,254!
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);
193,526,764✔
2838
            if (active_colors != NULL && active_colors[pix] == 0) {
193,526,764!
2839
                active_colors[pix] = 1;
11,294,516✔
2840
                if (state->active_color_count < ncolors
11,294,516!
2841
                    && active_color_index != NULL) {
11,299,799!
2842
                    active_color_index[state->active_color_count] = pix;
11,299,324✔
2843
                    state->active_color_count += 1;
11,299,324✔
2844
                }
5,220,915✔
2845
            }
5,221,483✔
2846
        } else if (!palstate) {
111,869,421!
2847
            state->fillable = 0;
24,058,349✔
2848
        }
9,576,846✔
2849
    }
93,128,398✔
2850

2851
    state->row_in_band += 1;
1,869,400✔
2852
    status = SIXEL_OK;
1,869,400✔
2853

2854
end:
1,054,907✔
2855
    return status;
2,340,134✔
2856
}
470,737✔
2857

2858
static SIXELSTATUS
2859
sixel_band_compose(sixel_encode_work_t *work,
335,237✔
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;
335,237✔
2868
    int color_index;
173,134✔
2869
    int c;
173,134✔
2870
    unsigned char *row;
173,134✔
2871
    int sx;
173,134✔
2872
    int mx;
173,134✔
2873
    int gap;
173,134✔
2874
    int gap_reached_end;
173,134✔
2875
    sixel_node_t *np;
173,134✔
2876
    sixel_node_t *column_head;
173,134✔
2877
    sixel_node_t *column_tail;
173,134✔
2878
    sixel_node_t top;
173,134✔
2879

2880
    (void)ncolors;
234,882✔
2881
    (void)keycolor;
234,882✔
2882
    row = NULL;
335,237✔
2883
    gap_reached_end = 0;
335,237✔
2884
    np = NULL;
335,237✔
2885
    column_head = NULL;
335,237✔
2886
    column_tail = NULL;
335,237✔
2887
    top.next = NULL;
335,237✔
2888

2889
    if (work->columns != NULL) {
335,237!
2890
        memset(work->columns, 0, work->columns_size);
335,205✔
2891
    }
146,562✔
2892
    output->node_top = NULL;
335,237✔
2893

2894
    for (color_index = 0;
5,526,449!
2895
         color_index < state->active_color_count;
11,607,866✔
2896
         color_index++) {
11,272,676✔
2897
        c = work->active_color_index[color_index];
11,269,702✔
2898
        row = (unsigned char *)(work->map + c * width);
11,269,702✔
2899
        sx = 0;
11,269,702✔
2900
        while (sx < width) {
28,421,037!
2901
            sx = sixel_compose_find_run_start(
27,647,696✔
2902
                row,
12,695,790✔
2903
                width,
12,695,790✔
2904
                sx);
12,695,790✔
2905
            if (sx >= width) {
27,646,983✔
2906
                break;
7,843,689✔
2907
            }
2908

2909
            mx = sx + 1;
17,148,100✔
2910
            while (mx < width) {
94,629,243!
2911
                if (row[mx] != 0) {
93,840,044✔
2912
                    mx += 1;
63,571,475✔
2913
                    continue;
63,571,475✔
2914
                }
2915

2916
                gap = sixel_compose_measure_gap(
30,274,889✔
2917
                    row,
13,834,968✔
2918
                    width,
13,834,968✔
2919
                    mx + 1,
13,834,968✔
2920
                    &gap_reached_end);
2921
                if (gap >= 9 || gap_reached_end) {
30,271,025!
2922
                    break;
7,495,261✔
2923
                }
2924
                mx += gap + 1;
13,909,573✔
2925
            }
2926

2927
            if ((np = output->node_free) != NULL) {
17,145,381✔
2928
                output->node_free = np->next;
2,321,282✔
2929
            } else {
150,777✔
2930
                status = sixel_node_new(&np, allocator);
14,824,099✔
2931
                if (SIXEL_FAILED(status)) {
14,828,026!
2932
                    goto end;
2933
                }
2934
            }
2935

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

2942
            if (work->columns != NULL) {
17,150,811!
2943
                column_head = work->columns[sx];
17,150,821✔
2944
                if (column_head == NULL
17,150,821✔
2945
                    || column_head->mx <= np->mx) {
11,971,351!
2946
                    np->next = column_head;
13,369,062✔
2947
                    work->columns[sx] = np;
13,369,062✔
2948
                } else {
6,050,383✔
2949
                    column_tail = column_head;
2,852,292✔
2950
                    while (column_tail->next != NULL
6,174,377!
2951
                           && column_tail->next->mx > np->mx) {
5,063,123!
2952
                        column_tail = column_tail->next;
966,964✔
2953
                    }
2954
                    np->next = column_tail->next;
3,783,925✔
2955
                    column_tail->next = np;
3,783,925✔
2956
                }
2957
            } else {
7,853,958✔
2958
                top.next = output->node_top;
175✔
2959
                column_tail = &top;
175✔
2960

2961
                while (column_tail->next != NULL) {
175!
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;
175✔
2972
                column_tail->next = np;
175✔
2973
                output->node_top = top.next;
175✔
2974
            }
2975

2976
            sx = mx;
12,784,396✔
2977
        }
2978
    }
5,191,212✔
2979

2980
    if (work->columns != NULL) {
337,860!
2981
        top.next = NULL;
335,215✔
2982
        column_tail = &top;
335,215✔
2983
        for (sx = 0; sx < width; sx++) {
38,988,374!
2984
            column_head = work->columns[sx];
38,652,399✔
2985
            if (column_head == NULL) {
38,652,399✔
2986
                continue;
29,271,531✔
2987
            }
2988
            column_tail->next = column_head;
9,381,630✔
2989
            while (column_tail->next != NULL) {
26,576,017!
2990
                column_tail = column_tail->next;
12,834,326✔
2991
            }
2992
            work->columns[sx] = NULL;
9,381,656✔
2993
        }
4,206,685✔
2994
        output->node_top = top.next;
335,215✔
2995
    }
146,566✔
2996

2997
    status = SIXEL_OK;
337,931✔
2998

2999
end:
191,326✔
3000
    return status;
422,792✔
3001
}
84,863✔
3002

3003
static SIXELSTATUS
3004
sixel_band_emit(sixel_encode_work_t *work,
335,175✔
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;
335,175✔
3012
    sixel_node_t *np;
173,083✔
3013
    sixel_node_t *next;
173,083✔
3014
    int x;
173,083✔
3015
    int emit_next_line;
173,083✔
3016

3017
    emit_next_line = (last_row_index >= 6);
335,175✔
3018
    if (emit_next_line) {
335,175✔
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] = '-';
276,624✔
3024
        sixel_advance(output, 1);
276,624✔
3025
    }
120,921✔
3026

3027
    for (x = 0; (np = output->node_top) != NULL;) {
2,587,000!
3028
        if (x > np->sx) {
2,252,128✔
3029
            output->buffer[output->pos] = '$';
1,927,458✔
3030
            sixel_advance(output, 1);
1,927,458✔
3031
            x = 0;
1,927,451✔
3032
        }
884,760✔
3033

3034
        if (state->fillable) {
2,345,076✔
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,278,819✔
3040
                                &x,
3041
                                np,
1,026,764✔
3042
                                ncolors,
1,026,764✔
3043
                                keycolor);
1,026,764✔
3044
        if (SIXEL_FAILED(status)) {
2,252,011!
3045
            goto end;
3046
        }
3047
        next = np->next;
2,252,011✔
3048
        sixel_node_del(output, np);
2,252,011✔
3049
        np = next;
2,252,022✔
3050

3051
        while (np != NULL) {
93,536,561!
3052
            if (np->sx < x) {
91,282,752✔
3053
                np = np->next;
76,410,738✔
3054
                continue;
76,410,738✔
3055
            }
3056

3057
            if (state->fillable) {
14,876,538!
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,677,836✔
3063
                                    &x,
3064
                                    np,
6,803,891✔
3065
                                    ncolors,
6,803,891✔
3066
                                    keycolor);
6,803,891✔
3067
            if (SIXEL_FAILED(status)) {
14,870,948!
3068
                goto end;
3069
            }
3070
            next = np->next;
14,870,948✔
3071
            sixel_node_del(output, np);
14,870,948✔
3072
            np = next;
14,872,668✔
3073
        }
3074

3075
        state->fillable = 0;
2,251,828✔
3076
    }
3077

3078
    status = SIXEL_OK;
246,607✔
3079

3080
end:
188,352✔
3081
    (void)work;
234,809✔
3082
    return status;
419,680✔
3083
}
84,796✔
3084

3085

3086
SIXELSTATUS
3087
sixel_encode_body(
22,916✔
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,916✔
3103
    int band_start;
11,732✔
3104
    int band_height;
11,732✔
3105
    int row_index;
11,732✔
3106
    int absolute_row;
11,732✔
3107
    int last_row_index;
11,732✔
3108
    sixel_node_t *np;
11,732✔
3109
    sixel_encode_work_t work;
11,732✔
3110
    sixel_band_state_t band;
11,732✔
3111
    int logging_active;
11,732✔
3112
    int job_index;
11,732✔
3113

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

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

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

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

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

3133
    status = sixel_encode_emit_palette(bodyonly,
30,070✔
3134
                                       ncolors,
7,154✔
3135
                                       keycolor,
7,154✔
3136
                                       palette,
7,154✔
3137
                                       palette_float,
7,154✔
3138
                                       output);
7,154✔
3139
    if (SIXEL_FAILED(status)) {
22,916!
3140
        goto cleanup;
×
3141
    }
3142

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

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

3169
    if (logging_active) {
17,448!
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,921✔
3178
                                        width,
4,360✔
3179
                                        ncolors,
4,360✔
3180
                                        allocator);
4,360✔
3181
    if (SIXEL_FAILED(status)) {
17,921!
3182
        goto cleanup;
×
3183
    }
3184

3185
    band_start = 0;
11,371✔
3186
    while (band_start < height) {
101,189!
3187
        band_height = height - band_start;
83,268✔
3188
        if (band_height > 6) {
83,268✔
3189
            band_height = 6;
38,747✔
3190
        }
10,927✔
3191

3192
        band.row_in_band = 0;
84,004✔
3193
        band.fillable = 0;
84,004✔
3194
        band.active_color_count = 0;
84,004✔
3195

3196
        if (logging_active) {
84,004!
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++) {
530,762!
3205
            absolute_row = band_start + row_index;
447,494✔
3206
            status = sixel_band_classify_row(&work,
447,494✔
3207
                                             &band,
3208
                                             pixels,
76,419✔
3209
                                             width,
76,419✔
3210
                                             absolute_row,
76,419✔
3211
                                             ncolors,
76,419✔
3212
                                             keycolor,
76,419✔
3213
                                             palstate,
76,419✔
3214
                                             output->encode_policy);
76,419✔
3215
            if (SIXEL_FAILED(status)) {
447,494!
3216
                goto cleanup;
×
3217
            }
3218
        }
76,419✔
3219

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

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

3242
        sixel_band_finish(&work, &band);
83,268✔
3243

3244
        sixel_band_clear_map(&work);
83,268✔
3245

3246
        if (logging_active) {
83,268!
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;
83,268✔
3255
        sixel_band_state_reset(&band);
83,268✔
3256
        job_index += 1;
83,268✔
3257
    }
3258

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

3262
finalize:
15,762✔
3263
    if (palstate) {
24,074✔
3264
        output->buffer[output->pos] = '$';
3,078✔
3265
        sixel_advance(output, 1);
3,078✔
3266
    }
1,158✔
3267

3268
cleanup:
21,393✔
3269
    while ((np = output->node_free) != NULL) {
847,357!
3270
        output->node_free = np->next;
824,441✔
3271
        sixel_allocator_free(allocator, np);
824,441✔
3272
    }
3273
    output->node_top = NULL;
22,916✔
3274

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

3277
    return status;
22,916✔
3278
}
4,181✔
3279
SIXELSTATUS
3280
sixel_encode_footer(sixel_output_t *output)
56,135✔
3281
{
3282
    SIXELSTATUS status = SIXEL_FALSE;
56,135✔
3283

3284
    if (output->pos > 0) {
56,135✔
3285
        status = sixel_output_write_bytes(output,
95,281✔
3286
                                          (char *)output->buffer,
55,982✔
3287
                                          output->pos);
24,674✔
3288
        if (SIXEL_FAILED(status)) {
55,982!
3289
            return status;
3290
        }
3291
        output->pos = 0;
55,982✔
3292
    }
24,674✔
3293

3294
    status = sixel_output_end_image(output);
56,135✔
3295

3296
    return status;
56,135✔
3297
}
24,737✔
3298

3299
static SIXELSTATUS
3300
sixel_encode_body_ormode(
120✔
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;
60✔
3310
    int n;
60✔
3311
    int nplanes;
60✔
3312
    uint8_t *buf;
60✔
3313
    uint8_t *buf_p;
60✔
3314
    int x;
60✔
3315
    int cur_h;
60✔
3316
    int nwrite;
60✔
3317
    int plane;
60✔
3318

3319
    if (pixels == NULL) {
120✔
3320
        return SIXEL_BAD_ARGUMENT;
3321
    }
3322
    buf = pixels;
2,248✔
3323

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

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

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

3344
            buf_p = buf;
5,280✔
3345
            for (x = 0; x < width; x++, buf_p++) {
344,740!
3346
                sixel_put_pixel(output,
464,640✔
3347
                                ((buf_p[0] >> plane) & 0x1) |
464,640✔
3348
                                (((buf_p[width] >> plane) << 1) & 0x2) |
464,640✔
3349
                                (((buf_p[width * 2] >> plane) << 2) & 0x4) |
464,640✔
3350
                                (((buf_p[width * 3] >> plane) << 3) & 0x8) |
464,640✔
3351
                                (((buf_p[width * 4] >> plane) << 4) & 0x10) |
464,640✔
3352
                                (((buf_p[width * 5] >> plane) << 5) & 0x20));
337,920✔
3353
            }
126,720✔
3354
            status = sixel_put_flash(output);
5,280✔
3355
            if (SIXEL_FAILED(status)) {
5,280!
3356
                return status;
3357
            }
3358
            sixel_putc(output->buffer + output->pos, '$');
5,280✔
3359
            sixel_advance(output, 1);
5,280✔
3360
        }
1,980✔
3361
        sixel_putc(output->buffer + output->pos, '-');
1,200✔
3362
        sixel_advance(output, 1);
1,200✔
3363
        buf += (width * 6);
1,200✔
3364
        }
450✔
3365

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

3373
            buf_p = buf;
528✔
3374
            for (x = 0; x < width; x++, buf_p++) {
34,474!
3375
                int pix = ((buf_p[0] >> plane) & 0x1);
33,792✔
3376

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

3394
                sixel_put_pixel(output, pix);
33,792✔
3395
            }
12,672✔
3396
            status = sixel_put_flash(output);
528✔
3397
            if (SIXEL_FAILED(status)) {
528!
3398
                return status;
3399
            }
3400

3401
            sixel_putc(output->buffer + output->pos, '$');
528✔
3402
            sixel_advance(output, 1);
528✔
3403
        }
198✔
3404
    }
45✔
3405

3406
    return 0;
85✔
3407
}
45✔
3408

3409

3410
static SIXELSTATUS
3411
sixel_encode_dither(
55,649✔
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;
55,649✔
3419
    sixel_index_t *paletted_pixels = NULL;
55,649✔
3420
    sixel_index_t *input_pixels;
28,780✔
3421
    size_t bufsize;
28,780✔
3422
    unsigned char *palette_entries = NULL;
55,649✔
3423
    float *palette_entries_float32 = NULL;
55,649✔
3424
    sixel_palette_t *palette_obj = NULL;
55,649✔
3425
    size_t palette_count = 0U;
55,649✔
3426
    size_t palette_float_count = 0U;
55,649✔
3427
    size_t palette_bytes = 0U;
55,649✔
3428
    size_t palette_float_bytes = 0U;
55,649✔
3429
    size_t palette_channels = 0U;
55,649✔
3430
    size_t palette_index = 0U;
55,649✔
3431
    int palette_source_colorspace;
28,780✔
3432
    int palette_float_pixelformat;
28,780✔
3433
    int output_float_pixelformat;
28,780✔
3434
    int palette_float_depth;
28,780✔
3435
    int pipeline_active;
28,780✔
3436
    int pipeline_threads = 0;  /* set to a deterministic default before use */
55,649✔
3437
    int pipeline_nbands;
28,780✔
3438
    sixel_parallel_dither_config_t dither_parallel;
28,780✔
3439
    char const *band_env_text;
28,780✔
3440
    int ormode_enabled;
28,780✔
3441
    sixel_palette_entries_view_t palette_view;
28,780✔
3442
    sixel_palette_float32_entries_view_t palette_float_view;
28,780✔
3443
#if SIXEL_ENABLE_THREADS
3444
    sixel_timeline_logger_t *serial_logger;
24,670✔
3445
    int logger_owned = 0;
47,429✔
3446
#endif  /* SIXEL_ENABLE_THREADS */
3447
    sixel_timeline_logger_t *logger = NULL;
55,649✔
3448

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

3453
#if SIXEL_ENABLE_THREADS
3454
    serial_logger = NULL;
47,429✔
3455
#endif  /* SIXEL_ENABLE_THREADS */
3456
    palette_source_colorspace = SIXEL_COLORSPACE_GAMMA;
55,649✔
3457
    palette_float_pixelformat =
39,078✔
3458
        sixel_palette_float_pixelformat_for_colorspace(
55,649✔
3459
            palette_source_colorspace);
24,551✔
3460
    palette_float_depth =
39,078✔
3461
        sixel_helper_compute_depth(palette_float_pixelformat);
55,649✔
3462
    output_float_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
55,649✔
3463
    memset(&palette_view, 0, sizeof(palette_view));
55,649!
3464
    memset(&palette_float_view, 0, sizeof(palette_float_view));
55,649✔
3465
    palette_obj = dither->palette;
55,649✔
3466
    if (palette_obj == NULL || palette_obj->vtbl == NULL ||
78,111!
3467
            palette_obj->vtbl->get_entries == NULL ||
55,649!
3468
            palette_obj->vtbl->get_entries_float32 == NULL) {
55,649!
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;
55,649✔
3476
#if SIXEL_ENABLE_THREADS
3477
    #endif
3478
    dither_parallel.enabled = 0;
55,649✔
3479
    dither_parallel.band_height = 0;
55,649✔
3480
    dither_parallel.overlap = 0;
55,649✔
3481
    dither_parallel.dither_threads = 0;
55,649✔
3482
    dither_parallel.encode_threads = 0;
55,649✔
3483
    ormode_enabled = output != NULL && output->ormode != 0;
55,649!
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 =
55,922✔
3489
        dither->pipeline_pin_threads != 0 ? 1 : 0;
55,922✔
3490
    dither_parallel.pin_threads = dither->pipeline_pin_threads;
55,922✔
3491
    switch (dither->pixelformat) {
55,922!
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:
28,222✔
3523
        /* apply palette */
3524
        pipeline_threads = sixel_threads_resolve();
49,988✔
3525
        band_env_text = sixel_compat_getenv(
49,988✔
3526
            "SIXEL_DITHER_PARALLEL_BAND_WIDTH");
3527
        if (pipeline_threads <= 1 && band_env_text != NULL
49,988!
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;
49,983✔
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
68,245✔
3548
                && pipeline_threads > 1
49,943✔
3549
                && pipeline_nbands > 1) {
40,316!
3550
            pipeline_active = 1;
27,993✔
3551
            input_pixels = NULL;
27,993✔
3552
        } else {
18,595✔
3553
            paletted_pixels = sixel_dither_apply_palette(dither, pixels,
17,284✔
3554
                                                         width, height);
3,171✔
3555
            if (paletted_pixels == NULL) {
14,113!
3556
                status = SIXEL_RUNTIME_ERROR;
×
3557
                goto end;
×
3558
            }
3559
            input_pixels = paletted_pixels;
8,808✔
3560
        }
3561
        break;
36,801✔
3562
    }
3563

3564
    if (pipeline_active) {
49,004✔
3565
        sixel_parallel_dither_configure(height,
54,470✔
3566
                                        dither->ncolors,
18,595✔
3567
                                        pipeline_threads,
18,595✔
3568
                                        dither->pipeline_pin_threads,
18,595✔
3569
                                        &dither_parallel);
3570
        if (dither_parallel.enabled) {
35,875!
3571
            dither->pipeline_parallel_active = 1;
35,875✔
3572
            dither->pipeline_band_height = dither_parallel.band_height;
35,875✔
3573
            dither->pipeline_band_overlap = dither_parallel.overlap;
35,875✔
3574
            dither->pipeline_dither_threads =
35,875✔
3575
                dither_parallel.dither_threads;
35,875✔
3576
            pipeline_threads = dither_parallel.encode_threads;
35,875✔
3577
        }
18,595✔
3578
        if (pipeline_threads <= 1) {
38,141✔
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,595✔
3598

3599
#if SIXEL_ENABLE_THREADS
3600
    if (!pipeline_active) {
45,873!
3601
        logger = dither->pipeline_logger;
11,738✔
3602
        if (logger == NULL) {
11,738!
3603
            sixel_timeline_logger_prepare_default(dither->allocator,
11,738✔
3604
                                                  &serial_logger);
3605
            if (serial_logger != NULL) {
11,738!
3606
                logger_owned = 1;
12✔
3607
                dither->pipeline_logger = serial_logger;
12✔
3608
                logger = serial_logger;
12✔
3609
            } else {
12✔
3610
                logger = NULL;
9,146✔
3611
            }
3612
        }
6,041✔
3613
        if (logger != NULL) {
9,784!
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
    }
6,041✔
3627
#endif
3628

3629
    if (output != NULL) {
55,649!
3630
        palette_source_colorspace = output->source_colorspace;
55,649✔
3631
        palette_float_pixelformat =
39,078✔
3632
            sixel_palette_float_pixelformat_for_colorspace(
55,649✔
3633
                palette_source_colorspace);
24,551✔
3634
        palette_float_depth =
39,078✔
3635
            sixel_helper_compute_depth(palette_float_pixelformat);
55,649✔
3636
    }
24,551✔
3637

3638
    status = palette_obj->vtbl->get_entries(palette_obj, &palette_view);
55,649✔
3639
    if (SIXEL_FAILED(status) || palette_view.entries == NULL ||
78,111!
3640
            palette_view.depth != 3 || palette_view.entry_count == 0U) {
55,649!
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;
55,649✔
3647
    palette_bytes = palette_count * 3U;
55,649✔
3648
    palette_entries = (unsigned char *)sixel_allocator_malloc(
55,649✔
3649
        dither->allocator,
24,551✔
3650
        palette_bytes);
24,551✔
3651
    if (palette_entries == NULL) {
55,649!
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);
55,649✔
3658

3659
    status = palette_obj->vtbl->get_entries_float32(palette_obj,
55,649✔
3660
                                                    &palette_float_view);
3661
    if (SIXEL_FAILED(status)) {
55,649!
3662
        goto end;
×
3663
    }
3664
    if (palette_float_view.entries != NULL &&
56,671!
3665
            palette_float_view.entry_count == palette_count &&
4,721!
3666
            palette_float_view.depth == palette_float_depth) {
4,721!
3667
        palette_float_count = palette_float_view.entry_count;
4,721✔
3668
        palette_float_bytes =
4,721✔
3669
            palette_float_count * (size_t)palette_float_view.depth;
4,721✔
3670
        palette_entries_float32 = (float *)sixel_allocator_malloc(
4,721✔
3671
            dither->allocator,
1,139✔
3672
            palette_float_bytes);
1,139✔
3673
        if (palette_entries_float32 == NULL) {
4,721!
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,838✔
3680
               palette_float_view.entries,
3,699✔
3681
               palette_float_bytes);
117✔
3682
    }
1,139✔
3683
    if (palette_entries != NULL && palette_entries_float32 != NULL
56,671!
3684
            && palette_count == palette_float_count
28,133!
3685
            && palette_count > 0U
4,721!
3686
            && !sixel_palette_float32_matches_u8(
4,721!
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
78,111!
3697
            && output != NULL
41,122!
3698
            && output->source_colorspace != output->colorspace) {
55,649!
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) {
41,122!
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);
55,649✔
3757
    if (SIXEL_FAILED(status)) {
55,649!
3758
        goto end;
×
3759
    }
3760

3761
    if (output->ormode) {
55,649✔
3762
        status = sixel_encode_body_ormode(input_pixels,
165✔
3763
                                          width,
45✔
3764
                                          height,
45✔
3765
                                          palette_entries,
45✔
3766
                                          dither->ncolors,
45✔
3767
                                          dither->keycolor,
45✔
3768
                                          output);
45✔
3769
    } else if (pipeline_active) {
55,574!
3770
        status = sixel_encode_body_pipeline(pixels,
54,201✔
3771
                                            width,
18,510✔
3772
                                            height,
18,510✔
3773
                                            palette_entries,
18,510✔
3774
                                            palette_entries_float32,
18,510✔
3775
                                            dither,
18,510✔
3776
                                            output,
18,510✔
3777
                                            pipeline_threads);
18,510✔
3778
    } else {
18,510✔
3779
        status = sixel_encode_body(input_pixels,
25,834✔
3780
                                   width,
5,996✔
3781
                                   height,
5,996✔
3782
                                   palette_entries,
5,996✔
3783
                                   palette_entries_float32,
5,996✔
3784
                                   dither->ncolors,
5,996✔
3785
                                   dither->keycolor,
5,996✔
3786
                                   dither->bodyonly,
5,996✔
3787
                                   output,
5,996✔
3788
                                   NULL,
3789
                                   dither->allocator,
5,996✔
3790
                                   dither->pipeline_pin_threads,
5,996✔
3791
                                   logger != NULL ?
5,996✔
3792
                                       logger :
12✔
3793
                                       NULL);
3794
    }
3795

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

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

3805
end:
31,098✔
3806
#if SIXEL_ENABLE_THREADS
3807
    if (logger_owned) {
47,429!
3808
        dither->pipeline_logger = NULL;
12✔
3809
        sixel_timeline_logger_unref(serial_logger);
12✔
3810
    }
12✔
3811
#endif
3812
    if (palette_entries != NULL) {
55,649!
3813
        sixel_allocator_free(dither->allocator, palette_entries);
55,649✔
3814
    }
24,551✔
3815
    if (palette_entries_float32 != NULL) {
58,424✔
3816
        sixel_allocator_free(dither->allocator, palette_entries_float32);
4,721✔
3817
    }
1,139✔
3818
    sixel_allocator_free(dither->allocator, paletted_pixels);
55,649✔
3819

3820
    return status;
55,649✔
3821
}
24,551✔
3822

3823
SIXEL_INTERNAL_API SIXELSTATUS
3824
sixel_encoder_core_encode_dispatch(
56,231✔
3825
    sixel_encoder_core_encode_request_t const *request)
3826
{
3827
    SIXELSTATUS status;
29,072✔
3828

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

3837
    (void)request->depth;
39,404✔
3838
    if (request->dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
56,135✔
3839
        status = sixel_encode_highcolor(request->pixels,
672✔
3840
                                        request->width,
346✔
3841
                                        request->height,
346✔
3842
                                        request->dither,
346✔
3843
                                        request->output);
346✔
3844
    } else {
186✔
3845
        status = sixel_encode_dither(request->pixels,
80,200✔
3846
                                     request->width,
41,122✔
3847
                                     request->height,
41,122✔
3848
                                     request->dither,
41,122✔
3849
                                     request->output);
41,122✔
3850
    }
3851

3852
    return status;
41,468✔
3853
}
24,773✔
3854

3855
SIXELAPI SIXELSTATUS
3856
sixel_encode(
56,111✔
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;
56,111✔
3865
    sixel_encoder_core_t *core;
29,012✔
3866
    sixel_encoder_core_encode_request_t request;
29,012✔
3867

3868
    if (pixels == NULL) {
56,111!
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) {
56,111!
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) {
56,111!
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);
56,111✔
3891
    sixel_output_ref(output);
56,111✔
3892

3893
    if (width < 1) {
56,111!
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) {
56,111!
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);
56,111✔
3910
    if (core == NULL || core->vtbl == NULL ||
56,111!
3911
        core->vtbl->encode == NULL) {
56,111!
3912
        status = SIXEL_BAD_ARGUMENT;
×
3913
        goto end;
×
3914
    }
3915
    request.pixels = pixels;
56,111✔
3916
    request.width = width;
56,111✔
3917
    request.height = height;
56,111✔
3918
    request.depth = depth;
56,111✔
3919
    request.dither = dither;
56,111✔
3920
    request.output = output;
56,111✔
3921
    status = core->vtbl->encode(core, &request);
56,111✔
3922

3923
end:
31,383✔
3924
    sixel_output_unref(output);
56,111✔
3925
    sixel_dither_unref(dither);
56,111✔
3926

3927
    return status;
56,111✔
3928
}
24,728✔
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