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

saitoha / libsixel / 25911327627

15 May 2026 09:42AM UTC coverage: 84.805% (-2.0%) from 86.779%
25911327627

push

github

saitoha
Merge branch 'ci/triage/fix-193-checkout-flow' into develop

27747 of 61487 branches covered (45.13%)

35106 of 41396 relevant lines covered (84.81%)

42859736.69 hits per line

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

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

259
    *logger = NULL;
56,186✔
260
    (void)sixel_timeline_logger_prepare_env(allocator, logger);
56,186✔
261
}
31,107✔
262
#endif
263

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

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

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

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

295
    dither_env_override = 0;
38,379✔
296
    dither_threads = (pipeline_threads * 7 + 9) / 10;
38,379✔
297
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_THREADS_MAX");
38,379✔
298
    if (text != NULL && text[0] != '\0') {
38,379!
299
        errno = 0;
105✔
300
        parsed = strtol(text, &endptr, 10);
105✔
301
        if (endptr != text && errno != ERANGE && parsed > 0) {
105!
302
            if (parsed > INT_MAX) {
75!
303
                parsed = INT_MAX;
304
            }
305
            dither_threads = (int)parsed;
90✔
306
            dither_env_override = 1;
90✔
307
        }
50✔
308
    }
50✔
309
    if (dither_threads < 1) {
28,233!
310
        dither_threads = 1;
311
    }
312
    if (dither_threads > pipeline_threads) {
36,105!
313
        dither_threads = pipeline_threads;
314
    }
315

316
    if (!dither_env_override && pipeline_threads >= 4 && dither_threads < 2) {
38,374!
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;
38,374✔
325
    if (encode_threads < 2 && pipeline_threads > 2) {
38,374!
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;
38,080✔
332
        dither_threads = pipeline_threads - encode_threads;
38,080✔
333
    }
20,959✔
334
    if (encode_threads < 1) {
40,616✔
335
        encode_threads = 1;
194✔
336
        dither_threads = pipeline_threads - encode_threads;
194✔
337
    }
95✔
338
    if (dither_threads < 1) {
32,078!
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;
38,379✔
349
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_WIDTH");
38,379✔
350
    if (text != NULL && text[0] != '\0') {
38,379!
351
        errno = 0;
105✔
352
        parsed = strtol(text, &endptr, 10);
105✔
353
        if (endptr != text && errno != ERANGE && parsed > 0) {
105!
354
            if (parsed > INT_MAX) {
75!
355
                parsed = INT_MAX;
356
            }
357
            band_height = (int)parsed;
90✔
358
        }
50✔
359
    }
50✔
360
    if (band_height <= 0) {
30,502✔
361
        band_height = (height + dither_threads - 1) / dither_threads;
38,274✔
362
    }
21,054✔
363
    if (band_height < 6) {
40,593✔
364
        band_height = 6;
2,348✔
365
    }
974✔
366
    if ((band_height % 6) != 0) {
36,955✔
367
        band_height = ((band_height + 5) / 6) * 6;
30,229✔
368
    }
17,807✔
369

370
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_OVERWRAP");
38,379✔
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) {
38,379✔
376
        overlap = 6;
16,984✔
377
    } else {
11,668✔
378
        overlap = 0;
16,960✔
379
    }
380
    if (text != NULL && text[0] != '\0') {
38,379!
381
        errno = 0;
105✔
382
        parsed = strtol(text, &endptr, 10);
105✔
383
        if (endptr != text && errno != ERANGE && parsed >= 0) {
105!
384
            if (parsed > INT_MAX) {
75!
385
                parsed = INT_MAX;
386
            }
387
            overlap = (int)parsed;
90✔
388
        }
50✔
389
    }
50✔
390
    if (overlap < 0) {
28,233!
391
        overlap = 0;
392
    }
393
    if (overlap > band_height / 2) {
36,158✔
394
        overlap = band_height / 2;
3,759✔
395
    }
1,317✔
396

397
    config->enabled = 1;
38,379✔
398
    config->band_height = band_height;
38,379✔
399
    config->overlap = overlap;
38,379✔
400
    config->dither_threads = dither_threads;
38,379✔
401
    config->encode_threads = encode_threads;
38,379✔
402
}
21,104!
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)
43,616✔
440
{
441
    memset(ctx, 0, sizeof(*ctx));
43,616✔
442
    ctx->pixels = NULL;
43,616✔
443
    ctx->keycolor = (-1);
43,616✔
444
    ctx->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
43,616✔
445
    ctx->writer_error = SIXEL_OK;
43,616✔
446
}
36,513✔
447

448
static void
449
sixel_parallel_worker_release_nodes(sixel_parallel_worker_state_t *state,
346,579✔
450
                                    sixel_allocator_t *allocator)
451
{
452
    sixel_node_t *np;
190,034✔
453

454
    if (state == NULL || state->output == NULL) {
346,579!
455
        return;
4✔
456
    }
457

458
    while ((np = state->output->node_free) != NULL) {
15,511,648!
459
        state->output->node_free = np->next;
15,165,068✔
460
        sixel_allocator_free(allocator, np);
15,165,068✔
461
    }
462
    state->output->node_top = NULL;
346,581✔
463
}
186,838!
464

465
static void
466
sixel_parallel_worker_cleanup(sixel_parallel_worker_state_t *state,
126,616✔
467
                              sixel_allocator_t *allocator)
468
{
469
    if (state == NULL) {
126,616✔
470
        return;
39,949✔
471
    }
472
    sixel_parallel_worker_release_nodes(state, allocator);
77,045✔
473
    if (state->output != NULL) {
77,045!
474
        sixel_output_unref(state->output);
77,045✔
475
        state->output = NULL;
77,045✔
476
    }
37,894✔
477
    sixel_encode_work_cleanup(&state->work, allocator);
77,045✔
478
    sixel_band_state_reset(&state->band);
77,045✔
479
    state->initialized = 0;
77,045✔
480
    state->index = 0;
77,045✔
481
    state->writer_error = SIXEL_OK;
77,045✔
482
    state->band_buffer = NULL;
77,045✔
483
    state->context = NULL;
77,045✔
484
}
67,357✔
485

486
static void
487
sixel_parallel_context_cleanup(sixel_parallel_context_t *ctx)
43,616✔
488
{
489
    int i;
24,170✔
490

491
    if (ctx->workers != NULL) {
43,616!
492
        for (i = 0; i < ctx->worker_capacity; i++) {
170,232!
493
            sixel_parallel_worker_cleanup(ctx->workers[i], ctx->allocator);
126,616✔
494
        }
67,357✔
495
        free(ctx->workers);
43,616✔
496
        ctx->workers = NULL;
43,616✔
497
    }
24,239✔
498
    sixel_parallel_writer_stop(ctx, 1);
43,616✔
499
    if (ctx->bands != NULL) {
43,616!
500
        if (ctx->band_count < 0) {
43,616!
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++) {
313,203!
508
            free(ctx->bands[i].data);
269,587✔
509
            ctx->bands[i].data = NULL;
269,587✔
510
        }
148,995✔
511
#if defined(_MSC_VER)
512
#pragma warning(pop)
513
#endif
514
        free(ctx->bands);
43,616✔
515
        ctx->bands = NULL;
43,616✔
516
    }
24,239✔
517
    ctx->band_count = 0;
43,616✔
518
    if (ctx->pool != NULL) {
43,616!
519
        ctx->pool->vtbl->unref(ctx->pool);
43,616✔
520
        ctx->pool = NULL;
43,616✔
521
    }
24,239✔
522
    if (ctx->cond_ready) {
43,616!
523
        sixel_cond_destroy(&ctx->cond_band_ready);
43,616✔
524
        ctx->cond_ready = 0;
43,616✔
525
    }
24,239✔
526
    if (ctx->mutex_ready) {
43,616!
527
        sixel_mutex_destroy(&ctx->mutex);
43,616✔
528
        ctx->mutex_ready = 0;
43,616✔
529
    }
24,239✔
530
}
43,616✔
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)
269,569✔
569
{
570
    int accept;
149,122✔
571

572
    if (ctx == NULL) {
269,569✔
573
        return 0;
574
    }
575
    if (!ctx->mutex_ready) {
269,569!
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);
269,569✔
583
    accept = (!ctx->writer_should_stop && ctx->writer_error == SIXEL_OK);
269,587!
584
    sixel_mutex_unlock(&ctx->mutex);
269,587✔
585
    return accept;
269,584✔
586
}
148,995✔
587

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

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

607
static SIXELSTATUS
608
sixel_parallel_worker_prepare(sixel_parallel_worker_state_t *state,
269,582✔
609
                              sixel_parallel_context_t *ctx)
610
{
611
    SIXELSTATUS status;
149,119✔
612

613
    if (state->initialized) {
269,582✔
614
        return SIXEL_OK;
154,854✔
615
    }
616

617
    sixel_encode_work_init(&state->work);
77,040✔
618
    sixel_band_state_reset(&state->band);
77,032✔
619
    state->writer_error = SIXEL_OK;
77,028✔
620
    state->band_buffer = NULL;
77,028✔
621
    state->context = ctx;
77,028✔
622

623
    status = sixel_encode_work_allocate(&state->work,
132,361✔
624
                                        ctx->width,
37,893✔
625
                                        ctx->ncolors,
37,893✔
626
                                        ctx->allocator);
37,893✔
627
    if (SIXEL_FAILED(status)) {
77,039!
628
        return status;
629
    }
630

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

655
    state->initialized = 1;
77,037✔
656
    state->index = (-1);
77,037✔
657

658
    if (ctx->mutex_ready) {
77,037!
659
        sixel_mutex_lock(&ctx->mutex);
77,008✔
660
    }
37,871✔
661
    if (ctx->worker_registered < ctx->worker_capacity) {
77,051!
662
        state->index = ctx->worker_registered;
77,045✔
663
        ctx->workers[state->index] = state;
77,045✔
664
        ctx->worker_registered += 1;
77,045✔
665
    }
37,894✔
666
    if (ctx->mutex_ready) {
77,051!
667
        sixel_mutex_unlock(&ctx->mutex);
77,045✔
668
    }
37,894✔
669

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

675
    return SIXEL_OK;
59,604✔
676
}
148,995✔
677

678
static SIXELSTATUS
679
sixel_parallel_context_grow(sixel_parallel_context_t *ctx, int target_threads)
38,185✔
680
{
681
    int capped_target;
21,031✔
682
    int delta;
21,031✔
683
    int status;
21,031✔
684

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

689
    capped_target = target_threads;
38,185✔
690
    if (capped_target > ctx->worker_capacity) {
38,185✔
691
        capped_target = ctx->worker_capacity;
13,829✔
692
    }
7,457✔
693
    if (ctx->band_count > 0 && capped_target > ctx->band_count) {
38,185!
694
        capped_target = ctx->band_count;
7,837✔
695
    }
696
    if (capped_target <= ctx->thread_count) {
38,185✔
697
        return SIXEL_OK;
8,653✔
698
    }
699

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

707
    if (ctx->logger != NULL) {
27,377✔
708
        sixel_timeline_logger_logf(ctx->logger,
46✔
709
                          "controller",
710
                          "encode",
711
                          "grow_workers",
712
                          -1);
713
    }
24✔
714

715
    return SIXEL_OK;
21,695✔
716
}
21,009✔
717

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

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

741
    required = band->used + (size_t)size;
268,634✔
742
    if (required < band->used) {
268,634!
743
        state->writer_error = SIXEL_BAD_INTEGER_OVERFLOW;
744
        return size;
745
    }
746
    capacity = band->size;
