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

saitoha / libsixel / 25410478234

06 May 2026 12:39AM UTC coverage: 85.548% (-0.02%) from 85.564%
25410478234

push

github

saitoha
fix: avoid tty fd unused in no-isatty builds

125661 of 267416 branches covered (46.99%)

151048 of 176566 relevant lines covered (85.55%)

8930818.64 hits per line

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

94.57
/src/encoder-core-encode.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2025 libsixel developers. See `AUTHORS`.
5
 * Copyright (c) 2014-2016 Hayaki Saito
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
8
 * this software and associated documentation files (the "Software"), to deal in
9
 * the Software without restriction, including without limitation the rights to
10
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11
 * the Software, and to permit persons to whom the Software is furnished to do so,
12
 * subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in all
15
 * copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
 */
24

25
/*
26
 * this file is derived from "sixel" original version (2014-3-2)
27
 * http://nanno.dip.jp/softlib/man/rlogin/sixel.tar.gz
28
 *
29
 * Initial developer of this file is kmiya@culti.
30
 *
31
 * He distributes it under very permissive license which permits
32
 * using, copying, modification, redistribution, and all other
33
 * public activities without any restrictions.
34
 *
35
 * He declares this is compatible with MIT/BSD/GPL.
36
 *
37
 * Hayaki Saito (saitoha@me.com) modified this and re-licensed
38
 * it under the MIT license.
39
 *
40
 * Araki Ken added high-color encoding mode(sixel_encode_highcolor)
41
 * extension.
42
 *
43
 */
44
#if defined(HAVE_CONFIG_H)
45
#include "config.h"
46
#endif
47

48
/* STDC_HEADERS */
49
#include <stdio.h>
50
#include <stdlib.h>
51
#include <errno.h>
52

53
#if HAVE_STRING_H
54
# include <string.h>
55
#endif  /* HAVE_STRING_H */
56
#if HAVE_LIMITS_H
57
# include <limits.h>
58
#endif  /* HAVE_LIMITS_H */
59
#if HAVE_INTTYPES_H
60
# include <inttypes.h>
61
#endif  /* HAVE_INTTYPES_H */
62

63
#include <sixel.h>
64
#include "compat_stub.h"
65
#include "encoder-core-private.h"
66
#include "output-factory.h"
67
#include "dither.h"
68
#include "pixelformat.h"
69
#include "timeline-logger.h"
70
#include "threading.h"
71
#include "encoder-core-highcolor.h"
72
#if SIXEL_ENABLE_THREADS
73
# include "sixel_atomic.h"
74
# include <6cells.h>
75
#endif
76

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

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

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

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

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

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

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

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

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

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

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

214
static void sixel_encode_work_init(sixel_encode_work_t *work);
215
static SIXELSTATUS sixel_encode_work_allocate(sixel_encode_work_t *work,
216
                                              int width,
217
                                              int ncolors,
218
                                              sixel_allocator_t *allocator);
219
static void sixel_encode_work_cleanup(sixel_encode_work_t *work,
220
                                      sixel_allocator_t *allocator);
221
static void sixel_band_state_reset(sixel_band_state_t *state);
222
static void sixel_band_finish(sixel_encode_work_t *work,
223
                              sixel_band_state_t *state);
224
static void sixel_band_clear_map(sixel_encode_work_t *work);
225
static SIXELSTATUS sixel_band_classify_row(sixel_encode_work_t *work,
226
                                           sixel_band_state_t *state,
227
                                           sixel_index_t *pixels,
228
                                           int width,
229
                                           int absolute_row,
230
                                           int ncolors,
231
                                           int keycolor,
232
                                           unsigned char *palstate,
233
                                           int encode_policy);
234
static SIXELSTATUS sixel_band_compose(sixel_encode_work_t *work,
235
                                      sixel_band_state_t *state,
236
                                      sixel_output_t *output,
237
                                      int width,
238
                                      int ncolors,
239
                                      int keycolor,
240
                                      sixel_allocator_t *allocator);
241
static SIXELSTATUS sixel_band_emit(sixel_encode_work_t *work,
242
                                   sixel_band_state_t *state,
243
                                   sixel_output_t *output,
244
                                   int ncolors,
245
                                   int keycolor,
246
                                   int last_row_index);
247
static SIXELSTATUS sixel_put_flash(sixel_output_t *const output);
248
static void sixel_advance(sixel_output_t *output, int nwrite);
249

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

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

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

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

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

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

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

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

324
    encode_threads = pipeline_threads - dither_threads;
34,220✔
325
    if (encode_threads < 2 && pipeline_threads > 2) {
34,220!
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;
33,941✔
332
        dither_threads = pipeline_threads - encode_threads;
33,941✔
333
    }
17,682✔
334
    if (encode_threads < 1) {
36,384✔
335
        encode_threads = 1;
184✔
336
        dither_threads = pipeline_threads - encode_threads;
184✔
337
    }
85✔
338
    if (dither_threads < 1) {
28,239!
339
        return;
340
    }
341

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

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

397
    config->enabled = 1;
34,225✔
398
    config->band_height = band_height;
34,225✔
399
    config->overlap = overlap;
34,225✔
400
    config->dither_threads = dither_threads;
34,225✔
401
    config->encode_threads = encode_threads;
34,225✔
402
}
17,812!
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)
38,912✔
440
{
441
    memset(ctx, 0, sizeof(*ctx));
38,912✔
442
    ctx->pixels = NULL;
38,912✔
443
    ctx->keycolor = (-1);
38,912✔
444
    ctx->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
38,912✔
445
    ctx->writer_error = SIXEL_OK;
38,912✔
446
}
32,152✔
447

448
static void
449
sixel_parallel_worker_release_nodes(sixel_parallel_worker_state_t *state,
306,697✔
450
                                    sixel_allocator_t *allocator)
451
{
452
    sixel_node_t *np;
158,577✔
453

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

458
    while ((np = state->output->node_free) != NULL) {
14,066,369!
459
        state->output->node_free = np->next;
13,759,670✔
460
        sixel_allocator_free(allocator, np);
13,759,670✔
461
    }
462
    state->output->node_top = NULL;
306,699✔
463
}
156,270!
464

465
static void
466
sixel_parallel_worker_cleanup(sixel_parallel_worker_state_t *state,
112,337✔
467
                              sixel_allocator_t *allocator)
468
{
469
    if (state == NULL) {
112,337✔
470
        return;
34,636✔
471
    }
472
    sixel_parallel_worker_release_nodes(state, allocator);
68,687✔
473
    if (state->output != NULL) {
68,687!
474
        sixel_output_unref(state->output);
68,687✔
475
        state->output = NULL;
68,687✔
476
    }
31,376✔
477
    sixel_encode_work_cleanup(&state->work, allocator);
68,687✔
478
    sixel_band_state_reset(&state->band);
68,687✔
479
    state->initialized = 0;
68,687✔
480
    state->index = 0;
68,687✔
481
    state->writer_error = SIXEL_OK;
68,687✔
482
    state->band_buffer = NULL;
68,687✔
483
    state->context = NULL;
68,687✔
484
}
56,151✔
485