268,634✔
747
    if (required > capacity) {
268,634!
748
        if (capacity == 0) {
268,619!
749
            new_capacity = (size_t)SIXEL_OUTPUT_PACKET_SIZE;
213,654✔
750
        } else {
148,459✔
751
            new_capacity = capacity;
41✔
752
        }
753
        while (new_capacity < required) {
268,622!
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);
268,543✔
761
        if (tmp == NULL) {
268,543!
762
            state->writer_error = SIXEL_BAD_ALLOCATION;
763
            return size;
764
        }
765
        band->data = tmp;
268,543✔
766
        band->size = new_capacity;
268,543✔
767
    }
148,431✔
768

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

772
    return size;
268,554✔
773
}
148,428✔
774

775
static SIXELSTATUS
776
sixel_parallel_create_pool(sixel_thread_pool_t **pool,
43,616✔
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;
24,170✔
784
    sixel_thread_pool_create_request_t request;
24,170✔
785
    void *service_object;
24,170✔
786
    SIXELSTATUS status;
24,170✔
787

788
    if (pool != NULL) {
43,616!
789
        *pool = NULL;
43,616✔
790
    }
24,239✔
791
    if (pool == NULL) {
43,616!
792
        return SIXEL_BAD_ARGUMENT;
793
    }
794

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

823
    return status;
34,778✔
824
}
24,239✔
825

826
static SIXELSTATUS
827
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
43,616✔
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;
24,170✔
843
    int nbands;
24,170✔
844
    int threads;
24,170✔
845
    int i;
24,170✔
846

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

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

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

887
    if (logger != NULL) {
41,086✔
888
        sixel_timeline_logger_logf(logger,
256✔
889
                          "controller",
890
                          "encode",
891
                          "context_begin",
892
                          -1);
893
    }
124✔
894

895
    ctx->bands = (sixel_parallel_band_buffer_t *)calloc((size_t)nbands,
43,616✔
896
                                                        sizeof(*ctx->bands));
897
    if (ctx->bands == NULL) {
43,616!
898
        return SIXEL_BAD_ALLOCATION;
899
    }
900
    for (i = 0; i < nbands; ++i) {
313,203!
901
        ctx->bands[i].data = NULL;
269,587✔
902
        ctx->bands[i].size = 0;
269,587✔
903
        ctx->bands[i].used = 0;
269,587✔
904
        ctx->bands[i].status = SIXEL_OK;
269,587✔
905
        ctx->bands[i].ready = 0;
269,587✔
906
        ctx->bands[i].dispatched = 0;
269,587✔
907
    }
148,995✔
908
    ctx->band_count = nbands;
43,616✔
909

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

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

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

928
    ctx->queue_capacity = queue_capacity;
43,616✔
929
    if (ctx->queue_capacity < 1) {
43,616!
930
        ctx->queue_capacity = nbands;
931
    }
932
    if (ctx->queue_capacity > nbands) {
41,073!
933
        ctx->queue_capacity = nbands;
934
    }
935

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

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

958
    status = sixel_thread_create(&ctx->writer_thread,
67,855✔
959
                                 sixel_parallel_writer_main,
960
                                 ctx);
24,239✔
961
    if (SIXEL_FAILED(status)) {
43,616!
962
        sixel_mutex_unlock(&ctx->mutex);
963
        return status;
964
    }
965
    ctx->writer_started = 1;
43,616✔
966
    sixel_mutex_unlock(&ctx->mutex);
43,616✔
967

968
    return SIXEL_OK;
43,616✔
969
}
24,239✔
970

971
static void
972
sixel_parallel_submit_band(sixel_parallel_context_t *ctx, int band_index)
269,587✔
973
{
974
    sixel_thread_pool_job_t job;
149,123✔
975
    SIXELSTATUS status;
149,123✔
976
    int dispatch;
149,123✔
977

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

985
    dispatch = 0;
269,587✔
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) {
269,587!
992
        sixel_mutex_lock(&ctx->mutex);
269,587✔
993
        if (!ctx->bands[band_index].dispatched
407,472!
994
                && !ctx->writer_should_stop
258,876!
995
                && ctx->writer_error == SIXEL_OK) {
269,587!
996
            ctx->bands[band_index].dispatched = 1;
269,587✔
997
            dispatch = 1;
269,587✔
998
        }
148,995✔
999
        sixel_mutex_unlock(&ctx->mutex);
269,587✔
1000
    } else {
148,995✔
1001
        if (!ctx->bands[band_index].dispatched
×
1002
                && sixel_parallel_jobs_allowed(ctx)) {
×
1003
            ctx->bands[band_index].dispatched = 1;
1004
            dispatch = 1;
1005
        }
1006
    }
1007

1008
    if (!dispatch) {
269,586!
1009
        return;
1010
    }
1011

1012
    sixel_fence_release();
269,586✔
1013
    if (ctx->logger != NULL) {
269,586✔
1014
        sixel_timeline_logger_logf(ctx->logger,
1,264✔
1015
                          "controller",
1016
                          "encode",
1017
                          "dispatch",
1018
                          band_index);
412✔
1019
    }
412✔
1020
    job.band_index = band_index;
253,902✔
1021
    status = ctx->pool->vtbl->push(ctx->pool, job);
253,902✔
1022
    if (SIXEL_FAILED(status)) {
253,903!
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
}
148,995!
1032

1033
static SIXELSTATUS
1034
sixel_parallel_context_wait(sixel_parallel_context_t *ctx, int force_abort)
43,616✔
1035
{
1036
    int pool_error;
24,170✔
1037

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

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

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

1053
    return SIXEL_OK;
34,778✔
1054
}
24,239✔
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,321,223✔
1063
{
1064
    sixel_parallel_row_notifier_t *notifier;
731,030✔
1065
    sixel_parallel_context_t *ctx;
731,030✔
1066
    sixel_timeline_logger_t *logger;
731,030✔
1067
    int band_height;
731,030✔
1068
    int band_index;
731,030✔
1069

1070
    notifier = (sixel_parallel_row_notifier_t *)priv;
1,321,223✔
1071
    if (notifier == NULL) {
1,321,223✔
1072
        return;
1073
    }
1074
    ctx = notifier->context;
1,321,223✔
1075
    logger = notifier->logger;
1,321,223✔
1076
    if (ctx == NULL || ctx->band_count <= 0 || ctx->height <= 0) {
1,321,223!
1077
        return;
21✔
1078
    }
1079
    if (row_index < 0) {
1,321,225!
1080
        return;
1081
    }
1082
    band_height = notifier->band_height;
1,321,225✔
1083
    if (band_height < 1) {
1,321,225!
1084
        band_height = 6;
1085
    }
1086
    if ((row_index % band_height) != band_height - 1
1,309,329✔
1087
            && row_index != ctx->height - 1) {
1,219,432!
1088
        return;
864,323✔
1089
    }
1090

1091
    band_index = row_index / band_height;
220,564✔
1092
    if (band_index >= ctx->band_count) {
220,564!
1093
        band_index = ctx->band_count - 1;
1094
    }
1095
    if (band_index < 0) {
234,487!
1096
        return;
1097
    }
1098

1099
    if (logger != NULL) {
234,487✔
1100
        sixel_timeline_logger_logf(logger,
1,264✔
1101
                          "controller",
1102
                          "encode",
1103
                          "row_gate",
1104
                          band_index);
412✔
1105
    }
412✔
1106

1107
    sixel_parallel_submit_band(ctx, band_index);
234,487✔
1108
}
730,628!
1109

1110
static SIXELSTATUS
1111
sixel_parallel_flush_band(sixel_parallel_context_t *ctx, int band_index)
269,587✔
1112
{
1113
    sixel_parallel_band_buffer_t *band;
149,123✔
1114
    size_t offset;
149,123✔
1115
    size_t chunk;
149,123✔
1116

1117
    band = &ctx->bands[band_index];
269,587✔
1118
    if (ctx->logger != NULL) {
269,587✔
1119
        sixel_timeline_logger_logf(ctx->logger,
1,264✔
1120
                          "worker",
1121
                          "encode",
1122
                          "writer_flush",
1123
                          band_index);
412✔
1124
    }
412✔
1125
    offset = 0;
214,458✔
1126
    while (offset < band->used) {
542,200!
1127
        chunk = band->used - offset;
272,613✔
1128
        if (chunk > (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos)) {
272,613✔
1129
            chunk = (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos);
3,133✔
1130
        }
2,119✔
1131
        memcpy(ctx->output->buffer + ctx->output->pos,
283,850✔
1132
               band->data + offset,
133,226✔
1133
               chunk);
11,237✔
1134
        sixel_advance(ctx->output, (int)chunk);
272,613✔
1135
        offset += chunk;
272,613✔
1136
    }
1137
    return SIXEL_OK;
269,587✔
1138
}
93,994✔
1139

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

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

1159
    if (ctx == NULL || state == NULL) {
269,587!
1160
        return SIXEL_BAD_ARGUMENT;
16✔
1161
    }
1162

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

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

1188
    sixel_fence_acquire();
269,516✔
1189

1190
    status = sixel_parallel_worker_prepare(state, ctx);
269,516✔
1191
    if (SIXEL_FAILED(status)) {
269,517!
1192
        goto cleanup;
1193
    }
1194

1195
    if (!sixel_parallel_jobs_allowed(ctx)) {
269,518!
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;
269,513✔
1213
    sixel_parallel_worker_reset(state);
269,513✔
1214

1215
    band_start = band_index * 6;
269,503✔
1216
    band_height = ctx->height - band_start;
269,503✔
1217
    if (band_height > 6) {
269,503✔
1218
        band_height = 6;
179,676✔
1219
    }
124,754✔
1220
    if (band_height <= 0) {
223,220!
1221
        goto cleanup;
1222
    }
1223

1224
    if (ctx->logger != NULL) {
269,501✔
1225
        sixel_timeline_logger_logf(ctx->logger,
1,264✔
1226
                          "worker",
1227
                          "encode",
1228
                          "worker_start",
1229
                          band_index);
412✔
1230
    }
412✔
1231

1232
    for (row_index = 0; row_index < band_height; row_index++) {
1,789,960!
1233
        absolute_row = band_start + row_index;
1,520,471✔
1234
        status = sixel_band_classify_row(&state->work,
2,357,582✔
1235
                                         &state->band,
837,111✔
1236
                                         ctx->pixels,
837,111✔
1237
                                         ctx->width,
837,111✔
1238
                                         absolute_row,
837,111✔
1239
                                         ctx->ncolors,
837,111✔
1240
                                         ctx->keycolor,
837,111✔
1241
                                         ctx->palstate,
837,111✔
1242
                                         ctx->encode_policy);
837,111✔
1243
        if (SIXEL_FAILED(status)) {
1,520,467!
1244
            goto cleanup;
1245
        }
1246
    }
837,099✔
1247

1248
    status = sixel_band_compose(&state->work,
418,425✔
1249
                                &state->band,
148,926✔
1250
                                state->output,
148,926✔
1251
                                ctx->width,
148,926✔
1252
                                ctx->ncolors,
148,926✔
1253
                                ctx->keycolor,
148,926✔
1254
                                ctx->allocator);
148,926✔
1255
    if (SIXEL_FAILED(status)) {
269,512!
1256
        goto cleanup;
1257
    }
1258

1259
    last_row_index = band_start + band_height - 1;
269,503✔
1260
    status = sixel_band_emit(&state->work,
418,420✔
1261
                             &state->band,
148,917✔
1262
                             state->output,
148,917✔
1263
                             ctx->ncolors,
148,917✔
1264
                             ctx->keycolor,
148,917✔
1265
                             last_row_index);
148,917✔
1266
    if (SIXEL_FAILED(status)) {
269,479!
1267
        goto cleanup;
1268
    }
1269

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

1275
    if (state->output->pos > 0) {
269,473✔
1276
        state->writer_error = sixel_output_write_bytes(
388,681✔
1277
            state->output,
148,455✔
1278
            (char *)state->output->buffer,
268,551✔
1279
            state->output->pos);
213,648✔
1280
        state->output->pos = 0;
268,585✔
1281
    }
148,455✔
1282
    if (state->writer_error != SIXEL_OK) {
269,508!
1283
        status = state->writer_error;
1284
        goto cleanup;
1285
    }
1286

1287
    sixel_band_finish(&state->work, &state->band);
269,508✔
1288
    status = SIXEL_OK;
269,514✔
1289

1290
cleanup:
120,592✔
1291
    sixel_parallel_worker_release_nodes(state, ctx->allocator);
269,577✔
1292
    if (band != NULL && ctx->mutex_ready && ctx->cond_ready) {
269,577!
1293
        sixel_fence_release();
269,537✔
1294
        sixel_mutex_lock(&ctx->mutex);
269,537✔
1295
        band->status = status;
269,539✔
1296
        band->ready = 1;
269,539✔
1297
        sixel_cond_broadcast(&ctx->cond_band_ready);
269,539✔
1298
        sixel_mutex_unlock(&ctx->mutex);
269,539✔
1299
    }
148,947✔
1300
    if (ctx->logger != NULL) {
285,311✔
1301
        sixel_timeline_logger_logf(ctx->logger,
1,264✔
1302
                          "worker",
1303
                          "encode",
1304
                          "worker_done",
1305
                          band_index);
412✔
1306
    }
412✔
1307
    if (SIXEL_FAILED(status)) {
269,581!
1308
        return status;
1309
    }
1310
    return SIXEL_OK;
214,452✔
1311
}
148,994✔
1312