486
static void
487
sixel_parallel_context_cleanup(sixel_parallel_context_t *ctx)
38,912✔
488
{
489
    int i;
20,325✔
490

491
    if (ctx->workers != NULL) {
38,912!
492
        for (i = 0; i < ctx->worker_capacity; i++) {
151,249!
493
            sixel_parallel_worker_cleanup(ctx->workers[i], ctx->allocator);
112,337✔
494
        }
56,151✔
495
        free(ctx->workers);
38,912✔
496
        ctx->workers = NULL;
38,912✔
497
    }
20,467✔
498
    sixel_parallel_writer_stop(ctx, 1);
38,912✔
499
    if (ctx->bands != NULL) {
38,912!
500
        if (ctx->band_count < 0) {
38,912!
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++) {
276,963!
508
            free(ctx->bands[i].data);
238,051✔
509
            ctx->bands[i].data = NULL;
238,051✔
510
        }
124,932✔
511
#if defined(_MSC_VER)
512
#pragma warning(pop)
513
#endif
514
        free(ctx->bands);
38,912✔
515
        ctx->bands = NULL;
38,912✔
516
    }
20,467✔
517
    ctx->band_count = 0;
38,912✔
518
    if (ctx->pool != NULL) {
38,912!
519
        ctx->pool->vtbl->unref(ctx->pool);
38,912✔
520
        ctx->pool = NULL;
38,912✔
521
    }
20,467✔
522
    if (ctx->cond_ready) {
38,912!
523
        sixel_cond_destroy(&ctx->cond_band_ready);
38,912✔
524
        ctx->cond_ready = 0;
38,912✔
525
    }
20,467✔
526
    if (ctx->mutex_ready) {
38,912!
527
        sixel_mutex_destroy(&ctx->mutex);
38,912✔
528
        ctx->mutex_ready = 0;
38,912✔
529
    }
20,467✔
530
}
38,912✔
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)
238,034✔
569
{
570
    int accept;
124,368✔
571

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

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

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

607
static SIXELSTATUS
608
sixel_parallel_worker_prepare(sixel_parallel_worker_state_t *state,
238,044✔
609
                              sixel_parallel_context_t *ctx)
610
{
611
    SIXELSTATUS status;
124,373✔
612

613
    if (state->initialized) {
238,044✔
614
        return SIXEL_OK;
134,302✔
615
    }
616

617
    sixel_encode_work_init(&state->work);
68,680✔
618
    sixel_band_state_reset(&state->band);
68,682✔
619
    state->writer_error = SIXEL_OK;
68,682✔
620
    state->band_buffer = NULL;
68,682✔
621
    state->context = ctx;
68,682✔
622

623
    status = sixel_encode_work_allocate(&state->work,
116,692✔
624
                                        ctx->width,
31,375✔
625
                                        ctx->ncolors,
31,375✔
626
                                        ctx->allocator);
31,375✔
627
    if (SIXEL_FAILED(status)) {
68,686!
628
        return status;
629
    }
630

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

655
    state->initialized = 1;
68,671✔
656
    state->index = (-1);
68,671✔
657

658
    if (ctx->mutex_ready) {
68,671!
659
        sixel_mutex_lock(&ctx->mutex);
68,655✔
660
    }
31,360✔
661
    if (ctx->worker_registered < ctx->worker_capacity) {
68,691!
662
        state->index = ctx->worker_registered;
68,687✔
663
        ctx->workers[state->index] = state;
68,687✔
664
        ctx->worker_registered += 1;
68,687✔
665
    }
31,376✔
666
    if (ctx->mutex_ready) {
68,691!
667
        sixel_mutex_unlock(&ctx->mutex);
68,687✔
668
    }
31,376✔
669

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

675
    return SIXEL_OK;
52,052✔
676
}
124,932✔
677

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

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

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

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

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

715
    return SIXEL_OK;
19,158✔
716
}
17,727✔
717

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

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

741
    required = band->used + (size_t)size;
237,145✔
742
    if (required < band->used) {
237,145!
743
        state->writer_error = SIXEL_BAD_INTEGER_OVERFLOW;
744
        return size;
745
    }
746
    capacity = band->size;
237,151✔
747
    if (required > capacity) {
237,151!
748
        if (capacity == 0) {
237,153!
749
            new_capacity = (size_t)SIXEL_OUTPUT_PACKET_SIZE;
185,636✔
750
        } else {
124,471✔
751
            new_capacity = capacity;
26✔
752
        }
753
        while (new_capacity < required) {
237,153!
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);
237,110✔
761
        if (tmp == NULL) {
237,110!
762
            state->writer_error = SIXEL_BAD_ALLOCATION;
763
            return size;
764
        }
765
        band->data = tmp;
237,110✔
766
        band->size = new_capacity;
237,110✔
767
    }
124,449✔
768

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

772
    return size;
237,107✔
773
}
124,448✔
774

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

788
    if (pool != NULL) {
38,912!
789
        *pool = NULL;
38,912✔
790
    }
20,467✔
791
    if (pool == NULL) {
38,912!
792
        return SIXEL_BAD_ARGUMENT;
793
    }
794

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

823
    return status;
30,501✔
824
}
20,467✔
825

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

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

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

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

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

895
    ctx->bands = (sixel_parallel_band_buffer_t *)calloc((size_t)nbands,
38,912✔
896
                                                        sizeof(*ctx->bands));
897
    if (ctx->bands == NULL) {
38,912!
898
        return SIXEL_BAD_ALLOCATION;
899
    }
900
    for (i = 0; i < nbands; ++i) {
276,963!
901
        ctx->bands[i].data = NULL;
238,051✔
902
        ctx->bands[i].size = 0;
238,051✔
903
        ctx->bands[i].used = 0;
238,051✔
904
        ctx->bands[i].status = SIXEL_OK;
238,051✔
905
        ctx->bands[i].ready = 0;
238,051✔
906
        ctx->bands[i].dispatched = 0;
238,051✔
907
    }
124,932✔
908
    ctx->band_count = nbands;
38,912✔
909

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

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

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

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

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

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

958
    status = sixel_thread_create(&ctx->writer_thread,
59,379✔
959
                                 sixel_parallel_writer_main,
960
                                 ctx);
20,467✔
961
    if (SIXEL_FAILED(status)) {
38,912!
962
        sixel_mutex_unlock(&ctx->mutex);
963
        return status;
964
    }
965
    ctx->writer_started = 1;
38,912✔
966
    sixel_mutex_unlock(&ctx->mutex);
38,912✔
967

968
    return SIXEL_OK;
38,912✔
969
}
20,467✔
970

971
static void
972
sixel_parallel_submit_band(sixel_parallel_context_t *ctx, int band_index)
238,047✔
973
{
974
    sixel_thread_pool_job_t job;
124,373✔
975
    SIXELSTATUS status;
124,373✔
976
    int dispatch;
124,373✔
977

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

985
    dispatch = 0;
238,050✔
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) {
238,050!
992
        sixel_mutex_lock(&ctx->mutex);
238,050✔
993
        if (!ctx->bands[band_index].dispatched
352,547!
994
                && !ctx->writer_should_stop
228,014!
995
                && ctx->writer_error == SIXEL_OK) {
238,051!
996
            ctx->bands[band_index].dispatched = 1;
238,051✔
997
            dispatch = 1;
238,051✔
998
        }
124,932✔
999
        sixel_mutex_unlock(&ctx->mutex);
238,052✔
1000
    } else {
124,933✔
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) {
238,050!
1009
        return;
1010
    }
1011

1012
    sixel_fence_release();
238,050✔
1013
    if (ctx->logger != NULL) {
238,050✔
1014
        sixel_timeline_logger_logf(ctx->logger,
1,178✔
1015
                          "controller",
1016
                          "encode",
1017
                          "dispatch",
1018
                          band_index);
369✔
1019
    }
369✔
1020
    job.band_index = band_index;
223,039✔
1021
    status = ctx->pool->vtbl->push(ctx->pool, job);
223,039✔
1022
    if (SIXEL_FAILED(status)) {
223,039!
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
}
124,931!
1032

1033
static SIXELSTATUS
1034
sixel_parallel_context_wait(sixel_parallel_context_t *ctx, int force_abort)
38,912✔
1035
{
1036
    int pool_error;
20,325✔
1037

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

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

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

1053
    return SIXEL_OK;
30,501✔
1054
}
20,467✔
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,157,770✔
1063
{
1064
    sixel_parallel_row_notifier_t *notifier;
605,290✔
1065
    sixel_parallel_context_t *ctx;
605,290✔
1066
    sixel_timeline_logger_t *logger;
605,290✔
1067
    int band_height;
605,290✔
1068
    int band_index;
605,290✔
1069

1070
    notifier = (sixel_parallel_row_notifier_t *)priv;
1,157,770✔
1071
    if (notifier == NULL) {
1,157,770✔
1072
        return;
1073
    }
1074
    ctx = notifier->context;
1,157,770✔
1075
    logger = notifier->logger;
1,157,770✔
1076
    if (ctx == NULL || ctx->band_count <= 0 || ctx->height <= 0) {
1,157,770!
1077
        return;
8✔
1078
    }
1079
    if (row_index < 0) {
1,157,775!
1080
        return;
1081
    }
1082
    band_height = notifier->band_height;
1,157,775✔
1083
    if (band_height < 1) {
1,157,775!
1084
        band_height = 6;
1085
    }
1086
    if ((row_index % band_height) != band_height - 1
1,146,461✔
1087
            && row_index != ctx->height - 1) {
1,063,010!
1088
        return;
745,471✔
1089
    }
1090

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

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

1107
    sixel_parallel_submit_band(ctx, band_index);
205,542✔
1108
}
608,634!
1109

1110
static SIXELSTATUS
1111
sixel_parallel_flush_band(sixel_parallel_context_t *ctx, int band_index)
238,051✔
1112
{
1113
    sixel_parallel_band_buffer_t *band;
124,374✔
1114
    size_t offset;
124,374✔
1115
    size_t chunk;
124,374✔
1116

1117
    band = &ctx->bands[band_index];
238,051✔
1118
    if (ctx->logger != NULL) {
238,051✔
1119
        sixel_timeline_logger_logf(ctx->logger,
1,178✔
1120
                          "worker",
1121
                          "encode",
1122
                          "writer_flush",
1123
                          band_index);
369✔
1124
    }
369✔
1125
    offset = 0;
186,354✔
1126
    while (offset < band->used) {
478,985!
1127
        chunk = band->used - offset;
240,934✔
1128
        if (chunk > (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos)) {
240,934✔
1129
            chunk = (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos);
2,900✔
1130
        }
1,880✔
1131
        memcpy(ctx->output->buffer + ctx->output->pos,
251,500✔
1132
               band->data + offset,
125,104✔
1133
               chunk);
10,566✔
1134
        sixel_advance(ctx->output, (int)chunk);
240,934✔
1135
        offset += chunk;
240,934✔
1136
    }
1137
    return SIXEL_OK;
238,051✔
1138
}
72,677✔
1139

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

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

1159
    if (ctx == NULL || state == NULL) {
238,046!
1160
        return SIXEL_BAD_ARGUMENT;
12✔
1161
    }
1162

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

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

1188
    sixel_fence_acquire();
238,001✔
1189

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

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

1215
    band_start = band_index * 6;
237,970✔
1216
    band_height = ctx->height - band_start;
237,970✔
1217
    if (band_height > 6) {
237,970✔
1218
        band_height = 6;
155,839✔
1219
    }
104,457✔
1220
    if (band_height <= 0) {
194,701!
1221
        goto cleanup;
1222
    }
1223

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

1232
    for (row_index = 0; row_index < band_height; row_index++) {
1,580,709!
1233
        absolute_row = band_start + row_index;
1,342,734✔
1234
        status = sixel_band_classify_row(&state->work,
2,044,846✔
1235
                                         &state->band,
702,112✔
1236
                                         ctx->pixels,
702,112✔
1237
                                         ctx->width,
702,112✔
1238
                                         absolute_row,
702,112✔
1239
                                         ctx->ncolors,
702,112✔
1240
                                         ctx->keycolor,
702,112✔
1241
                                         ctx->palstate,
702,112✔
1242
                                         ctx->encode_policy);
702,112✔
1243
        if (SIXEL_FAILED(status)) {
1,342,743!
1244
            goto cleanup;
1245
        }
1246
    }
702,104✔
1247

1248
    status = sixel_band_compose(&state->work,
362,869✔
1249
                                &state->band,
124,883✔
1250
                                state->output,
124,883✔
1251
                                ctx->width,
124,883✔
1252
                                ctx->ncolors,
124,883✔
1253
                                ctx->keycolor,
124,883✔
1254
                                ctx->allocator);
124,883✔
1255
    if (SIXEL_FAILED(status)) {
237,995!
1256
        goto cleanup;
1257
    }
1258

1259
    last_row_index = band_start + band_height - 1;
237,987✔
1260
    status = sixel_band_emit(&state->work,
362,862✔
1261
                             &state->band,
124,875✔
1262
                             state->output,
124,875✔
1263
                             ctx->ncolors,
124,875✔
1264
                             ctx->keycolor,
124,875✔
1265
                             last_row_index);
124,875✔
1266
    if (SIXEL_FAILED(status)) {
237,973!
1267
        goto cleanup;
1268
    }
1269

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

1275
    if (state->output->pos > 0) {
237,975✔
1276
        state->writer_error = sixel_output_write_bytes(
349,792✔
1277
            state->output,
124,473✔
1278
            (char *)state->output->buffer,
237,128✔
1279
            state->output->pos);
185,640✔
1280
        state->output->pos = 0;
237,137✔
1281
    }
124,473✔
1282
    if (state->writer_error != SIXEL_OK) {
237,992!
1283
        status = state->writer_error;
1284
        goto cleanup;
1285
    }
1286

1287
    sixel_band_finish(&state->work, &state->band);
237,992✔
1288
    status = SIXEL_OK;
237,993✔
1289

1290
cleanup:
113,117✔
1291
    sixel_parallel_worker_release_nodes(state, ctx->allocator);
238,043✔
1292
    if (band != NULL && ctx->mutex_ready && ctx->cond_ready) {
238,044!
1293
        sixel_fence_release();
238,015✔
1294
        sixel_mutex_lock(&ctx->mutex);
238,015✔
1295
        band->status = status;
238,018✔
1296
        band->ready = 1;
238,018✔
1297
        sixel_cond_broadcast(&ctx->cond_band_ready);
238,018✔
1298
        sixel_mutex_unlock(&ctx->mutex);
238,018✔
1299
    }
124,899✔
1300
    if (ctx->logger != NULL) {
253,101✔
1301
        sixel_timeline_logger_logf(ctx->logger,
1,178✔
1302
                          "worker",
1303
                          "encode",
1304
                          "worker_done",
1305
                          band_index);
369✔
1306
    }
369✔
1307
    if (SIXEL_FAILED(status)) {
238,045!
1308
        return status;
1309
    }
1310
    return SIXEL_OK;
186,347✔
1311
}
124,932✔
1312