1313
static void
1314
sixel_parallel_writer_stop(sixel_parallel_context_t *ctx, int force_abort)
87,232✔
1315
{
1316
    int should_signal;
48,340✔
1317

1318
    if (ctx == NULL || !ctx->writer_started) {
87,232!
1319
        return;
34,778✔
1320
    }
1321

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

1334
    sixel_thread_join(&ctx->writer_thread);
43,616✔
1335
    ctx->writer_started = 0;
43,616✔
1336
    ctx->writer_should_stop = 0;
43,616✔
1337
    if (ctx->logger != NULL) {
43,616✔
1338
        sixel_timeline_logger_logf(ctx->logger,
256✔
1339
                          "writer",
1340
                          "encode",
1341
                          "writer_stop",
1342
                          -1);
1343
    }
124✔
1344
}
48,478!
1345

1346
static int
1347
sixel_parallel_writer_main(void *arg)
43,616✔
1348
{
1349
    sixel_parallel_context_t *ctx;
24,170✔
1350
    sixel_parallel_band_buffer_t *band;
24,170✔
1351
    SIXELSTATUS status;
24,170✔
1352
    int band_index;
24,170✔
1353

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

1359
    if (ctx->logger != NULL) {
43,616✔
1360
        sixel_timeline_logger_logf(ctx->logger,
256✔
1361
                                   "writer",
1362
                                   "encode",
1363
                                   "writer_start",
1364
                                   -1);
1365
    }
124✔
1366

1367
    for (;;) {
237,201✔
1368
        sixel_mutex_lock(&ctx->mutex);
313,203✔
1369
        while (!ctx->writer_should_stop &&
705,122!
1370
               ctx->next_band_to_flush < ctx->band_count) {
467,095✔
1371
    band_index = ctx->next_band_to_flush;
423,479✔
1372
    band = &ctx->bands[band_index];
423,479✔
1373
    if (band->ready) {
423,479✔
1374
        break;
214,458✔
1375
    }
1376
            sixel_cond_wait(&ctx->cond_band_ready, &ctx->mutex);
153,892✔
1377
        }
1378

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

1384
        if (ctx->next_band_to_flush >= ctx->band_count) {
313,203✔
1385
            sixel_mutex_unlock(&ctx->mutex);
43,616✔
1386
            break;
43,616✔
1387
        }
1388

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

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

1417
    return SIXEL_OK;
34,778✔
1418
}
24,239✔
1419

1420
static SIXELSTATUS
1421
sixel_encode_body_parallel(sixel_index_t *pixels,
5,431✔
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};
5,431✔
1433
    SIXELSTATUS status;
3,139✔
1434
    int nbands;
3,139✔
1435
    int threads;
3,139✔
1436
    int i;
3,139✔
1437
    int queue_depth;
3,139✔
1438
    sixel_timeline_logger_t *logger;
3,139✔
1439

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

1448
    threads = requested_threads;
5,431✔
1449
    if (threads > nbands) {
5,431✔
1450
        threads = nbands;
1,940✔
1451
    }
1,400✔
1452
    if (threads < 1) {
5,335!
1453
        threads = 1;
1454
    }
1455
    ctx.thread_count = threads;
5,370✔
1456
    queue_depth = threads * 3;
5,370✔
1457
    if (queue_depth > nbands) {
5,370✔
1458
        queue_depth = nbands;
3,688✔
1459
    }
2,680✔
1460
    if (queue_depth < 1) {
5,587!
1461
        queue_depth = 1;
1462
    }
1463

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

1484
    for (i = 0; i < nbands; i++) {
40,525!
1485
        sixel_parallel_submit_band(&ctx, i);
35,094✔
1486
    }
19,064✔
1487

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

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

1537
    if (pixels == NULL
57,617!
1538
            || (palette == NULL && palette_float == NULL)
38,185!
1539
            || dither == NULL
38,185!
1540
            || output == NULL) {
38,185!
1541
        return SIXEL_BAD_ARGUMENT;
1542
    }
1543

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

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

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

1576
    status = sixel_encode_emit_palette(dither->bodyonly,
59,194✔
1577
                                       dither->ncolors,
21,009✔
1578
                                       dither->keycolor,
21,009✔
1579
                                       palette,
21,009✔
1580
                                       palette_float,
21,009✔
1581
                                       output);
21,009✔
1582
    if (SIXEL_FAILED(status)) {
38,185!
1583
        goto cleanup;
1584
    }
1585

1586
    queue_depth = threads * 3;
38,185✔
1587
    if (queue_depth > nbands) {
38,185✔
1588
        queue_depth = nbands;
19,680✔
1589
    }
13,278✔
1590
    if (queue_depth < 1) {
38,706!
1591
        queue_depth = 1;
1592
    }
1593

1594
    dither_threads_budget = dither->pipeline_dither_threads;
35,920✔
1595
    worker_capacity = threads + dither_threads_budget;
35,920✔
1596
    if (worker_capacity < threads) {
35,920!
1597
        worker_capacity = threads;
1598
    }
1599
    if (worker_capacity > nbands) {
36,547✔
1600
        worker_capacity = nbands;
13,829✔
1601
    }
7,457✔
1602

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

1611
    if (logger != NULL) {
37,161✔
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,
256✔
1619
                          "controller",
1620
                          "pipeline",
1621
                          "configure",
1622
                          -1);
1623
    }
124✔
1624

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

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

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

1670
cleanup:
17,176✔
1671
    dither->pipeline_row_callback = NULL;
38,185✔
1672
    dither->pipeline_row_priv = NULL;
38,185✔
1673
    dither->pipeline_index_buffer = NULL;
38,185✔
1674
    dither->pipeline_index_size = 0;
38,185✔
1675
    dither->pipeline_image_width = 0;
38,185✔
1676
    dither->pipeline_image_height = 0;
38,185✔
1677
    dither->pipeline_transparent_mask = NULL;
38,185✔
1678
    dither->pipeline_transparent_mask_size = 0;
38,185✔
1679
    dither->pipeline_transparent_keycolor = (-1);
38,185✔
1680
    if (!waited && ctx.pool != NULL) {
38,185!
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);
35,933✔
1687
    if (owns_logger) {
35,933✔
1688
        sixel_timeline_logger_unref(logger);
256✔
1689
    }
124✔
1690
    if (indexes != NULL) {
38,185!
1691
        sixel_allocator_free(allocator, indexes);
38,185✔
1692
    }
21,009✔
1693
    return status;
38,185✔
1694
}
21,009✔
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)
178,936,810✔
1722
{
1723
    if ((output->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) {
178,936,810✔
1724
        (void)sixel_output_write_bytes(output,
7,463✔
1725
                                       (char *)output->buffer,
5,164✔
1726
                                       SIXEL_OUTPUT_PACKET_SIZE);
1727
        memcpy(output->buffer,
5,351✔
1728
               output->buffer + SIXEL_OUTPUT_PACKET_SIZE,
1,713✔
1729
               (size_t)(output->pos -= SIXEL_OUTPUT_PACKET_SIZE));
3,052✔
1730
    }
2,299✔
1731
}
178,942,239✔
1732

1733

1734
static void
1735
sixel_putc(unsigned char *buffer, unsigned char value)
50,763,474✔
1736
{
1737
    *buffer = value;
50,763,474✔
1738
}
38,445,991✔
1739

1740

1741
static void
1742
sixel_puts(unsigned char *buffer, char const *value, int size)
4,751,060✔
1743
{
1744
    memcpy(buffer, (void *)value, (size_t)size);
4,751,060✔
1745
}
3,902,740✔
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,
75,473,052✔
1754
                          unsigned char value,
1755
                          int count)
1756
{
1757
    int chunk;
41,259,243✔
1758

1759
    if (count <= 0) {
75,473,052!
1760
        return;
1761
    }
1762

1763
    while (count > 0) {
150,996,140!
1764
        chunk = SIXEL_OUTPUT_PACKET_SIZE - output->pos;
75,501,897✔
1765
        if (chunk > count) {
75,501,897✔
1766
            chunk = count;
56,874,585✔
1767
        }
35,702,164✔
1768
        memset(output->buffer + output->pos, value, (size_t)chunk);
75,506,198✔
1769
        sixel_advance(output, chunk);
75,506,198✔
1770
        count -= chunk;
75,523,134✔
1771
    }
1772
}
35,660,576!
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,
29,441,209✔
1781
                             int width,
1782
                             int start)
1783
{
1784
    int idx;
16,332,621✔
1785
    size_t chunk_size;
16,332,621✔
1786
    unsigned char const *cursor;
16,332,621✔
1787
    unsigned long block;
16,332,621✔
1788

1789
    idx = start;
29,441,209✔
1790
    chunk_size = sizeof(unsigned long);
29,441,209✔
1791
    cursor = row + start;
29,441,209✔
1792

1793
    while ((width - idx) >= (int)chunk_size) {
263,914,299!
1794
        memcpy(&block, cursor, chunk_size);
252,394,604✔
1795
        if (block != 0UL) {
252,394,604✔
1796
            break;
13,641,796✔
1797
        }
1798
        idx += (int)chunk_size;
234,473,090✔
1799
        cursor += chunk_size;
234,473,090✔
1800
    }
1801

1802
    while (idx < width) {
110,818,003!
1803
        if (*cursor != 0) {
99,637,803✔
1804
            break;
13,909,201✔
1805
        }
1806
        idx += 1;
81,375,428✔
1807
        cursor += 1;
81,375,428✔
1808
    }
1809

1810
    return idx;
38,764,349✔
1811
}
9,324,545✔
1812

1813

1814
static int
1815
sixel_compose_measure_gap(unsigned char const *row,
32,227,273✔
1816
                          int width,
1817
                          int start,
1818
                          int *reached_end)
1819
{
1820
    int gap;
17,835,579✔
1821
    size_t chunk_size;
17,835,579✔
1822
    unsigned char const *cursor;
17,835,579✔
1823
    unsigned long block;
17,835,579✔
1824
    int remaining;
17,835,579✔
1825

1826
    gap = 0;
32,227,273✔
1827
    *reached_end = 0;
32,227,273✔
1828
    if (start >= width) {
32,227,273✔
1829
        *reached_end = 1;
381,843✔
1830
        return gap;
381,843✔
1831
    }
1832

1833
    chunk_size = sizeof(unsigned long);
31,845,451✔
1834
    cursor = row + start;
31,845,451✔
1835
    remaining = width - start;
31,845,451✔
1836

1837
    while (remaining >= (int)chunk_size) {
161,039,084!
1838
        memcpy(&block, cursor, chunk_size);
149,315,452✔
1839
        if (block != 0UL) {
149,315,452✔
1840
            break;
15,289,863✔
1841
        }
1842
        gap += (int)chunk_size;
129,193,633✔
1843
        cursor += chunk_size;
129,193,633✔
1844
        remaining -= (int)chunk_size;
129,193,633✔
1845
    }
1846

1847
    while (remaining > 0) {
100,837,973!
1848
        if (*cursor != 0) {
90,022,737✔
1849
            return gap;
16,013,083✔
1850
        }
1851
        gap += 1;
68,989,327✔
1852
        cursor += 1;
68,989,327✔
1853
        remaining -= 1;
68,989,327✔
1854
    }
1855

1856
    *reached_end = 1;
10,814,142✔
1857
    return gap;
10,814,142✔
1858
}
15,841,214✔
1859