1313
static void
1314
sixel_parallel_writer_stop(sixel_parallel_context_t *ctx, int force_abort)
77,824✔
1315
{
1316
    int should_signal;
40,650✔
1317

1318
    if (ctx == NULL || !ctx->writer_started) {
77,824!
1319
        return;
30,501✔
1320
    }
1321

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

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

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

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

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

1367
    for (;;) {
205,507✔
1368
        sixel_mutex_lock(&ctx->mutex);
276,963✔
1369
        while (!ctx->writer_should_stop &&
613,943!
1370
               ctx->next_band_to_flush < ctx->band_count) {
414,511!
1371
    band_index = ctx->next_band_to_flush;
375,599✔
1372
    band = &ctx->bands[band_index];
375,599✔
1373
    if (band->ready) {
375,599✔
1374
        break;
186,354✔
1375
    }
1376
            sixel_cond_wait(&ctx->cond_band_ready, &ctx->mutex);
137,548✔
1377
        }
1378

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

1384
        if (ctx->next_band_to_flush >= ctx->band_count) {
276,963✔
1385
            sixel_mutex_unlock(&ctx->mutex);
38,912✔
1386
            break;
38,912✔
1387
        }
1388

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

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

1417
    return SIXEL_OK;
30,501✔
1418
}
20,467✔
1419

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

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

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

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

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

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

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

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

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

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

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

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

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

1586
    queue_depth = threads * 3;
34,041✔
1587
    if (queue_depth > nbands) {
34,041✔
1588
        queue_depth = nbands;
17,694✔
1589
    }
11,449✔
1590
    if (queue_depth < 1) {
34,588!
1591
        queue_depth = 1;
1592
    }
1593

1594
    dither_threads_budget = dither->pipeline_dither_threads;
31,854✔
1595
    worker_capacity = threads + dither_threads_budget;
31,854✔
1596
    if (worker_capacity < threads) {
31,854!
1597
        worker_capacity = threads;
1598
    }
1599
    if (worker_capacity > nbands) {
32,455✔
1600
        worker_capacity = nbands;
11,848✔
1601
    }
5,633✔
1602

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

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

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

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

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

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

1670
cleanup:
16,314✔
1671
    dither->pipeline_row_callback = NULL;
34,041✔
1672
    dither->pipeline_row_priv = NULL;
34,041✔
1673
    dither->pipeline_index_buffer = NULL;
34,041✔
1674
    dither->pipeline_index_size = 0;
34,041✔
1675
    dither->pipeline_image_width = 0;
34,041✔
1676
    dither->pipeline_image_height = 0;
34,041✔
1677
    dither->pipeline_transparent_mask = NULL;
34,041✔
1678
    dither->pipeline_transparent_mask_size = 0;
34,041✔
1679
    dither->pipeline_transparent_keycolor = (-1);
34,041✔
1680
    if (!waited && ctx.pool != NULL) {
34,041!
1681
        wait_status = sixel_parallel_context_wait(&ctx, status != SIXEL_OK);
1682
        if (status == SIXEL_OK) {
×
1683
            status = wait_status;
7,444✔
1684
        }
1685
    }
1686
    sixel_parallel_context_cleanup(&ctx);
31,867✔
1687
    if (owns_logger) {
31,867✔
1688
        sixel_timeline_logger_unref(logger);
243✔
1689
    }
111✔
1690
    if (indexes != NULL) {
34,041!
1691
        sixel_allocator_free(allocator, indexes);
34,041✔
1692
    }
17,727✔
1693
    return status;
34,041✔
1694
}
17,727✔
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)
158,814,303✔
1722
{
1723
    if ((output->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) {
158,814,303✔
1724
        (void)sixel_output_write_bytes(output,
6,904✔
1725
                                       (char *)output->buffer,
4,862✔
1726
                                       SIXEL_OUTPUT_PACKET_SIZE);
1727
        memcpy(output->buffer,
5,050✔
1728
               output->buffer + SIXEL_OUTPUT_PACKET_SIZE,
1,692✔
1729
               (size_t)(output->pos -= SIXEL_OUTPUT_PACKET_SIZE));
3,008✔
1730
    }
2,042✔
1731
}
158,819,891✔
1732

1733

1734
static void
1735
sixel_putc(unsigned char *buffer, unsigned char value)
46,631,016✔
1736
{
1737
    *buffer = value;
46,631,016✔
1738
}
34,623,407✔
1739

1740

1741
static void
1742
sixel_puts(unsigned char *buffer, char const *value, int size)
4,391,617✔
1743
{
1744
    memcpy(buffer, (void *)value, (size_t)size);
4,391,617✔
1745
}
3,560,473✔
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,
63,640,020✔
1754
                          unsigned char value,
1755
                          int count)
1756
{
1757
    int chunk;
33,142,132✔
1758

1759
    if (count <= 0) {
63,640,020!
1760
        return;
1761
    }
1762

1763
    while (count > 0) {
127,336,834!
1764
        chunk = SIXEL_OUTPUT_PACKET_SIZE - output->pos;
63,671,486✔
1765
        if (chunk > count) {
63,671,486✔
1766
            chunk = count;
47,239,003✔
1767
        }
28,562,336✔
1768
        memset(output->buffer + output->pos, value, (size_t)chunk);
63,673,487✔
1769
        sixel_advance(output, chunk);
63,673,487✔
1770
        count -= chunk;
63,698,435✔
1771
    }
1772
}
28,528,428!
1773

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

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

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

1793
    while ((width - idx) >= (int)chunk_size) {
249,953,635!
1794
        memcpy(&block, cursor, chunk_size);
239,349,299✔
1795
        if (block != 0UL) {
239,349,299✔
1796
            break;
12,274,552✔
1797
        }
1798
        idx += (int)chunk_size;
222,893,095✔
1799
        cursor += chunk_size;
222,893,095✔
1800
    }
1801

1802
    while (idx < width) {
100,959,070!
1803
        if (*cursor != 0) {
90,657,038✔
1804
            break;
12,512,653✔
1805
        }
1806
        idx += 1;
73,895,817✔
1807
        cursor += 1;
73,895,817✔
1808
    }
1809

1810
    return idx;
34,420,639✔
1811
}
7,355,908✔
1812

1813

1814
static int
1815
sixel_compose_measure_gap(unsigned char const *row,
28,678,351✔
1816
                          int width,
1817
                          int start,
1818
                          int *reached_end)
1819
{
1820
    int gap;
15,052,387✔
1821
    size_t chunk_size;
15,052,387✔
1822
    unsigned char const *cursor;
15,052,387✔
1823
    unsigned long block;
15,052,387✔
1824
    int remaining;
15,052,387✔
1825

1826
    gap = 0;
28,678,351✔
1827
    *reached_end = 0;
28,678,351✔
1828
    if (start >= width) {
28,678,351✔
1829
        *reached_end = 1;
343,388✔
1830
        return gap;
343,388✔
1831
    }
1832

1833
    chunk_size = sizeof(unsigned long);
28,334,689✔
1834
    cursor = row + start;
28,334,689✔
1835
    remaining = width - start;
28,334,689✔
1836

1837
    while (remaining >= (int)chunk_size) {
150,601,276!
1838
        memcpy(&block, cursor, chunk_size);
139,835,253✔
1839
        if (block != 0UL) {
139,835,253✔
1840
            break;
13,117,318✔
1841
        }
1842
        gap += (int)chunk_size;
122,266,587✔
1843
        cursor += chunk_size;
122,266,587✔
1844
        remaining -= (int)chunk_size;
122,266,587✔
1845
    }
1846

1847
    while (remaining > 0) {
90,418,548!
1848
        if (*cursor != 0) {
80,439,688✔
1849
            return gap;
13,738,573✔
1850
        }
1851
        gap += 1;
62,080,133✔
1852
        cursor += 1;
62,080,133✔
1853
        remaining -= 1;
62,080,133✔
1854
    }
1855

1856
    *reached_end = 1;
9,977,046✔
1857
    return gap;
9,977,046✔
1858
}
13,251,943✔
1859

1860

1861
#if HAVE_LDIV
1862
static int
1863
sixel_putnum_impl(char *buffer, long value, int pos)
84,413,178✔
1864
{
1865
    ldiv_t r;
44,126,762✔
1866

1867
    r = ldiv(value, 10);
84,413,178✔
1868
    if (r.quot > 0) {
84,419,806✔
1869
        pos = sixel_putnum_impl(buffer, r.quot, pos);
41,900,012✔
1870
    }
19,161,314✔
1871
    /*
1872
     * r.rem is guaranteed to be in [0, 9] because the divisor is 10, so the
1873
     * explicit cast documents the safe narrowing from long to char.
1874
     */
1875
    *(buffer + pos) = (char)('0' + r.rem);
84,423,035✔
1876
    return pos + 1;
106,912,595✔
1877
}
22,489,560✔
1878
#endif  /* HAVE_LDIV */
1879

1880

1881
static int
1882
sixel_putnum(char *buffer, int value)
42,565,450✔
1883
{
1884
    int pos;
22,209,759✔
1885

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

1892
    return pos;
53,806,567✔
1893
}
11,237,136✔
1894

1895