1860

1861
#if HAVE_LDIV
1862
static int
1863
sixel_putnum_impl(char *buffer, long value, int pos)
91,478,636✔
1864
{
1865
    ldiv_t r;
50,454,861✔
1866

1867
    r = ldiv(value, 10);
91,478,636✔
1868
    if (r.quot > 0) {
91,483,860✔
1869
        pos = sixel_putnum_impl(buffer, r.quot, pos);
45,284,758✔
1870
    }
22,174,508✔
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);
91,487,661✔
1876
    return pos + 1;
119,865,829✔
1877
}
28,378,168✔
1878
#endif  /* HAVE_LDIV */
1879

1880

1881
static int
1882
sixel_putnum(char *buffer, int value)
46,257,936✔
1883
{
1884
    int pos;
25,454,354✔
1885

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

1892
    return pos;
60,477,481✔
1893
}
14,217,062✔
1894

1895

1896
static SIXELSTATUS
1897
sixel_put_flash(sixel_output_t *const output)
84,984,198✔
1898
{
1899
    int nwrite;
46,498,043✔
1900

1901
    if (output->save_count <= 0) {
84,984,198✔
1902
        return SIXEL_OK;
214,377✔
1903
    }
1904

1905
    if (output->has_gri_arg_limit) {  /* VT240 Max 255 ? */
84,712,479✔
1906
            while (output->save_count > 255) {
80,544!
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;
53,619✔
1914
            }
1915
        }
10,770✔
1916

1917
    if (output->save_count > 3) {
84,783,087✔
1918
        /* DECGRI Graphics Repeat Introducer ! Pn Ch */
1919
        sixel_putc(output->buffer + output->pos, '!');
9,279,105✔
1920
        sixel_advance(output, 1);
9,278,843✔
1921
        nwrite = sixel_putnum((char *)output->buffer + output->pos, output->save_count);
9,279,144✔
1922
        sixel_advance(output, nwrite);
9,279,789✔
1923
        sixel_putc(output->buffer + output->pos,
13,754,946✔
1924
                   (unsigned char)output->save_pixel);
9,279,384✔
1925
        sixel_advance(output, 1);
9,279,278✔
1926
    } else {
4,475,562✔
1927
        sixel_output_emit_literal(output,
111,211,882✔
1928
                                  (unsigned char)output->save_pixel,
75,503,982✔
1929
                                  output->save_count);
35,707,900✔
1930
    }
1931

1932
    output->save_pixel = 0;
84,738,002✔
1933
    output->save_count = 0;
84,738,002✔
1934

1935
    return SIXEL_OK;
84,738,002✔
1936
}
40,318,358✔
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)
84,667,199✔
1946
{
1947
    SIXELSTATUS status = SIXEL_FALSE;
84,667,199✔
1948

1949
    if (count <= 0) {
84,667,199!
1950
        return SIXEL_OK;
1951
    }
1952

1953
    if (output->save_count > 0) {
84,667,336✔
1954
        if (output->save_pixel == symbol) {
66,636,228✔
1955
            output->save_count += count;
64,992✔
1956
            return SIXEL_OK;
64,992✔
1957
        }
1958

1959
        status = sixel_put_flash(output);
66,571,484✔
1960
        if (SIXEL_FAILED(status)) {
66,549,847!
1961
            return status;
1962
        }
1963
    }
31,231,240✔
1964

1965
    output->save_pixel = symbol;
84,573,459✔
1966
    output->save_count = count;
84,573,459✔
1967

1968
    return SIXEL_OK;
84,573,459✔
1969
}
40,060,033✔
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,
18,343,899✔
1978
                         unsigned char const *map,
1979
                         int length)
1980
{
1981
    SIXELSTATUS status = SIXEL_FALSE;
18,343,899✔
1982
    int index;
10,164,275✔
1983
    int run_length;
10,164,275✔
1984
    unsigned char value;
10,164,275✔
1985
    size_t chunk_size;
10,164,275✔
1986
    unsigned long pattern;
10,164,275✔
1987
    unsigned long block;
10,164,275✔
1988
    int chunk_mismatch;
10,164,275✔
1989
    int remain;
10,164,275✔
1990
    int byte_index;
10,164,275✔
1991

1992
    if (length <= 0) {
18,343,899!
1993
        return SIXEL_OK;
1994
    }
1995

1996
    for (index = 0; index < length; index += run_length) {
95,750,750!
1997
        value = map[index];
77,531,232✔
1998
        if (value > '?') {
77,531,232!
1999
            value = 0;
×
2000
        }
2001

2002
        run_length = 1;
77,529,211✔
2003
        chunk_size = sizeof(unsigned long);
77,529,211✔
2004
        chunk_mismatch = 0;
77,529,211✔
2005
        if (chunk_size > 1) {
77,529,211!
2006
            remain = length - (index + run_length);
77,470,267✔
2007
            pattern = (~0UL / 0xffUL) * (unsigned long)value;
77,470,267✔
2008

2009
            while (remain >= (int)chunk_size) {
80,854,932!
2010
                memcpy(&block,
39,976,833✔
2011
                       map + index + run_length,
25,514,163✔
2012
                       chunk_size);
1,844,116✔
2013
                block ^= pattern;
39,976,833✔
2014
                if (block != 0UL) {
39,976,833✔
2015
                    for (byte_index = 0;
35,672,322!
2016
                         byte_index < (int)chunk_size;
55,031,718!
2017
                         byte_index++) {
18,439,015✔
2018
                        if ((block & 0xffUL) != 0UL) {
55,026,630✔
2019
                            chunk_mismatch = 1;
26,570,954✔
2020
                            break;
26,570,954✔
2021
                        }
2022
                        block >>= 8;
18,439,040✔
2023
                        run_length += 1;
18,439,040✔
2024
                    }
9,107,178✔
2025
                    break;
26,570,394✔
2026
                }
2027
                run_length += (int)chunk_size;
3,384,665✔
2028
                remain -= (int)chunk_size;
3,384,665✔
2029
            }
2030
        }
36,654,032✔
2031

2032
        if (!chunk_mismatch) {
78,981,485✔
2033
            while (index + run_length < length) {
54,156,982!
2034
                unsigned char next;
20,649,970✔
2035

2036
                next = map[index + run_length];
36,008,822✔
2037
                if (next > '?') {
36,008,822!
2038
                    next = 0;
×
2039
                }
2040
                if (next != value) {
36,016,377✔
2041
                    break;
18,360,937✔
2042
                }
2043
                run_length += 1;
12,875,537✔
2044
            }
13,435,802!
2045
        }
21,612,147✔
2046

2047
        status = sixel_emit_run(output,
114,015,479✔
2048
                                 (int)value + '?',
58,320,033✔
2049
                                 run_length);
36,593,593✔
2050
        if (SIXEL_FAILED(status)) {
77,406,897!
2051
            return status;
2052
        }
2053
    }
36,593,593✔
2054

2055
    return SIXEL_OK;
13,868,710✔
2056
}
8,962,108✔
2057

2058

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

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

2069
static SIXELSTATUS
2070
sixel_node_new(sixel_node_t **np, sixel_allocator_t *allocator)
15,956,875✔
2071
{
2072
    SIXELSTATUS status = SIXEL_FALSE;
15,956,875✔
2073

2074
    *np = (sixel_node_t *)sixel_allocator_malloc(allocator,
15,956,875✔
2075
                                                 sizeof(sixel_node_t));
2076
    if (np == NULL) {
15,958,000!
2077
        sixel_helper_set_additional_message(
61✔
2078
            "sixel_node_new: sixel_allocator_malloc() failed.");
2079
        status = SIXEL_BAD_ALLOCATION;
2080
        goto end;
2081
    }
2082

2083
    status = SIXEL_OK;
12,677,905✔
2084

2085
end:
7,113,859✔
2086
    return status;
21,644,148✔
2087
}
5,685,820✔
2088

2089
static void
2090
sixel_node_del(sixel_output_t *output, sixel_node_t *np)
18,257,696✔
2091
{
2092
    sixel_node_t *tp;
10,119,927✔
2093

2094
    if ((tp = output->node_top) == np) {
18,257,696✔
2095
        output->node_top = np->next;
4,209,964✔
2096
    } else {
2,006,405✔
2097
        while (tp->next != NULL) {
664,657,079!
2098
            if (tp->next == np) {
664,673,814✔
2099
                tp->next = np->next;
14,074,048✔
2100
                break;
14,074,048✔
2101
            }
2102
            tp = tp->next;
499,563,640✔
2103
        }
2104
    }
2105

2106
    np->next = output->node_free;
18,302,004✔
2107
    output->node_free = np;
18,302,004✔
2108
}
18,302,004✔
2109

2110

2111
static SIXELSTATUS
2112
sixel_put_node(
18,274,740✔
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;
18,274,740✔
2120
    int nwrite;
10,134,137✔
2121

2122
    if (ncolors != 2 || keycolor == (-1)) {
18,274,740!
2123
        /* designate palette index */
2124
        if (output->active_palette != np->pal) {
18,249,076✔
2125
            sixel_putc(output->buffer + output->pos, '#');
18,029,058✔
2126
            sixel_advance(output, 1);
18,028,690✔
2127
            nwrite = sixel_putnum((char *)output->buffer + output->pos, np->pal);
18,028,906✔
2128
            sixel_advance(output, nwrite);
18,029,729✔
2129
            output->active_palette = np->pal;
18,029,215✔
2130
        }
8,891,404✔
2131
    }
8,982,642✔
2132

2133
    if (*x < np->sx) {
18,852,279✔
2134
        int span;
3,850,808✔
2135

2136
        span = np->sx - *x;
6,970,662✔
2137
        status = sixel_emit_run(output, '?', span);
6,970,662✔
2138
        if (SIXEL_FAILED(status)) {
6,970,491!
2139
            goto end;
2140
        }
2141
        *x = np->sx;
6,970,418✔
2142
    }
3,401,527!
2143

2144
    if (*x < np->mx) {
18,247,259!
2145
        int span;
10,104,233✔
2146

2147
        span = np->mx - *x;
18,243,796✔
2148
        status = sixel_emit_span_from_map(output,
31,564,962✔
2149
                                          (unsigned char const *)np->map + *x,
18,243,796✔
2150
                                          span);
8,964,501✔
2151
        if (SIXEL_FAILED(status)) {
18,242,137!
2152
            goto end;
2153
        }
2154
        *x = np->mx;
18,240,647✔
2155
    }
8,963,011✔
2156

2157
    status = sixel_put_flash(output);
18,244,227✔
2158
    if (SIXEL_FAILED(status)) {
18,242,934!
2159
        goto end;
2160
    }
2161

2162
end:
9,275,747✔
2163
    return status;
18,234,565✔
2164
}
5,744,405✔
2165

2166

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

2175
    if (output->ormode) {
59,479✔
2176
        p[0] = 7;
90✔
2177
        p[1] = 5;
90✔
2178
    } else if (keycolor >= 0) {
59,404!
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,968✔
2184
    }
2,553✔
2185

2186
    output->pos = 0;
59,479✔
2187

2188
    if (p[2] == 0) {
59,479!
2189
        pcount--;
59,479✔
2190
        if (p[1] == 0) {
59,479✔
2191
            pcount--;
54,386✔
2192
            if (p[0] == 0) {
54,386!
2193
                pcount--;
54,386✔
2194
            }
25,482✔
2195
        }
25,482✔
2196
    }
28,085✔
2197

2198
    status = sixel_output_begin_image(output,
87,564✔
2199
                                      width,
28,085✔
2200
                                      height,
28,085✔
2201
                                      p[0],
28,085✔
2202
                                      p[1],
28,085✔
2203
                                      p[2],
28,085✔
2204
                                      pcount,
28,085✔
2205
                                      use_raster_attributes);
28,085✔
2206

2207
    return status;
77,188✔
2208
}
17,709✔
2209

2210

2211
static int
2212
sixel_palette_float_pixelformat_for_colorspace(int colorspace)
103,750✔
2213
{
2214
    switch (colorspace) {
103,750✔
2215
    case SIXEL_COLORSPACE_LINEAR:
112✔
2216
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
252✔
2217
    case SIXEL_COLORSPACE_OKLAB:
312✔
2218
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
702✔
2219
    case SIXEL_COLORSPACE_CIELAB:
88✔
2220
        return SIXEL_PIXELFORMAT_CIELABFLOAT32;
198✔
2221
    case SIXEL_COLORSPACE_DIN99D:
80✔
2222
        return SIXEL_PIXELFORMAT_DIN99DFLOAT32;
180✔
2223
    default:
32,684✔
2224
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
87,728✔
2225
    }
2226
}
55,784✔
2227

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

2239
    if (palette == NULL || palette_float == NULL || count == 0U) {
4,857!
2240
        return 1;
2241
    }
2242
    if (count > SIZE_MAX / 3U) {
4,857!
2243
        return 0;
2244
    }
2245
    limit = count * 3U;
4,857✔
2246
    for (index = 0U; index < limit; ++index) {
790,524!
2247
        channel = (int)(index % 3U);
785,667✔
2248
        expected = sixel_pixelformat_float_channel_to_byte(
1,160,841✔
2249
            float_pixelformat,
32,217✔
2250
            channel,
32,217✔
2251
            palette_float[index]);
785,667✔
2252
        if (palette[index] != expected) {
785,667!
2253
            return 0;
2254
        }
2255
    }
32,217✔
2256
    return 1;
3,124✔
2257
}
1,274✔
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,
14,078,424✔
2287
                                    float const *palette_float,
2288
                                    int n,
2289
                                    int channel)
2290
{
2291
    size_t index;
7,703,397✔
2292
    float value;
7,703,397✔
2293
    int percent;
7,703,397✔
2294

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

2312
    if (palette != NULL) {
13,319,421!
2313
        return (palette[index] * 100 + 127) / 255;
13,319,422✔
2314
    }
2315

2316
    return 0;
2317
}
6,681,780✔
2318

2319
static double
2320
sixel_output_palette_channel_to_float(unsigned char const *palette,
174,750✔
2321
                                      float const *palette_float,
2322
                                      int n,
2323
                                      int channel)
2324
{
2325
    size_t index;
91,422✔
2326
    double value;
91,422✔
2327

2328
    index = (size_t)n * 3U + (size_t)channel;
174,750✔
2329
    value = 0.0;
174,750✔
2330
    if (palette_float != NULL) {
174,750!
2331
        value = palette_float[index];
6,144✔
2332
    } else if (palette != NULL) {
168,606!
2333
        value = (double)palette[index] / 255.0;
168,606✔
2334
    }
71,340✔
2335
    if (value < 0.0) {
174,750!
2336
        value = 0.0;
2337
    } else if (value > 1.0) {
174,750!
2338
        value = 1.0;
2339
    }
2340

2341
    return value;
217,914✔
2342
}
43,164✔
2343

2344
static SIXELSTATUS
2345
output_rgb_palette_definition(
4,696,318✔
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,696,318✔
2354
    int nwrite;
2,569,738✔
2355

2356
    if (n != keycolor) {
4,696,318✔
2357
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
2358
        sixel_putc(output->buffer + output->pos, '#');
4,692,810✔
2359
        sixel_advance(output, 1);
4,692,808✔
2360
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
4,692,809✔
2361
        sixel_advance(output, nwrite);
4,692,810✔
2362
        sixel_puts(output->buffer + output->pos, ";2;", 3);
4,692,810✔
2363
        sixel_advance(output, 3);
4,692,810✔
2364
        nwrite = sixel_putnum(
5,850,083✔
2365
            (char *)output->buffer + output->pos,
4,692,810✔
2366
            sixel_output_palette_channel_to_pct(palette,
4,454,520✔
2367
                                                palette_float,
2,227,260✔
2368
                                                n,
2,227,260✔
2369
                                                0));
2370
        sixel_advance(output, nwrite);
4,692,810✔
2371
        sixel_putc(output->buffer + output->pos, ';');
4,692,809✔
2372
        sixel_advance(output, 1);
4,692,810✔
2373
        nwrite = sixel_putnum(
5,850,083✔
2374
            (char *)output->buffer + output->pos,
4,692,810✔
2375
            sixel_output_palette_channel_to_pct(palette,
4,454,520✔
2376
                                                palette_float,
2,227,260✔
2377
                                                n,
2,227,260✔
2378
                                                1));
2379
        sixel_advance(output, nwrite);
4,692,810✔
2380
        sixel_putc(output->buffer + output->pos, ';');
4,692,810✔
2381
        sixel_advance(output, 1);
4,692,810✔
2382
        nwrite = sixel_putnum(
5,850,083✔
2383
            (char *)output->buffer + output->pos,
4,692,810✔
2384
            sixel_output_palette_channel_to_pct(palette,
4,454,520✔
2385
                                                palette_float,
2,227,260✔
2386
                                                n,
2,227,260✔
2387
                                                2));
2388
        sixel_advance(output, nwrite);
4,692,810✔
2389
    }
2,227,260✔
2390

2391
    status = SIXEL_OK;
4,696,317✔
2392

2393
    return status;
6,107,942✔
2394
}
1,411,625✔
2395

2396