1896
static SIXELSTATUS
1897
sixel_put_flash(sixel_output_t *const output)
72,340,172✔
1898
{
1899
    int nwrite;
37,694,868✔
1900

1901
    if (output->save_count <= 0) {
72,340,172✔
1902
        return SIXEL_OK;
186,302✔
1903
    }
1904

1905
    if (output->has_gri_arg_limit) {  /* VT240 Max 255 ? */
72,104,225✔
1906
            while (output->save_count > 255) {
73,209!
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;
47,361✔
1914
            }
1915
        }
9,693✔
1916

1917
    if (output->save_count > 3) {
72,163,017✔
1918
        /* DECGRI Graphics Repeat Introducer ! Pn Ch */
1919
        sixel_putc(output->buffer + output->pos, '!');
8,480,936✔
1920
        sixel_advance(output, 1);
8,480,456✔
1921
        nwrite = sixel_putnum((char *)output->buffer + output->pos, output->save_count);
8,480,507✔
1922
        sixel_advance(output, nwrite);
8,481,596✔
1923
        sixel_putc(output->buffer + output->pos,
12,304,659✔
1924
                   (unsigned char)output->save_pixel);
8,481,107✔
1925
        sixel_advance(output, 1);
8,481,059✔
1926
    } else {
3,823,552✔
1927
        sixel_output_emit_literal(output,
92,254,855✔
1928
                                  (unsigned char)output->save_pixel,
63,682,081✔
1929
                                  output->save_count);
28,572,774✔
1930
    }
1931

1932
    output->save_pixel = 0;
72,131,516✔
1933
    output->save_count = 0;
72,131,516✔
1934

1935
    return SIXEL_OK;
72,131,516✔
1936
}
32,505,338✔
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)
72,031,580✔
1946
{
1947
    SIXELSTATUS status = SIXEL_FALSE;
72,031,580✔
1948

1949
    if (count <= 0) {
72,031,580!
1950
        return SIXEL_OK;
1951
    }
1952

1953
    if (output->save_count > 0) {
72,032,628✔
1954
        if (output->save_pixel == symbol) {
55,451,941✔
1955
            output->save_count += count;
11,766✔
1956
            return SIXEL_OK;
11,766✔
1957
        }
1958

1959
        status = sixel_put_flash(output);
55,440,838✔
1960
        if (SIXEL_FAILED(status)) {
55,422,167!
1961
            return status;
1962
        }
1963
    }
24,705,687✔
1964

1965
    output->save_pixel = symbol;
72,000,698✔
1966
    output->save_count = count;
72,000,698✔
1967

1968
    return SIXEL_OK;
72,000,698✔
1969
}
32,282,843✔
1970

1971

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

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

1996
    for (index = 0; index < length; index += run_length) {
82,463,441!
1997
        value = map[index];
65,730,240✔
1998
        if (value > '?') {
65,730,240!
1999
            value = 0;
×
2000
        }
2001

2002
        run_length = 1;
65,728,936✔
2003
        chunk_size = sizeof(unsigned long);
65,728,936✔
2004
        chunk_mismatch = 0;
65,728,936✔
2005
        if (chunk_size > 1) {
65,728,936!
2006
            remain = length - (index + run_length);
65,686,442✔
2007
            pattern = (~0UL / 0xffUL) * (unsigned long)value;
65,686,442✔
2008

2009
            while (remain >= (int)chunk_size) {
69,118,930!
2010
                memcpy(&block,
32,056,037✔
2011
                       map + index + run_length,
21,393,466✔
2012
                       chunk_size);
1,584,025✔
2013
                block ^= pattern;
32,056,037✔
2014
                if (block != 0UL) {
32,056,037✔
2015
                    for (byte_index = 0;
28,146,396!
2016
                         byte_index < (int)chunk_size;
45,265,717!
2017
                         byte_index++) {
16,641,399✔
2018
                        if ((block & 0xffUL) != 0UL) {
45,268,082✔
2019
                            chunk_mismatch = 1;
20,445,508✔
2020
                            break;
20,445,508✔
2021
                        }
2022
                        block >>= 8;
16,641,627✔
2023
                        run_length += 1;
16,641,627✔
2024
                    }
7,709,228✔
2025
                    break;
20,443,732✔
2026
                }
2027
                run_length += (int)chunk_size;
3,432,488✔
2028
                remain -= (int)chunk_size;
3,432,488✔
2029
            }
2030
        }
29,458,680✔
2031

2032
        if (!chunk_mismatch) {
66,947,251✔
2033
            while (index + run_length < length) {
49,095,856!
2034
                unsigned char next;
17,511,822✔
2035

2036
                next = map[index + run_length];
32,424,279✔
2037
                if (next > '?') {
32,424,279!
2038
                    next = 0;
×
2039
                }
2040
                if (next != value) {
32,430,204✔
2041
                    break;
16,140,298✔
2042
                }
2043
                run_length += 1;
11,703,173✔
2044
            }
10,532,705!
2045
        }
18,368,835✔
2046

2047
        status = sixel_emit_run(output,
95,081,202✔
2048
                                 (int)value + '?',
48,696,224✔
2049
                                 run_length);
29,418,762✔
2050
        if (SIXEL_FAILED(status)) {
65,647,505!
2051
            return status;
2052
        }
2053
    }
29,418,762✔
2054

2055
    return SIXEL_OK;
12,482,353✔
2056
}
7,694,513✔
2057

2058

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

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

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

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

2083
    status = SIXEL_OK;
11,319,193✔
2084

2085
end:
6,944,963✔
2086
    return status;
18,997,682✔
2087
}
4,475,677✔
2088

2089
static void
2090
sixel_node_del(sixel_output_t *output, sixel_node_t *np)
16,760,879✔
2091
{
2092
    sixel_node_t *tp;
8,800,390✔
2093

2094
    if ((tp = output->node_top) == np) {
16,760,879✔
2095
        output->node_top = np->next;
3,810,456✔
2096
    } else {
1,702,638✔
2097
        while (tp->next != NULL) {
615,609,007!
2098
            if (tp->next == np) {
615,620,361✔
2099
                tp->next = np->next;
12,973,634✔
2100
                break;
12,973,634✔
2101
            }
2102
            tp = tp->next;
453,128,947✔
2103
        }
2104
    }
2105

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

2110

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

2122
    if (ncolors != 2 || keycolor == (-1)) {
16,777,017!
2123
        /* designate palette index */
2124
        if (output->active_palette != np->pal) {
16,758,111✔
2125
            sixel_putc(output->buffer + output->pos, '#');
16,566,617✔
2126
            sixel_advance(output, 1);
16,566,147✔
2127
            nwrite = sixel_putnum((char *)output->buffer + output->pos, np->pal);
16,565,925✔
2128
            sixel_advance(output, nwrite);
16,567,035✔
2129
            output->active_palette = np->pal;
16,566,881✔
2130
        }
7,638,183✔
2131
    }
7,712,542✔
2132

2133
    if (*x < np->sx) {
17,349,704✔
2134
        int span;
3,323,362✔
2135

2136
        span = np->sx - *x;
6,343,305✔
2137
        status = sixel_emit_run(output, '?', span);
6,343,305✔
2138
        if (SIXEL_FAILED(status)) {
6,343,091!
2139
            goto end;
2140
        }
2141
        *x = np->sx;
6,342,963✔
2142
    }
2,898,242!
2143

2144
    if (*x < np->mx) {
16,755,273!
2145
        int span;
8,789,614✔
2146

2147
        span = np->mx - *x;
16,752,119✔
2148
        status = sixel_emit_span_from_map(output,
28,700,120✔
2149
                                          (unsigned char const *)np->map + *x,
16,752,119✔
2150
                                          span);
7,696,372✔
2151
        if (SIXEL_FAILED(status)) {
16,750,160!
2152
            goto end;
2153
        }
2154
        *x = np->mx;
16,748,904✔
2155
    }
7,695,116✔
2156

2157
    status = sixel_put_flash(output);
16,752,077✔
2158
    if (SIXEL_FAILED(status)) {
16,751,629!
2159
        goto end;
2160
    }
2161

2162
end:
9,052,638✔
2163
    return status;
16,744,737✔
2164
}
4,534,992✔
2165

2166

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

2175
    if (output->ormode) {
53,643✔
2176
        p[0] = 7;
17✔
2177
        p[1] = 5;
17✔
2178
    } else if (keycolor >= 0) {
53,628!
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,136✔
2184
    }
2,007✔
2185

2186
    output->pos = 0;
53,643✔
2187

2188
    if (p[2] == 0) {
53,643!
2189
        pcount--;
53,643✔
2190
        if (p[1] == 0) {
53,643✔
2191
            pcount--;
49,483✔
2192
            if (p[0] == 0) {
49,483!
2193
                pcount--;
49,483✔
2194
            }
21,740✔
2195
        }
21,740✔
2196
    }
23,756✔
2197

2198
    status = sixel_output_begin_image(output,
77,399✔
2199
                                      width,
23,756✔
2200
                                      height,
23,756✔
2201
                                      p[0],
23,756✔
2202
                                      p[1],
23,756✔
2203
                                      p[2],
23,756✔
2204
                                      pcount,
23,756✔
2205
                                      use_raster_attributes);
23,756✔
2206

2207
    return status;
67,441✔
2208
}
13,798✔
2209

2210

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

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

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

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

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

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

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

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

2316
    return 0;
2317
}
5,764,929✔
2318

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

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

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

2344
static SIXELSTATUS
2345
output_rgb_palette_definition(
4,339,137✔
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,339,137✔
2354
    int nwrite;
2,253,281✔
2355

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

2391
    status = SIXEL_OK;
4,339,137✔
2392

2393
    return status;
5,458,517✔
2394
}
1,119,380✔
2395

2396

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

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

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

2518

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

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

2545
    len = ncolors * width;
85,867✔
2546
    work->map = (char *)sixel_allocator_calloc(allocator,
121,446✔
2547
                                               (size_t)len,
35,579✔
2548
                                               sizeof(char));
2549
    if (work->map == NULL && len > 0) {
85,876!
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;
85,873✔
2556

2557
    columns_size = sizeof(sixel_node_t *) * (size_t)width;
85,873✔
2558
    if (width > 0) {
85,873!
2559
        work->columns = (sixel_node_t **)sixel_allocator_malloc(
85,873✔
2560
            allocator,
35,576✔
2561
            columns_size);
35,576✔
2562
        if (work->columns == NULL) {
85,872!
2563
            sixel_helper_set_additional_message(
×
2564
                "sixel_encode_body: sixel_allocator_malloc() failed.");
2565
            status = SIXEL_BAD_ALLOCATION;
×
2566
            goto end;
×
2567
        }
2568
        memset(work->columns, 0, columns_size);
85,870✔
2569
        work->columns_size = columns_size;
85,870✔
2570
    } else {
35,574✔
2571
        work->columns = NULL;
×
2572
        work->columns_size = 0;
×
2573
    }
2574

2575
    active_colors_size = (size_t)ncolors;
85,871✔
2576
    if (active_colors_size > 0) {
85,871!
2577
        work->active_colors =
108,774✔
2578
            (unsigned char *)sixel_allocator_malloc(allocator,
121,436✔
2579
                                                    active_colors_size);
35,570✔
2580
        if (work->active_colors == NULL) {
85,867!
2581
            sixel_helper_set_additional_message(
1✔
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);
85,862✔
2587
        work->active_colors_size = active_colors_size;
85,862✔
2588
    } else {
35,566✔
2589
        work->active_colors = NULL;
7✔
2590
        work->active_colors_size = 0;
7✔
2591
    }
2592

2593
    active_color_index_size = sizeof(int) * (size_t)ncolors;
85,872✔
2594
    if (active_color_index_size > 0) {
85,872!
2595
        work->active_color_index = (int *)sixel_allocator_malloc(
85,871✔
2596
            allocator,
35,575✔
2597
            active_color_index_size);
35,575✔
2598
        if (work->active_color_index == NULL) {
85,868!
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);
85,869✔
2605
        work->active_color_index_size = active_color_index_size;
85,869✔
2606
    } else {
35,576✔
2607
        work->active_color_index = NULL;
1✔
2608
        work->active_color_index_size = 0;
1✔
2609
    }
2610

2611
    status = SIXEL_OK;
62,964✔
2612

2613
end:
27,389✔
2614
    if (SIXEL_FAILED(status)) {
62,967!
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;
105,851✔
2638
}
19,981✔
2639

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

2666
static void
2667
sixel_band_state_reset(sixel_band_state_t *state)
454,656✔
2668
{
2669
    state->row_in_band = 0;
454,656✔
2670
    state->fillable = 0;
454,656✔
2671
    state->active_color_count = 0;
452,399✔
2672
}
353,746✔
2673

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

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

2686
    for (color_index = 0;
5,373,719!
2687
         color_index < state->active_color_count;
11,395,030!
2688
         color_index++) {
11,076,857✔
2689
        c = work->active_color_index[color_index];
11,076,865✔
2690
        if (c >= 0
11,076,865!
2691
            && (size_t)c < work->active_colors_size) {
11,076,878!
2692
            work->active_colors[c] = 0;
11,076,853✔
2693
        }
5,138,935✔
2694
    }
5,139,033✔
2695
    state->active_color_count = 0;
318,158✔
2696
}
140,007!
2697

2698
static void
2699
sixel_band_clear_map(sixel_encode_work_t *work)
318,199✔
2700
{
2701
    if (work->map != NULL && work->map_size > 0) {
318,199!
2702
        memset(work->map, 0, work->map_size);
318,204✔
2703
    }
140,071✔
2704
}
318,200✔
2705

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

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

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

2727
    if (output->palette_type == SIXEL_PALETTETYPE_HLS) {
55,251✔
2728
        for (n = 0; n < ncolors; n++) {
55,971!
2729
            status = output_hls_palette_definition(output,
77,034✔
2730
                                                   palette,
21,282✔
2731
                                                   palette_float,
21,282✔
2732
                                                   n,
21,282✔
2733
                                                   keycolor);
21,282✔
2734
            if (SIXEL_FAILED(status)) {
55,752!
2735
                goto end;
2736
            }
2737
        }
21,282✔
2738
    } else {
84✔
2739
        for (n = 0; n < ncolors; n++) {
4,388,025!
2740
            status = output_rgb_palette_definition(output,
6,253,820✔
2741
                                                   palette,
1,920,827✔
2742
                                                   palette_float,
1,920,827✔
2743
                                                   n,
1,920,827✔
2744
                                                   keycolor);
1,920,827✔
2745
            if (SIXEL_FAILED(status)) {
4,332,993!
2746
                goto end;
2747
            }
2748
        }
1,920,827✔
2749
    }
2750

2751
    status = SIXEL_OK;
40,727✔
2752

2753
end:
31,248✔
2754
    return status;
40,727✔
2755
}
24,683✔
2756

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

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

2784
    if (row_bit == 0) {
1,777,870✔
2785
        if (encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
318,183✔
2786
            state->fillable = 0;
317,926✔
2787
        } else if (palstate) {
140,223!
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;
258✔
2800
        }
2801
        state->active_color_count = 0;
318,184✔
2802
    }
140,064✔
2803