2397
static SIXELSTATUS
2398
output_hls_palette_definition(
58,250✔
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;
58,250✔
2407
    double r;
30,474✔
2408
    double g;
30,474✔
2409
    double b;
30,474✔
2410
    double maxc;
30,474✔
2411
    double minc;
30,474✔
2412
    double lightness;
30,474✔
2413
    double saturation;
30,474✔
2414
    double hue;
30,474✔
2415
    double diff;
30,474✔
2416
    int h;
30,474✔
2417
    int l;
30,474✔
2418
    int s;
30,474✔
2419
    int nwrite;
30,474✔
2420

2421
    if (n != keycolor) {
58,250!
2422
        r = sixel_output_palette_channel_to_float(palette,
82,030✔
2423
                                                  palette_float,
23,780✔
2424
                                                  n,
23,780✔
2425
                                                  0);
2426
        g = sixel_output_palette_channel_to_float(palette,
82,030✔
2427
                                                  palette_float,
23,780✔
2428
                                                  n,
23,780✔
2429
                                                  1);
2430
        b = sixel_output_palette_channel_to_float(palette,
82,030✔
2431
                                                  palette_float,
23,780✔
2432
                                                  n,
23,780✔
2433
                                                  2);
2434
        maxc = r > g ? (r > b ? r : b) : (g > b ? g : b);
58,250!
2435
        minc = r < g ? (r < b ? r : b) : (g < b ? g : b);
58,250!
2436
        lightness = (maxc + minc) * 0.5;
58,250✔
2437
        saturation = 0.0;
58,250✔
2438
        hue = 0.0;
58,250✔
2439
        diff = maxc - minc;
58,250✔
2440
        if (diff <= 0.0) {
58,250✔
2441
            h = 0;
4,482✔
2442
            s = 0;
4,482✔
2443
        } else {
2,490✔
2444
            if (lightness < 0.5) {
52,025✔
2445
                saturation = diff / (maxc + minc);
44,325✔
2446
            } else {
18,190✔
2447
                saturation = diff / (2.0 - maxc - minc);
7,700✔
2448
            }
2449
            if (maxc == r) {
52,025✔
2450
                hue = (g - b) / diff;
35,653✔
2451
            } else if (maxc == g) {
31,044!
2452
                hue = 2.0 + (b - r) / diff;
15,904✔
2453
            } else {
6,431✔
2454
                hue = 4.0 + (r - g) / diff;
468✔
2455
            }
2456
            hue *= 60.0;
52,025✔
2457
            if (hue < 0.0) {
52,025✔
2458
                hue += 360.0;
25✔
2459
            }
10✔
2460
            if (hue >= 360.0) {
49,778!
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;
52,025✔
2470
            while (hue >= 360.0) {
52,115!
2471
                hue -= 360.0;
90✔
2472
            }
2473
            while (hue < 0.0) {
52,025!
2474
                hue += 360.0;
×
2475
            }
2476
            s = (int)(saturation * 100.0 + 0.5);
52,025✔
2477
            if (s < 0) {
52,025!
2478
                s = 0;
2479
            } else if (s > 100) {
52,025!
2480
                s = 100;
2481
            }
2482
            h = (int)(hue + 0.5);
52,025✔
2483
            if (h >= 360) {
52,025!
2484
                h -= 360;
×
2485
            } else if (h < 0) {
52,025!
2486
                h = 0;
2487
            }
2488
        }
2489
        l = (int)(lightness * 100.0 + 0.5);
58,250✔
2490
        if (l < 0) {
58,250!
2491
            l = 0;
2492
        } else if (l > 100) {
58,250!
2493
            l = 100;
2494
        }
2495
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
2496
        sixel_putc(output->buffer + output->pos, '#');
58,250✔
2497
        sixel_advance(output, 1);
58,250✔
2498
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
58,250✔
2499
        sixel_advance(output, nwrite);
58,250✔
2500
        sixel_puts(output->buffer + output->pos, ";1;", 3);
58,250✔
2501
        sixel_advance(output, 3);
58,250✔
2502
        nwrite = sixel_putnum((char *)output->buffer + output->pos, h);
58,250✔
2503
        sixel_advance(output, nwrite);
58,250✔
2504
        sixel_putc(output->buffer + output->pos, ';');
58,250✔
2505
        sixel_advance(output, 1);
58,250✔
2506
        nwrite = sixel_putnum((char *)output->buffer + output->pos, l);
58,250✔
2507
        sixel_advance(output, nwrite);
58,250✔
2508
        sixel_putc(output->buffer + output->pos, ';');
58,250✔
2509
        sixel_advance(output, 1);
58,250✔
2510
        nwrite = sixel_putnum((char *)output->buffer + output->pos, s);
58,250✔
2511
        sixel_advance(output, nwrite);
58,250✔
2512
    }
23,780✔
2513

2514
    status = SIXEL_OK;
58,250✔
2515
    return status;
72,638✔
2516
}
14,388✔
2517

2518

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

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

2545
    len = ncolors * width;
95,462✔
2546
    work->map = (char *)sixel_allocator_calloc(allocator,
138,220✔
2547
                                               (size_t)len,
42,758✔
2548
                                               sizeof(char));
2549
    if (work->map == NULL && len > 0) {
95,469!
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;
95,467✔
2556

2557
    columns_size = sizeof(sixel_node_t *) * (size_t)width;
95,467✔
2558
    if (width > 0) {
95,467!
2559
        work->columns = (sixel_node_t **)sixel_allocator_malloc(
95,465✔
2560
            allocator,
42,756✔
2561
            columns_size);
42,756✔
2562
        if (work->columns == NULL) {
95,469!
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);
95,469✔
2569
        work->columns_size = columns_size;
95,469✔
2570
    } else {
42,757✔
2571
        work->columns = NULL;
2✔
2572
        work->columns_size = 0;
2✔
2573
    }
2574

2575
    active_colors_size = (size_t)ncolors;
95,471✔
2576
    if (active_colors_size > 0) {
95,471!
2577
        work->active_colors =
119,465✔
2578
            (unsigned char *)sixel_allocator_malloc(allocator,
138,218✔
2579
                                                    active_colors_size);
42,753✔
2580
        if (work->active_colors == NULL) {
95,466!
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);
95,465✔
2587
        work->active_colors_size = active_colors_size;
95,465✔
2588
    } else {
42,752✔
2589
        work->active_colors = NULL;
6✔
2590
        work->active_colors_size = 0;
6✔
2591
    }
2592

2593
    active_color_index_size = sizeof(int) * (size_t)ncolors;
95,471✔
2594
    if (active_color_index_size > 0) {
95,471!
2595
        work->active_color_index = (int *)sixel_allocator_malloc(
95,469✔
2596
            allocator,
42,756✔
2597
            active_color_index_size);
42,756✔
2598
        if (work->active_color_index == NULL) {
95,469!
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);
95,470✔
2605
        work->active_color_index_size = active_color_index_size;
95,470✔
2606
    } else {
42,757✔
2607
        work->active_color_index = NULL;
2✔
2608
        work->active_color_index_size = 0;
2✔
2609
    }
2610

2611
    status = SIXEL_OK;
71,472✔
2612

2613
end:
28,716✔
2614
    if (SIXEL_FAILED(status)) {
71,473!
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;
121,967✔
2638
}
26,494✔
2639

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

2666
static void
2667
sixel_band_state_reset(sixel_band_state_t *state)
508,551✔
2668
{
2669
    state->row_in_band = 0;
508,551✔
2670
    state->fillable = 0;
508,551✔
2671
    state->active_color_count = 0;
506,256✔
2672
}
401,773✔
2673

2674
static void
2675
sixel_band_finish(sixel_encode_work_t *work, sixel_band_state_t *state)
354,518✔
2676
{
2677
    int color_index;
192,586✔
2678
    int c;
192,586✔
2679

2680
    if (work->active_colors == NULL
354,518!
2681
        || work->active_color_index == NULL) {
354,518!
2682
        state->active_color_count = 0;
4✔
2683
        return;
4✔
2684
    }
2685

2686
    for (color_index = 0;
6,248,473!
2687
         color_index < state->active_color_count;
12,404,599✔
2688
         color_index++) {
12,050,081✔
2689
        c = work->active_color_index[color_index];
12,050,179✔
2690
        if (c >= 0
12,050,179!
2691
            && (size_t)c < work->active_colors_size) {
12,050,186!
2692
            work->active_colors[c] = 0;
12,050,122✔
2693
        }
5,982,288✔
2694
    }
5,982,457✔
2695
    state->active_color_count = 0;
354,418✔
2696
}
165,882!
2697

2698
static void
2699
sixel_band_clear_map(sixel_encode_work_t *work)
354,567✔
2700
{
2701
    if (work->map != NULL && work->map_size > 0) {
354,567!
2702
        memset(work->map, 0, work->map_size);
354,569✔
2703
    }
166,045✔
2704
}
354,568✔
2705

2706
static SIXELSTATUS
2707
sixel_encode_emit_palette(int bodyonly,
62,054✔
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;
62,054✔
2715
    int n;
33,723✔
2716

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

2721
    if (palette == NULL && palette_float == NULL) {
57,697!
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) {
60,594✔
2728
        for (n = 0; n < ncolors; n++) {
58,479!
2729
            status = output_hls_palette_definition(output,
82,030✔
2730
                                                   palette,
23,780✔
2731
                                                   palette_float,
23,780✔
2732
                                                   n,
23,780✔
2733
                                                   keycolor);
23,780✔
2734
            if (SIXEL_FAILED(status)) {
58,250!
2735
                goto end;
2736
            }
2737
        }
23,780✔
2738
    } else {
94✔
2739
        for (n = 0; n < ncolors; n++) {
4,749,083!
2740
            status = output_rgb_palette_definition(output,
6,914,646✔
2741
                                                   palette,
2,225,928✔
2742
                                                   palette_float,
2,225,928✔
2743
                                                   n,
2,225,928✔
2744
                                                   keycolor);
2,225,928✔
2745
            if (SIXEL_FAILED(status)) {
4,688,718!
2746
                goto end;
2747
            }
2748
        }
2,225,928✔
2749
    }
2750

2751
    status = SIXEL_OK;
45,485✔
2752

2753
end:
32,611✔
2754
    return status;
45,485✔
2755
}
29,115✔
2756

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

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

2784
    if (row_bit == 0) {
2,067,245✔
2785
        if (encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
354,568✔
2786
            state->fillable = 0;
354,293✔
2787
        } else if (palstate) {
166,206!
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;
275✔
2800
        }
2801
        state->active_color_count = 0;
354,568✔
2802
    }
166,041✔
2803

2804
    for (x = 0; x < width; x++) {
238,515,883!
2805
        if (absolute_row > INT_MAX / width) {
236,538,954!
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;
236,538,850✔
2813
        if (check_integer_overflow > INT_MAX - x) {
236,538,850!
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];
236,540,035✔
2821
        if (pix >= 0 && pix < ncolors && pix != keycolor) {
236,540,035!
2822
            if (pix > INT_MAX / width) {
204,016,359!
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;
204,016,335✔
2830
            if (check_integer_overflow > INT_MAX - x) {
204,016,335!
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);
204,016,380✔
2838
            if (active_colors != NULL && active_colors[pix] == 0) {
204,016,380!
2839
                active_colors[pix] = 1;
12,034,055✔
2840
                if (state->active_color_count < ncolors
12,034,055!
2841
                    && active_color_index != NULL) {
12,041,533!
2842
                    active_color_index[state->active_color_count] = pix;
12,041,114✔
2843
                    state->active_color_count += 1;
12,041,114✔
2844
                }
5,976,351✔
2845
            }
5,976,846✔
2846
        } else if (!palstate) {
123,877,766!
2847
            state->fillable = 0;
25,162,883✔
2848
        }
10,686,082✔
2849
    }
105,224,356✔
2850

2851
    state->row_in_band += 1;
1,976,449✔
2852
    status = SIXEL_OK;
1,976,449✔
2853

2854
end:
1,054,185✔
2855
    return status;
2,555,206✔
2856
}
578,762✔
2857

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

2880
    (void)ncolors;
254,317✔
2881
    (void)keycolor;
254,317✔
2882
    row = NULL;
354,557✔
2883
    gap_reached_end = 0;
354,557✔
2884
    np = NULL;
354,557✔
2885
    column_head = NULL;
354,557✔
2886
    column_tail = NULL;
354,557✔
2887
    top.next = NULL;
354,557✔
2888

2889
    if (work->columns != NULL) {
354,557!
2890
        memset(work->columns, 0, work->columns_size);
354,543✔
2891
    }
166,015✔
2892
    output->node_top = NULL;
354,557✔
2893

2894
    for (color_index = 0;
6,307,175!
2895
         color_index < state->active_color_count;
12,376,019✔
2896
         color_index++) {
12,021,482✔
2897
        c = work->active_color_index[color_index];
12,018,605✔
2898
        row = (unsigned char *)(work->map + c * width);
12,018,605✔
2899
        sx = 0;
12,018,605✔
2900
        while (sx < width) {
30,304,130!
2901
            sx = sixel_compose_find_run_start(
29,484,420✔
2902
                row,
14,560,607✔
2903
                width,
14,560,607✔
2904
                sx);
14,560,607✔
2905
            if (sx >= width) {
29,484,580✔
2906
                break;
8,550,564✔
2907
            }
2908

2909
            mx = sx + 1;
18,282,225✔
2910
            while (mx < width) {
100,284,047!
2911
                if (row[mx] != 0) {
99,446,955✔
2912
                    mx += 1;
67,187,834✔
2913
                    continue;
67,187,834✔
2914
                }
2915

2916
                gap = sixel_compose_measure_gap(
32,263,865✔
2917
                    row,
15,852,169✔
2918
                    width,
15,852,169✔
2919
                    mx + 1,
15,852,169✔
2920
                    &gap_reached_end);
2921
                if (gap >= 9 || gap_reached_end) {
32,260,765!
2922
                    break;
8,594,346✔
2923
                }
2924
                mx += gap + 1;
14,813,955✔
2925
            }
2926

2927
            if ((np = output->node_free) != NULL) {
18,280,467✔
2928
                output->node_free = np->next;
2,326,350✔
2929
            } else {
160,010✔
2930
                status = sixel_node_new(&np, allocator);
15,954,117✔
2931
                if (SIXEL_FAILED(status)) {
15,956,694!
2932
                    goto end;
2933
                }
2934
            }
2935

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

2942
            if (work->columns != NULL) {
18,285,091!
2943
                column_head = work->columns[sx];
18,284,798✔
2944
                if (column_head == NULL
18,284,798✔
2945
                    || column_head->mx <= np->mx) {
13,112,304!
2946
                    np->next = column_head;
14,239,005✔
2947
                    work->columns[sx] = np;
14,239,005✔
2948
                } else {
6,931,335✔
2949
                    column_tail = column_head;
3,118,323✔
2950
                    while (column_tail->next != NULL
6,744,863!
2951
                           && column_tail->next->mx > np->mx) {
5,418,910!
2952
                        column_tail = column_tail->next;
1,059,753✔
2953
                    }
2954
                    np->next = column_tail->next;
4,047,671✔
2955
                    column_tail->next = np;
4,047,671✔
2956
                }
2957
            } else {
9,003,954✔
2958
                top.next = output->node_top;
278✔
2959
                column_tail = &top;
278✔
2960

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

2976
            sx = mx;
13,925,579✔
2977
        }
2978
    }
5,952,618✔
2979

2980
    if (work->columns != NULL) {
357,236!
2981
        top.next = NULL;
354,546✔
2982
        column_tail = &top;
354,546✔
2983
        for (sx = 0; sx < width; sx++) {
41,036,734!
2984
            column_head = work->columns[sx];
40,681,973✔
2985
            if (column_head == NULL) {
40,681,973✔
2986
                continue;
30,714,527✔
2987
            }
2988
            column_tail->next = column_head;
9,967,619✔
2989
            while (column_tail->next != NULL) {
28,282,547!
2990
                column_tail = column_tail->next;
13,961,114✔
2991
            }
2992
            work->columns[sx] = NULL;
9,967,654✔
2993
        }
4,798,549✔
2994
        output->node_top = top.next;
354,538✔
2995
    }
166,009✔
2996

2997
    status = SIXEL_OK;
357,263✔
2998

2999
end:
191,224✔
3000
    return status;
461,607✔
3001
}
104,344✔
3002

3003
static SIXELSTATUS
3004
sixel_band_emit(sixel_encode_work_t *work,
354,524✔
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;
354,524✔
3012
    sixel_node_t *np;
192,596✔
3013
    sixel_node_t *next;
192,596✔
3014
    int x;
192,596✔
3015
    int emit_next_line;
192,596✔
3016

3017
    emit_next_line = (last_row_index >= 6);
354,524✔
3018
    if (emit_next_line) {
354,524✔
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] = '-';
292,512✔
3024
        sixel_advance(output, 1);
292,512✔
3025
    }
136,918✔
3026

3027
    for (x = 0; (np = output->node_top) != NULL;) {
2,750,416!
3028
        if (x > np->sx) {
2,396,111✔
3029
            output->buffer[output->pos] = '$';
2,052,454✔
3030
            sixel_advance(output, 1);
2,052,454✔
3031
            x = 0;
2,052,448✔
3032
        }
1,011,528✔
3033

3034
        if (state->fillable) {
2,488,925✔
3035
            memset(np->map + np->sx,
297✔
3036
                   (1 << state->row_in_band) - 1,
176✔
3037
                   (size_t)(np->mx - np->sx));
176✔
3038
        }
110✔
3039
        status = sixel_put_node(output,
3,568,652✔
3040
                                &x,
3041
                                np,
1,172,613✔
3042
                                ncolors,
1,172,613✔
3043
                                keycolor);
1,172,613✔
3044
        if (SIXEL_FAILED(status)) {
2,395,955!
3045
            goto end;
3046
        }
3047
        next = np->next;
2,395,955✔
3048
        sixel_node_del(output, np);
2,395,955✔
3049
        np = next;
2,395,955✔
3050

3051
        while (np != NULL) {
100,032,646!
3052
            if (np->sx < x) {
97,632,619✔
3053
                np = np->next;
81,776,898✔
3054
                continue;
81,776,898✔
3055
            }
3056

3057
            if (state->fillable) {
15,861,126!
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,
23,662,786✔
3063
                                    &x,
3064
                                    np,
7,802,906✔
3065
                                    ncolors,
7,802,906✔
3066
                                    keycolor);
7,802,906✔
3067
            if (SIXEL_FAILED(status)) {
15,857,290!
3068
                goto end;
3069
            }
3070
            next = np->next;
15,857,290✔
3071
            sixel_node_del(output, np);
15,857,290✔
3072
            np = next;
15,858,736✔
3073
        }
3074

3075
        state->fillable = 0;
2,395,891✔
3076
    }
3077

3078
    status = SIXEL_OK;
266,043✔
3079

3080
end:
188,327✔
3081
    (void)work;
254,273✔
3082
    return status;
458,625✔
3083
}
104,304✔
3084

3085

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

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

3118
    sixel_encode_work_init(&work);
23,869✔
3119
    sixel_band_state_reset(&band);
23,869✔
3120

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

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

3130
    logging_active = logger != NULL;
23,869✔
3131
    job_index = 0;
23,869✔
3132

3133
    status = sixel_encode_emit_palette(bodyonly,
31,975✔
3134
                                       ncolors,
8,106✔
3135
                                       keycolor,
8,106✔
3136
                                       palette,
8,106✔
3137
                                       palette_float,
8,106✔
3138
                                       output);
8,106✔
3139
    if (SIXEL_FAILED(status)) {
23,869!
3140
        goto cleanup;
×
3141
    }
3142

3143
#if SIXEL_ENABLE_THREADS
3144
    {
3145
        int nbands;
8,338✔
3146
        int threads;
8,338✔
3147

3148
        nbands = (height + 5) / 6;
15,161✔
3149
        threads = work.requested_threads;
15,161✔
3150
        if (nbands > 1 && threads > 1) {
15,161!
3151
            status = sixel_encode_body_parallel(pixels,
8,661✔
3152
                                                width,
3,230✔
3153
                                                height,
3,230✔
3154
                                                ncolors,
3,230✔
3155
                                                keycolor,
3,230✔
3156
                                                output,
3,230✔
3157
                                                palstate,
3,230✔
3158
                                                allocator,
3,230✔
3159
                                                threads,
3,230✔
3160
                                                pin_threads);
3,230✔
3161
            if (SIXEL_FAILED(status)) {
5,431!
3162
                goto cleanup;
3163
            }
3164
            goto finalize;
5,431✔
3165
        }
3166
    }
5,133!
3167
#endif
3168

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

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

3185
    band_start = 0;
11,880✔
3186
    while (band_start < height) {
103,434!
3187
        band_height = height - band_start;
84,996✔
3188
        if (band_height > 6) {
84,996✔
3189
            band_height = 6;
39,950✔
3190
        }
12,175✔
3191

3192
        band.row_in_band = 0;
85,732✔
3193
        band.fillable = 0;
85,732✔
3194
        band.active_color_count = 0;
85,732✔
3195

3196
        if (logging_active) {
85,732!
3197
            sixel_timeline_logger_logf(logger,
32✔
3198
                              "worker",
3199
                              "encode",
3200
                              "start",
3201
                              job_index);
16✔
3202
        }
16✔
3203

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

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

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

3242
        sixel_band_finish(&work, &band);
84,996✔
3243

3244
        sixel_band_clear_map(&work);
84,996✔
3245

3246
        if (logging_active) {
84,996!
3247
            sixel_timeline_logger_logf(logger,
32✔
3248
                              "worker",
3249
                              "encode",
3250
                              "finish",
3251
                              job_index);
16✔
3252
        }
16✔
3253

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

3259
    status = SIXEL_OK;
18,438✔
3260
    goto finalize;
18,438✔
3261

3262
finalize:
15,763✔
3263
    if (palstate) {
25,157✔
3264
        output->buffer[output->pos] = '$';
3,208✔
3265
        sixel_advance(output, 1);
3,208✔
3266
    }
1,288✔
3267

3268
cleanup:
21,402✔
3269
    while ((np = output->node_free) != NULL) {
864,738!
3270
        output->node_free = np->next;
840,869✔
3271
        sixel_allocator_free(allocator, np);
840,869✔
3272
    }
3273
    output->node_top = NULL;
23,869✔
3274

3275
    sixel_encode_work_cleanup(&work, allocator);
23,869✔
3276

3277
    return status;
23,869✔
3278
}
5,133✔
3279
SIXELSTATUS
3280
sixel_encode_footer(sixel_output_t *output)
59,479✔
3281
{
3282
    SIXELSTATUS status = SIXEL_FALSE;
59,479✔
3283

3284
    if (output->pos > 0) {
59,479✔
3285
        status = sixel_output_write_bytes(output,
101,963✔
3286
                                          (char *)output->buffer,
59,317✔
3287
                                          output->pos);
28,013✔
3288
        if (SIXEL_FAILED(status)) {
59,317!
3289
            return status;
3290
        }
3291
        output->pos = 0;
59,317✔
3292
    }
28,013✔
3293

3294
    status = sixel_output_end_image(output);
59,479✔
3295

3296
    return status;
59,479✔
3297
}
28,085✔
3298

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

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

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

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

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

3344
            buf_p = buf;
5,500✔
3345
            for (x = 0; x < width; x++, buf_p++) {
359,040!
3346
                sixel_put_pixel(output,
492,800✔
3347
                                ((buf_p[0] >> plane) & 0x1) |
492,800✔
3348
                                (((buf_p[width] >> plane) << 1) & 0x2) |
492,800✔
3349
                                (((buf_p[width * 2] >> plane) << 2) & 0x4) |
492,800✔
3350
                                (((buf_p[width * 3] >> plane) << 3) & 0x8) |
492,800✔
3351
                                (((buf_p[width * 4] >> plane) << 4) & 0x10) |
492,800✔
3352
                                (((buf_p[width * 5] >> plane) << 5) & 0x20));
352,000✔
3353
            }
140,800✔
3354
            status = sixel_put_flash(output);
5,500✔
3355
            if (SIXEL_FAILED(status)) {
5,500!
3356
                return status;
3357
            }
3358
            sixel_putc(output->buffer + output->pos, '$');
5,500✔
3359
            sixel_advance(output, 1);
5,500✔
3360
        }
2,200✔
3361
        sixel_putc(output->buffer + output->pos, '-');
1,250✔
3362
        sixel_advance(output, 1);
1,250✔
3363
        buf += (width * 6);
1,250✔
3364
        }
500✔
3365

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

3373
            buf_p = buf;
550✔
3374
            for (x = 0; x < width; x++, buf_p++) {
35,904!
3375
                int pix = ((buf_p[0] >> plane) & 0x1);
35,200✔
3376

3377
                switch(cur_h - height) {
35,200!
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);
35,200✔
3383
                    /* Fall through */
3384
                case 3:
21,120✔
3385
                    pix |= (((buf_p[width * 2] >> plane) << 2) & 0x4);
35,200✔
3386
                    /* Fall through */
3387
                case 4:
21,120✔
3388
                    pix |= (((buf_p[width] >> plane) << 1) & 0x2);
35,200✔
3389
                    /* Fall through */
3390
                default:
11,264✔
3391
                    break;
35,200✔
3392
                }
3393

3394
                sixel_put_pixel(output, pix);
35,200✔
3395
            }
14,080✔
3396
            status = sixel_put_flash(output);
550✔
3397
            if (SIXEL_FAILED(status)) {
550!
3398
                return status;
3399
            }
3400

3401
            sixel_putc(output->buffer + output->pos, '$');
550✔
3402
            sixel_advance(output, 1);
550✔
3403
        }
220✔
3404
    }
50✔
3405

3406
    return 0;
90✔
3407
}
50✔
3408

3409

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

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

3453
#if SIXEL_ENABLE_THREADS
3454
    serial_logger = NULL;
50,755✔
3455
#endif  /* SIXEL_ENABLE_THREADS */
3456
    palette_source_colorspace = SIXEL_COLORSPACE_GAMMA;
58,971✔
3457
    palette_float_pixelformat =
42,412✔
3458
        sixel_palette_float_pixelformat_for_colorspace(
58,971✔
3459
            palette_source_colorspace);
27,877✔
3460
    palette_float_depth =
42,412✔
3461
        sixel_helper_compute_depth(palette_float_pixelformat);
58,971✔
3462
    output_float_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
58,971✔
3463
    memset(&palette_view, 0, sizeof(palette_view));
58,971!
3464
    memset(&palette_float_view, 0, sizeof(palette_float_view));
58,971✔
3465
    palette_obj = dither->palette;
58,971✔
3466
    if (palette_obj == NULL || palette_obj->vtbl == NULL ||
84,760!
3467
            palette_obj->vtbl->get_entries == NULL ||
58,971!
3468
            palette_obj->vtbl->get_entries_float32 == NULL) {
58,971!
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;
58,971✔
3476
#if SIXEL_ENABLE_THREADS
3477
    #endif
3478
    dither_parallel.enabled = 0;
58,971✔
3479
    dither_parallel.band_height = 0;
58,971✔
3480
    dither_parallel.overlap = 0;
58,971✔
3481
    dither_parallel.dither_threads = 0;
58,971✔
3482
    dither_parallel.encode_threads = 0;
58,971✔
3483
    ormode_enabled = output != NULL && output->ormode != 0;
58,971!
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 =
59,244✔
3489
        dither->pipeline_pin_threads != 0 ? 1 : 0;
59,244✔
3490
    dither_parallel.pin_threads = dither->pipeline_pin_threads;
59,244✔
3491
    switch (dither->pixelformat) {
59,244!
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);
20✔
3499
        paletted_pixels = (sixel_index_t *)sixel_allocator_malloc(dither->allocator, bufsize);
20✔
3500
        if (paletted_pixels == NULL) {
20!
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,
40✔
3507
                                                    &dither->pixelformat,
20✔
3508
                                                    pixels,
20✔
3509
                                                    dither->pixelformat,
20✔
3510
                                                    width, height);
20✔
3511
        if (SIXEL_FAILED(status)) {
20!
3512
            goto end;
×
3513
        }
3514
        input_pixels = paletted_pixels;
20✔
3515
        break;
20✔
3516
    case SIXEL_PIXELFORMAT_PAL8:
1,531✔
3517
    case SIXEL_PIXELFORMAT_G8:
3518
    case SIXEL_PIXELFORMAT_GA88:
3519
    case SIXEL_PIXELFORMAT_AG88:
3520
        input_pixels = pixels;
4,740✔
3521
        break;
4,740✔
3522
    default:
28,218✔
3523
        /* apply palette */
3524
        pipeline_threads = sixel_threads_resolve();
52,866✔
3525
        band_env_text = sixel_compat_getenv(
52,866✔
3526
            "SIXEL_DITHER_PARALLEL_BAND_WIDTH");
3527
        if (pipeline_threads <= 1 && band_env_text != NULL
52,866!
3528
                && band_env_text[0] != '\0') {
1,981!
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;
52,861✔
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
73,801✔
3548
                && pipeline_threads > 1
52,821✔
3549
                && pipeline_nbands > 1) {
43,190!
3550
            pipeline_active = 1;
30,497✔
3551
            input_pixels = NULL;
30,497✔
3552
        } else {
21,104✔
3553
            paletted_pixels = sixel_dither_apply_palette(dither, pixels,
18,031✔
3554
                                                         width, height);
3,544✔
3555
            if (paletted_pixels == NULL) {
14,487!
3556
                status = SIXEL_RUNTIME_ERROR;
×
3557
                goto end;
×
3558
            }
3559
            input_pixels = paletted_pixels;
9,179✔
3560
        }
3561
        break;
39,676✔
3562
    }