2804
    for (x = 0; x < width; x++) {
219,731,690!
2805
        if (absolute_row > INT_MAX / width) {
217,979,120!
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;
217,978,838✔
2813
        if (check_integer_overflow > INT_MAX - x) {
217,978,838!
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];
217,941,170✔
2821
        if (pix >= 0 && pix < ncolors && pix != keycolor) {
217,941,170!
2822
            if (pix > INT_MAX / width) {
188,274,618!
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;
188,277,194✔
2830
            if (check_integer_overflow > INT_MAX - x) {
188,277,194!
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);
188,278,855✔
2838
            if (active_colors != NULL && active_colors[pix] == 0) {
188,278,855!
2839
                active_colors[pix] = 1;
11,060,944✔
2840
                if (state->active_color_count < ncolors
11,060,944!
2841
                    && active_color_index != NULL) {
11,067,326!
2842
                    active_color_index[state->active_color_count] = pix;
11,066,947✔
2843
                    state->active_color_count += 1;
11,066,947✔
2844
                }
5,131,793✔
2845
            }
5,132,299✔
2846
        } else if (!palstate) {
108,721,115!
2847
            state->fillable = 0;
22,592,133✔
2848
        }
9,049,840✔
2849
    }
91,045,542✔
2850

2851
    state->row_in_band += 1;
1,774,351✔
2852
    status = SIXEL_OK;
1,774,351✔
2853

2854
end:
995,984✔
2855
    return status;
2,224,909✔
2856
}
450,551✔
2857

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

2880
    (void)ncolors;
223,459✔
2881
    (void)keycolor;
223,459✔
2882
    row = NULL;
318,207✔
2883
    gap_reached_end = 0;
318,207✔
2884
    np = NULL;
318,207✔
2885
    column_head = NULL;
318,207✔
2886
    column_tail = NULL;
318,207✔
2887
    top.next = NULL;
318,207✔
2888

2889
    if (work->columns != NULL) {
318,207!
2890
        memset(work->columns, 0, work->columns_size);
318,194✔
2891
    }
140,057✔
2892
    output->node_top = NULL;
318,207✔
2893

2894
    for (color_index = 0;
5,431,428!
2895
         color_index < state->active_color_count;
11,370,367!
2896
         color_index++) {
11,052,172✔
2897
        c = work->active_color_index[color_index];
11,049,938✔
2898
        row = (unsigned char *)(work->map + c * width);
11,049,938✔
2899
        sx = 0;
11,049,938✔
2900
        while (sx < width) {
27,827,312!
2901
            sx = sixel_compose_find_run_start(
27,097,222✔
2902
                row,
12,501,698✔
2903
                width,
12,501,698✔
2904
                sx);
12,501,698✔
2905
            if (sx >= width) {
27,096,984✔
2906
                break;
7,720,989✔
2907
            }
2908

2909
            mx = sx + 1;
16,774,992✔
2910
            while (mx < width) {
88,382,025!
2911
                if (row[mx] != 0) {
87,634,077✔
2912
                    mx += 1;
58,933,195✔
2913
                    continue;
58,933,195✔
2914
                }
2915

2916
                gap = sixel_compose_measure_gap(
28,705,711✔
2917
                    row,
13,260,636✔
2918
                    width,
13,260,636✔
2919
                    mx + 1,
13,260,636✔
2920
                    &gap_reached_end);
2921
                if (gap >= 9 || gap_reached_end) {
28,709,434!
2922
                    break;
7,381,249✔
2923
                }
2924
                mx += gap + 1;
12,673,243✔
2925
            }
2926

2927
            if ((np = output->node_free) != NULL) {
16,778,720✔
2928
                output->node_free = np->next;
2,255,199✔
2929
            } else {
143,459✔
2930
                status = sixel_node_new(&np, allocator);
14,523,521✔
2931
                if (SIXEL_FAILED(status)) {
14,520,151!
2932
                    goto end;
2933
                }
2934
            }
2935

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

2942
            if (work->columns != NULL) {
16,777,235!
2943
                column_head = work->columns[sx];
16,777,272✔
2944
                if (column_head == NULL
16,777,272✔
2945
                    || column_head->mx <= np->mx) {
11,760,211!
2946
                    np->next = column_head;
13,054,394✔
2947
                    work->columns[sx] = np;
13,054,394✔
2948
                } else {
5,940,067✔
2949
                    column_tail = column_head;
2,809,676✔
2950
                    while (column_tail->next != NULL
6,082,149!
2951
                           && column_tail->next->mx > np->mx) {
4,983,281!
2952
                        column_tail = column_tail->next;
952,264✔
2953
                    }
2954
                    np->next = column_tail->next;
3,724,382✔
2955
                    column_tail->next = np;
3,724,382✔
2956
                }
2957
            } else {
7,720,290✔
2958
                top.next = output->node_top;
128✔
2959
                column_tail = &top;
128✔
2960

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

2976
            sx = mx;
12,523,183✔
2977
        }
2978
    }
5,113,221✔
2979

2980
    if (work->columns != NULL) {
320,506!
2981
        top.next = NULL;
318,203✔
2982
        column_tail = &top;
318,203✔
2983
        for (sx = 0; sx < width; sx++) {
37,793,338!
2984
            column_head = work->columns[sx];
37,474,853✔
2985
            if (column_head == NULL) {
37,474,853✔
2986
                continue;
28,352,730✔
2987
            }
2988
            column_tail->next = column_head;
9,122,428✔
2989
            while (column_tail->next != NULL) {
25,930,692!
2990
                column_tail = column_tail->next;
12,559,431✔
2991
            }
2992
            work->columns[sx] = NULL;
9,122,404✔
2993
        }
4,108,922✔
2994
        output->node_top = top.next;
318,199✔
2995
    }
140,053✔
2996

2997
    status = SIXEL_OK;
320,540✔
2998

2999
end:
180,466✔
3000
    return status;
401,722✔
3001
}
81,182✔
3002

3003
static SIXELSTATUS
3004
sixel_band_emit(sixel_encode_work_t *work,
318,158✔
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;
318,158✔
3012
    sixel_node_t *np;
164,532✔
3013
    sixel_node_t *next;
164,532✔
3014
    int x;
164,532✔
3015
    int emit_next_line;
164,532✔
3016

3017
    emit_next_line = (last_row_index >= 6);
318,158✔
3018
    if (emit_next_line) {
318,158✔
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] = '-';
262,100✔
3024
        sixel_advance(output, 1);
262,100✔
3025
    }
115,382✔
3026

3027
    for (x = 0; (np = output->node_top) != NULL;) {
2,470,079!
3028
        if (x > np->sx) {
2,152,274✔
3029
            output->buffer[output->pos] = '$';
1,844,235✔
3030
            sixel_advance(output, 1);
1,844,235✔
3031
            x = 0;
1,844,216✔
3032
        }
852,755✔
3033

3034
        if (state->fillable) {
2,242,456✔
3035
            memset(np->map + np->sx,
286✔
3036
                   (1 << state->row_in_band) - 1,
176✔
3037
                   (size_t)(np->mx - np->sx));
176✔
3038
        }
99✔
3039
        status = sixel_put_node(output,
3,140,683✔
3040
                                &x,
3041
                                np,
988,482✔
3042
                                ncolors,
988,482✔
3043
                                keycolor);
988,482✔
3044
        if (SIXEL_FAILED(status)) {
2,152,187!
3045
            goto end;
3046
        }
3047
        next = np->next;
2,152,187✔
3048
        sixel_node_del(output, np);
2,152,187✔
3049
        np = next;
2,152,188✔
3050

3051
        while (np != NULL) {
91,671,609!
3052
            if (np->sx < x) {
89,518,098✔
3053
                np = np->next;
74,910,685✔
3054
                continue;
74,910,685✔
3055
            }
3056

3057
            if (state->fillable) {
14,610,415✔
3058
                memset(np->map + np->sx,
26✔
3059
                       (1 << state->row_in_band) - 1,
16✔
3060
                       (size_t)(np->mx - np->sx));
16✔
3061
            }
9✔
3062
            status = sixel_put_node(output,
21,325,343✔
3063
                                    &x,
3064
                                    np,
6,716,317✔
3065
                                    ncolors,
6,716,317✔
3066
                                    keycolor);
6,716,317✔
3067
            if (SIXEL_FAILED(status)) {
14,606,480!
3068
                goto end;
3069
            }
3070
            next = np->next;
14,606,480✔
3071
            sixel_node_del(output, np);
14,606,480✔
3072
            np = next;
14,608,198✔
3073
        }
3074

3075
        state->fillable = 0;
2,151,912✔
3076
    }
3077

3078
    status = SIXEL_OK;
234,447✔
3079

3080
end:
177,807✔
3081
    (void)work;
223,405✔
3082
    return status;
398,975✔
3083
}
81,146✔
3084

3085

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

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

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

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

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

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

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

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

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

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

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

3185
    band_start = 0;