3563

3564
    if (pipeline_active) {
52,318✔
3565
        sixel_parallel_dither_configure(height,
59,483✔
3566
                                        dither->ncolors,
21,104✔
3567
                                        pipeline_threads,
21,104✔
3568
                                        dither->pipeline_pin_threads,
21,104✔
3569
                                        &dither_parallel);
3570
        if (dither_parallel.enabled) {
38,379!
3571
            dither->pipeline_parallel_active = 1;
38,379✔
3572
            dither->pipeline_band_height = dither_parallel.band_height;
38,379✔
3573
            dither->pipeline_band_overlap = dither_parallel.overlap;
38,379✔
3574
            dither->pipeline_dither_threads =
38,379✔
3575
                dither_parallel.dither_threads;
38,379✔
3576
            pipeline_threads = dither_parallel.encode_threads;
38,379✔
3577
        }
21,104✔
3578
        if (pipeline_threads <= 1) {
40,644✔
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;
194✔
3586
            dither->pipeline_parallel_active = 0;
194✔
3587
            if (paletted_pixels == NULL) {
194!
3588
                paletted_pixels = sixel_dither_apply_palette(dither, pixels,
289✔
3589
                                                             width, height);
95✔
3590
                if (paletted_pixels == NULL) {
194!
3591
                    status = SIXEL_RUNTIME_ERROR;
×
3592
                    goto end;
×
3593
                }
3594
            }
95✔
3595
            input_pixels = paletted_pixels;
149✔
3596
        }
95✔
3597
    }
21,104✔
3598

3599
#if SIXEL_ENABLE_THREADS
3600
    if (!pipeline_active) {
49,188!
3601
        logger = dither->pipeline_logger;
12,570✔
3602
        if (logger == NULL) {
12,570!
3603
            sixel_timeline_logger_prepare_default(dither->allocator,
12,570✔
3604
                                                  &serial_logger);
3605
            if (serial_logger != NULL) {
12,570!
3606
                logger_owned = 1;
16✔
3607
                dither->pipeline_logger = serial_logger;
16✔
3608
                logger = serial_logger;
16✔
3609
            } else {
16✔
3610
                logger = NULL;
9,964✔
3611
            }
3612
        }
6,868✔
3613
        if (logger != NULL) {
10,606!
3614
            sixel_timeline_logger_logf(logger,
32✔
3615
                              "controller",
3616
                              "pipeline",
3617
                              "configure",
3618
                              -1,
3619
                              -1,
3620
                              0,
3621
                              height,
16✔
3622
                              0,
3623
                              height,
16✔
3624
                              "serial path threads=1");
3625
        }
16✔
3626
    }
6,868✔
3627
#endif
3628

3629
    if (output != NULL) {
58,971!
3630
        palette_source_colorspace = output->source_colorspace;
58,971✔
3631
        palette_float_pixelformat =
42,412✔
3632
            sixel_palette_float_pixelformat_for_colorspace(
58,971✔
3633
                palette_source_colorspace);
27,877✔
3634
        palette_float_depth =
42,412✔
3635
            sixel_helper_compute_depth(palette_float_pixelformat);
58,971✔
3636
    }
27,877✔
3637

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

3659
    status = palette_obj->vtbl->get_entries_float32(palette_obj,
58,971✔
3660
                                                    &palette_float_view);
3661
    if (SIXEL_FAILED(status)) {
58,971!
3662
        goto end;
×
3663
    }
3664
    if (palette_float_view.entries != NULL &&
60,128!
3665
            palette_float_view.entry_count == palette_count &&
4,857!
3666
            palette_float_view.depth == palette_float_depth) {
4,857!
3667
        palette_float_count = palette_float_view.entry_count;
4,857✔
3668
        palette_float_bytes =
4,857✔
3669
            palette_float_count * (size_t)palette_float_view.depth;
4,857✔
3670
        palette_entries_float32 = (float *)sixel_allocator_malloc(
4,857✔
3671
            dither->allocator,
1,274✔
3672
            palette_float_bytes);
1,274✔
3673
        if (palette_entries_float32 == NULL) {
4,857!
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,974✔
3680
               palette_float_view.entries,
3,700✔
3681
               palette_float_bytes);
117✔
3682
    }
1,274✔
3683
    if (palette_entries != NULL && palette_entries_float32 != NULL
60,128!
3684
            && palette_count == palette_float_count
31,460!
3685
            && palette_count > 0U
4,857!
3686
            && !sixel_palette_float32_matches_u8(
4,857!
3687
                    palette_entries,
1,274✔
3688
                    palette_entries_float32,
1,274✔
3689
                    palette_count,
1,274✔
3690
                    palette_float_pixelformat)) {
1,274✔
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
84,760!
3697
            && output != NULL
44,436!
3698
            && output->source_colorspace != output->colorspace) {
58,971!
3699
        palette_bytes = palette_count * 3U;
1,775✔
3700
        if (palette_entries_float32 != NULL
1,775!
3701
                && palette_float_count == palette_count) {
1,365!
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);
343✔
3710
            status = sixel_helper_convert_colorspace(
343✔
3711
                (unsigned char *)palette_entries_float32,
30✔
3712
                palette_float_bytes,
30✔
3713
                palette_float_pixelformat,
30✔
3714
                output->source_colorspace,
30✔
3715
                output->colorspace);
30✔
3716
            if (SIXEL_FAILED(status)) {
343!
3717
                sixel_helper_set_additional_message(
×
3718
                    "sixel_encode_dither: float palette colorspace conversion failed.");
3719
                goto end;
×
3720
            }
3721
            output_float_pixelformat =
185✔
3722
                sixel_palette_float_pixelformat_for_colorspace(
343!
3723
                    output->colorspace);
30✔
3724
            palette_channels = palette_count * 3U;
343✔
3725
            for (palette_index = 0U; palette_index < palette_channels;
206,842!
3726
                    ++palette_index) {
206,499✔
3727
                int channel;
103,263✔
3728

3729
                channel = (int)(palette_index % 3U);
206,499✔
3730
                palette_entries[palette_index] =
206,499✔
3731
                    sixel_pixelformat_float_channel_to_byte(
206,499✔
3732
                        output_float_pixelformat,
270✔
3733
                        channel,
270✔
3734
                        palette_entries_float32[palette_index]);
206,499✔
3735
            }
270✔
3736
        } else {
30✔
3737
            status = sixel_helper_convert_colorspace(palette_entries,
2,112✔
3738
                                                     palette_bytes,
680✔
3739
                                                     SIXEL_PIXELFORMAT_RGB888,
3740
                                                     output->source_colorspace,
680✔
3741
                                                     output->colorspace);
680✔
3742
            if (SIXEL_FAILED(status)) {
1,432!
3743
                sixel_helper_set_additional_message(
×
3744
                    "sixel_encode_dither: palette colorspace "
3745
                    "conversion failed.");
3746
                goto end;
×
3747
            }
3748
        }
3749
    }
710✔
3750
    if (SIXEL_FAILED(status) || palette_entries == NULL) {
44,436!
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);
58,971✔
3757
    if (SIXEL_FAILED(status)) {
58,971!
3758
        goto end;
×
3759
    }
3760

3761
    if (output->ormode) {
58,971✔
3762
        status = sixel_encode_body_ormode(input_pixels,
175✔
3763
                                          width,
50✔
3764
                                          height,
50✔
3765
                                          palette_entries,
50✔
3766
                                          dither->ncolors,
50✔
3767
                                          dither->keycolor,
50✔
3768
                                          output);
50✔
3769
    } else if (pipeline_active) {
58,896!
3770
        status = sixel_encode_body_pipeline(pixels,
59,194✔
3771
                                            width,
21,009✔
3772
                                            height,
21,009✔
3773
                                            palette_entries,
21,009✔
3774
                                            palette_entries_float32,
21,009✔
3775
                                            dither,
21,009✔
3776
                                            output,
21,009✔
3777
                                            pipeline_threads);
21,009✔
3778
    } else {
21,009✔
3779
        status = sixel_encode_body(input_pixels,
27,479✔
3780
                                   width,
6,818✔
3781
                                   height,
6,818✔
3782
                                   palette_entries,
6,818✔
3783
                                   palette_entries_float32,
6,818✔
3784
                                   dither->ncolors,
6,818✔
3785
                                   dither->keycolor,
6,818✔
3786
                                   dither->bodyonly,
6,818✔
3787
                                   output,
6,818✔
3788
                                   NULL,
3789
                                   dither->allocator,
6,818✔
3790
                                   dither->pipeline_pin_threads,
6,818✔
3791
                                   logger != NULL ?
6,818✔
3792
                                       logger :
16✔
3793
                                       NULL);
3794
    }
3795

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

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

3805
end:
31,094✔
3806
#if SIXEL_ENABLE_THREADS
3807
    if (logger_owned) {
50,755!
3808
        dither->pipeline_logger = NULL;
16✔
3809
        sixel_timeline_logger_unref(serial_logger);
16✔
3810
    }
16✔
3811
#endif
3812
    if (palette_entries != NULL) {
58,971!
3813
        sixel_allocator_free(dither->allocator, palette_entries);
58,971✔
3814
    }
27,877✔
3815
    if (palette_entries_float32 != NULL) {
61,745✔
3816
        sixel_allocator_free(dither->allocator, palette_entries_float32);
4,857✔
3817
    }
1,274✔
3818
    sixel_allocator_free(dither->allocator, paletted_pixels);
58,971✔
3819

3820
    return status;
58,971✔
3821
}
27,877✔
3822

3823
SIXEL_INTERNAL_API SIXELSTATUS
3824
sixel_encoder_core_encode_dispatch(
59,579✔
3825
    sixel_encoder_core_encode_request_t const *request)
3826
{
3827
    SIXELSTATUS status;
32,436✔
3828

3829
    if (request == NULL || request->pixels == NULL ||
85,556!
3830
        request->dither == NULL || request->output == NULL) {
59,479!
3831
        return SIXEL_BAD_ARGUMENT;
72✔
3832
    }
3833
    if (request->width < 1 || request->height < 1) {
59,479!
3834
        return SIXEL_BAD_INPUT;
3835
    }
3836

3837
    (void)request->depth;
42,760✔
3838
    if (request->dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
59,479✔
3839
        status = sixel_encode_highcolor(request->pixels,
716✔
3840
                                        request->width,
368✔
3841
                                        request->height,
368✔
3842
                                        request->dither,
368✔
3843
                                        request->output);
368✔
3844
    } else {
208✔
3845
        status = sixel_encode_dither(request->pixels,
86,848✔
3846
                                     request->width,
44,436✔
3847
                                     request->height,
44,436✔
3848
                                     request->dither,
44,436✔
3849
                                     request->output);
44,436✔
3850
    }
3851

3852
    return status;
44,804✔
3853
}
28,125✔
3854

3855
SIXELAPI SIXELSTATUS
3856
sixel_encode(
59,454✔
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;
59,454✔
3865
    sixel_encoder_core_t *core;
32,371✔
3866
    sixel_encoder_core_encode_request_t request;
32,371✔
3867

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

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

3923
end:
31,379✔
3924
    sixel_output_unref(output);
59,454✔
3925
    sixel_dither_unref(dither);
59,454✔
3926

3927
    return status;
59,454✔
3928
}
28,075✔
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