10,929✔
3186
    while (band_start < height) {
97,381!
3187
        band_height = height - band_start;
80,178✔
3188
        if (band_height > 6) {
80,178✔
3189
            band_height = 6;
37,549✔
3190
        }
10,927✔
3191

3192
        band.row_in_band = 0;
80,930✔
3193
        band.fillable = 0;
80,930✔
3194
        band.active_color_count = 0;
80,930✔
3195

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

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

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

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

3242
        sixel_band_finish(&work, &band);
80,178✔
3243

3244
        sixel_band_clear_map(&work);
80,178✔
3245

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

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

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

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

3268
cleanup:
20,529✔
3269
    while ((np = output->node_free) != NULL) {
834,471!
3270
        output->node_free = np->next;
812,397✔
3271
        sixel_allocator_free(allocator, np);
812,397✔
3272
    }
3273
    output->node_top = NULL;
22,074✔
3274

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

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

3284
    if (output->pos > 0) {
53,643✔
3285
        status = sixel_output_write_bytes(output,
91,265✔
3286
                                          (char *)output->buffer,
53,586✔
3287
                                          output->pos);
23,729✔
3288
        if (SIXEL_FAILED(status)) {
53,586!
3289
            return status;
3290
        }
3291
        output->pos = 0;
53,586✔
3292
    }
23,729✔
3293

3294
    status = sixel_output_end_image(output);
53,643✔
3295

3296
    return status;
53,643✔
3297
}
23,756✔
3298

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

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

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

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

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

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

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

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

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

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

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

3406
    return 0;
17✔
3407
}
9✔
3408

3409

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

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

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

3475
    pipeline_active = 0;
53,205✔
3476
#if SIXEL_ENABLE_THREADS
3477
    #endif
3478
    dither_parallel.enabled = 0;
53,205✔
3479
    dither_parallel.band_height = 0;
53,205✔
3480
    dither_parallel.overlap = 0;
53,205✔
3481
    dither_parallel.dither_threads = 0;
53,205✔
3482
    dither_parallel.encode_threads = 0;
53,205✔
3483
    ormode_enabled = output != NULL && output->ormode != 0;
53,205!
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 =
53,478✔
3489
        dither->pipeline_pin_threads != 0 ? 1 : 0;
53,478!
3490
    dither_parallel.pin_threads = dither->pipeline_pin_threads;
53,478✔
3491
    switch (dither->pixelformat) {
53,478!
3492
    case SIXEL_PIXELFORMAT_PAL1:
×
3493
    case SIXEL_PIXELFORMAT_PAL2:
×
3494
    case SIXEL_PIXELFORMAT_PAL4:
×
3495
    case SIXEL_PIXELFORMAT_G1:
×
3496
    case SIXEL_PIXELFORMAT_G2:
×
3497
    case SIXEL_PIXELFORMAT_G4:
×
3498
        bufsize = (sizeof(sixel_index_t) * (size_t)width * (size_t)height * 3UL);
15✔
3499
        paletted_pixels = (sixel_index_t *)sixel_allocator_malloc(dither->allocator, bufsize);
15✔
3500
        if (paletted_pixels == NULL) {
15!
3501
            sixel_helper_set_additional_message(
×
3502
                "sixel_encode_dither: sixel_allocator_malloc() failed.");
3503
            status = SIXEL_BAD_ALLOCATION;
×
3504
            goto end;
×
3505
        }
3506
        status = sixel_helper_normalize_pixelformat(paletted_pixels,
30✔
3507
                                                    &dither->pixelformat,
15✔
3508
                                                    pixels,
15✔
3509
                                                    dither->pixelformat,
15✔
3510
                                                    width, height);
15✔
3511
        if (SIXEL_FAILED(status)) {
15!
3512
            goto end;
×
3513
        }
3514
        input_pixels = paletted_pixels;
15✔
3515
        break;
15✔
3516
    case SIXEL_PIXELFORMAT_PAL8:
1,536!
3517
    case SIXEL_PIXELFORMAT_G8:
×
3518
    case SIXEL_PIXELFORMAT_GA88:
×
3519
    case SIXEL_PIXELFORMAT_AG88:
×
3520
        input_pixels = pixels;
4,306✔
3521
        break;
4,306✔
3522
    default:
26,740!
3523
        /* apply palette */
3524
        pipeline_threads = sixel_threads_resolve();
47,543✔
3525
        band_env_text = sixel_compat_getenv(
47,543✔
3526
            "SIXEL_DITHER_PARALLEL_BAND_WIDTH");
3527
        if (pipeline_threads <= 1 && band_env_text != NULL
47,543!
3528
                && band_env_text[0] != '\0') {
1,781!
3529
            /*
3530
             * Parallel band dithering was explicitly requested via the
3531
             * environment.  When SIXEL_THREADS is absent, prefer hardware
3532
             * concurrency instead of silently running a single worker so
3533
             * that multiple dither jobs appear in the log.
3534
             */
3535
            pipeline_threads = sixel_threads_normalize(0);
20✔
3536
        }
3537
        pipeline_nbands = (height + 5) / 6;
47,542✔
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
64,972✔
3548
                && pipeline_threads > 1
47,534✔
3549
                && pipeline_nbands > 1) {
38,348!
3550
            pipeline_active = 1;
26,736✔
3551
            input_pixels = NULL;
26,736✔
3552
        } else {
17,812✔
3553
            paletted_pixels = sixel_dither_apply_palette(dither, pixels,
16,309✔
3554
                                                         width, height);
2,991✔
3555
            if (paletted_pixels == NULL) {
13,318!
3556
                status = SIXEL_RUNTIME_ERROR;
×
3557
                goto end;
×
3558
            }
3559
            input_pixels = paletted_pixels;
8,310✔
3560
        }
3561
        break;
35,046✔
3562
    }
3563

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

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

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

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

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

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

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

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

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

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

3805
end:
29,617✔
3806
#if SIXEL_ENABLE_THREADS
3807
    if (logger_owned) {
45,381!
3808
        dither->pipeline_logger = NULL;
12✔
3809
        sixel_timeline_logger_unref(serial_logger);
12✔
3810
    }
12✔
3811
#endif
3812
    if (palette_entries != NULL) {
53,205!
3813
        sixel_allocator_free(dither->allocator, palette_entries);
53,205✔
3814
    }
23,588✔
3815
    if (palette_entries_float32 != NULL) {
55,881✔
3816
        sixel_allocator_free(dither->allocator, palette_entries_float32);
4,703✔
3817
    }
1,139✔
3818
    sixel_allocator_free(dither->allocator, paletted_pixels);
53,205✔
3819

3820
    return status;
53,205✔
3821
}
23,588✔
3822

3823
SIXEL_INTERNAL_API SIXELSTATUS
3824
sixel_encoder_core_encode_dispatch(
53,739✔
3825
    sixel_encoder_core_encode_request_t const *request)
3826
{
3827
    SIXELSTATUS status;
27,810✔
3828

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

3837
    (void)request->depth;
37,720✔
3838
    if (request->dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
53,643✔
3839
        status = sixel_encode_highcolor(request->pixels,
606✔
3840
                                        request->width,
312✔
3841
                                        request->height,
312✔
3842
                                        request->dither,
312✔
3843
                                        request->output);
312✔
3844
    } else {
168✔
3845
        status = sixel_encode_dither(request->pixels,
76,793✔
3846
                                     request->width,
39,367✔
3847
                                     request->height,
39,367✔
3848
                                     request->dither,
39,367✔
3849
                                     request->output);
39,367✔
3850
    }
3851

3852
    return status;
39,679✔
3853
}
23,792✔
3854

3855
SIXELAPI SIXELSTATUS
3856
sixel_encode(
53,619✔
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;
53,619✔
3865
    sixel_encoder_core_t *core;
27,750✔
3866
    sixel_encoder_core_encode_request_t request;
27,750✔
3867

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

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

3923
end:
29,872✔
3924
    sixel_output_unref(output);
53,619✔
3925
    sixel_dither_unref(dither);
53,619✔
3926

3927
    return status;
53,619✔
3928
}
23,747✔
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