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

saitoha / libsixel / 29176094696

10 Jul 2026 03:01PM UTC coverage: 85.292% (+0.03%) from 85.263%
29176094696

push

github

saitoha
fix: shorten decoder test identifiers

78943 of 143474 branches covered (55.02%)

2 of 2 new or added lines in 2 files covered. (100.0%)

209 existing lines in 9 files now uncovered.

140656 of 164911 relevant lines covered (85.29%)

6100630.51 hits per line

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

92.01
/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 encoded_width;
157
    int encoded_height;
158
    int offset_left;
159
    int offset_top;
160
    int ncolors;
161
    int keycolor;
162
    unsigned char *palstate;
163
    int encode_policy;
164
    sixel_allocator_t *allocator;
165
    sixel_output_t *output;
166
    int thread_count;
167
    int band_count;
168
    sixel_parallel_band_buffer_t *bands;
169
    int *band_source_rows_total;
170
    int *band_source_rows_ready;
171
    sixel_parallel_worker_state_t **workers;
172
    int worker_capacity;
173
    int worker_registered;
174
    sixel_thread_pool_t *pool;
175
    sixel_timeline_logger_t *logger;
176
    sixel_mutex_t mutex;
177
    int mutex_ready;
178
    sixel_cond_t cond_band_ready;
179
    int cond_ready;
180
    sixel_thread_t writer_thread;
181
    int writer_started;
182
    int next_band_to_flush;
183
    int writer_should_stop;
184
    SIXELSTATUS writer_error;
185
    int queue_capacity;
186
    int pin_threads;
187
} sixel_parallel_context_t;
188

189
typedef struct sixel_parallel_row_notifier {
190
    sixel_parallel_context_t *context;
191
    sixel_timeline_logger_t *logger;
192
    int band_height;
193
    int image_height;
194
} sixel_parallel_row_notifier_t;
195

196
static void sixel_parallel_writer_stop(sixel_parallel_context_t *ctx,
197
                                       int force_abort);
198
static int sixel_parallel_writer_main(void *arg);
199
#endif
200

201
#if SIXEL_ENABLE_THREADS
202
static int sixel_parallel_jobs_allowed(sixel_parallel_context_t *ctx);
203
static void sixel_parallel_context_abort_locked(sixel_parallel_context_t *ctx,
204
                                               SIXELSTATUS status);
205
#endif
206

207
#if SIXEL_ENABLE_THREADS
208
struct sixel_parallel_worker_state {
209
    int initialized;
210
    int index;
211
    SIXELSTATUS writer_error;
212
    sixel_parallel_band_buffer_t *band_buffer;
213
    sixel_parallel_context_t *context;
214
    sixel_output_t *output;
215
    sixel_encode_work_t work;
216
    sixel_band_state_t band;
217
};
218
#endif
219

220
static void sixel_encode_work_init(sixel_encode_work_t *work);
221
static SIXELSTATUS sixel_encode_work_allocate(sixel_encode_work_t *work,
222
                                              int width,
223
                                              int ncolors,
224
                                              sixel_allocator_t *allocator);
225
static void sixel_encode_work_cleanup(sixel_encode_work_t *work,
226
                                      sixel_allocator_t *allocator);
227
static void sixel_band_state_reset(sixel_band_state_t *state);
228
static void sixel_band_finish(sixel_encode_work_t *work,
229
                              sixel_band_state_t *state);
230
static void sixel_band_clear_map(sixel_encode_work_t *work);
231
static int sixel_output_has_transparent_offset(sixel_output_t const *output);
232
static int sixel_count_sixel_bands(int height);
233
static SIXELSTATUS
234
sixel_output_compute_transparent_extent(sixel_output_t const *output,
235
                                        int width,
236
                                        int height,
237
                                        int *encoded_width,
238
                                        int *encoded_height);
239
static SIXELSTATUS sixel_band_classify_row(sixel_encode_work_t *work,
240
                                           sixel_band_state_t *state,
241
                                           sixel_index_t *pixels,
242
                                           int width,
243
                                           int height,
244
                                           int map_width,
245
                                           int absolute_row,
246
                                           int offset_left,
247
                                           int offset_top,
248
                                           int ncolors,
249
                                           int keycolor,
250
                                           unsigned char *palstate,
251
                                           int encode_policy);
252
static SIXELSTATUS sixel_band_compose(sixel_encode_work_t *work,
253
                                      sixel_band_state_t *state,
254
                                      sixel_output_t *output,
255
                                      int width,
256
                                      int ncolors,
257
                                      int keycolor,
258
                                      sixel_allocator_t *allocator);
259
static SIXELSTATUS sixel_band_emit(sixel_encode_work_t *work,
260
                                   sixel_band_state_t *state,
261
                                   sixel_output_t *output,
262
                                   int ncolors,
263
                                   int keycolor,
264
                                   int last_row_index);
265
static SIXELSTATUS sixel_put_flash(sixel_output_t *const output);
266
static void sixel_advance(sixel_output_t *output, int nwrite);
267
static int sixel_encode_body_ormode_nplanes(int ncolors);
268
static SIXELSTATUS
269
sixel_encode_body_ormode_emit_palette(unsigned char const *palette,
270
                                      int ncolors,
271
                                      int keycolor,
272
                                      sixel_output_t *output);
273
static SIXELSTATUS
274
sixel_encode_body_ormode_band(sixel_index_t const *pixels,
275
                              int width,
276
                              int height,
277
                              int band_index,
278
                              int nplanes,
279
                              sixel_output_t *output);
280

281
#if SIXEL_ENABLE_THREADS
282
static void
283
sixel_timeline_logger_prepare_default(sixel_allocator_t *allocator,
27,819✔
284
                                      sixel_timeline_logger_t **logger)
285
{
286
    if (logger == NULL) {
27,819!
287
        return;
288
    }
289

290
    *logger = NULL;
27,819✔
291
    (void)sixel_timeline_logger_prepare_env(allocator, logger);
27,819✔
292
}
2,328✔
293
#endif
294

295
static void
296
sixel_parallel_dither_configure(int height,
19,168✔
297
                                int ncolors,
298
                                int pipeline_threads,
299
                                int pin_threads,
300
                                sixel_parallel_dither_config_t *config)
301
{
302
    char const *text;
8,001✔
303
    long parsed;
8,001✔
304
    char *endptr;
8,001✔
305
    int band_height;
8,001✔
306
    int overlap;
8,001✔
307
    int dither_threads;
8,001✔
308
    int encode_threads;
8,001✔
309
    int dither_env_override;
8,001✔
310

311
    if (config == NULL) {
19,168✔
312
        return;
×
313
    }
314

315
    config->enabled = 0;
19,168✔
316
    config->band_height = 0;
19,168✔
317
    config->overlap = 0;
19,168✔
318
    config->dither_threads = 0;
19,168✔
319
    config->encode_threads = pipeline_threads;
19,168✔
320
    config->pin_threads = (pin_threads != 0) ? 1 : 0;
19,168✔
321

322
    if (pipeline_threads <= 1 || height <= 0) {
19,168!
323
        return;
324
    }
325

326
    dither_env_override = 0;
19,168✔
327
    dither_threads = (pipeline_threads * 7 + 9) / 10;
19,168✔
328
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_THREADS_MAX");
19,168✔
329
    if (text != NULL && text[0] != '\0') {
19,168!
330
        errno = 0;
72✔
331
        parsed = strtol(text, &endptr, 10);
72✔
332
        if (endptr != text && errno != ERANGE && parsed > 0) {
72!
333
            if (parsed > INT_MAX) {
36!
334
                parsed = INT_MAX;
335
            }
336
            dither_threads = (int)parsed;
54✔
337
            dither_env_override = 1;
54✔
338
        }
6✔
339
    }
6✔
340
    if (dither_threads < 1) {
11,167!
341
        dither_threads = 1;
342
    }
343
    if (dither_threads > pipeline_threads) {
19,168!
344
        dither_threads = pipeline_threads;
345
    }
346

347
    if (!dither_env_override && pipeline_threads >= 4 && dither_threads < 2) {
19,168!
348
        /*
349
         * When the total budget is ample, keep at least two dither workers so
350
         * the banded producer can feed the encoder fast enough to pipeline.
351
         */
352
        dither_threads = pipeline_threads - 2;
×
353
    }
354

355
    encode_threads = pipeline_threads - dither_threads;
19,168✔
356
    if (encode_threads < 2 && pipeline_threads > 2) {
19,168✔
357
        /*
358
         * Preserve a minimal pair of encoder workers to keep the pipeline
359
         * alive while leaving the rest to dithering. Small budgets fall back
360
         * to the serial encoder path later in the caller.
361
         */
362
        encode_threads = 2;
18,844✔
363
        dither_threads = pipeline_threads - encode_threads;
18,844✔
364
    }
1,586✔
365
    if (encode_threads < 1) {
19,168✔
366
        encode_threads = 1;
252✔
367
        dither_threads = pipeline_threads - encode_threads;
252✔
368
    }
21✔
369
    if (dither_threads < 1) {
12,823!
370
        return;
371
    }
372

373
    /*
374
     * Choose the band height from the environment when present. Otherwise
375
     * split the image across the initial dither workers so each thread starts
376
     * with a single band. The result is rounded to a six-line multiple to
377
     * stay aligned with the encoder's natural cadence.
378
     */
379
    band_height = 0;
19,168✔
380
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_WIDTH");
19,168✔
381
    if (text != NULL && text[0] != '\0') {
19,168!
382
        errno = 0;
72✔
383
        parsed = strtol(text, &endptr, 10);
72✔
384
        if (endptr != text && errno != ERANGE && parsed > 0) {
72!
385
            if (parsed > INT_MAX) {
36!
386
                parsed = INT_MAX;
387
            }
388
            band_height = (int)parsed;
54✔
389
        }
6✔
390
    }
6✔
391
    if (band_height <= 0) {
11,179✔
392
        band_height = (height + dither_threads - 1) / dither_threads;
19,096✔
393
    }
1,607✔
394
    if (band_height < 6) {
19,168✔
395
        band_height = 6;
1,624✔
396
    }
232✔
397
    if ((band_height % 6) != 0) {
18,008✔
398
        band_height = ((band_height + 5) / 6) * 6;
13,626✔
399
    }
1,150✔
400

401
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_OVERWRAP");
19,168✔
402
    /*
403
     * Default overlap favors quality for small palettes and speed when
404
     * colors are plentiful. The environment can override this policy.
405
     */
406
    if (ncolors <= 32) {
19,168✔
407
        overlap = 6;
6,387✔
408
    } else {
916✔
409
        overlap = 0;
8,222✔
410
    }
411
    if (text != NULL && text[0] != '\0') {
19,168!
412
        errno = 0;
72✔
413
        parsed = strtol(text, &endptr, 10);
72✔
414
        if (endptr != text && errno != ERANGE && parsed >= 0) {
72!
415
            if (parsed > INT_MAX) {
36!
416
                parsed = INT_MAX;
417
            }
418
            overlap = (int)parsed;
54✔
419
        }
6✔
420
    }
6✔
421
    if (overlap < 0) {
11,167!
422
        overlap = 0;
423
    }
424
    if (overlap > band_height / 2) {
19,168✔
425
        overlap = band_height / 2;
2,912✔
426
    }
416✔
427

428
    config->enabled = 1;
19,168✔
429
    config->band_height = band_height;
19,168✔
430
    config->overlap = overlap;
19,168✔
431
    config->dither_threads = dither_threads;
19,168✔
432
    config->encode_threads = encode_threads;
19,168✔
433
}
1,613✔
434

435
static int
436
sixel_output_has_transparent_offset(sixel_output_t const *output)
101,279✔
437
{
438
    if (output == NULL) {
101,279!
439
        return 0;
440
    }
441

442
    return output->transparent_offset_left != 0 ||
183,544✔
443
           output->transparent_offset_top != 0;
88,633!
444
}
6,377✔
445

446
static int
447
sixel_count_sixel_bands(int height)
81,256✔
448
{
449
    int bands;
34,521✔
450

451
    if (height <= 0) {
81,256!
452
        return 0;
453
    }
454

455
    bands = height / 6;
81,256✔
456
    if ((height % 6) != 0) {
81,256✔
457
        bands += 1;
60,677✔
458
    }
5,058✔
459

460
    return bands;
46,735✔
461
}
6,177✔
462

463
static SIXELSTATUS
464
sixel_output_compute_transparent_extent(sixel_output_t const *output,
93,134✔
465
                                        int width,
466
                                        int height,
467
                                        int *encoded_width,
468
                                        int *encoded_height)
469
{
470
    int left;
40,268✔
471
    int top;
40,268✔
472

473
    if (output == NULL || encoded_width == NULL ||
93,134!
474
        encoded_height == NULL || width < 1 || height < 1) {
93,134!
475
        return SIXEL_BAD_ARGUMENT;
476
    }
477

478
    left = output->transparent_offset_left;
93,134✔
479
    top = output->transparent_offset_top;
93,134✔
480
    if (left < 0 || top < 0 ||
93,134!
481
        left > INT_MAX - width || top > INT_MAX - height) {
93,134!
482
        sixel_helper_set_additional_message(
×
483
            "transparent-offset makes encoded size overflow.");
484
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
485
    }
486

487
    *encoded_width = width + left;
93,134✔
488
    *encoded_height = height + top;
93,134✔
489
    return SIXEL_OK;
93,134✔
490
}
6,370✔
491

492
#if SIXEL_ENABLE_THREADS
493
static int sixel_parallel_band_writer(char *data, int size, void *priv);
494
static int sixel_parallel_worker_main(sixel_thread_pool_job_t job,
495
                                      void *userdata,
496
                                      void *workspace);
497
static SIXELSTATUS
498
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
499
                             sixel_index_t *pixels,
500
                             int width,
501
                             int height,
502
                             int ncolors,
503
                             int keycolor,
504
                             unsigned char *palstate,
505
                             sixel_output_t *output,
506
                             sixel_allocator_t *allocator,
507
                             int requested_threads,
508
                             int worker_capacity,
509
                             int queue_capacity,
510
                             int pin_threads,
511
                             sixel_timeline_logger_t *logger);
512
static SIXELSTATUS sixel_parallel_context_grow(sixel_parallel_context_t *ctx,
513
                                              int target_threads);
514
static void sixel_parallel_submit_band(sixel_parallel_context_t *ctx,
515
                                       int band_index);
516
static void
517
sixel_parallel_submit_source_empty_bands(sixel_parallel_context_t *ctx);
518
static SIXELSTATUS sixel_parallel_context_wait(sixel_parallel_context_t *ctx,
519
                                               int force_abort);
520
static void sixel_parallel_palette_row_ready(void *priv, int row_index);
521
static int
522
sixel_parallel_context_is_ormode(sixel_parallel_context_t const *ctx);
523
static SIXELSTATUS
524
sixel_parallel_worker_flush_output(sixel_parallel_worker_state_t *state);
525
static SIXELSTATUS sixel_encode_emit_palette(int bodyonly,
526
                          int ncolors,
527
                          int keycolor,
528
                          unsigned char const *palette,
529
                          float const *palette_float,
530
                          sixel_output_t *output);
531

532
static void
533
sixel_parallel_context_init(sixel_parallel_context_t *ctx)
21,474✔
534
{
535
    memset(ctx, 0, sizeof(*ctx));
21,474✔
536
    ctx->pixels = NULL;
21,474✔
537
    ctx->keycolor = (-1);
21,474✔
538
    ctx->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
21,474✔
539
    ctx->writer_error = SIXEL_OK;
21,474✔
540
}
14,275✔
541

542
static int
543
sixel_parallel_context_is_ormode(sixel_parallel_context_t const *ctx)
251,858✔
544
{
545
    if (ctx == NULL || ctx->output == NULL) {
251,623!
546
        return 0;
7✔
547
    }
548
    return ctx->output->ormode != 0 ? 1 : 0;
233,914✔
549
}
25,922✔
550

551
static void
552
sixel_parallel_worker_release_nodes(sixel_parallel_worker_state_t *state,
175,032✔
553
                                    sixel_allocator_t *allocator)
554
{
555
    sixel_node_t *np;
72,796✔
556

557
    if (state == NULL || state->output == NULL) {
175,032!
558
        return;
559
    }
560

561
    while ((np = state->output->node_free) != NULL) {
6,932,483✔
562
        state->output->node_free = np->next;
6,757,451✔
563
        sixel_allocator_free(allocator, np);
6,757,451✔
564
    }
565
    state->output->node_top = NULL;
175,032✔
566
}
14,806✔
567

568
static void
569
sixel_parallel_worker_cleanup(sixel_parallel_worker_state_t *state,
65,495✔
570
                              sixel_allocator_t *allocator)
571
{
572
    if (state == NULL) {
65,495✔
573
        return;
12,556✔
574
    }
575
    sixel_parallel_worker_release_nodes(state, allocator);
43,283✔
576
    if (state->output != NULL) {
43,283!
577
        sixel_output_unref(state->output);
43,283✔
578
        state->output = NULL;
43,283✔
579
    }
3,682✔
580
    sixel_encode_work_cleanup(&state->work, allocator);
43,283✔
581
    sixel_band_state_reset(&state->band);
43,283✔
582
    state->initialized = 0;
43,283✔
583
    state->index = 0;
43,283✔
584
    state->writer_error = SIXEL_OK;
43,283✔
585
    state->band_buffer = NULL;
43,283✔
586
    state->context = NULL;
43,283✔
587
}
5,521✔
588

589
static void
590
sixel_parallel_context_cleanup(sixel_parallel_context_t *ctx)
21,474✔
591
{
592
    int i;
8,962✔
593

594
    if (ctx->workers != NULL) {
21,474!
595
        for (i = 0; i < ctx->worker_capacity; i++) {
86,969✔
596
            sixel_parallel_worker_cleanup(ctx->workers[i], ctx->allocator);
65,495✔
597
        }
5,521✔
598
        free(ctx->workers);
21,474✔
599
        ctx->workers = NULL;
21,474✔
600
    }
1,806✔
601
    sixel_parallel_writer_stop(ctx, 1);
21,474✔
602
    if (ctx->bands != NULL) {
21,474!
603
        if (ctx->band_count < 0) {
21,474!
604
            ctx->band_count = 0;
605
        }
606
#if defined(_MSC_VER)
607
#pragma warning(push)
608
#pragma warning(disable : 6001)
609
#endif
610
        for (i = 0; i < ctx->band_count; i++) {
153,224✔
611
            free(ctx->bands[i].data);
131,750✔
612
            ctx->bands[i].data = NULL;
131,750✔
613
        }
11,124✔
614
#if defined(_MSC_VER)
615
#pragma warning(pop)
616
#endif
617
        free(ctx->bands);
21,474✔
618
        ctx->bands = NULL;
21,474✔
619
    }
1,806✔
620
    free(ctx->band_source_rows_total);
21,474✔
621
    ctx->band_source_rows_total = NULL;
21,474✔
622
    free(ctx->band_source_rows_ready);
21,474✔
623
    ctx->band_source_rows_ready = NULL;
21,474✔
624
    ctx->band_count = 0;
21,474✔
625
    if (ctx->pool != NULL) {
21,474!
626
        ctx->pool->vtbl->unref(ctx->pool);
21,474✔
627
        ctx->pool = NULL;
21,474✔
628
    }
1,806✔
629
    if (ctx->cond_ready) {
21,474!
630
        sixel_cond_destroy(&ctx->cond_band_ready);
21,474✔
631
        ctx->cond_ready = 0;
21,474✔
632
    }
1,806✔
633
    if (ctx->mutex_ready) {
21,474!
634
        sixel_mutex_destroy(&ctx->mutex);
21,474✔
635
        ctx->mutex_ready = 0;
21,474✔
636
    }
1,806✔
637
}
21,474✔
638

639
/*
640
 * Abort the pipeline when either a worker or the writer encounters an error.
641
 * The helper normalizes the `writer_error` bookkeeping so later callers see a
642
 * consistent stop request regardless of whether the mutex has been
643
 * initialized.
644
 */
645
static void
646
sixel_parallel_context_abort_locked(sixel_parallel_context_t *ctx,
647
                                    SIXELSTATUS status)
648
{
649
    if (ctx == NULL) {
×
650
        return;
651
    }
652
    if (!ctx->mutex_ready) {
×
653
        if (ctx->writer_error == SIXEL_OK) {
×
654
            ctx->writer_error = status;
655
        }
656
        ctx->writer_should_stop = 1;
657
        return;
658
    }
659

660
    sixel_mutex_lock(&ctx->mutex);
661
    if (ctx->writer_error == SIXEL_OK) {
×
662
        ctx->writer_error = status;
663
    }
664
    ctx->writer_should_stop = 1;
665
    sixel_cond_broadcast(&ctx->cond_band_ready);
666
    sixel_mutex_unlock(&ctx->mutex);
667
}
668

669
/*
670
 * Determine whether additional bands should be queued or executed.  The
671
 * producer and workers call this guard to avoid redundant work once the writer
672
 * decides to shut the pipeline down.
673
 */
674
static int
675
sixel_parallel_jobs_allowed(sixel_parallel_context_t *ctx)
131,731✔
676
{
677
    int accept;
55,087✔
678

679
    if (ctx == NULL) {
131,731✔
680
        return 0;
681
    }
682
    if (!ctx->mutex_ready) {
131,731!
683
        if (ctx->writer_should_stop || ctx->writer_error != SIXEL_OK) {
×
684
            return 0;
685
        }
686
        return 1;
687
    }
688

689
    sixel_mutex_lock(&ctx->mutex);
131,731✔
690
    accept = (!ctx->writer_should_stop && ctx->writer_error == SIXEL_OK);
131,750!
691
    sixel_mutex_unlock(&ctx->mutex);
131,750✔
692
    return accept;
131,747✔
693
}
11,124✔
694

695
static void
696
sixel_parallel_worker_reset(sixel_parallel_worker_state_t *state)
131,743✔
697
{
698
    if (state == NULL || state->output == NULL) {
131,743!
699
        return;
6✔
700
    }
701

702
    sixel_band_state_reset(&state->band);
131,737✔
703
    if (!sixel_parallel_context_is_ormode(state->context)) {
131,736✔
704
        sixel_band_clear_map(&state->work);
131,043✔
705
    }
11,064✔
706
    /* Parallel workers reset band-local buffers and output. */
707
    state->writer_error = SIXEL_OK;
131,709✔
708
    state->output->pos = 0;
131,709✔
709
    state->output->save_count = 0;
131,709✔
710
    state->output->save_pixel = 0;
131,709✔
711
    state->output->active_palette = (-1);
131,709✔
712
    state->output->node_top = NULL;
131,709✔
713
    state->output->node_free = NULL;
131,709✔
714
}
11,121✔
715

716
static SIXELSTATUS
717
sixel_parallel_worker_prepare(sixel_parallel_worker_state_t *state,
131,743✔
718
                              sixel_parallel_context_t *ctx)
719
{
720
    SIXELSTATUS status;
55,088✔
721

722
    if (state->initialized) {
131,743✔
723
        return SIXEL_OK;
51,080✔
724
    }
725

726
    sixel_encode_work_init(&state->work);
43,280✔
727
    sixel_band_state_reset(&state->band);
43,262✔
728
    state->writer_error = SIXEL_OK;
43,270✔
729
    state->band_buffer = NULL;
43,270✔
730
    state->context = ctx;
43,270✔
731

732
    if (!sixel_parallel_context_is_ormode(ctx)) {
43,270✔
733
        status = sixel_encode_work_allocate(&state->work,
46,791✔
734
                                            ctx->encoded_width,
3,669✔
735
                                            ctx->ncolors,
3,669✔
736
                                            ctx->allocator);
3,669✔
737
        if (SIXEL_FAILED(status)) {
43,134!
738
            return status;
739
        }
740
    }
3,669✔
741

742
    status = sixel_encoder_core_create_output_from_factory(&state->output,
46,966✔
743
                                              sixel_parallel_band_writer,
744
                                              state,
3,682✔
745
                                              ctx->allocator);
3,682✔
746
    if (SIXEL_FAILED(status)) {
43,277!
747
        if (!sixel_parallel_context_is_ormode(ctx)) {
×
748
            sixel_encode_work_cleanup(&state->work, ctx->allocator);
749
        }
750
        return status;
751
    }
752

753
    state->output->has_8bit_control = ctx->output->has_8bit_control;
43,277✔
754
    state->output->has_sixel_scrolling = ctx->output->has_sixel_scrolling;
43,277✔
755
    state->output->has_sdm_glitch = ctx->output->has_sdm_glitch;
43,277✔
756
    state->output->has_gri_arg_limit = ctx->output->has_gri_arg_limit;
43,277✔
757
    state->output->skip_dcs_envelope = 1;
43,277✔
758
    state->output->skip_header = 1;
43,277✔
759
    state->output->palette_type = ctx->output->palette_type;
43,277✔
760
    state->output->colorspace = ctx->output->colorspace;
43,277✔
761
    state->output->source_colorspace = ctx->output->source_colorspace;
43,277✔
762
    state->output->pixelformat = ctx->output->pixelformat;
43,277✔
763
    state->output->penetrate_multiplexer =
43,277✔
764
        ctx->output->penetrate_multiplexer;
43,277✔
765
    state->output->encode_policy = ctx->output->encode_policy;
43,277✔
766
    state->output->ormode = ctx->output->ormode;
43,277✔
767

768
    state->initialized = 1;
43,277✔
769
    state->index = (-1);
43,277✔
770

771
    if (ctx->mutex_ready) {
43,277!
772
        sixel_mutex_lock(&ctx->mutex);
43,277✔
773
    }
3,681✔
774
    if (ctx->worker_registered < ctx->worker_capacity) {
43,282!
775
        state->index = ctx->worker_registered;
43,283✔
776
        ctx->workers[state->index] = state;
43,283✔
777
        ctx->worker_registered += 1;
43,283✔
778
    }
3,682✔
779
    if (ctx->mutex_ready) {
43,282!
780
        sixel_mutex_unlock(&ctx->mutex);
43,283✔
781
    }
3,682✔
782

783
    if (state->index < 0) {
43,280!
784
        sixel_parallel_worker_cleanup(state, ctx->allocator);
785
        return SIXEL_RUNTIME_ERROR;
786
    }
787

788
    return SIXEL_OK;
25,577✔
789
}
11,124✔
790

791
static SIXELSTATUS
792
sixel_parallel_context_grow(sixel_parallel_context_t *ctx, int target_threads)
18,928✔
793
{
794
    int capped_target;
7,901✔
795
    int delta;
7,901✔
796
    int status;
7,901✔
797

798
    if (ctx == NULL || ctx->pool == NULL) {
18,928!
799
        return SIXEL_BAD_ARGUMENT;
800
    }
801

802
    capped_target = target_threads;
18,928✔
803
    if (capped_target > ctx->worker_capacity) {
18,928✔
804
        capped_target = ctx->worker_capacity;
7,504✔
805
    }
1,072✔
806
    if (ctx->band_count > 0 && capped_target > ctx->band_count) {
18,928!
807
        capped_target = ctx->band_count;
7,901✔
808
    }
809
    if (capped_target <= ctx->thread_count) {
18,928✔
810
        return SIXEL_OK;
3,080✔
811
    }
812

813
    delta = capped_target - ctx->thread_count;
13,658✔
814
    status = ctx->pool->vtbl->grow(ctx->pool, delta);
13,658✔
815
    if (SIXEL_FAILED(status)) {
13,658!
816
        return status;
817
    }
818
    ctx->thread_count += delta;
13,658✔
819

820
    if (ctx->logger != NULL) {
13,658✔
821
        sixel_timeline_logger_logf(ctx->logger,
36✔
822
                          "controller",
823
                          "encode",
824
                          "grow_workers",
825
                          -1);
826
    }
3✔
827

828
    return SIXEL_OK;
7,947✔
829
}
1,593✔
830

831
static int
832
sixel_parallel_band_writer(char *data, int size, void *priv)
131,134✔
833
{
834
    sixel_parallel_worker_state_t *state;
54,840✔
835
    sixel_parallel_band_buffer_t *band;
54,840✔
836
    size_t required;
54,840✔
837
    size_t capacity;
54,840✔
838
    size_t new_capacity;
54,840✔
839
    unsigned char *tmp;
54,840✔
840

841
    state = (sixel_parallel_worker_state_t *)priv;
131,134✔
842
    if (state == NULL || data == NULL || size <= 0) {
131,134!
843
        return size;
10✔
844
    }
845
    band = state->band_buffer;
131,126✔
846
    if (band == NULL) {
131,126!
847
        state->writer_error = SIXEL_RUNTIME_ERROR;
848
        return size;
849
    }
850
    if (state->writer_error != SIXEL_OK) {
131,126!
851
        return size;
852
    }
853

854
    required = band->used + (size_t)size;
131,126✔
855
    if (required < band->used) {
131,126!
856
        state->writer_error = SIXEL_BAD_INTEGER_OVERFLOW;
857
        return size;
858
    }
859
    capacity = band->size;
131,126✔
860
    if (required > capacity) {
131,126!
861
        if (capacity == 0) {
131,129!
862
            new_capacity = (size_t)SIXEL_OUTPUT_PACKET_SIZE;
109,515✔
863
        } else {
11,073✔
864
            new_capacity = capacity;
1✔
865
        }
866
        while (new_capacity < required) {
131,129!
867
            if (new_capacity > SIZE_MAX / 2) {
×
868
                new_capacity = required;
869
                break;
870
            }
871
            new_capacity *= 2;
872
        }
873
        tmp = (unsigned char *)realloc(band->data, new_capacity);
131,129✔
874
        if (tmp == NULL) {
131,129!
875
            state->writer_error = SIXEL_BAD_ALLOCATION;
876
            return size;
877
        }
878
        band->data = tmp;
131,129✔
879
        band->size = new_capacity;
131,129✔
880
    }
11,074✔
881

882
    memcpy(band->data + band->used, data, (size_t)size);
131,126✔
883
    band->used += (size_t)size;
131,126✔
884

885
    return size;
131,126✔
886
}
11,074✔
887

888
static SIXELSTATUS
889
sixel_parallel_worker_flush_output(sixel_parallel_worker_state_t *state)
131,733✔
890
{
891
    if (state == NULL || state->output == NULL) {
131,733!
892
        return SIXEL_BAD_ARGUMENT;
3✔
893
    }
894
    if (state->output->pos > 0) {
131,736✔
895
        state->writer_error = sixel_output_write_bytes(
251,204✔
896
            state->output,
11,072✔
897
            (char *)state->output->buffer,
131,129✔
898
            state->output->pos);
76,298✔
899
        state->output->pos = 0;
131,147✔
900
    }
11,072✔
901
    if (state->writer_error != SIXEL_OK) {
131,754!
902
        return state->writer_error;
903
    }
904
    return SIXEL_OK;
76,661✔
905
}
11,124✔
906

907
static SIXELSTATUS
908
sixel_parallel_create_pool(sixel_thread_pool_t **pool,
21,474✔
909
                           int threads,
910
                           int queue_depth,
911
                           size_t workspace_size,
912
                           sixel_thread_pool_worker_function_t worker,
913
                           void *userdata)
914
{
915
    sixel_threadpool_service_t *service;
8,962✔
916
    sixel_thread_pool_create_request_t request;
8,962✔
917
    void *service_object;
8,962✔
918
    SIXELSTATUS status;
8,962✔
919

920
    if (pool != NULL) {
21,474✔
921
        *pool = NULL;
21,474✔
922
    }
1,806✔
923
    if (pool == NULL) {
21,474!
924
        return SIXEL_BAD_ARGUMENT;
925
    }
926

927
    service = NULL;
21,474✔
928
    service_object = NULL;
21,474✔
929
    status = sixel_components_getservice("services/threadpool",
21,474✔
930
                                         &service_object);
931
    if (SIXEL_FAILED(status)) {
21,474!
932
        return status;
933
    }
934
    service = (sixel_threadpool_service_t *)service_object;
21,474✔
935
    if (service == NULL || service->vtbl == NULL ||
21,474!
936
        service->vtbl->create_pool == NULL) {
21,474!
937
        if (service != NULL && service->vtbl != NULL &&
×
938
            service->vtbl->unref != NULL) {
×
939
            service->vtbl->unref(service);
940
        }
941
        return SIXEL_BAD_ARGUMENT;
942
    }
943

944
    request.threads = threads;
21,474✔
945
    request.queue_size = queue_depth;
21,474✔
946
    request.workspace_size = workspace_size;
21,474✔
947
    request.worker = worker;
21,474✔
948
    request.userdata = userdata;
21,474✔
949
    request.workspace_cleanup = NULL;
21,474✔
950
    status = service->vtbl->create_pool(service, &request, pool);
21,474✔
951
    if (service->vtbl->unref != NULL) {
21,474!
952
        service->vtbl->unref(service);
21,474✔
953
    }
1,806✔
954

955
    return status;
12,512✔
956
}
1,806✔
957

958
static void
959
sixel_parallel_prepare_band_source_rows(sixel_parallel_context_t *ctx)
21,474✔
960
{
961
    int band_index;
8,962✔
962
    int band_start;
8,962✔
963
    int band_end;
8,962✔
964
    int source_start;
8,962✔
965
    int source_end;
8,962✔
966

967
    if (ctx == NULL || ctx->band_source_rows_total == NULL ||
21,474!
968
            ctx->band_source_rows_ready == NULL) {
21,474✔
969
        return;
970
    }
971

972
    /*
973
     * PaletteApply reports readiness in source-image coordinates.  The encoder
974
     * consumes fixed six-line bands in encoded-image coordinates, and a
975
     * transparent offset shifts those two spaces apart.  Precomputing the
976
     * source rows required by each encoded band lets the producer dispatch only
977
     * after every row that band can read has actually been written.
978
     */
979
    for (band_index = 0; band_index < ctx->band_count; band_index++) {
153,224✔
980
        band_start = band_index * 6;
131,750✔
981
        band_end = ctx->encoded_height;
131,750✔
982
        if (band_start <= ctx->encoded_height - 6) {
131,750✔
983
            band_end = band_start + 6;
114,462✔
984
        }
9,668✔
985

986
        source_start = band_start - ctx->offset_top;
131,750✔
987
        source_end = band_end - ctx->offset_top;
131,750✔
988
        if (source_start < 0) {
131,750✔
989
            source_start = 0;
14✔
990
        }
2✔
991
        if (source_start > ctx->height) {
131,750!
992
            source_start = ctx->height;
993
        }
994
        if (source_end < 0) {
131,750!
995
            source_end = 0;
996
        }
997
        if (source_end > ctx->height) {
131,750!
998
            source_end = ctx->height;
999
        }
1000

1001
        ctx->band_source_rows_total[band_index] = source_end - source_start;
131,750✔
1002
        ctx->band_source_rows_ready[band_index] = 0;
131,750✔
1003
    }
11,124✔
1004
}
1,806✔
1005

1006
static void
1007
sixel_parallel_submit_source_empty_bands(sixel_parallel_context_t *ctx)
18,928✔
1008
{
1009
    int band_index;
7,901✔
1010

1011
    if (ctx == NULL || ctx->band_source_rows_total == NULL) {
18,928!
1012
        return;
1013
    }
1014

1015
    /*
1016
     * A positive top offset can create leading bands that contain only
1017
     * transparent padding.  No PaletteApply row can ever close those bands, so
1018
     * enqueue them explicitly before waiting for source-row notifications.
1019
     */
1020
    for (band_index = 0; band_index < ctx->band_count; band_index++) {
133,032✔
1021
        if (ctx->band_source_rows_total[band_index] == 0) {
114,104!
1022
            sixel_parallel_submit_band(ctx, band_index);
1023
        }
1024
    }
9,651✔
1025
}
1,593✔
1026

1027
static SIXELSTATUS
1028
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
21,474✔
1029
                             sixel_index_t *pixels,
1030
                             int width,
1031
                             int height,
1032
                             int ncolors,
1033
                             int keycolor,
1034
                             unsigned char *palstate,
1035
                             sixel_output_t *output,
1036
                             sixel_allocator_t *allocator,
1037
                             int requested_threads,
1038
                             int worker_capacity,
1039
                             int queue_capacity,
1040
                             int pin_threads,
1041
                             sixel_timeline_logger_t *logger)
1042
{
1043
    SIXELSTATUS status;
8,962✔
1044
    int nbands;
8,962✔
1045
    int encoded_width;
8,962✔
1046
    int encoded_height;
8,962✔
1047
    int threads;
8,962✔
1048
    int i;
8,962✔
1049

1050
    if (ctx == NULL || pixels == NULL || output == NULL) {
21,474!
1051
        return SIXEL_BAD_ARGUMENT;
1052
    }
1053
    status = sixel_output_compute_transparent_extent(output,
23,280✔
1054
                                                     width,
1,806✔
1055
                                                     height,
1,806✔
1056
                                                     &encoded_width,
1057
                                                     &encoded_height);
1058
    if (SIXEL_FAILED(status)) {
21,474!
1059
        return status;
1060
    }
1061

1062
    ctx->pixels = pixels;
21,474✔
1063
    ctx->width = width;
21,474✔
1064
    ctx->height = height;
21,474✔
1065
    ctx->encoded_width = encoded_width;
21,474✔
1066
    ctx->encoded_height = encoded_height;
21,474✔
1067
    ctx->offset_left = output->transparent_offset_left;
21,474✔
1068
    ctx->offset_top = output->transparent_offset_top;
21,474✔
1069
    ctx->ncolors = ncolors;
21,474✔
1070
    ctx->keycolor = keycolor;
21,474✔
1071
    ctx->palstate = palstate;
21,474✔
1072
    ctx->encode_policy = output->encode_policy;
21,474✔
1073
    ctx->allocator = allocator;
21,474✔
1074
    ctx->output = output;
21,474✔
1075
    ctx->logger = logger;
21,474✔
1076
    ctx->pin_threads = (pin_threads != 0) ? 1 : 0;
21,474✔
1077
    ctx->bands = NULL;
21,474✔
1078
    ctx->band_count = 0;
21,474✔
1079
    ctx->workers = NULL;
21,474✔
1080
    ctx->worker_capacity = 0;
21,474✔
1081
    ctx->band_source_rows_total = NULL;
21,474✔
1082
    ctx->band_source_rows_ready = NULL;
21,474✔
1083

1084
    nbands = sixel_count_sixel_bands(encoded_height);
25,018!
1085
    if (nbands <= 0) {
21,474!
1086
        return SIXEL_OK;
1087
    }
1088
    threads = requested_threads;
21,474✔
1089
    if (threads > nbands) {
21,474!
1090
        threads = nbands;
1091
    }
1092
    if (threads < 1) {
21,474!
1093
        threads = 1;
1094
    }
1095
    ctx->thread_count = threads;
21,474✔
1096
    if (worker_capacity < threads) {
21,474!
1097
        worker_capacity = threads;
1098
    }
1099
    if (worker_capacity > nbands) {
21,474!
1100
        worker_capacity = nbands;
1101
    }
1102
    ctx->worker_capacity = worker_capacity;
21,474✔
1103

1104
    if (logger != NULL) {
21,474✔
1105
        sixel_timeline_logger_logf(logger,
132✔
1106
                          "controller",
1107
                          "encode",
1108
                          "context_begin",
1109
                          -1);
1110
    }
11✔
1111

1112
    ctx->bands = (sixel_parallel_band_buffer_t *)calloc((size_t)nbands,
21,474✔
1113
                                                        sizeof(*ctx->bands));
1114
    if (ctx->bands == NULL) {
21,474!
1115
        return SIXEL_BAD_ALLOCATION;
1116
    }
1117
    for (i = 0; i < nbands; ++i) {
153,224✔
1118
        ctx->bands[i].data = NULL;
131,750✔
1119
        ctx->bands[i].size = 0;
131,750✔
1120
        ctx->bands[i].used = 0;
131,750✔
1121
        ctx->bands[i].status = SIXEL_OK;
131,750✔
1122
        ctx->bands[i].ready = 0;
131,750✔
1123
        ctx->bands[i].dispatched = 0;
131,750✔
1124
    }
11,124✔
1125
    ctx->band_count = nbands;
21,474✔
1126

1127
    ctx->band_source_rows_total = (int *)calloc((size_t)nbands,
21,474✔
1128
                                                sizeof(int));
1129
    if (ctx->band_source_rows_total == NULL) {
21,474!
1130
        return SIXEL_BAD_ALLOCATION;
1131
    }
1132
    ctx->band_source_rows_ready = (int *)calloc((size_t)nbands,
21,474✔
1133
                                                sizeof(int));
1134
    if (ctx->band_source_rows_ready == NULL) {
21,474!
1135
        return SIXEL_BAD_ALLOCATION;
1136
    }
1137
    sixel_parallel_prepare_band_source_rows(ctx);
21,474✔
1138

1139
    ctx->workers = (sixel_parallel_worker_state_t **)
21,474✔
1140
        calloc((size_t)ctx->worker_capacity, sizeof(*ctx->workers));
21,474✔
1141
    if (ctx->workers == NULL) {
21,474!
1142
        return SIXEL_BAD_ALLOCATION;
1143
    }
1144

1145
    status = sixel_mutex_init(&ctx->mutex);
21,474✔
1146
    if (status != SIXEL_OK) {
21,474!
1147
        return status;
1148
    }
1149
    ctx->mutex_ready = 1;
21,474✔
1150

1151
    status = sixel_cond_init(&ctx->cond_band_ready);
21,474✔
1152
    if (status != SIXEL_OK) {
21,474!
1153
        return status;
1154
    }
1155
    ctx->cond_ready = 1;
21,474✔
1156

1157
    ctx->queue_capacity = queue_capacity;
21,474✔
1158
    if (ctx->queue_capacity < 1) {
21,474!
1159
        ctx->queue_capacity = nbands;
1160
    }
1161
    if (ctx->queue_capacity > nbands) {
21,474!
1162
        ctx->queue_capacity = nbands;
1163
    }
1164

1165
    status = sixel_parallel_create_pool(
21,474✔
1166
        &ctx->pool,
1,806✔
1167
        threads,
1,806✔
1168
        ctx->queue_capacity,
1,806✔
1169
        sizeof(sixel_parallel_worker_state_t),
1170
        sixel_parallel_worker_main,
1171
        ctx);
1,806✔
1172
    if (SIXEL_FAILED(status)) {
21,474!
1173
        return status;
1174
    }
1175

1176
    ctx->pool->vtbl->set_affinity(ctx->pool, ctx->pin_threads);
21,474✔
1177

1178
    /* Initialize writer-visible fields before the writer thread starts.
1179
     * Serialize initialization of writer-visible state so the writer thread
1180
     * cannot observe partially initialized fields on startup.
1181
     */
1182
    sixel_mutex_lock(&ctx->mutex);
21,474✔
1183
    ctx->next_band_to_flush = 0;
21,474✔
1184
    ctx->writer_should_stop = 0;
21,474✔
1185
    ctx->writer_error = SIXEL_OK;
21,474✔
1186

1187
    status = sixel_thread_create(&ctx->writer_thread,
23,280✔
1188
                                 sixel_parallel_writer_main,
1189
                                 ctx);
1,806✔
1190
    if (SIXEL_FAILED(status)) {
21,474!
1191
        sixel_mutex_unlock(&ctx->mutex);
1192
        return status;
1193
    }
1194
    ctx->writer_started = 1;
21,474✔
1195
    sixel_mutex_unlock(&ctx->mutex);
21,474✔
1196

1197
    return SIXEL_OK;
21,474✔
1198
}
1,806✔
1199

1200
static void
1201
sixel_parallel_submit_band(sixel_parallel_context_t *ctx, int band_index)
131,747✔
1202
{
1203
    sixel_thread_pool_job_t job;
55,090✔
1204
    SIXELSTATUS status;
55,090✔
1205
    int dispatch;
55,090✔
1206

1207
    if (ctx == NULL || ctx->pool == NULL) {
131,747!
1208
        return;
1209
    }
1210
    if (band_index < 0 || band_index >= ctx->band_count) {
131,750!
1211
        return;
1212
    }
1213

1214
    dispatch = 0;
131,750✔
1215
    /*
1216
     * Multiple producers may notify the same band when PaletteApply runs in
1217
     * parallel.  Guard the dispatched flag so only the first notifier pushes
1218
     * work into the encoder queue.
1219
     */
1220
    if (ctx->mutex_ready) {
131,750!
1221
        sixel_mutex_lock(&ctx->mutex);
131,750✔
1222
        if (!ctx->bands[band_index].dispatched
131,750!
1223
                && !ctx->writer_should_stop
121,025!
1224
                && ctx->writer_error == SIXEL_OK) {
131,750!
1225
            ctx->bands[band_index].dispatched = 1;
131,750✔
1226
            dispatch = 1;
131,750✔
1227
        }
11,124✔
1228
        sixel_mutex_unlock(&ctx->mutex);
131,750✔
1229
    } else {
11,124✔
1230
        if (!ctx->bands[band_index].dispatched
×
1231
                && sixel_parallel_jobs_allowed(ctx)) {
×
1232
            ctx->bands[band_index].dispatched = 1;
1233
            dispatch = 1;
1234
        }
1235
    }
1236

1237
    if (!dispatch) {
131,750!
1238
        return;
1239
    }
1240

1241
    sixel_fence_release();
131,750✔
1242
    if (ctx->logger != NULL) {
131,750✔
1243
        sixel_timeline_logger_logf(ctx->logger,
611✔
1244
                          "controller",
1245
                          "encode",
1246
                          "dispatch",
1247
                          band_index);
47✔
1248
    }
47✔
1249
    job.band_index = band_index;
131,750✔
1250
    status = ctx->pool->vtbl->push(ctx->pool, job);
131,750✔
1251
    if (SIXEL_FAILED(status)) {
131,749!
1252
        if (ctx->mutex_ready) {
×
1253
            sixel_mutex_lock(&ctx->mutex);
1254
            sixel_parallel_context_abort_locked(ctx, status);
1255
            sixel_mutex_unlock(&ctx->mutex);
1256
        } else {
1257
            ctx->writer_error = status;
1258
        }
1259
    }
1260
}
11,124✔
1261

1262
static SIXELSTATUS
1263
sixel_parallel_context_wait(sixel_parallel_context_t *ctx, int force_abort)
21,474✔
1264
{
1265
    int pool_error;
8,962✔
1266

1267
    if (ctx == NULL || ctx->pool == NULL) {
21,474!
1268
        return SIXEL_BAD_ARGUMENT;
1269
    }
1270

1271
    ctx->pool->vtbl->finish(ctx->pool);
21,474✔
1272
    pool_error = ctx->pool->vtbl->get_error(ctx->pool);
21,474✔
1273
    sixel_parallel_writer_stop(ctx, force_abort || pool_error != SIXEL_OK);
21,474!
1274

1275
    if (pool_error != SIXEL_OK) {
21,474!
1276
        return pool_error;
1277
    }
1278
    if (ctx->writer_error != SIXEL_OK) {
21,474!
1279
        return ctx->writer_error;
1280
    }
1281

1282
    return SIXEL_OK;
12,512✔
1283
}
1,806✔
1284

1285
/*
1286
 * Producer callback invoked after PaletteApply finishes a scanline.  The
1287
 * helper promotes every sixth row (or the final partial band) into the job
1288
 * queue so workers can begin encoding while dithering continues.
1289
 */
1290
static void
1291
sixel_parallel_palette_row_ready(void *priv, int row_index)
643,959✔
1292
{
1293
    sixel_parallel_row_notifier_t *notifier;
269,422✔
1294
    sixel_parallel_context_t *ctx;
269,422✔
1295
    sixel_timeline_logger_t *logger;
269,422✔
1296
    int band_height;
269,422✔
1297
    int band_index;
269,422✔
1298
    int ready_row;
269,422✔
1299
    int should_dispatch;
269,422✔
1300
    int rows_ready;
269,422✔
1301
    int rows_total;
269,422✔
1302

1303
    notifier = (sixel_parallel_row_notifier_t *)priv;
643,959✔
1304
    if (notifier == NULL) {
643,959✔
1305
        return;
1306
    }
1307
    ctx = notifier->context;
643,959✔
1308
    logger = notifier->logger;
643,959✔
1309
    if (ctx == NULL || ctx->band_count <= 0 || ctx->height <= 0) {
643,959!
1310
        return;
8✔
1311
    }
1312
    if (row_index < 0 || row_index >= ctx->height) {
643,960!
1313
        return;
3✔
1314
    }
1315
    band_height = notifier->band_height;
643,959✔
1316
    if (band_height < 1) {
643,959!
1317
        band_height = 6;
1318
    }
1319

1320
    /*
1321
     * Dither workers may complete source-row ranges out of order.  A shifted
1322
     * encoded band can also straddle two dither bands, so the last source row
1323
     * seen by one worker is not enough to prove the encoded band is complete.
1324
     * Count source-row arrivals per encoded six-line band and dispatch only
1325
     * when every source row for that encoded band is present.
1326
     */
1327
    ready_row = ctx->offset_top + row_index;
643,959✔
1328
    band_index = ready_row / band_height;
643,959✔
1329
    if (band_index >= ctx->band_count) {
643,959!
1330
        band_index = ctx->band_count - 1;
1331
    }
1332
    if (band_index < 0) {
643,959!
1333
        return;
1334
    }
1335

1336
    should_dispatch = 0;
643,959✔
1337
    if (ctx->band_source_rows_total != NULL &&
643,959!
1338
            ctx->band_source_rows_ready != NULL) {
643,956!
1339
        if (ctx->mutex_ready) {
643,959!
1340
            sixel_mutex_lock(&ctx->mutex);
643,961✔
1341
        }
54,482✔
1342
        rows_total = ctx->band_source_rows_total[band_index];
643,981✔
1343
        rows_ready = ctx->band_source_rows_ready[band_index];
643,981✔
1344
        if (rows_total > 0 && rows_ready < rows_total) {
643,981!
1345
            rows_ready += 1;
643,984✔
1346
            ctx->band_source_rows_ready[band_index] = rows_ready;
643,984✔
1347
            if (rows_ready == rows_total) {
643,984✔
1348
                should_dispatch = 1;
335,802✔
1349
            }
9,651✔
1350
        }
54,483✔
1351
        if (ctx->mutex_ready) {
643,981!
1352
            sixel_mutex_unlock(&ctx->mutex);
643,984✔
1353
        }
54,483✔
1354
    } else if ((ready_row % band_height) == band_height - 1 ||
54,488!
1355
            row_index == ctx->height - 1) {
×
1356
        should_dispatch = 1;
1357
    }
1358

1359
    if (!should_dispatch) {
643,982✔
1360
        return;
395,383✔
1361
    }
1362

1363
    if (logger != NULL) {
114,106✔
1364
        sixel_timeline_logger_logf(logger,
611✔
1365
                          "controller",
1366
                          "encode",
1367
                          "row_gate",
1368
                          band_index);
47✔
1369
    }
47✔
1370

1371
    sixel_parallel_submit_band(ctx, band_index);
114,106✔
1372
}
54,484✔
1373

1374
static SIXELSTATUS
1375
sixel_parallel_flush_band(sixel_parallel_context_t *ctx, int band_index)
131,750✔
1376
{
1377
    sixel_parallel_band_buffer_t *band;
55,090✔
1378
    size_t offset;
55,090✔
1379
    size_t chunk;
55,090✔
1380

1381
    band = &ctx->bands[band_index];
131,750✔
1382
    if (ctx->logger != NULL) {
131,750✔
1383
        sixel_timeline_logger_logf(ctx->logger,
611✔
1384
                          "worker",
1385
                          "encode",
1386
                          "writer_flush",
1387
                          band_index);
47✔
1388
    }
47✔
1389
    offset = 0;
110,032✔
1390
    while (offset < band->used) {
264,700✔
1391
        chunk = band->used - offset;
132,950✔
1392
        if (chunk > (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos)) {
132,950✔
1393
            chunk = (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos);
1,050✔
1394
        }
150✔
1395
        memcpy(ctx->output->buffer + ctx->output->pos,
155,398✔
1396
               band->data + offset,
132,950✔
1397
               chunk);
11,224✔
1398
        sixel_advance(ctx->output, (int)chunk);
132,950✔
1399
        offset += chunk;
132,950✔
1400
    }
1401
    return SIXEL_OK;
131,750✔
1402
}
1403

1404
static int
1405
sixel_parallel_worker_main(sixel_thread_pool_job_t job,
131,746✔
1406
                           void *userdata,
1407
                           void *workspace)
1408
{
1409
    sixel_parallel_context_t *ctx;
55,089✔
1410
    sixel_parallel_worker_state_t *state;
55,089✔
1411
    sixel_parallel_band_buffer_t *band;
55,089✔
1412
    SIXELSTATUS status;
55,089✔
1413
    int band_index;
55,089✔
1414
    int band_start;
55,089✔
1415
    int band_height;
55,089✔
1416
    int row_index;
55,089✔
1417
    int absolute_row;
55,089✔
1418
    int last_row_index;
55,089✔
1419
    int nplanes;
55,089✔
1420

1421
    ctx = (sixel_parallel_context_t *)userdata;
131,746✔
1422
    state = (sixel_parallel_worker_state_t *)workspace;
131,746✔
1423

1424
    if (ctx == NULL || state == NULL) {
131,746!
1425
        return SIXEL_BAD_ARGUMENT;
4✔
1426
    }
1427

1428
    band = NULL;
131,748✔
1429
    status = SIXEL_OK;
131,748✔
1430
    band_index = job.band_index;
131,748✔
1431
    band_start = 0;
131,748✔
1432
    band_height = 0;
131,748✔
1433
    last_row_index = -1;
131,748✔
1434
    if (band_index < 0 || band_index >= ctx->band_count) {
131,748!
1435
        status = SIXEL_BAD_ARGUMENT;
6✔
1436
        goto cleanup;
6✔
1437
    }
1438

1439
    band = &ctx->bands[band_index];
131,747✔
1440
    if (ctx->mutex_ready) {
131,747!
1441
        /* Synchronize band state reset with the writer thread. */
1442
        sixel_mutex_lock(&ctx->mutex);
131,746✔
1443
        band->used = 0;
131,750✔
1444
        band->status = SIXEL_OK;
131,750✔
1445
        band->ready = 0;
131,750✔
1446
        sixel_mutex_unlock(&ctx->mutex);
131,750✔
1447
    } else {
11,124✔
1448
        band->used = 0;
1✔
1449
        band->status = SIXEL_OK;
1✔
1450
        band->ready = 0;
1✔
1451
    }
1452

1453
    sixel_fence_acquire();
131,749✔
1454

1455
    status = sixel_parallel_worker_prepare(state, ctx);
131,749✔
1456
    if (SIXEL_FAILED(status)) {
131,744!
1457
        goto cleanup;
1458
    }
1459

1460
    if (!sixel_parallel_jobs_allowed(ctx)) {
131,744!
1461
        if (ctx->mutex_ready) {
×
1462
            sixel_mutex_lock(&ctx->mutex);
1463
            if (ctx->writer_error != SIXEL_OK) {
×
1464
                status = ctx->writer_error;
1465
            } else {
1466
                status = SIXEL_RUNTIME_ERROR;
1467
            }
1468
            sixel_mutex_unlock(&ctx->mutex);
1469
        } else if (ctx->writer_error != SIXEL_OK) {
×
1470
            status = ctx->writer_error;
1471
        } else {
1472
            status = SIXEL_RUNTIME_ERROR;
1473
        }
1474
        goto cleanup;
1475
    }
1476

1477
    state->band_buffer = band;
131,747✔
1478
    sixel_parallel_worker_reset(state);
131,747✔
1479

1480
    band_start = band_index * 6;
131,708✔
1481
    band_height = ctx->encoded_height - band_start;
131,708✔
1482
    if (band_height > 6) {
131,708✔
1483
        band_height = 6;
64,134✔
1484
    }
9,318✔
1485
    if (band_height <= 0) {
85,593!
1486
        goto cleanup;
1487
    }
1488

1489
    if (ctx->logger != NULL) {
131,708✔
1490
        sixel_timeline_logger_logf(ctx->logger,
611✔
1491
                          "worker",
1492
                          "encode",
1493
                          "worker_start",
1494
                          band_index);
47✔
1495
    }
47✔
1496

1497
    if (sixel_parallel_context_is_ormode(ctx)) {
186,786!
1498
        nplanes = sixel_encode_body_ormode_nplanes(ctx->ncolors);
986✔
1499
        status = sixel_encode_body_ormode_band(ctx->pixels,
1,044✔
1500
                                               ctx->width,
58✔
1501
                                               ctx->height,
58✔
1502
                                               band_index,
58✔
1503
                                               nplanes,
58✔
1504
                                               state->output);
58✔
1505
        if (SIXEL_FAILED(status)) {
696!
1506
            goto cleanup;
1507
        }
1508
        status = sixel_parallel_worker_flush_output(state);
696✔
1509
        if (SIXEL_FAILED(status)) {
696!
1510
            goto cleanup;
1511
        }
1512
        goto done;
696✔
1513
    }
1514

1515
    for (row_index = 0; row_index < band_height; row_index++) {
873,178✔
1516
        absolute_row = band_start + row_index;
742,135✔
1517
        status = sixel_band_classify_row(&state->work,
804,815✔
1518
                                         &state->band,
62,680✔
1519
                                         ctx->pixels,
62,680✔
1520
                                         ctx->width,
62,680✔
1521
                                         ctx->height,
62,680✔
1522
                                         ctx->encoded_width,
62,680✔
1523
                                         absolute_row,
62,680✔
1524
                                         ctx->offset_left,
62,680✔
1525
                                         ctx->offset_top,
62,680✔
1526
                                         ctx->ncolors,
62,680✔
1527
                                         ctx->keycolor,
62,680✔
1528
                                         ctx->palstate,
62,680✔
1529
                                         ctx->encode_policy);
62,680✔
1530
        if (SIXEL_FAILED(status)) {
742,155!
1531
            goto cleanup;
1532
        }
1533
    }
62,680✔
1534

1535
    status = sixel_band_compose(&state->work,
142,109✔
1536
                                &state->band,
11,066✔
1537
                                state->output,
11,066✔
1538
                                ctx->encoded_width,
11,066✔
1539
                                ctx->ncolors,
11,066✔
1540
                                ctx->keycolor,
11,066✔
1541
                                ctx->allocator);
11,066✔
1542
    if (SIXEL_FAILED(status)) {
131,050!
1543
        goto cleanup;
1544
    }
1545

1546
    last_row_index = band_start + band_height - 1;
131,050✔
1547
    status = sixel_band_emit(&state->work,
142,116✔
1548
                             &state->band,
11,066✔
1549
                             state->output,
11,066✔
1550
                             ctx->ncolors,
11,066✔
1551
                             ctx->keycolor,
11,066✔
1552
                             last_row_index);
11,066✔
1553
    if (SIXEL_FAILED(status)) {
131,029!
1554
        goto cleanup;
1555
    }
1556

1557
    status = sixel_put_flash(state->output);
131,029✔
1558
    if (SIXEL_FAILED(status)) {
131,011!
1559
        goto cleanup;
1560
    }
1561

1562
    status = sixel_parallel_worker_flush_output(state);
131,011✔
1563
    if (SIXEL_FAILED(status)) {
131,052!
1564
        goto cleanup;
1565
    }
1566

1567
    sixel_band_finish(&state->work, &state->band);
131,052✔
1568
done:
98,907✔
1569
    status = SIXEL_OK;
76,659✔
1570

1571
cleanup:
120,626✔
1572
    sixel_parallel_worker_release_nodes(state, ctx->allocator);
131,750✔
1573
    if (band != NULL && ctx->mutex_ready && ctx->cond_ready) {
131,746!
1574
        sixel_fence_release();
131,744✔
1575
        sixel_mutex_lock(&ctx->mutex);
131,744✔
1576
        band->status = status;
131,749✔
1577
        band->ready = 1;
131,749✔
1578
        sixel_cond_broadcast(&ctx->cond_band_ready);
131,749✔
1579
        sixel_mutex_unlock(&ctx->mutex);
131,749✔
1580
    }
11,123✔
1581
    if (ctx->logger != NULL) {
131,745✔
1582
        sixel_timeline_logger_logf(ctx->logger,
611✔
1583
                          "worker",
1584
                          "encode",
1585
                          "worker_done",
1586
                          band_index);
47✔
1587
    }
47✔
1588
    if (SIXEL_FAILED(status)) {
131,744!
1589
        return status;
1590
    }
1591
    return SIXEL_OK;
76,658✔
1592
}
11,124✔
1593

1594
static void
1595
sixel_parallel_writer_stop(sixel_parallel_context_t *ctx, int force_abort)
42,948✔
1596
{
1597
    int should_signal;
17,924✔
1598

1599
    if (ctx == NULL || !ctx->writer_started) {
42,948!
1600
        return;
12,512✔
1601
    }
1602

1603
    should_signal = ctx->mutex_ready && ctx->cond_ready;
21,474!
1604
    if (should_signal) {
21,474!
1605
        sixel_mutex_lock(&ctx->mutex);
21,474✔
1606
        if (force_abort) {
21,474!
1607
            ctx->writer_should_stop = 1;
1608
        }
1609
        sixel_cond_broadcast(&ctx->cond_band_ready);
21,474✔
1610
        sixel_mutex_unlock(&ctx->mutex);
21,474✔
1611
    } else if (force_abort) {
1,806!
1612
        ctx->writer_should_stop = 1;
1613
    }
1614

1615
    sixel_thread_join(&ctx->writer_thread);
21,474✔
1616
    ctx->writer_started = 0;
21,474✔
1617
    ctx->writer_should_stop = 0;
21,474✔
1618
    if (ctx->logger != NULL) {
21,474✔
1619
        sixel_timeline_logger_logf(ctx->logger,
132✔
1620
                          "writer",
1621
                          "encode",
1622
                          "writer_stop",
1623
                          -1);
1624
    }
11✔
1625
}
3,612✔
1626

1627
static int
1628
sixel_parallel_writer_main(void *arg)
21,474✔
1629
{
1630
    sixel_parallel_context_t *ctx;
8,962✔
1631
    sixel_parallel_band_buffer_t *band;
8,962✔
1632
    SIXELSTATUS status;
8,962✔
1633
    int band_index;
8,962✔
1634

1635
    ctx = (sixel_parallel_context_t *)arg;
21,474✔
1636
    if (ctx == NULL) {
21,474✔
1637
        return SIXEL_BAD_ARGUMENT;
1638
    }
1639

1640
    if (ctx->logger != NULL) {
21,474✔
1641
        sixel_timeline_logger_logf(ctx->logger,
132✔
1642
                                   "writer",
1643
                                   "encode",
1644
                                   "writer_start",
1645
                                   -1);
1646
    }
11✔
1647

1648
    for (;;) {
76,982✔
1649
        sixel_mutex_lock(&ctx->mutex);
153,224✔
1650
        while (!ctx->writer_should_stop &&
271,590!
1651
               ctx->next_band_to_flush < ctx->band_count) {
250,727✔
1652
    band_index = ctx->next_band_to_flush;
229,253✔
1653
    band = &ctx->bands[band_index];
229,253✔
1654
    if (band->ready) {
229,253✔
1655
        break;
76,660✔
1656
    }
1657
            sixel_cond_wait(&ctx->cond_band_ready, &ctx->mutex);
97,503✔
1658
        }
1659

1660
        if (ctx->writer_should_stop) {
153,224!
1661
            sixel_mutex_unlock(&ctx->mutex);
1662
            break;
1663
        }
1664

1665
        if (ctx->next_band_to_flush >= ctx->band_count) {
153,224✔
1666
            sixel_mutex_unlock(&ctx->mutex);
21,474✔
1667
            break;
21,474✔
1668
        }
1669

1670
        band_index = ctx->next_band_to_flush;
131,750✔
1671
        band = &ctx->bands[band_index];
131,750✔
1672
        if (!band->ready) {
131,750!
1673
            sixel_mutex_unlock(&ctx->mutex);
1674
            continue;
1675
        }
1676
        band->ready = 0;
131,750✔
1677
        ctx->next_band_to_flush += 1;
131,750✔
1678
        sixel_mutex_unlock(&ctx->mutex);
131,750✔
1679

1680
        sixel_fence_acquire();
131,750✔
1681
        status = band->status;
131,750✔
1682
        if (ctx->logger != NULL) {
131,750✔
1683
            sixel_timeline_logger_logf(ctx->logger,
611✔
1684
                              "writer",
1685
                              "encode",
1686
                              "writer_dequeue",
1687
                              band_index);
47✔
1688
        }
47✔
1689
        if (SIXEL_SUCCEEDED(status)) {
131,750!
1690
            status = sixel_parallel_flush_band(ctx, band_index);
131,750✔
1691
        }
11,124✔
1692
        if (SIXEL_FAILED(status)) {
131,750!
1693
            sixel_parallel_context_abort_locked(ctx, status);
1694
            break;
1695
        }
1696
    }
1697

1698
    return SIXEL_OK;
12,512✔
1699
}
1,806✔
1700

1701
static SIXELSTATUS
1702
sixel_encode_body_parallel(sixel_index_t *pixels,
2,546✔
1703
                           int width,
1704
                           int height,
1705
                           int ncolors,
1706
                           int keycolor,
1707
                           sixel_output_t *output,
1708
                           unsigned char *palstate,
1709
                           sixel_allocator_t *allocator,
1710
                           int requested_threads,
1711
                           int pin_threads)
1712
{
1713
    sixel_parallel_context_t ctx = {0};
2,546✔
1714
    SIXELSTATUS status;
1,061✔
1715
    int nbands;
1,061✔
1716
    int threads;
1,061✔
1717
    int i;
1,061✔
1718
    int queue_depth;
1,061✔
1719
    int encoded_width;
1,061✔
1720
    int encoded_height;
1,061✔
1721
    sixel_timeline_logger_t *logger;
1,061✔
1722

1723
    sixel_parallel_context_init(&ctx);
2,546✔
1724
    sixel_timeline_logger_prepare_default(allocator, &logger);
2,546✔
1725
    status = sixel_output_compute_transparent_extent(output,
2,759✔
1726
                                                     width,
213✔
1727
                                                     height,
213✔
1728
                                                     &encoded_width,
1729
                                                     &encoded_height);
1730
    if (SIXEL_FAILED(status)) {
2,546!
1731
        sixel_timeline_logger_unref(logger);
1732
        return status;
1733
    }
1734
    (void)encoded_width;
1,274✔
1735
    nbands = sixel_count_sixel_bands(encoded_height);
2,546!
1736
    if (nbands <= 0) {
2,546!
1737
        sixel_timeline_logger_unref(logger);
1738
        return SIXEL_OK;
1739
    }
1740

1741
    threads = requested_threads;
2,546✔
1742
    if (threads > nbands) {
2,546✔
1743
        threads = nbands;
631✔
1744
    }
91✔
1745
    if (threads < 1) {
2,546!
1746
        threads = 1;
1747
    }
1748
    ctx.thread_count = threads;
2,546✔
1749
    queue_depth = threads * 3;
2,546✔
1750
    if (queue_depth > nbands) {
2,546✔
1751
        queue_depth = nbands;
1,268✔
1752
    }
182✔
1753
    if (queue_depth < 1) {
2,546!
1754
        queue_depth = 1;
1755
    }
1756

1757
    status = sixel_parallel_context_begin(&ctx,
2,546✔
1758
                                          pixels,
213✔
1759
                                          width,
213✔
1760
                                          height,
213✔
1761
                                          ncolors,
213✔
1762
                                          keycolor,
213✔
1763
                                          palstate,
213✔
1764
                                          output,
213✔
1765
                                          allocator,
213✔
1766
                                          threads,
213✔
1767
                                          threads,
213✔
1768
                                          queue_depth,
213✔
1769
                                          pin_threads,
213✔
1770
                                          logger);
213✔
1771
    if (SIXEL_FAILED(status)) {
2,546!
1772
        sixel_parallel_context_cleanup(&ctx);
1773
        sixel_timeline_logger_unref(logger);
1774
        return status;
1775
    }
1776

1777
    for (i = 0; i < nbands; i++) {
20,192✔
1778
        sixel_parallel_submit_band(&ctx, i);
17,646✔
1779
    }
1,473✔
1780

1781
    status = sixel_parallel_context_wait(&ctx, 0);
2,546✔
1782
    if (SIXEL_FAILED(status)) {
2,546!
1783
        sixel_parallel_context_cleanup(&ctx);
1784
        sixel_timeline_logger_unref(logger);
1785
        return status;
1786
    }
1787

1788
    sixel_parallel_context_cleanup(&ctx);
2,546✔
1789
    sixel_timeline_logger_unref(logger);
2,546✔
1790
    return SIXEL_OK;
2,546✔
1791
}
213✔
1792
#endif
1793

1794
#if SIXEL_ENABLE_THREADS
1795
/*
1796
 * Execute PaletteApply, band encoding, and output emission as a pipeline.
1797
 * The producer owns the dithered index buffer and enqueues bands once every
1798
 * six rows have been produced.  Worker threads encode in parallel while the
1799
 * writer emits completed bands in-order to preserve deterministic output.
1800
 */
1801
static SIXELSTATUS
1802
sixel_encode_body_pipeline(unsigned char *pixels,
18,856✔
1803
                           int width,
1804
                           int height,
1805
                           unsigned char const *palette,
1806
                           float const *palette_float,
1807
                           sixel_dither_t *dither,
1808
                           sixel_output_t *output,
1809
                           int encode_threads)
1810
{
1811
    SIXELSTATUS status;
7,871✔
1812
    SIXELSTATUS wait_status;
7,871✔
1813
    sixel_parallel_context_t ctx = {0};
18,856✔
1814
    sixel_index_t *indexes;
7,871✔
1815
    sixel_index_t *result;
7,871✔
1816
    sixel_allocator_t *allocator;
7,871✔
1817
    size_t pixel_count;
7,871✔
1818
    size_t buffer_size;
7,871✔
1819
    int threads;
7,871✔
1820
    int nbands;
7,871✔
1821
    int queue_depth;
7,871✔
1822
    int waited;
7,871✔
1823
    int dither_threads_budget;
7,871✔
1824
    int worker_capacity;
7,871✔
1825
    int boost_target;
7,871✔
1826
    int encoded_width;
7,871✔
1827
    int encoded_height;
7,871✔
1828
    sixel_timeline_logger_t *logger;
7,871✔
1829
    int owns_logger;
7,871✔
1830
    sixel_parallel_row_notifier_t notifier;
7,871✔
1831

1832
    if (pixels == NULL
18,856!
1833
            || (palette == NULL && palette_float == NULL)
18,856!
1834
            || dither == NULL
18,856!
1835
            || output == NULL) {
18,856!
1836
        return SIXEL_BAD_ARGUMENT;
1837
    }
1838

1839
    threads = encode_threads;
18,856✔
1840
    status = sixel_output_compute_transparent_extent(output,
20,443✔
1841
                                                     width,
1,587✔
1842
                                                     height,
1,587✔
1843
                                                     &encoded_width,
1844
                                                     &encoded_height);
1845
    if (SIXEL_FAILED(status)) {
18,856!
1846
        return status;
1847
    }
1848
    (void)encoded_width;
9,458✔
1849
    nbands = sixel_count_sixel_bands(encoded_height);
18,856!
1850
    if (threads <= 1 || nbands <= 1) {
18,856!
1851
        return SIXEL_RUNTIME_ERROR;
1852
    }
1853

1854
    pixel_count = (size_t)width * (size_t)height;
18,856✔
1855
    if (height != 0 && pixel_count / (size_t)height != (size_t)width) {
18,856!
1856
        return SIXEL_BAD_INTEGER_OVERFLOW;
1857
    }
1858
    buffer_size = pixel_count * sizeof(*indexes);
18,856✔
1859
    allocator = dither->allocator;
18,856✔
1860
    indexes = (sixel_index_t *)sixel_allocator_malloc(allocator, buffer_size);
18,856✔
1861
    if (indexes == NULL) {
18,856!
1862
        return SIXEL_BAD_ALLOCATION;
1863
    }
1864

1865
    sixel_parallel_context_init(&ctx);
18,856✔
1866
    logger = dither->pipeline_logger;
18,856✔
1867
    owns_logger = 0;
18,856✔
1868
    if (logger == NULL) {
18,856!
1869
        logger = NULL;
18,856✔
1870
        sixel_timeline_logger_prepare_default(allocator, &logger);
18,856✔
1871
        owns_logger = logger != NULL ? 1 : 0;
18,856✔
1872
    }
1,587✔
1873
    notifier.context = &ctx;
18,856✔
1874
    notifier.logger = logger;
18,856✔
1875
    notifier.band_height = 6;
18,856✔
1876
    notifier.image_height = encoded_height;
18,856✔
1877
    waited = 0;
18,856✔
1878
    status = SIXEL_OK;
18,856✔
1879

1880
    status = sixel_encode_emit_palette(dither->bodyonly,
20,443✔
1881
                                       dither->ncolors,
1,587✔
1882
                                       dither->keycolor,
1,587✔
1883
                                       palette,
1,587✔
1884
                                       palette_float,
1,587✔
1885
                                       output);
1,587✔
1886
    if (SIXEL_FAILED(status)) {
18,856!
1887
        goto cleanup;
1888
    }
1889

1890
    queue_depth = threads * 3;
18,856✔
1891
    if (queue_depth > nbands) {
18,856✔
1892
        queue_depth = nbands;
7,532✔
1893
    }
1,076✔
1894
    if (queue_depth < 1) {
18,856!
1895
        queue_depth = 1;
1896
    }
1897

1898
    dither_threads_budget = dither->pipeline_dither_threads;
18,856✔
1899
    worker_capacity = threads + dither_threads_budget;
18,856✔
1900
    if (worker_capacity < threads) {
18,856!
1901
        worker_capacity = threads;
1902
    }
1903
    if (worker_capacity > nbands) {
18,856✔
1904
        worker_capacity = nbands;
7,497✔
1905
    }
1,071✔
1906

1907
    dither->pipeline_index_buffer = indexes;
18,856✔
1908
    dither->pipeline_index_size = buffer_size;
18,856✔
1909
    dither->pipeline_row_callback = sixel_parallel_palette_row_ready;
18,856✔
1910
    dither->pipeline_row_priv = &notifier;
18,856✔
1911
    dither->pipeline_logger = logger;
18,856✔
1912
    dither->pipeline_image_width = width;
18,856✔
1913
    dither->pipeline_image_height = height;
18,856✔
1914

1915
    if (logger != NULL) {
18,856✔
1916
        /*
1917
         * Record the thread split and band geometry before spawning workers.
1918
         * This clarifies why only a subset of hardware threads might appear
1919
         * in the log when the encoder side is clamped to keep the pipeline
1920
         * draining.
1921
         */
1922
        sixel_timeline_logger_logf(logger,
132✔
1923
                          "controller",
1924
                          "pipeline",
1925
                          "configure",
1926
                          -1);
1927
    }
11✔
1928

1929
    status = sixel_parallel_context_begin(&ctx,
18,856✔
1930
                                          indexes,
1,587✔
1931
                                          width,
1,587✔
1932
                                          height,
1,587✔
1933
                                          dither->ncolors,
1,587✔
1934
                                          dither->keycolor,
1,587✔
1935
                                          NULL,
1936
                                          output,
1,587✔
1937
                                          allocator,
1,587✔
1938
                                          threads,
1,587✔
1939
                                          worker_capacity,
1,587✔
1940
                                          queue_depth,
1,587✔
1941
                                          dither->pipeline_pin_threads,
1,587✔
1942
                                          logger);
1,587✔
1943
    if (SIXEL_FAILED(status)) {
18,856!
1944
        goto cleanup;
1945
    }
1946
    sixel_parallel_submit_source_empty_bands(&ctx);
18,856✔
1947

1948
    result = sixel_dither_apply_palette(dither, pixels, width, height);
18,856✔
1949
    if (result == NULL) {
18,856!
1950
        status = SIXEL_RUNTIME_ERROR;
1951
        goto cleanup;
1952
    }
1953
    if (result != indexes) {
18,856!
1954
        status = SIXEL_RUNTIME_ERROR;
1955
        goto cleanup;
1956
    }
1957
    if (dither->pipeline_accumulation_result_enabled != 0) {
18,856✔
1958
        status = sixel_dither_set_pipeline_accumulation_result_rgb(
119✔
1959
            dither,
7✔
1960
            indexes,
7✔
1961
            pixel_count,
7✔
1962
            palette,
7✔
1963
            (size_t)dither->ncolors);
84✔
1964
        if (SIXEL_FAILED(status)) {
84!
1965
            goto cleanup;
1966
        }
1967
    }
7✔
1968

1969
    /*
1970
     * All dithering work has finished at this point.  Reclaim the idle dither
1971
     * workers for encoding so the tail of the pipeline drains with additional
1972
     * parallelism instead of leaving those CPU resources unused.
1973
     */
1974
    boost_target = threads + dither_threads_budget;
18,856✔
1975
    status = sixel_parallel_context_grow(&ctx, boost_target);
18,856✔
1976
    if (SIXEL_FAILED(status)) {
18,856!
1977
        goto cleanup;
1978
    }
1979

1980
    status = sixel_parallel_context_wait(&ctx, 0);
18,856✔
1981
    waited = 1;
18,856✔
1982
    if (SIXEL_FAILED(status)) {
18,856!
1983
        goto cleanup;
1984
    }
1985

1986
cleanup:
17,269✔
1987
    dither->pipeline_row_callback = NULL;
18,856✔
1988
    dither->pipeline_row_priv = NULL;
18,856✔
1989
    dither->pipeline_index_buffer = NULL;
18,856✔
1990
    dither->pipeline_index_size = 0;
18,856✔
1991
    dither->pipeline_image_width = 0;
18,856✔
1992
    dither->pipeline_image_height = 0;
18,856✔
1993
    dither->pipeline_transparent_mask = NULL;
18,856✔
1994
    dither->pipeline_transparent_mask_size = 0;
18,856✔
1995
    dither->pipeline_transparent_keycolor = (-1);
18,856✔
1996
    dither->pipeline_accumulation_result_enabled = 0;
18,856✔
1997
    if (!waited && ctx.pool != NULL) {
18,856!
1998
        wait_status = sixel_parallel_context_wait(&ctx, status != SIXEL_OK);
1999
        if (status == SIXEL_OK) {
×
2000
            status = wait_status;
7,871✔
2001
        }
2002
    }
2003
    sixel_parallel_context_cleanup(&ctx);
18,856✔
2004
    if (owns_logger) {
18,856✔
2005
        sixel_timeline_logger_unref(logger);
132✔
2006
    }
11✔
2007
    if (indexes != NULL) {
18,856!
2008
        sixel_allocator_free(allocator, indexes);
18,856✔
2009
    }
1,587✔
2010
    return status;
14,095✔
2011
}
1,587✔
2012
#else
2013
static SIXELSTATUS
2014
sixel_encode_body_pipeline(unsigned char *pixels,
2015
                           int width,
2016
                           int height,
2017
                           unsigned char const *palette,
2018
                           float const *palette_float,
2019
                           sixel_dither_t *dither,
2020
                           sixel_output_t *output,
2021
                           int encode_threads)
2022
{
2023
    (void)pixels;
2024
    (void)width;
2025
    (void)height;
2026
    (void)palette;
2027
    (void)palette_float;
2028
    (void)dither;
2029
    (void)output;
2030
    (void)encode_threads;
2031
    return SIXEL_RUNTIME_ERROR;
2032
}
2033
#endif
2034

2035
#if SIXEL_ENABLE_THREADS
2036
/*
2037
 * OR mode shares the producer/writer pipeline with the normal encoder, but its
2038
 * worker body emits bit-planes from the finished palette-index rows directly.
2039
 * The palette is emitted before workers start so every band can stay
2040
 * independent and the writer only has to concatenate ordered body fragments.
2041
 */
2042
SIXEL_INTERNAL_API SIXELSTATUS
2043
sixel_encode_body_ormode_pipeline(unsigned char *pixels,
72✔
2044
                                  int width,
2045
                                  int height,
2046
                                  unsigned char const *palette,
2047
                                  sixel_dither_t *dither,
2048
                                  sixel_output_t *output,
2049
                                  int encode_threads)
2050
{
2051
    SIXELSTATUS status;
30✔
2052
    SIXELSTATUS wait_status;
30✔
2053
    sixel_parallel_context_t ctx = {0};
72✔
2054
    sixel_index_t *indexes;
30✔
2055
    sixel_index_t *result;
30✔
2056
    sixel_allocator_t *allocator;
30✔
2057
    size_t pixel_count;
30✔
2058
    size_t buffer_size;
30✔
2059
    int threads;
30✔
2060
    int nbands;
30✔
2061
    int queue_depth;
30✔
2062
    int waited;
30✔
2063
    int dither_threads_budget;
30✔
2064
    int worker_capacity;
30✔
2065
    int boost_target;
30✔
2066
    sixel_timeline_logger_t *logger;
30✔
2067
    int owns_logger;
30✔
2068
    sixel_parallel_row_notifier_t notifier;
30✔
2069

2070
    if (pixels == NULL || palette == NULL || dither == NULL ||
72!
2071
            output == NULL) {
36!
2072
        return SIXEL_BAD_ARGUMENT;
2073
    }
2074

2075
    threads = encode_threads;
72✔
2076
    nbands = (height + 5) / 6;
72✔
2077
    if (threads <= 1 || nbands <= 1) {
72!
2078
        return SIXEL_RUNTIME_ERROR;
2079
    }
2080

2081
    pixel_count = (size_t)width * (size_t)height;
72✔
2082
    if (height != 0 && pixel_count / (size_t)height != (size_t)width) {
72!
2083
        return SIXEL_BAD_INTEGER_OVERFLOW;
2084
    }
2085
    buffer_size = pixel_count * sizeof(*indexes);
72✔
2086
    allocator = dither->allocator;
72✔
2087
    indexes = (sixel_index_t *)sixel_allocator_malloc(allocator, buffer_size);
72✔
2088
    if (indexes == NULL) {
72!
2089
        return SIXEL_BAD_ALLOCATION;
2090
    }
2091

2092
    sixel_parallel_context_init(&ctx);
72✔
2093
    logger = dither->pipeline_logger;
72✔
2094
    owns_logger = 0;
72✔
2095
    if (logger == NULL) {
72!
2096
        logger = NULL;
72✔
2097
        sixel_timeline_logger_prepare_default(allocator, &logger);
72✔
2098
        owns_logger = logger != NULL ? 1 : 0;
72✔
2099
    }
6✔
2100
    notifier.context = &ctx;
72✔
2101
    notifier.logger = logger;
72✔
2102
    notifier.band_height = 6;
72✔
2103
    notifier.image_height = height;
72✔
2104
    waited = 0;
72✔
2105
    status = SIXEL_OK;
72✔
2106

2107
    status = sixel_encode_body_ormode_emit_palette(palette,
78✔
2108
                                                   dither->ncolors,
6✔
2109
                                                   dither->keycolor,
6✔
2110
                                                   output);
6✔
2111
    if (SIXEL_FAILED(status)) {
72!
2112
        goto cleanup;
2113
    }
2114

2115
    queue_depth = threads * 3;
72✔
2116
    if (queue_depth > nbands) {
72✔
2117
        queue_depth = nbands;
7✔
2118
    }
1✔
2119
    if (queue_depth < 1) {
72!
2120
        queue_depth = 1;
2121
    }
2122

2123
    dither_threads_budget = dither->pipeline_dither_threads;
72✔
2124
    worker_capacity = threads + dither_threads_budget;
72✔
2125
    if (worker_capacity < threads) {
72!
2126
        worker_capacity = threads;
2127
    }
2128
    if (worker_capacity > nbands) {
72✔
2129
        worker_capacity = nbands;
7✔
2130
    }
1✔
2131

2132
    dither->pipeline_index_buffer = indexes;
72✔
2133
    dither->pipeline_index_size = buffer_size;
72✔
2134
    dither->pipeline_row_callback = sixel_parallel_palette_row_ready;
72✔
2135
    dither->pipeline_row_priv = &notifier;
72✔
2136
    dither->pipeline_logger = logger;
72✔
2137
    dither->pipeline_image_width = width;
72✔
2138
    dither->pipeline_image_height = height;
72✔
2139

2140
    if (logger != NULL) {
72!
2141
        sixel_timeline_logger_logf(logger,
2142
                          "controller",
2143
                          "pipeline",
2144
                          "configure",
2145
                          -1);
2146
    }
2147

2148
    status = sixel_parallel_context_begin(&ctx,
72✔
2149
                                          indexes,
6✔
2150
                                          width,
6✔
2151
                                          height,
6✔
2152
                                          dither->ncolors,
6✔
2153
                                          dither->keycolor,
6✔
2154
                                          NULL,
2155
                                          output,
6✔
2156
                                          allocator,
6✔
2157
                                          threads,
6✔
2158
                                          worker_capacity,
6✔
2159
                                          queue_depth,
6✔
2160
                                          dither->pipeline_pin_threads,
6✔
2161
                                          logger);
6✔
2162
    if (SIXEL_FAILED(status)) {
72!
2163
        goto cleanup;
2164
    }
2165
    sixel_parallel_submit_source_empty_bands(&ctx);
72✔
2166

2167
    result = sixel_dither_apply_palette(dither, pixels, width, height);
72✔
2168
    if (result == NULL) {
72!
2169
        status = SIXEL_RUNTIME_ERROR;
2170
        goto cleanup;
2171
    }
2172
    if (result != indexes) {
72!
2173
        status = SIXEL_RUNTIME_ERROR;
2174
        goto cleanup;
2175
    }
2176
    if (dither->pipeline_accumulation_result_enabled != 0) {
72!
2177
        status = sixel_dither_set_pipeline_accumulation_result_rgb(
2178
            dither,
2179
            indexes,
2180
            pixel_count,
2181
            palette,
2182
            (size_t)dither->ncolors);
2183
        if (SIXEL_FAILED(status)) {
×
2184
            goto cleanup;
2185
        }
2186
    }
2187

2188
    /*
2189
     * PaletteApply is complete, so the encode queue can borrow the dither-side
2190
     * thread budget and finish any remaining bands with a wider worker set.
2191
     */
2192
    boost_target = threads + dither_threads_budget;
72✔
2193
    status = sixel_parallel_context_grow(&ctx, boost_target);
72✔
2194
    if (SIXEL_FAILED(status)) {
72!
2195
        goto cleanup;
2196
    }
2197

2198
    status = sixel_parallel_context_wait(&ctx, 0);
72✔
2199
    waited = 1;
72✔
2200
    if (SIXEL_FAILED(status)) {
72!
2201
        goto cleanup;
2202
    }
2203

2204
cleanup:
66✔
2205
    dither->pipeline_row_callback = NULL;
72✔
2206
    dither->pipeline_row_priv = NULL;
72✔
2207
    dither->pipeline_index_buffer = NULL;
72✔
2208
    dither->pipeline_index_size = 0;
72✔
2209
    dither->pipeline_image_width = 0;
72✔
2210
    dither->pipeline_image_height = 0;
72✔
2211
    dither->pipeline_transparent_mask = NULL;
72✔
2212
    dither->pipeline_transparent_mask_size = 0;
72✔
2213
    dither->pipeline_transparent_keycolor = (-1);
72✔
2214
    dither->pipeline_accumulation_result_enabled = 0;
72✔
2215
    if (!waited && ctx.pool != NULL) {
72!
2216
        wait_status = sixel_parallel_context_wait(&ctx, status != SIXEL_OK);
2217
        if (status == SIXEL_OK) {
×
2218
            status = wait_status;
30✔
2219
        }
2220
    }
2221
    sixel_parallel_context_cleanup(&ctx);
72✔
2222
    if (owns_logger) {
72!
2223
        sixel_timeline_logger_unref(logger);
2224
    }
2225
    if (indexes != NULL) {
72!
2226
        sixel_allocator_free(allocator, indexes);
72✔
2227
    }
6✔
2228
    return status;
54✔
2229
}
6✔
2230
#else
2231
SIXEL_INTERNAL_API SIXELSTATUS
2232
sixel_encode_body_ormode_pipeline(unsigned char *pixels,
2233
                                  int width,
2234
                                  int height,
2235
                                  unsigned char const *palette,
2236
                                  sixel_dither_t *dither,
2237
                                  sixel_output_t *output,
2238
                                  int encode_threads)
2239
{
2240
    (void)pixels;
2241
    (void)width;
2242
    (void)height;
2243
    (void)palette;
2244
    (void)dither;
2245
    (void)output;
2246
    (void)encode_threads;
2247
    return SIXEL_RUNTIME_ERROR;
2248
}
2249
#endif
2250

2251
/* implementation */
2252

2253
static void
2254
sixel_advance(sixel_output_t *output, int nwrite)
95,867,920✔
2255
{
2256
    if ((output->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) {
95,867,920✔
2257
        (void)sixel_output_write_bytes(output,
2,916✔
2258
                                       (char *)output->buffer,
2,748✔
2259
                                       SIXEL_OUTPUT_PACKET_SIZE);
2260
        memcpy(output->buffer,
3,084✔
2261
               output->buffer + SIXEL_OUTPUT_PACKET_SIZE,
1,542✔
2262
               (size_t)(output->pos -= SIXEL_OUTPUT_PACKET_SIZE));
2,748✔
2263
    }
168✔
2264
}
95,867,920✔
2265

2266

2267
static void
2268
sixel_putc(unsigned char *buffer, unsigned char value)
26,971,891✔
2269
{
2270
    *buffer = value;
26,971,891✔
2271
}
15,120,349✔
2272

2273

2274
static void
2275
sixel_puts(unsigned char *buffer, char const *value, int size)
2,668,381✔
2276
{
2277
    memcpy(buffer, (void *)value, (size_t)size);
2,668,381✔
2278
}
1,821,592✔
2279

2280
/*
2281
 * Append a literal byte several times while respecting the output packet
2282
 * boundary.  The helper keeps `sixel_advance` responsible for flushing and
2283
 * preserves the repeating logic used by DECGRI sequences.
2284
 */
2285
static void
2286
sixel_output_emit_literal(sixel_output_t *output,
40,282,406✔
2287
                          unsigned char value,
2288
                          int count)
2289
{
2290
    int chunk;
17,678,497✔
2291

2292
    if (count <= 0) {
40,282,406✔
2293
        return;
2294
    }
2295

2296
    while (count > 0) {
80,580,711✔
2297
        chunk = SIXEL_OUTPUT_PACKET_SIZE - output->pos;
40,286,233✔
2298
        if (chunk > count) {
40,286,233✔
2299
            chunk = count;
22,613,444✔
2300
        }
2,526,217✔
2301
        memset(output->buffer + output->pos, value, (size_t)chunk);
40,286,233✔
2302
        sixel_advance(output, chunk);
40,286,233✔
2303
        count -= chunk;
40,298,305✔
2304
    }
2305
}
2,525,475✔
2306

2307
/*
2308
 * Compose helpers accelerate palette sweeps by skipping zero columns in
2309
 * word-sized chunks.
2310
 */
2311

2312
static int
2313
sixel_compose_find_run_start(unsigned char const *row,
15,443,852✔
2314
                             int width,
2315
                             int start)
2316
{
2317
    int idx;
6,790,743✔
2318
    size_t chunk_size;
6,790,743✔
2319
    unsigned char const *cursor;
6,790,743✔
2320
    unsigned long block;
6,790,743✔
2321

2322
    idx = start;
15,443,852✔
2323
    chunk_size = sizeof(unsigned long);
15,443,852✔
2324
    cursor = row + start;
15,443,852✔
2325

2326
    while ((width - idx) >= (int)chunk_size) {
166,833,657✔
2327
        memcpy(&block, cursor, chunk_size);
160,773,795✔
2328
        if (block != 0UL) {
160,773,795✔
2329
            break;
5,258,257✔
2330
        }
2331
        idx += (int)chunk_size;
151,389,805✔
2332
        cursor += chunk_size;
151,389,805✔
2333
    }
2334

2335
    while (idx < width) {
50,443,554✔
2336
        if (*cursor != 0) {
44,527,057✔
2337
            break;
5,339,456✔
2338
        }
2339
        idx += 1;
34,999,702✔
2340
        cursor += 1;
34,999,702✔
2341
    }
2342

2343
    return idx;
15,443,852✔
2344
}
2345

2346

2347
static int
2348
sixel_compose_measure_gap(unsigned char const *row,
16,680,437✔
2349
                          int width,
2350
                          int start,
2351
                          int *reached_end)
2352
{
2353
    int gap;
7,308,985✔
2354
    size_t chunk_size;
7,308,985✔
2355
    unsigned char const *cursor;
7,308,985✔
2356
    unsigned long block;
7,308,985✔
2357
    int remaining;
7,308,985✔
2358

2359
    gap = 0;
16,680,437✔
2360
    *reached_end = 0;
16,680,437✔
2361
    if (start >= width) {
16,680,437✔
2362
        *reached_end = 1;
206,495✔
2363
        return gap;
206,495✔
2364
    }
2365

2366
    chunk_size = sizeof(unsigned long);
16,473,942✔
2367
    cursor = row + start;
16,473,942✔
2368
    remaining = width - start;
16,473,942✔
2369

2370
    while (remaining >= (int)chunk_size) {
99,792,968✔
2371
        memcpy(&block, cursor, chunk_size);
93,692,748✔
2372
        if (block != 0UL) {
93,692,748✔
2373
            break;
5,839,022✔
2374
        }
2375
        gap += (int)chunk_size;
83,319,026✔
2376
        cursor += chunk_size;
83,319,026✔
2377
        remaining -= (int)chunk_size;
83,319,026✔
2378
    }
2379

2380
    while (remaining > 0) {
46,156,662✔
2381
        if (*cursor != 0) {
40,442,261✔
2382
            return gap;
6,056,671✔
2383
        }
2384
        gap += 1;
29,682,720✔
2385
        cursor += 1;
29,682,720✔
2386
        remaining -= 1;
29,682,720✔
2387
    }
2388

2389
    *reached_end = 1;
5,714,401✔
2390
    return gap;
5,714,401✔
2391
}
1,060,676✔
2392

2393

2394
#if HAVE_LDIV
2395
static int
2396
sixel_putnum_impl(char *buffer, long value, int pos)
49,002,061✔
2397
{
2398
    ldiv_t r;
21,530,700✔
2399

2400
    r = ldiv(value, 10);
49,002,061✔
2401
    if (r.quot > 0) {
49,015,184✔
2402
        pos = sixel_putnum_impl(buffer, r.quot, pos);
24,177,998✔
2403
    }
1,539,579✔
2404
    /*
2405
     * r.rem is guaranteed to be in [0, 9] because the divisor is 10, so the
2406
     * explicit cast documents the safe narrowing from long to char.
2407
     */
2408
    *(buffer + pos) = (char)('0' + r.rem);
49,017,913✔
2409
    return pos + 1;
49,017,913✔
2410
}
2411
#endif  /* HAVE_LDIV */
2412

2413

2414
static int
2415
sixel_putnum(char *buffer, int value)
24,847,266✔
2416
{
2417
    int pos;
10,917,590✔
2418

2419
#if HAVE_LDIV
2420
    pos = sixel_putnum_impl(buffer, value, 0);
27,869,678✔
2421
#else
2422
    pos = sprintf(buffer, "%d", value);
2423
#endif  /* HAVE_LDIV */
2424

2425
    return pos;
24,851,847✔
2426
}
2427

2428

2429
static SIXELSTATUS
2430
sixel_put_flash(sixel_output_t *const output)
45,168,073✔
2431
{
2432
    int nwrite;
19,811,319✔
2433

2434
    if (output->save_count <= 0) {
45,168,073✔
2435
        return SIXEL_OK;
76,231✔
2436
    }
2437

2438
    if (output->has_gri_arg_limit) {  /* VT240 Max 255 ? */
45,037,062✔
2439
            while (output->save_count > 255) {
64,801!
2440
                /* argument of DECGRI('!') is limited to 255 in real VT */
2441
                sixel_puts(output->buffer + output->pos, "!255", 4);
×
2442
                sixel_advance(output, 4);
×
2443
                sixel_putc(output->buffer + output->pos,
×
2444
                           (unsigned char)output->save_pixel);
×
2445
                sixel_advance(output, 1);
×
2446
                output->save_count -= 255;
46,897✔
2447
            }
2448
        }
1,130✔
2449

2450
    if (output->save_count > 3) {
45,083,959✔
2451
        /* DECGRI Graphics Repeat Introducer ! Pn Ch */
2452
        sixel_putc(output->buffer + output->pos, '!');
4,794,462✔
2453
        sixel_advance(output, 1);
4,794,083✔
2454
        nwrite = sixel_putnum((char *)output->buffer + output->pos, output->save_count);
4,794,138✔
2455
        sixel_advance(output, nwrite);
4,794,861✔
2456
        sixel_putc(output->buffer + output->pos,
5,099,392✔
2457
                   (unsigned char)output->save_pixel);
4,794,589✔
2458
        sixel_advance(output, 1);
4,794,475✔
2459
    } else {
304,803✔
2460
        sixel_output_emit_literal(output,
42,814,598✔
2461
                                  (unsigned char)output->save_pixel,
40,289,497✔
2462
                                  output->save_count);
2,525,101✔
2463
    }
2464

2465
    output->save_pixel = 0;
45,044,181✔
2466
    output->save_count = 0;
45,044,181✔
2467

2468
    return SIXEL_OK;
45,044,181✔
2469
}
2,840,966✔
2470

2471

2472
/*
2473
 * Emit a run of identical SIXEL cells while keeping the existing repeat
2474
 * accumulator intact.  The helper extends the current run when possible and
2475
 * falls back to flushing through DECGRI before starting a new symbol.
2476
 */
2477
static SIXELSTATUS
2478
sixel_emit_run(sixel_output_t *output, int symbol, int count)
45,092,392✔
2479
{
2480
    SIXELSTATUS status = SIXEL_FALSE;
45,092,392✔
2481

2482
    if (count <= 0) {
45,092,392✔
2483
        return SIXEL_OK;
9✔
2484
    }
2485

2486
    if (output->save_count > 0) {
45,092,376✔
2487
        if (output->save_pixel == symbol) {
35,626,658✔
2488
            output->save_count += count;
46,240✔
2489
            return SIXEL_OK;
46,240✔
2490
        }
2491

2492
        status = sixel_put_flash(output);
35,580,418✔
2493
        if (SIXEL_FAILED(status)) {
35,556,447!
2494
            return status;
2495
        }
2496
    }
2,225,463✔
2497

2498
    output->save_pixel = symbol;
45,025,762✔
2499
    output->save_count = count;
45,025,762✔
2500

2501
    return SIXEL_OK;
45,025,762✔
2502
}
2,826,837✔
2503

2504

2505
/*
2506
 * Walk a composed node and coalesce identical columns into runs so the
2507
 * encoder core touches the repeat accumulator only once per symbol.
2508
 */
2509
static SIXELSTATUS
2510
sixel_emit_span_from_map(sixel_output_t *output,
9,534,417✔
2511
                         unsigned char const *map,
2512
                         int length)
2513
{
2514
    SIXELSTATUS status = SIXEL_FALSE;
9,534,417✔
2515
    int index;
4,187,777✔
2516
    int run_length;
4,187,777✔
2517
    unsigned char value;
4,187,777✔
2518
    size_t chunk_size;
4,187,777✔
2519
    unsigned long pattern;
4,187,777✔
2520
    unsigned long block;
4,187,777✔
2521
    int chunk_mismatch;
4,187,777✔
2522
    int remain;
4,187,777✔
2523
    int byte_index;
4,187,777✔
2524

2525
    if (length <= 0) {
9,534,417✔
2526
        return SIXEL_OK;
2527
    }
2528

2529
    for (index = 0; index < length; index += run_length) {
50,801,339✔
2530
        value = map[index];
41,292,963✔
2531
        if (value > '?') {
41,292,963!
2532
            value = 0;
×
2533
        }
2534

2535
        run_length = 1;
41,292,963✔
2536
        chunk_size = sizeof(unsigned long);
41,292,963✔
2537
        chunk_mismatch = 0;
41,292,963✔
2538
        if (chunk_size > 1) {
41,292,963!
2539
            remain = length - (index + run_length);
41,278,846✔
2540
            pattern = (~0UL / 0xffUL) * (unsigned long)value;
41,278,846✔
2541

2542
            while (remain >= (int)chunk_size) {
43,823,086✔
2543
                memcpy(&block,
27,691,587✔
2544
                       map + index + run_length,
25,947,681✔
2545
                       chunk_size);
1,743,906✔
2546
                block ^= pattern;
24,203,775✔
2547
                if (block != 0UL) {
24,203,775✔
2548
                    for (byte_index = 0;
12,685,622✔
2549
                         byte_index < (int)chunk_size;
30,807,756!
2550
                         byte_index++) {
9,148,221✔
2551
                        if ((block & 0xffUL) != 0UL) {
30,815,543✔
2552
                            chunk_mismatch = 1;
12,159,347✔
2553
                            break;
12,159,347✔
2554
                        }
2555
                        block >>= 8;
9,148,221✔
2556
                        run_length += 1;
9,148,221✔
2557
                    }
534,062✔
2558
                    break;
12,152,412✔
2559
                }
2560
                run_length += (int)chunk_size;
2,544,240✔
2561
                remain -= (int)chunk_size;
2,544,240✔
2562
            }
2563
        }
2,589,823✔
2564

2565
        if (!chunk_mismatch) {
41,293,815✔
2566
            while (index + run_length < length) {
25,058,601✔
2567
                unsigned char next;
6,787,802✔
2568

2569
                next = map[index + run_length];
15,543,168✔
2570
                if (next > '?') {
15,543,168!
2571
                    next = 0;
×
2572
                }
2573
                if (next != value) {
15,543,168✔
2574
                    break;
5,753,211✔
2575
                }
2576
                run_length += 1;
5,317,039✔
2577
            }
2578
        }
1,092,153✔
2579

2580
        status = sixel_emit_run(output,
43,869,313✔
2581
                                 (int)value + '?',
23,171,317✔
2582
                                 run_length);
2,585,048✔
2583
        if (SIXEL_FAILED(status)) {
41,266,922!
2584
            return status;
2585
        }
2586
    }
2,585,048✔
2587

2588
    return SIXEL_OK;
5,320,990✔
2589
}
603,930✔
2590

2591

2592
static SIXELSTATUS
2593
sixel_put_pixel(sixel_output_t *const output, int pix)
248,496✔
2594
{
2595
    if (pix < 0 || pix > '?') {
248,496!
2596
        pix = 0;
×
2597
    }
2598

2599
    return sixel_emit_run(output, pix + '?', 1);
248,496✔
2600
}
2601

2602
static SIXELSTATUS
2603
sixel_node_new(sixel_node_t **np, sixel_allocator_t *allocator)
7,439,826✔
2604
{
2605
    SIXELSTATUS status = SIXEL_FALSE;
7,439,826✔
2606

2607
    *np = (sixel_node_t *)sixel_allocator_malloc(allocator,
7,439,826✔
2608
                                                 sizeof(sixel_node_t));
2609
    if (np == NULL) {
7,440,832!
2610
        sixel_helper_set_additional_message(
26✔
2611
            "sixel_node_new: sixel_allocator_malloc() failed.");
2612
        status = SIXEL_BAD_ALLOCATION;
2613
        goto end;
2614
    }
2615

2616
    status = SIXEL_OK;
4,283,301✔
2617

2618
end:
6,849,425✔
2619
    return status;
7,440,883✔
2620
}
2621

2622
static void
2623
sixel_node_del(sixel_output_t *output, sixel_node_t *np)
9,523,245✔
2624
{
2625
    sixel_node_t *tp;
4,187,668✔
2626

2627
    if ((tp = output->node_top) == np) {
9,523,245✔
2628
        output->node_top = np->next;
2,319,796✔
2629
    } else {
145,530✔
2630
        while (tp->next != NULL) {
330,091,020!
2631
            if (tp->next == np) {
330,096,346✔
2632
                tp->next = np->next;
7,208,775✔
2633
                break;
7,208,775✔
2634
            }
2635
            tp = tp->next;
181,543,332✔
2636
        }
2637
    }
2638

2639
    np->next = output->node_free;
9,524,563✔
2640
    output->node_free = np;
9,524,563✔
2641
}
9,524,563✔
2642

2643

2644
static SIXELSTATUS
2645
sixel_put_node(
9,525,342✔
2646
    sixel_output_t /* in */     *output,  /* output context */
2647
    int            /* in/out */ *x,       /* header position */
2648
    sixel_node_t   /* in */     *np,      /* node object */
2649
    int            /* in */     ncolors,  /* number of palette colors */
2650
    int            /* in */     keycolor) /* transparent color number */
2651
{
2652
    SIXELSTATUS status = SIXEL_FALSE;
9,525,342✔
2653
    int nwrite;
4,187,201✔
2654

2655
    if (ncolors != 2 || keycolor == (-1)) {
9,525,342✔
2656
        /* designate palette index */
2657
        if (output->active_palette != np->pal) {
9,522,426✔
2658
            sixel_putc(output->buffer + output->pos, '#');
9,381,339✔
2659
            sixel_advance(output, 1);
9,380,972✔
2660
            nwrite = sixel_putnum((char *)output->buffer + output->pos, np->pal);
9,381,273✔
2661
            sixel_advance(output, nwrite);
9,382,560✔
2662
            output->active_palette = np->pal;
9,382,049✔
2663
        }
595,998✔
2664
    }
604,207✔
2665

2666
    if (*x < np->sx) {
9,526,052✔
2667
        int span;
1,576,771✔
2668

2669
        span = np->sx - *x;
3,590,474✔
2670
        status = sixel_emit_run(output, '?', span);
3,590,474✔
2671
        if (SIXEL_FAILED(status)) {
3,590,335!
2672
            goto end;
2673
        }
2674
        *x = np->sx;
3,590,335✔
2675
    }
228,470✔
2676

2677
    if (*x < np->mx) {
9,525,647!
2678
        int span;
4,187,964✔
2679

2680
        span = np->mx - *x;
9,526,216✔
2681
        status = sixel_emit_span_from_map(output,
14,317,333✔
2682
                                          (unsigned char const *)np->map + *x,
9,526,216✔
2683
                                          span);
603,749✔
2684
        if (SIXEL_FAILED(status)) {
9,523,821!
2685
            goto end;
2686
        }
2687
        *x = np->mx;
9,523,821✔
2688
    }
603,749✔
2689

2690
    status = sixel_put_flash(output);
9,523,252✔
2691
    if (SIXEL_FAILED(status)) {
9,522,495!
2692
        goto end;
2693
    }
2694

2695
end:
8,918,421✔
2696
    return status;
9,521,845✔
2697
}
2698

2699

2700
SIXELSTATUS
2701
sixel_encode_header(int width, int height, int keycolor, sixel_output_t *output)
33,893✔
2702
{
2703
    SIXELSTATUS status = SIXEL_FALSE;
33,893✔
2704
    int p[3] = {0, 0, 0};
33,893✔
2705
    int pcount = 3;
33,893✔
2706
    int use_raster_attributes = 1;
33,893✔
2707

2708
    if (output->ormode) {
33,893✔
2709
        p[0] = 7;
80✔
2710
        p[1] = 5;
80✔
2711
    } else if (sixel_output_has_transparent_offset(output)) {
33,818✔
2712
        /*
2713
         * Transparent offset uses omitted zero cells as positional padding.
2714
         * It therefore needs P2=1 even when the source image has no keycolor.
2715
         */
2716
        p[1] = 1;
40✔
2717
    } else if (keycolor >= 0) {
33,776✔
2718
        if (output->transparent_policy == SIXEL_TRANSPARENT_POLICY_KEEP) {
2,703✔
2719
            /*
2720
             * P2=1 asks the terminal to keep the current image-plane pixel
2721
             * when a zero/transparent SIXEL cell is omitted.
2722
             */
2723
            p[1] = 1;
224✔
2724
        } else if (output->transparent_policy ==
2,493✔
2725
                   SIXEL_TRANSPARENT_POLICY_BACKGROUND) {
2726
            /*
2727
             * Spell out P2=0 for transparent output so the header carries
2728
             * the clear-to-background contract even when P2's default value
2729
             * would be equivalent.
2730
             */
2731
            pcount = 2;
2,463✔
2732
        }
154✔
2733
    }
169✔
2734

2735
    output->pos = 0;
33,893✔
2736

2737
    if (pcount == 3 && p[2] == 0) {
33,893!
2738
        pcount--;
31,430✔
2739
        if (p[1] == 0) {
31,430✔
2740
            pcount--;
31,086✔
2741
            if (p[0] == 0) {
31,086!
2742
                pcount--;
31,086✔
2743
            }
1,958✔
2744
        }
1,958✔
2745
    }
1,980✔
2746

2747
    status = sixel_output_begin_image(output,
36,027✔
2748
                                      width,
2,134✔
2749
                                      height,
2,134✔
2750
                                      p[0],
2,134✔
2751
                                      p[1],
2,134✔
2752
                                      p[2],
2,134✔
2753
                                      pcount,
2,134✔
2754
                                      use_raster_attributes);
2,134✔
2755

2756
    return status;
33,893✔
2757
}
2758

2759

2760
static int
2761
sixel_palette_float_pixelformat_for_colorspace(int colorspace)
52,770✔
2762
{
2763
    switch (colorspace) {
52,770✔
2764
    case SIXEL_COLORSPACE_LINEAR:
112✔
2765
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
126✔
2766
    case SIXEL_COLORSPACE_OKLAB:
312✔
2767
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
351✔
2768
    case SIXEL_COLORSPACE_CIELAB:
88✔
2769
        return SIXEL_PIXELFORMAT_CIELABFLOAT32;
99✔
2770
    case SIXEL_COLORSPACE_DIN99D:
80✔
2771
        return SIXEL_PIXELFORMAT_DIN99DFLOAT32;
90✔
2772
    default:
33,100✔
2773
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
37,257✔
2774
    }
2775
}
4,231✔
2776

2777
static int
2778
sixel_palette_float32_matches_u8(unsigned char const *palette,
3,568✔
2779
                                 float const *palette_float,
2780
                                 size_t count,
2781
                                 int float_pixelformat)
2782
{
2783
    size_t index;
1,670✔
2784
    size_t limit;
1,670✔
2785
    unsigned char expected;
1,670✔
2786
    int channel;
1,670✔
2787

2788
    if (palette == NULL || palette_float == NULL || count == 0U) {
3,568!
2789
        return 1;
2790
    }
2791
    if (count > SIZE_MAX / 3U) {
3,568!
2792
        return 0;
2793
    }
2794
    limit = count * 3U;
3,568✔
2795
    for (index = 0U; index < limit; ++index) {
700,024✔
2796
        channel = (int)(index % 3U);
696,456✔
2797
        expected = sixel_pixelformat_float_channel_to_byte(
1,042,452✔
2798
            float_pixelformat,
2,232✔
2799
            channel,
2,232✔
2800
            palette_float[index]);
696,456✔
2801
        if (palette[index] != expected) {
696,456!
2802
            return 0;
2803
        }
2804
    }
2,232✔
2805
    return 1;
1,898✔
2806
}
114✔
2807

2808
static void
2809
sixel_palette_sync_float32_from_u8(unsigned char const *palette,
×
2810
                                   float *palette_float,
2811
                                   size_t count,
2812
                                   int float_pixelformat)
2813
{
2814
    size_t index;
2815
    size_t limit;
2816
    int channel;
2817

2818
    if (palette == NULL || palette_float == NULL || count == 0U) {
×
2819
        return;
2820
    }
2821
    if (count > SIZE_MAX / 3U) {
×
2822
        return;
2823
    }
2824
    limit = count * 3U;
×
2825
    for (index = 0U; index < limit; ++index) {
×
2826
        channel = (int)(index % 3U);
×
2827
        palette_float[index] = sixel_pixelformat_byte_to_float(
×
2828
            float_pixelformat,
2829
            channel,
2830
            palette[index]);
×
2831
    }
2832
}
2833

2834
static int
2835
sixel_output_palette_channel_to_pct(unsigned char const *palette,
7,894,832✔
2836
                                    float const *palette_float,
2837
                                    int n,
2838
                                    int channel)
2839
{
2840
    size_t index;
3,467,600✔
2841
    float value;
3,467,600✔
2842
    int percent;
3,467,600✔
2843

2844
    index = (size_t)n * 3U + (size_t)channel;
7,894,832✔
2845
    if (palette_float != NULL) {
7,894,832✔
2846
        value = palette_float[index];
689,532✔
2847
        if (value < 0.0f) {
689,532✔
2848
            value = 0.0f;
2849
        } else if (value > 1.0f) {
689,532!
2850
            value = 1.0f;
2851
        }
2852
        percent = (int)(value * 100.0f + 0.5f);
689,532✔
2853
        if (percent < 0) {
689,532!
2854
            percent = 0;
2855
        } else if (percent > 100) {
689,532!
2856
            percent = 100;
2857
        }
2858
        return percent;
689,532✔
2859
    }
2860

2861
    if (palette != NULL) {
7,205,300!
2862
        return (palette[index] * 100 + 127) / 255;
7,205,300✔
2863
    }
2864

2865
    return 0;
2866
}
500,772✔
2867

2868
static double
2869
sixel_output_palette_channel_to_float(unsigned char const *palette,
110,304✔
2870
                                      float const *palette_float,
2871
                                      int n,
2872
                                      int channel)
2873
{
2874
    size_t index;
48,258✔
2875
    double value;
48,258✔
2876

2877
    index = (size_t)n * 3U + (size_t)channel;
110,304✔
2878
    value = 0.0;
110,304✔
2879
    if (palette_float != NULL) {
110,304✔
2880
        value = palette_float[index];
6,144✔
2881
    } else if (palette != NULL) {
104,160!
2882
        value = (double)palette[index] / 255.0;
104,160✔
2883
    }
6,894✔
2884
    if (value < 0.0) {
110,304✔
2885
        value = 0.0;
2886
    } else if (value > 1.0) {
110,304!
2887
        value = 1.0;
2888
    }
2889

2890
    return value;
110,304✔
2891
}
2892

2893
static SIXELSTATUS
2894
output_rgb_palette_definition(
2,633,500✔
2895
    sixel_output_t /* in */ *output,
2896
    unsigned char const /* in */ *palette,
2897
    float const /* in */ *palette_float,
2898
    int            /* in */ n,
2899
    int            /* in */ keycolor
2900
)
2901
{
2902
    SIXELSTATUS status = SIXEL_FALSE;
2,633,500✔
2903
    int nwrite;
1,156,694✔
2904

2905
    if (n != keycolor) {
2,633,500✔
2906
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
2907
        sixel_putc(output->buffer + output->pos, '#');
2,631,613✔
2908
        sixel_advance(output, 1);
2,631,613✔
2909
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
2,631,613✔
2910
        sixel_advance(output, nwrite);
2,631,613✔
2911
        sixel_puts(output->buffer + output->pos, ";2;", 3);
2,631,613✔
2912
        sixel_advance(output, 3);
2,631,613✔
2913
        nwrite = sixel_putnum(
3,787,481✔
2914
            (char *)output->buffer + output->pos,
2,631,613✔
2915
            sixel_output_palette_channel_to_pct(palette,
333,848✔
2916
                                                palette_float,
166,924✔
2917
                                                n,
166,924✔
2918
                                                0));
2919
        sixel_advance(output, nwrite);
2,631,612✔
2920
        sixel_putc(output->buffer + output->pos, ';');
2,631,612✔
2921
        sixel_advance(output, 1);
2,631,612✔
2922
        nwrite = sixel_putnum(
3,787,481✔
2923
            (char *)output->buffer + output->pos,
2,631,612✔
2924
            sixel_output_palette_channel_to_pct(palette,
333,848✔
2925
                                                palette_float,
166,924✔
2926
                                                n,
166,924✔
2927
                                                1));
2928
        sixel_advance(output, nwrite);
2,631,613✔
2929
        sixel_putc(output->buffer + output->pos, ';');
2,631,612✔
2930
        sixel_advance(output, 1);
2,631,612✔
2931
        nwrite = sixel_putnum(
3,787,478✔
2932
            (char *)output->buffer + output->pos,
2,631,611✔
2933
            sixel_output_palette_channel_to_pct(palette,
333,848✔
2934
                                                palette_float,
166,924✔
2935
                                                n,
166,924✔
2936
                                                2));
2937
        sixel_advance(output, nwrite);
2,631,611✔
2938
    }
166,924✔
2939

2940
    status = SIXEL_OK;
2,633,499✔
2941

2942
    return status;
2,633,499✔
2943
}
2944

2945

2946
static SIXELSTATUS
2947
output_hls_palette_definition(
36,768✔
2948
    sixel_output_t /* in */ *output,
2949
    unsigned char const /* in */ *palette,
2950
    float const /* in */ *palette_float,
2951
    int            /* in */ n,
2952
    int            /* in */ keycolor
2953
)
2954
{
2955
    SIXELSTATUS status = SIXEL_FALSE;
36,768✔
2956
    double r;
16,086✔
2957
    double g;
16,086✔
2958
    double b;
16,086✔
2959
    double maxc;
16,086✔
2960
    double minc;
16,086✔
2961
    double lightness;
16,086✔
2962
    double saturation;
16,086✔
2963
    double hue;
16,086✔
2964
    double diff;
16,086✔
2965
    int h;
16,086✔
2966
    int l;
16,086✔
2967
    int s;
16,086✔
2968
    int nwrite;
16,086✔
2969

2970
    if (n != keycolor) {
36,768!
2971
        r = sixel_output_palette_channel_to_float(palette,
39,066✔
2972
                                                  palette_float,
2,298✔
2973
                                                  n,
2,298✔
2974
                                                  0);
2975
        g = sixel_output_palette_channel_to_float(palette,
39,066✔
2976
                                                  palette_float,
2,298✔
2977
                                                  n,
2,298✔
2978
                                                  1);
2979
        b = sixel_output_palette_channel_to_float(palette,
39,066✔
2980
                                                  palette_float,
2,298✔
2981
                                                  n,
2,298✔
2982
                                                  2);
2983
        maxc = r > g ? (r > b ? r : b) : (g > b ? g : b);
36,768✔
2984
        minc = r < g ? (r < b ? r : b) : (g < b ? g : b);
36,768✔
2985
        lightness = (maxc + minc) * 0.5;
36,768✔
2986
        saturation = 0.0;
36,768✔
2987
        hue = 0.0;
36,768✔
2988
        diff = maxc - minc;
36,768✔
2989
        if (diff <= 0.0) {
36,768✔
2990
            h = 0;
2,241✔
2991
            s = 0;
2,241✔
2992
        } else {
249✔
2993
            if (lightness < 0.5) {
32,784✔
2994
                saturation = diff / (maxc + minc);
27,876✔
2995
            } else {
1,741✔
2996
                saturation = diff / (2.0 - maxc - minc);
4,908✔
2997
            }
2998
            if (maxc == r) {
32,784✔
2999
                hue = (g - b) / diff;
22,380✔
3000
            } else if (maxc == g) {
11,803✔
3001
                hue = 2.0 + (b - r) / diff;
10,104✔
3002
            } else {
631✔
3003
                hue = 4.0 + (r - g) / diff;
300✔
3004
            }
3005
            hue *= 60.0;
32,784✔
3006
            if (hue < 0.0) {
32,784✔
3007
                hue += 360.0;
16✔
3008
            }
1✔
3009
            if (hue >= 360.0) {
32,784!
3010
                hue -= 360.0;
×
3011
            }
3012
            /*
3013
             * The DEC HLS color wheel used by DECGCI considers
3014
             * hue==0 to be blue instead of red.  Rotate the hue by
3015
             * +120 degrees so that RGB primaries continue to match
3016
             * the historical img2sixel output and VT340 behavior.
3017
             */
3018
            hue += 120.0;
32,784✔
3019
            while (hue >= 360.0) {
32,844✔
3020
                hue -= 360.0;
60✔
3021
            }
3022
            while (hue < 0.0) {
32,784!
3023
                hue += 360.0;
×
3024
            }
3025
            s = (int)(saturation * 100.0 + 0.5);
32,784✔
3026
            if (s < 0) {
32,784!
3027
                s = 0;
3028
            } else if (s > 100) {
32,784!
3029
                s = 100;
3030
            }
3031
            h = (int)(hue + 0.5);
32,784✔
3032
            if (h >= 360) {
32,784!
3033
                h -= 360;
×
3034
            } else if (h < 0) {
32,784!
3035
                h = 0;
3036
            }
3037
        }
3038
        l = (int)(lightness * 100.0 + 0.5);
36,768✔
3039
        if (l < 0) {
36,768!
3040
            l = 0;
3041
        } else if (l > 100) {
36,768!
3042
            l = 100;
3043
        }
3044
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
3045
        sixel_putc(output->buffer + output->pos, '#');
36,768✔
3046
        sixel_advance(output, 1);
36,768✔
3047
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
36,768✔
3048
        sixel_advance(output, nwrite);
36,768✔
3049
        sixel_puts(output->buffer + output->pos, ";1;", 3);
36,768✔
3050
        sixel_advance(output, 3);
36,768✔
3051
        nwrite = sixel_putnum((char *)output->buffer + output->pos, h);
36,768✔
3052
        sixel_advance(output, nwrite);
36,768✔
3053
        sixel_putc(output->buffer + output->pos, ';');
36,768✔
3054
        sixel_advance(output, 1);
36,768✔
3055
        nwrite = sixel_putnum((char *)output->buffer + output->pos, l);
36,768✔
3056
        sixel_advance(output, nwrite);
36,768✔
3057
        sixel_putc(output->buffer + output->pos, ';');
36,768✔
3058
        sixel_advance(output, 1);
36,768✔
3059
        nwrite = sixel_putnum((char *)output->buffer + output->pos, s);
36,768✔
3060
        sixel_advance(output, nwrite);
36,768✔
3061
    }
2,298✔
3062

3063
    status = SIXEL_OK;
36,768✔
3064
    return status;
36,768✔
3065
}
3066

3067

3068
static void
3069
sixel_encode_work_init(sixel_encode_work_t *work)
59,954✔
3070
{
3071
    work->map = NULL;
59,954✔
3072
    work->map_size = 0;
59,954✔
3073
    work->columns = NULL;
59,954✔
3074
    work->columns_size = 0;
59,954✔
3075
    work->active_colors = NULL;
59,954✔
3076
    work->active_colors_size = 0;
59,954✔
3077
    work->active_color_index = NULL;
59,954✔
3078
    work->active_color_index_size = 0;
59,954✔
3079
    work->requested_threads = 1;
59,954✔
3080
}
34,567✔
3081

3082
static SIXELSTATUS
3083
sixel_encode_work_allocate(sixel_encode_work_t *work,
57,266✔
3084
                           int width,
3085
                           int ncolors,
3086
                           sixel_allocator_t *allocator)
3087
{
3088
    SIXELSTATUS status = SIXEL_FALSE;
57,266✔
3089
    int len;
24,262✔
3090
    size_t columns_size;
24,262✔
3091
    size_t active_colors_size;
24,262✔
3092
    size_t active_color_index_size;
24,262✔
3093

3094
    len = ncolors * width;
57,266✔
3095
    work->map = (char *)sixel_allocator_calloc(allocator,
61,373✔
3096
                                               (size_t)len,
4,107✔
3097
                                               sizeof(char));
3098
    if (work->map == NULL && len > 0) {
57,274!
3099
        sixel_helper_set_additional_message(
×
3100
            "sixel_encode_body: sixel_allocator_calloc() failed.");
3101
        status = SIXEL_BAD_ALLOCATION;
×
3102
        goto end;
×
3103
    }
3104
    work->map_size = (size_t)len;
57,274✔
3105

3106
    columns_size = sizeof(sixel_node_t *) * (size_t)width;
57,274✔
3107
    if (width > 0) {
57,274!
3108
        work->columns = (sixel_node_t **)sixel_allocator_malloc(
57,273✔
3109
            allocator,
4,107✔
3110
            columns_size);
4,107✔
3111
        if (work->columns == NULL) {
57,275!
3112
            sixel_helper_set_additional_message(
3✔
3113
                "sixel_encode_body: sixel_allocator_malloc() failed.");
3114
            status = SIXEL_BAD_ALLOCATION;
×
3115
            goto end;
×
3116
        }
3117
        memset(work->columns, 0, columns_size);
57,272✔
3118
        work->columns_size = columns_size;
57,272✔
3119
    } else {
4,107✔
3120
        work->columns = NULL;
1✔
3121
        work->columns_size = 0;
1✔
3122
    }
3123

3124
    active_colors_size = (size_t)ncolors;
57,273✔
3125
    if (active_colors_size > 0) {
57,273!
3126
        work->active_colors =
81,544✔
3127
            (unsigned char *)sixel_allocator_malloc(allocator,
61,380✔
3128
                                                    active_colors_size);
4,107✔
3129
        if (work->active_colors == NULL) {
57,278!
3130
            sixel_helper_set_additional_message(
1✔
3131
                "sixel_encode_body: sixel_allocator_malloc() failed.");
3132
            status = SIXEL_BAD_ALLOCATION;
×
3133
            goto end;
×
3134
        }
3135
        memset(work->active_colors, 0, active_colors_size);
57,277✔
3136
        work->active_colors_size = active_colors_size;
57,277✔
3137
    } else {
4,107✔
3138
        work->active_colors = NULL;
2✔
3139
        work->active_colors_size = 0;
2✔
3140
    }
3141

3142
    active_color_index_size = sizeof(int) * (size_t)ncolors;
57,277✔
3143
    if (active_color_index_size > 0) {
57,277!
3144
        work->active_color_index = (int *)sixel_allocator_malloc(
57,272✔
3145
            allocator,
4,107✔
3146
            active_color_index_size);
4,107✔
3147
        if (work->active_color_index == NULL) {
57,276!
3148
            sixel_helper_set_additional_message(
2✔
3149
                "sixel_encode_body: sixel_allocator_malloc() failed.");
3150
            status = SIXEL_BAD_ALLOCATION;
×
3151
            goto end;
×
3152
        }
3153
        memset(work->active_color_index, 0, active_color_index_size);
57,274✔
3154
        work->active_color_index_size = active_color_index_size;
57,274✔
3155
    } else {
4,107✔
3156
        work->active_color_index = NULL;
5✔
3157
        work->active_color_index_size = 0;
5✔
3158
    }
3159

3160
    status = SIXEL_OK;
33,011✔
3161

3162
end:
28,904✔
3163
    if (SIXEL_FAILED(status)) {
47,296!
3164
        if (work->active_color_index != NULL) {
×
3165
            sixel_allocator_free(allocator, work->active_color_index);
×
3166
            work->active_color_index = NULL;
×
3167
        }
3168
        work->active_color_index_size = 0;
×
3169
        if (work->active_colors != NULL) {
×
3170
            sixel_allocator_free(allocator, work->active_colors);
×
3171
            work->active_colors = NULL;
×
3172
        }
3173
        work->active_colors_size = 0;
×
3174
        if (work->columns != NULL) {
×
3175
            sixel_allocator_free(allocator, work->columns);
×
3176
            work->columns = NULL;
×
3177
        }
3178
        work->columns_size = 0;
×
3179
        if (work->map != NULL) {
×
3180
            sixel_allocator_free(allocator, work->map);
×
3181
            work->map = NULL;
×
3182
        }
3183
        work->map_size = 0;
×
3184
    }
3185

3186
    return status;
57,279✔
3187
}
3188

3189
static void
3190
sixel_encode_work_cleanup(sixel_encode_work_t *work,
59,968✔
3191
                          sixel_allocator_t *allocator)
3192
{
3193
    if (work->active_color_index != NULL) {
59,968✔
3194
        sixel_allocator_free(allocator, work->active_color_index);
57,278✔
3195
        work->active_color_index = NULL;
57,278✔
3196
    }
4,107✔
3197
    work->active_color_index_size = 0;
59,968✔
3198
    if (work->active_colors != NULL) {
59,968✔
3199
        sixel_allocator_free(allocator, work->active_colors);
57,278✔
3200
        work->active_colors = NULL;
57,278✔
3201
    }
4,107✔
3202
    work->active_colors_size = 0;
59,968✔
3203
    if (work->columns != NULL) {
59,968✔
3204
        sixel_allocator_free(allocator, work->columns);
57,278✔
3205
        work->columns = NULL;
57,278✔
3206
    }
4,107✔
3207
    work->columns_size = 0;
59,968✔
3208
    if (work->map != NULL) {
59,968✔
3209
        sixel_allocator_free(allocator, work->map);
57,278✔
3210
        work->map = NULL;
57,278✔
3211
    }
4,107✔
3212
    work->map_size = 0;
59,968✔
3213
}
59,968✔
3214

3215
static void
3216
sixel_band_state_reset(sixel_band_state_t *state)
280,512✔
3217
{
3218
    state->row_in_band = 0;
280,512✔
3219
    state->fillable = 0;
280,512✔
3220
    state->active_color_count = 0;
278,185!
3221
}
173,240✔
3222

3223
static void
3224
sixel_band_finish(sixel_encode_work_t *work, sixel_band_state_t *state)
200,638✔
3225
{
3226
    int color_index;
87,940✔
3227
    int c;
87,940✔
3228

3229
    if (work->active_colors == NULL
200,638!
3230
        || work->active_color_index == NULL) {
200,638!
3231
        state->active_color_count = 0;
×
3232
        return;
×
3233
    }
3234

3235
    for (color_index = 0;
518,969✔
3236
         color_index < state->active_color_count;
6,573,131✔
3237
         color_index++) {
6,372,493✔
3238
        c = work->active_color_index[color_index];
6,372,511✔
3239
        if (c >= 0
6,372,511!
3240
            && (size_t)c < work->active_colors_size) {
6,372,509!
3241
            work->active_colors[c] = 0;
6,372,495✔
3242
        }
406,273✔
3243
    }
406,289✔
3244
    state->active_color_count = 0;
200,620✔
3245
}
12,689✔
3246

3247
static void
3248
sixel_band_clear_map(sixel_encode_work_t *work)
200,623✔
3249
{
3250
    if (work->map != NULL && work->map_size > 0) {
200,623!
3251
        memset(work->map, 0, work->map_size);
200,623✔
3252
    }
12,705✔
3253
}
200,623✔
3254

3255
static SIXELSTATUS
3256
sixel_encode_emit_palette(int bodyonly,
35,541✔
3257
                          int ncolors,
3258
                          int keycolor,
3259
                          unsigned char const *palette,
3260
                          float const *palette_float,
3261
                          sixel_output_t *output)
3262
{
3263
    SIXELSTATUS status = SIXEL_FALSE;
35,541✔
3264
    int n;
15,553✔
3265

3266
    if (bodyonly || (ncolors == 2 && keycolor != (-1))) {
35,541!
3267
        return SIXEL_OK;
459✔
3268
    }
3269

3270
    if (palette == NULL && palette_float == NULL) {
34,725!
3271
        sixel_helper_set_additional_message(
×
3272
            "sixel_encode_emit_palette: missing palette data.");
3273
        return SIXEL_BAD_ARGUMENT;
×
3274
    }
3275

3276
    if (output->palette_type == SIXEL_PALETTETYPE_HLS) {
34,725✔
3277
        for (n = 0; n < ncolors; n++) {
36,912✔
3278
            status = output_hls_palette_definition(output,
39,066✔
3279
                                                   palette,
2,298✔
3280
                                                   palette_float,
2,298✔
3281
                                                   n,
2,298✔
3282
                                                   keycolor);
2,298✔
3283
            if (SIXEL_FAILED(status)) {
36,768!
3284
                goto end;
3285
            }
3286
        }
2,298✔
3287
    } else {
9✔
3288
        for (n = 0; n < ncolors; n++) {
2,662,928✔
3289
            status = output_rgb_palette_definition(output,
2,795,066✔
3290
                                                   palette,
166,718✔
3291
                                                   palette_float,
166,718✔
3292
                                                   n,
166,718✔
3293
                                                   keycolor);
166,718✔
3294
            if (SIXEL_FAILED(status)) {
2,628,347!
3295
                goto end;
3296
            }
3297
        }
166,718✔
3298
    }
3299

3300
    status = SIXEL_OK;
19,529✔
3301

3302
end:
32,895✔
3303
    return status;
19,529✔
3304
}
2,237✔
3305

3306
static SIXELSTATUS
3307
sixel_band_classify_row(sixel_encode_work_t *work,
1,121,188✔
3308
                        sixel_band_state_t *state,
3309
                        sixel_index_t *pixels,
3310
                        int width,
3311
                        int height,
3312
                        int map_width,
3313
                        int absolute_row,
3314
                        int offset_left,
3315
                        int offset_top,
3316
                        int ncolors,
3317
                        int keycolor,
3318
                        unsigned char *palstate,
3319
                        int encode_policy)
3320
{
3321
    SIXELSTATUS status = SIXEL_FALSE;
1,121,188✔
3322
    int row_bit;
491,433✔
3323
    int band_start;
491,433✔
3324
    int source_row;
491,433✔
3325
    int source_band_start;
491,433✔
3326
    int target_x;
491,433✔
3327
    int pix;
491,433✔
3328
    int x;
491,433✔
3329
    int check_integer_overflow;
491,433✔
3330
    int source_index_base;
491,433✔
3331
    char *map;
491,433✔
3332
    unsigned char *active_colors;
491,433✔
3333
    int *active_color_index;
491,433✔
3334

3335
    map = work->map;
1,121,188✔
3336
    active_colors = work->active_colors;
1,121,188✔
3337
    active_color_index = work->active_color_index;
1,121,188✔
3338
    row_bit = state->row_in_band;
1,121,188✔
3339
    band_start = absolute_row - row_bit;
1,121,188✔
3340

3341
    if (row_bit == 0) {
1,121,188✔
3342
        if (encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
200,627✔
3343
            state->fillable = 0;
200,451✔
3344
        } else if (palstate) {
12,870!
3345
            source_band_start = band_start - offset_top;
×
3346
            if (width > 0) {
×
3347
                if (source_band_start < 0 ||
×
3348
                    source_band_start >= height) {
×
3349
                    state->fillable = 0;
×
3350
                } else {
3351
                    pix = pixels[source_band_start * width];
×
3352
                    if (pix >= ncolors) {
×
3353
                        state->fillable = 0;
×
3354
                    } else {
3355
                        state->fillable = 1;
×
3356
                    }
3357
                }
3358
            } else {
3359
                state->fillable = 0;
×
3360
            }
3361
        } else {
3362
            state->fillable = 1;
176✔
3363
        }
3364
        state->active_color_count = 0;
200,627✔
3365
    }
12,705✔
3366

3367
    source_row = absolute_row - offset_top;
1,121,188✔
3368
    if (source_row < 0 || source_row >= height) {
1,121,188!
3369
        state->row_in_band += 1;
169✔
3370
        return SIXEL_OK;
169✔
3371
    }
3372
    if (source_row > INT_MAX / width) {
1,121,061!
3373
        sixel_helper_set_additional_message(
×
3374
            "sixel_encode_body: integer overflow detected."
3375
            " (source_y > INT_MAX)");
3376
        status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3377
        goto end;
×
3378
    }
3379
    source_index_base = source_row * width;
1,121,061✔
3380

3381
    for (x = 0; x < width; x++) {
135,613,430✔
3382
        target_x = offset_left + x;
134,479,347✔
3383
        if (target_x < 0 || target_x >= map_width) {
134,479,347!
3384
            sixel_helper_set_additional_message(
94,527✔
3385
                "sixel_encode_body: transparent offset is out of range.");
3386
            status = SIXEL_BAD_INTEGER_OVERFLOW;
88,100✔
3387
            goto end;
88,100✔
3388
        }
3389
        check_integer_overflow = source_index_base;
134,492,389✔
3390
        if (check_integer_overflow > INT_MAX - x) {
134,492,389!
3391
            sixel_helper_set_additional_message(
×
3392
                "sixel_encode_body: integer overflow detected."
3393
                " (source_y * width > INT_MAX - x)");
3394
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3395
            goto end;
×
3396
        }
3397
        pix = pixels[check_integer_overflow + x];
134,492,389✔
3398
        if (pix >= 0 && pix < ncolors && pix != keycolor) {
134,492,389!
3399
            if (pix > INT_MAX / map_width) {
114,358,940!
3400
                sixel_helper_set_additional_message(
×
3401
                    "sixel_encode_body: integer overflow detected."
3402
                    " (pix > INT_MAX / width)");
3403
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3404
                goto end;
×
3405
            }
3406
            check_integer_overflow = pix * map_width;
114,358,940✔
3407
            if (check_integer_overflow > INT_MAX - target_x) {
114,358,940!
3408
                sixel_helper_set_additional_message(
×
3409
                    "sixel_encode_body: integer overflow detected."
3410
                    " (pix * width > INT_MAX - x)");
3411
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3412
                goto end;
×
3413
            }
3414
            map[check_integer_overflow + target_x] |= (1 << row_bit);
114,358,940✔
3415
            if (active_colors != NULL && active_colors[pix] == 0) {
114,358,940!
3416
                active_colors[pix] = 1;
6,362,672✔
3417
                if (state->active_color_count < ncolors
6,362,672!
3418
                    && active_color_index != NULL) {
6,369,800!
3419
                    active_color_index[state->active_color_count] = pix;
6,369,807✔
3420
                    state->active_color_count += 1;
6,369,807✔
3421
                }
406,102✔
3422
            }
406,130✔
3423
        } else if (!palstate) {
27,258,142✔
3424
            state->fillable = 0;
15,446,746✔
3425
        }
959,056✔
3426
    }
8,385,511✔
3427

3428
    state->row_in_band += 1;
1,134,083✔
3429
    status = SIXEL_OK;
1,134,083✔
3430

3431
end:
855,505✔
3432
    return status;
642,715✔
3433
}
71,056✔
3434

3435
static SIXELSTATUS
3436
sixel_band_compose(sixel_encode_work_t *work,
200,633✔
3437
                   sixel_band_state_t *state,
3438
                   sixel_output_t *output,
3439
                   int width,
3440
                   int ncolors,
3441
                   int keycolor,
3442
                   sixel_allocator_t *allocator)
3443
{
3444
    SIXELSTATUS status = SIXEL_FALSE;
200,633✔
3445
    int color_index;
87,934✔
3446
    int c;
87,934✔
3447
    unsigned char *row;
87,934✔
3448
    int sx;
87,934✔
3449
    int mx;
87,934✔
3450
    int gap;
87,934✔
3451
    int gap_reached_end;
87,934✔
3452
    sixel_node_t *np;
87,934✔
3453
    sixel_node_t *column_head;
87,934✔
3454
    sixel_node_t *column_tail;
87,934✔
3455
    sixel_node_t top;
87,934✔
3456

3457
    (void)ncolors;
100,641✔
3458
    (void)keycolor;
100,641✔
3459
    row = NULL;
200,633✔
3460
    gap_reached_end = 0;
200,633✔
3461
    np = NULL;
200,633✔
3462
    column_head = NULL;
200,633✔
3463
    column_tail = NULL;
200,633✔
3464
    top.next = NULL;
200,633✔
3465

3466
    if (work->columns != NULL) {
200,633!
3467
        memset(work->columns, 0, work->columns_size);
200,632✔
3468
    }
12,707✔
3469
    output->node_top = NULL;
200,633✔
3470

3471
    for (color_index = 0;
606,619✔
3472
         color_index < state->active_color_count;
6,574,312✔
3473
         color_index++) {
6,373,679✔
3474
        c = work->active_color_index[color_index];
6,370,740✔
3475
        row = (unsigned char *)(work->map + c * width);
6,370,740✔
3476
        sx = 0;
6,370,740✔
3477
        while (sx < width) {
15,901,246✔
3478
            sx = sixel_compose_find_run_start(
15,451,207✔
3479
                row,
982,721✔
3480
                width,
982,721✔
3481
                sx);
982,721✔
3482
            if (sx >= width) {
15,451,192✔
3483
                break;
3,317,636✔
3484
            }
3485

3486
            mx = sx + 1;
9,527,552✔
3487
            while (mx < width) {
53,819,972✔
3488
                if (row[mx] != 0) {
53,372,701✔
3489
                    mx += 1;
36,670,431✔
3490
                    continue;
36,670,431✔
3491
                }
3492

3493
                gap = sixel_compose_measure_gap(
16,702,270✔
3494
                    row,
1,061,053✔
3495
                    width,
1,061,053✔
3496
                    mx + 1,
1,061,053✔
3497
                    &gap_reached_end);
3498
                if (gap >= 9 || gap_reached_end) {
16,699,709✔
3499
                    break;
576,105✔
3500
                }
3501
                mx += gap + 1;
7,621,989✔
3502
            }
3503

3504
            if ((np = output->node_free) != NULL) {
9,524,991✔
3505
                output->node_free = np->next;
2,088,160✔
3506
            } else {
13,768✔
3507
                status = sixel_node_new(&np, allocator);
7,436,831✔
3508
                if (SIXEL_FAILED(status)) {
7,440,527!
3509
                    goto end;
3510
                }
3511
            }
3512

3513
            np->pal = c;
9,530,506✔
3514
            np->sx = sx;
9,530,506✔
3515
            np->mx = mx;
9,530,506✔
3516
            np->map = (char *)row;
9,530,506✔
3517
            np->next = NULL;
9,530,506✔
3518

3519
            if (work->columns != NULL) {
9,530,506!
3520
                column_head = work->columns[sx];
9,530,277✔
3521
                if (column_head == NULL
9,530,277✔
3522
                    || column_head->mx <= np->mx) {
4,594,291✔
3523
                    np->next = column_head;
7,482,392✔
3524
                    work->columns[sx] = np;
7,482,392✔
3525
                } else {
473,498✔
3526
                    column_tail = column_head;
1,146,835✔
3527
                    while (column_tail->next != NULL
2,501,539✔
3528
                           && column_tail->next->mx > np->mx) {
2,740,602✔
3529
                        column_tail = column_tail->next;
387,985✔
3530
                    }
3531
                    np->next = column_tail->next;
2,047,885✔
3532
                    column_tail->next = np;
2,047,885✔
3533
                }
3534
            } else {
605,203✔
3535
                top.next = output->node_top;
229✔
3536
                column_tail = &top;
229✔
3537

3538
                while (column_tail->next != NULL) {
229!
3539
                    if (np->sx < column_tail->next->sx) {
×
3540
                        break;
3541
                    } else if (np->sx == column_tail->next->sx
×
3542
                               && np->mx > column_tail->next->mx) {
×
3543
                        break;
3544
                    }
3545
                    column_tail = column_tail->next;
3546
                }
3547

3548
                np->next = column_tail->next;
229✔
3549
                column_tail->next = np;
229✔
3550
                output->node_top = top.next;
229✔
3551
            }
3552

3553
            sx = mx;
7,774,360✔
3554
        }
3555
    }
405,986✔
3556

3557
    if (work->columns != NULL) {
203,572!
3558
        top.next = NULL;
200,632✔
3559
        column_tail = &top;
200,632✔
3560
        for (sx = 0; sx < width; sx++) {
23,307,859✔
3561
            column_head = work->columns[sx];
23,107,227✔
3562
            if (column_head == NULL) {
23,107,227✔
3563
                continue;
17,843,171✔
3564
            }
3565
            column_tail->next = column_head;
5,264,056✔
3566
            while (column_tail->next != NULL) {
14,787,996✔
3567
                column_tail = column_tail->next;
5,340,588✔
3568
            }
3569
            work->columns[sx] = NULL;
5,264,056✔
3570
        }
331,990✔
3571
        output->node_top = top.next;
200,632✔
3572
    }
12,705✔
3573

3574
    status = SIXEL_OK;
203,572✔
3575

3576
end:
190,865✔
3577
    return status;
203,572✔
3578
}
3579

3580
static SIXELSTATUS
3581
sixel_band_emit(sixel_encode_work_t *work,
200,622✔
3582
                sixel_band_state_t *state,
3583
                sixel_output_t *output,
3584
                int ncolors,
3585
                int keycolor,
3586
                int last_row_index)
3587
{
3588
    SIXELSTATUS status = SIXEL_FALSE;
200,622✔
3589
    sixel_node_t *np;
87,930✔
3590
    sixel_node_t *next;
87,930✔
3591
    int x;
87,930✔
3592
    int emit_next_line;
87,930✔
3593

3594
    emit_next_line = (last_row_index >= 6);
200,622✔
3595
    if (emit_next_line) {
200,622✔
3596
        /*
3597
         * Emit DECGNL only after the first band. The first band starts at the
3598
         * origin, so leading DECGNL would shift short images down by 6 rows.
3599
         */
3600
        output->buffer[output->pos] = '-';
165,092✔
3601
        sixel_advance(output, 1);
165,092✔
3602
    }
10,469✔
3603

3604
    for (x = 0; (np = output->node_top) != NULL;) {
1,486,579✔
3605
        if (x > np->sx) {
1,285,880✔
3606
            output->buffer[output->pos] = '$';
1,091,989✔
3607
            sixel_advance(output, 1);
1,091,989✔
3608
            x = 0;
1,091,976✔
3609
        }
69,920✔
3610

3611
        if (state->fillable) {
1,285,867✔
3612
            memset(np->map + np->sx,
198✔
3613
                   (1 << state->row_in_band) - 1,
176✔
3614
                   (size_t)(np->mx - np->sx));
176✔
3615
        }
11✔
3616
        status = sixel_put_node(output,
1,368,070✔
3617
                                &x,
3618
                                np,
82,203✔
3619
                                ncolors,
82,203✔
3620
                                keycolor);
82,203✔
3621
        if (SIXEL_FAILED(status)) {
1,285,832!
3622
            goto end;
3623
        }
3624
        next = np->next;
1,285,832✔
3625
        sixel_node_del(output, np);
1,285,832✔
3626
        np = next;
1,285,848✔
3627

3628
        while (np != NULL) {
50,612,479✔
3629
            if (np->sx < x) {
49,326,517✔
3630
                np = np->next;
41,086,276✔
3631
                continue;
41,086,276✔
3632
            }
3633

3634
            if (state->fillable) {
8,240,241!
3635
                memset(np->map + np->sx,
×
3636
                       (1 << state->row_in_band) - 1,
×
3637
                       (size_t)(np->mx - np->sx));
×
3638
            }
3639
            status = sixel_put_node(output,
8,761,760✔
3640
                                    &x,
3641
                                    np,
521,519✔
3642
                                    ncolors,
521,519✔
3643
                                    keycolor);
521,519✔
3644
            if (SIXEL_FAILED(status)) {
8,237,971!
3645
                goto end;
3646
            }
3647
            next = np->next;
8,237,971✔
3648
            sixel_node_del(output, np);
8,237,971✔
3649
            np = next;
8,239,892✔
3650
        }
3651

3652
        state->fillable = 0;
1,285,954✔
3653
    }
3654

3655
    status = SIXEL_OK;
112,775✔
3656

3657
end:
187,995✔
3658
    (void)work;
100,628✔
3659
    return status;
200,699✔
3660
}
3661

3662

3663
SIXELSTATUS
3664
sixel_encode_body(
16,685✔
3665
    sixel_index_t       /* in */ *pixels,
3666
    int                 /* in */ width,
3667
    int                 /* in */ height,
3668
    unsigned char       /* in */ *palette,
3669
    float const         /* in */ *palette_float,
3670
    int                 /* in */ ncolors,
3671
    int                 /* in */ keycolor,
3672
    int                 /* in */ bodyonly,
3673
    sixel_output_t      /* in */ *output,
3674
    unsigned char       /* in */ *palstate,
3675
    sixel_allocator_t   /* in */ *allocator,
3676
    int                 /* in */ pin_threads,
3677
    sixel_timeline_logger_t      /* in */ *logger)
3678
{
3679
    SIXELSTATUS status = SIXEL_FALSE;
16,685✔
3680
    int band_start;
7,682✔
3681
    int band_height;
7,682✔
3682
    int row_index;
7,682✔
3683
    int absolute_row;
7,682✔
3684
    int last_row_index;
7,682✔
3685
    int encoded_width;
7,682✔
3686
    int encoded_height;
7,682✔
3687
    int offset_left;
7,682✔
3688
    int offset_top;
7,682✔
3689
    sixel_node_t *np;
7,682✔
3690
    sixel_encode_work_t work;
7,682✔
3691
    sixel_band_state_t band;
7,682✔
3692
    int logging_active;
7,682✔
3693
    int job_index;
7,682✔
3694

3695
#if !SIXEL_ENABLE_THREADS
3696
    (void) pin_threads;
4,402✔
3697
#endif
3698

3699
    sixel_encode_work_init(&work);
16,685✔
3700
    sixel_band_state_reset(&band);
16,685✔
3701

3702
    /* Record the caller/environment preference even before we fan out. */
3703
    work.requested_threads = sixel_threads_resolve();
16,685✔
3704

3705
    if (ncolors < 1) {
16,685!
3706
        status = SIXEL_BAD_ARGUMENT;
×
3707
        goto cleanup;
×
3708
    }
3709
    output->active_palette = (-1);
16,685✔
3710

3711
    status = sixel_output_compute_transparent_extent(output,
17,335✔
3712
                                                     width,
650✔
3713
                                                     height,
650✔
3714
                                                     &encoded_width,
3715
                                                     &encoded_height);
3716
    if (SIXEL_FAILED(status)) {
16,685!
3717
        goto cleanup;
×
3718
    }
3719
    offset_left = output->transparent_offset_left;
16,685✔
3720
    offset_top = output->transparent_offset_top;
16,685✔
3721

3722
    logging_active = logger != NULL;
16,685✔
3723
    job_index = 0;
16,685✔
3724

3725
    status = sixel_encode_emit_palette(bodyonly,
17,335✔
3726
                                       ncolors,
650✔
3727
                                       keycolor,
650✔
3728
                                       palette,
650✔
3729
                                       palette_float,
650✔
3730
                                       output);
650✔
3731
    if (SIXEL_FAILED(status)) {
16,685!
3732
        goto cleanup;
×
3733
    }
3734

3735
#if SIXEL_ENABLE_THREADS
3736
    {
3737
        int nbands;
3,280✔
3738
        int threads;
3,280✔
3739

3740
        nbands = sixel_count_sixel_bands(encoded_height);
7,881!
3741
        threads = work.requested_threads;
7,881✔
3742
        if (nbands > 1 && threads > 1) {
7,881✔
3743
            status = sixel_encode_body_parallel(pixels,
2,759✔
3744
                                                width,
213✔
3745
                                                height,
213✔
3746
                                                ncolors,
213✔
3747
                                                keycolor,
213✔
3748
                                                output,
213✔
3749
                                                palstate,
213✔
3750
                                                allocator,
213✔
3751
                                                threads,
213✔
3752
                                                pin_threads);
213✔
3753
            if (SIXEL_FAILED(status)) {
2,546!
3754
                goto cleanup;
3755
            }
3756
            goto finalize;
2,546✔
3757
        }
3758
    }
3759
#endif
3760

3761
    if (logging_active) {
14,139!
3762
        sixel_timeline_logger_logf(logger,
24✔
3763
                          "controller",
3764
                          "encode",
3765
                          "configure",
3766
                          -1);
3767
    }
2✔
3768

3769
    status = sixel_encode_work_allocate(&work,
14,139✔
3770
                                        encoded_width,
437✔
3771
                                        ncolors,
437✔
3772
                                        allocator);
437✔
3773
    if (SIXEL_FAILED(status)) {
14,139!
3774
        goto cleanup;
×
3775
    }
3776

3777
    band_start = 0;
7,518✔
3778
    while (band_start < encoded_height) {
83,726✔
3779
        band_height = encoded_height - band_start;
69,587✔
3780
        if (band_height > 6) {
69,587✔
3781
            band_height = 6;
28,928✔
3782
        }
1,204✔
3783

3784
        band.row_in_band = 0;
69,587✔
3785
        band.fillable = 0;
69,587✔
3786
        band.active_color_count = 0;
69,587✔
3787

3788
        if (logging_active) {
69,587!
3789
            sixel_timeline_logger_logf(logger,
52✔
3790
                              "worker",
3791
                              "encode",
3792
                              "start",
3793
                              job_index);
4✔
3794
        }
4✔
3795

3796
        for (row_index = 0; row_index < band_height; row_index++) {
448,648✔
3797
            absolute_row = band_start + row_index;
379,061✔
3798
            status = sixel_band_classify_row(&work,
379,061✔
3799
                                             &band,
3800
                                             pixels,
8,374✔
3801
                                             width,
8,374✔
3802
                                             height,
8,374✔
3803
                                             encoded_width,
8,374✔
3804
                                             absolute_row,
8,374✔
3805
                                             offset_left,
8,374✔
3806
                                             offset_top,
8,374✔
3807
                                             ncolors,
8,374✔
3808
                                             keycolor,
8,374✔
3809
                                             palstate,
8,374✔
3810
                                             output->encode_policy);
8,374✔
3811
            if (SIXEL_FAILED(status)) {
379,061!
3812
                goto cleanup;
×
3813
            }
3814
        }
8,374✔
3815

3816
        status = sixel_band_compose(&work,
69,587✔
3817
                                    &band,
3818
                                    output,
1,641✔
3819
                                    encoded_width,
1,641✔
3820
                                    ncolors,
1,641✔
3821
                                    keycolor,
1,641✔
3822
                                    allocator);
1,641✔
3823
        if (SIXEL_FAILED(status)) {
69,587!
3824
            goto cleanup;
3825
        }
3826

3827
        last_row_index = band_start + band_height - 1;
69,587✔
3828
        status = sixel_band_emit(&work,
69,587✔
3829
                                 &band,
3830
                                 output,
1,641✔
3831
                                 ncolors,
1,641✔
3832
                                 keycolor,
1,641✔
3833
                                 last_row_index);
1,641✔
3834
        if (SIXEL_FAILED(status)) {
69,587!
3835
            goto cleanup;
3836
        }
3837

3838
        sixel_band_finish(&work, &band);
69,587✔
3839

3840
        sixel_band_clear_map(&work);
69,586✔
3841

3842
        if (logging_active) {
69,587!
3843
            sixel_timeline_logger_logf(logger,
52✔
3844
                              "worker",
3845
                              "encode",
3846
                              "finish",
3847
                              job_index);
4✔
3848
        }
4✔
3849

3850
        band_start += band_height;
69,587✔
3851
        sixel_band_state_reset(&band);
69,587✔
3852
        job_index += 1;
69,587✔
3853
    }
3854

3855
    status = SIXEL_OK;
14,139✔
3856
    goto finalize;
14,139✔
3857

3858
finalize:
16,035✔
3859
    if (palstate) {
16,813✔
3860
        output->buffer[output->pos] = '$';
2,048✔
3861
        sixel_advance(output, 1);
2,048✔
3862
    }
128✔
3863

3864
cleanup:
18,146✔
3865
    while ((np = output->node_free) != NULL) {
702,093✔
3866
        output->node_free = np->next;
685,408✔
3867
        sixel_allocator_free(allocator, np);
685,408✔
3868
    }
3869
    output->node_top = NULL;
16,685✔
3870

3871
    sixel_encode_work_cleanup(&work, allocator);
16,685✔
3872

3873
    return status;
16,685✔
3874
}
3875
SIXELSTATUS
3876
sixel_encode_footer(sixel_output_t *output)
33,893✔
3877
{
3878
    SIXELSTATUS status = SIXEL_FALSE;
33,893✔
3879

3880
    if (output->pos > 0) {
33,893✔
3881
        status = sixel_output_write_bytes(output,
50,715✔
3882
                                          (char *)output->buffer,
33,797✔
3883
                                          output->pos);
2,128✔
3884
        if (SIXEL_FAILED(status)) {
33,797✔
3885
            return status;
3886
        }
3887
        output->pos = 0;
33,797✔
3888
    }
2,128✔
3889

3890
    status = sixel_output_end_image(output);
33,893✔
3891

3892
    return status;
33,893✔
3893
}
2,134✔
3894

3895
static int
3896
sixel_encode_body_ormode_nplanes(int ncolors)
740✔
3897
{
3898
    int nplanes;
290✔
3899

3900
    for (nplanes = 8; nplanes > 1; nplanes--) {
3,800!
3901
        if (ncolors > (1 << (nplanes - 1))) {
3,800✔
3902
            break;
450✔
3903
        }
3904
    }
240✔
3905

3906
    return nplanes;
756✔
3907
}
3908

3909
static SIXELSTATUS
3910
sixel_encode_body_ormode_emit_palette(unsigned char const *palette,
152✔
3911
                                      int ncolors,
3912
                                      int keycolor,
3913
                                      sixel_output_t *output)
3914
{
3915
    SIXELSTATUS status;
66✔
3916
    int n;
66✔
3917

3918
    if (palette == NULL || output == NULL) {
152!
3919
        return SIXEL_BAD_ARGUMENT;
3920
    }
3921
    for (n = 0; n < ncolors; n++) {
5,304✔
3922
        status = output_rgb_palette_definition(output,
5,476✔
3923
                                               palette,
324✔
3924
                                               NULL,
3925
                                               n,
324✔
3926
                                               keycolor);
324✔
3927
        if (SIXEL_FAILED(status)) {
5,152!
3928
            return status;
3929
        }
3930
    }
324✔
3931

3932
    return SIXEL_OK;
86✔
3933
}
10✔
3934

3935
static SIXELSTATUS
3936
sixel_encode_body_ormode_band(sixel_index_t const *pixels,
1,000✔
3937
                              int width,
3938
                              int height,
3939
                              int band_index,
3940
                              int nplanes,
3941
                              sixel_output_t *output)
3942
{
3943
    SIXELSTATUS status;
436✔
3944
    sixel_index_t const *band_pixels;
436✔
3945
    sixel_index_t const *column_pixels;
436✔
3946
    sixel_index_t const *row0;
436✔
3947
    sixel_index_t const *row1;
436✔
3948
    sixel_index_t const *row2;
436✔
3949
    sixel_index_t const *row3;
436✔
3950
    sixel_index_t const *row4;
436✔
3951
    sixel_index_t const *row5;
436✔
3952
    int band_start;
436✔
3953
    int band_height;
436✔
3954
    int nwrite;
436✔
3955
    int plane;
436✔
3956
    int plane_bit;
436✔
3957
    int full_mask;
436✔
3958
    int sample_mask;
436✔
3959
    int sample_width;
436✔
3960
    int first_x;
436✔
3961
    int first_pix;
436✔
3962
    int x;
436✔
3963
    int y;
436✔
3964
    int pix;
436✔
3965

3966
    if (pixels == NULL || output == NULL || width < 1 || height < 0 ||
1,000!
3967
            band_index < 0 || nplanes < 1) {
1,000!
3968
        return SIXEL_BAD_ARGUMENT;
3969
    }
3970

3971
    band_start = band_index * 6;
1,000✔
3972
    if (band_start >= height) {
1,000!
3973
        return SIXEL_OK;
3974
    }
3975
    band_height = height - band_start;
1,000✔
3976
    if (band_height > 6) {
1,000✔
3977
        band_height = 6;
478✔
3978
    }
54✔
3979
    band_pixels = pixels + (size_t)band_start * (size_t)width;
1,000✔
3980

3981
    /*
3982
     * Full six-row bands are the hot path for real images.  Keep the partial
3983
     * tail on the generic loop below, but avoid the inner row loop and repeated
3984
     * width multiplication for complete bands.
3985
     */
3986
    if (band_height == 6) {
1,000✔
3987
        row0 = band_pixels;
880✔
3988
        row1 = row0 + width;
880✔
3989
        row2 = row1 + width;
880✔
3990
        row3 = row2 + width;
880✔
3991
        row4 = row3 + width;
880✔
3992
        row5 = row4 + width;
880✔
3993
        if (output->encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
880✔
3994
            for (plane = 0; plane < nplanes; plane++) {
4,512✔
3995
                sixel_putc(output->buffer + output->pos, '#');
3,648✔
3996
                sixel_advance(output, 1);
3,648✔
3997
                nwrite = sixel_putnum((char *)output->buffer + output->pos,
3,878✔
3998
                                      1 << plane);
230✔
3999
                sixel_advance(output, nwrite);
3,648✔
4000

4001
                for (x = 0; x < width; x++) {
230,874✔
4002
                    pix = (((row0[x] >> plane) & 0x1) << 0) |
253,864✔
4003
                          (((row1[x] >> plane) & 0x1) << 1) |
239,748✔
4004
                          (((row2[x] >> plane) & 0x1) << 2) |
239,748✔
4005
                          (((row3[x] >> plane) & 0x1) << 3) |
239,748✔
4006
                          (((row4[x] >> plane) & 0x1) << 4) |
239,748✔
4007
                          (((row5[x] >> plane) & 0x1) << 5);
225,632✔
4008
                    sixel_put_pixel(output, pix);
225,632✔
4009
                }
14,116✔
4010
                status = sixel_put_flash(output);
3,648✔
4011
                if (SIXEL_FAILED(status)) {
3,648!
4012
                    return status;
4013
                }
4014

4015
                sixel_putc(output->buffer + output->pos, '$');
3,648✔
4016
                sixel_advance(output, 1);
3,648✔
4017
            }
230✔
4018
            sixel_putc(output->buffer + output->pos, '-');
864✔
4019
            sixel_advance(output, 1);
864✔
4020
            return SIXEL_OK;
864✔
4021
        }
4022

4023
        full_mask = (1 << nplanes) - 1;
16✔
4024
        sample_mask = 0;
16✔
4025
        sample_width = width < 64 ? width : 64;
16✔
4026
        for (x = 0; x < sample_width; x++) {
96✔
4027
            sample_mask |= row0[x] | row1[x] | row2[x] |
90✔
4028
                           row3[x] | row4[x] | row5[x];
85✔
4029
        }
5✔
4030

4031
        if ((sample_mask & full_mask) == full_mask) {
16!
4032
            for (plane = 0; plane < nplanes; plane++) {
×
4033
                plane_bit = 1 << plane;
×
4034
                sixel_putc(output->buffer + output->pos, '#');
×
4035
                sixel_advance(output, 1);
×
4036
                nwrite = sixel_putnum((char *)output->buffer + output->pos,
×
4037
                                      plane_bit);
4038
                sixel_advance(output, nwrite);
×
4039

4040
                for (x = 0; x < width; x++) {
×
4041
                    pix = ((row0[x] & plane_bit) ? 0x01 : 0) |
×
4042
                          ((row1[x] & plane_bit) ? 0x02 : 0) |
×
4043
                          ((row2[x] & plane_bit) ? 0x04 : 0) |
×
4044
                          ((row3[x] & plane_bit) ? 0x08 : 0) |
×
4045
                          ((row4[x] & plane_bit) ? 0x10 : 0) |
×
4046
                          ((row5[x] & plane_bit) ? 0x20 : 0);
×
4047
                    sixel_put_pixel(output, pix);
×
4048
                }
4049
                status = sixel_put_flash(output);
×
4050
                if (SIXEL_FAILED(status)) {
×
4051
                    return status;
4052
                }
4053

4054
                sixel_putc(output->buffer + output->pos, '$');
×
4055
                sixel_advance(output, 1);
×
4056
            }
4057
            sixel_putc(output->buffer + output->pos, '-');
×
4058
            sixel_advance(output, 1);
×
4059
            return SIXEL_OK;
×
4060
        }
4061

4062
        for (plane = 0; plane < nplanes; plane++) {
48✔
4063
            plane_bit = 1 << plane;
32✔
4064
            first_x = (-1);
32✔
4065
            pix = 0;
32✔
4066
            for (x = 0; x < width; x++) {
112✔
4067
                pix = ((row0[x] & plane_bit) ? 0x01 : 0) |
288✔
4068
                      ((row1[x] & plane_bit) ? 0x02 : 0) |
102✔
4069
                      ((row2[x] & plane_bit) ? 0x04 : 0) |
102✔
4070
                      ((row3[x] & plane_bit) ? 0x08 : 0) |
102✔
4071
                      ((row4[x] & plane_bit) ? 0x10 : 0) |
102✔
4072
                      ((row5[x] & plane_bit) ? 0x20 : 0);
96✔
4073
                if (pix != 0) {
96!
4074
                    first_x = x;
9✔
4075
                    break;
9✔
4076
                }
4077
            }
5✔
4078
            if (first_x < 0) {
32✔
4079
                continue;
16✔
4080
            }
4081

4082
            sixel_putc(output->buffer + output->pos, '#');
16✔
4083
            sixel_advance(output, 1);
16✔
4084
            nwrite = sixel_putnum((char *)output->buffer + output->pos,
17✔
4085
                                  plane_bit);
1✔
4086
            sixel_advance(output, nwrite);
16✔
4087

4088
            status = sixel_emit_run(output, '?', first_x);
16✔
4089
            if (SIXEL_FAILED(status)) {
16!
4090
                return status;
4091
            }
4092
            sixel_put_pixel(output, pix);
16✔
4093
            for (x = first_x + 1; x < width; x++) {
80✔
4094
                pix = ((row0[x] & plane_bit) ? 0x01 : 0) |
192✔
4095
                      ((row1[x] & plane_bit) ? 0x02 : 0) |
68!
4096
                      ((row2[x] & plane_bit) ? 0x04 : 0) |
68!
4097
                      ((row3[x] & plane_bit) ? 0x08 : 0) |
68!
4098
                      ((row4[x] & plane_bit) ? 0x10 : 0) |
68!
4099
                      ((row5[x] & plane_bit) ? 0x20 : 0);
64!
4100
                sixel_put_pixel(output, pix);
64✔
4101
            }
4✔
4102
            status = sixel_put_flash(output);
16✔
4103
            if (SIXEL_FAILED(status)) {
16!
4104
                return status;
4105
            }
4106

4107
            sixel_putc(output->buffer + output->pos, '$');
16✔
4108
            sixel_advance(output, 1);
16✔
4109
        }
1✔
4110
        sixel_putc(output->buffer + output->pos, '-');
16✔
4111
        sixel_advance(output, 1);
16✔
4112
        return SIXEL_OK;
16✔
4113
    }
4114

4115
    /*
4116
     * OR mode composes the final color by drawing powers-of-two bit-planes.
4117
     * Size policy may spend a little more branch work to avoid empty planes.
4118
     * Delay the plane header until the first non-zero cell.  A plane that never
4119
     * draws any cell cannot affect the OR-composited result, while a non-empty
4120
     * plane keeps its horizontal position by emitting the leading zero run
4121
     * before the first drawn cell.
4122
     */
4123
    if (output->encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
120!
4124
        for (plane = 0; plane < nplanes; plane++) {
552✔
4125
            sixel_putc(output->buffer + output->pos, '#');
432✔
4126
            sixel_advance(output, 1);
432✔
4127
            nwrite = sixel_putnum((char *)output->buffer + output->pos,
460✔
4128
                                  1 << plane);
28✔
4129
            sixel_advance(output, nwrite);
432✔
4130

4131
            column_pixels = band_pixels;
432✔
4132
            for (x = 0; x < width; x++, column_pixels++) {
23,404✔
4133
                pix = 0;
12,820✔
4134
                for (y = 0; y < band_height; y++) {
113,280✔
4135
                    pix |= (((column_pixels[width * y] >> plane) & 0x1) << y);
90,496✔
4136
                }
5,660✔
4137
                sixel_put_pixel(output, pix);
22,784✔
4138
            }
1,428✔
4139
            status = sixel_put_flash(output);
432✔
4140
            if (SIXEL_FAILED(status)) {
432!
4141
                return status;
4142
            }
4143

4144
            sixel_putc(output->buffer + output->pos, '$');
432✔
4145
            sixel_advance(output, 1);
432✔
4146
        }
28✔
4147
        return SIXEL_OK;
68✔
4148
    }
4149

4150
    full_mask = (1 << nplanes) - 1;
×
4151
    sample_mask = 0;
×
4152
    sample_width = width < 64 ? width : 64;
×
4153
    column_pixels = band_pixels;
×
4154
    for (x = 0; x < sample_width; x++, column_pixels++) {
×
4155
        for (y = 0; y < band_height; y++) {
×
4156
            sample_mask |= column_pixels[width * y];
×
4157
        }
4158
    }
4159

4160
    if ((sample_mask & full_mask) == full_mask) {
×
4161
        for (plane = 0; plane < nplanes; plane++) {
×
4162
            sixel_putc(output->buffer + output->pos, '#');
×
4163
            sixel_advance(output, 1);
×
4164
            nwrite = sixel_putnum((char *)output->buffer + output->pos,
×
4165
                                  1 << plane);
4166
            sixel_advance(output, nwrite);
×
4167

4168
            column_pixels = band_pixels;
×
4169
            for (x = 0; x < width; x++, column_pixels++) {
×
4170
                pix = 0;
4171
                for (y = 0; y < band_height; y++) {
×
4172
                    pix |= (((column_pixels[width * y] >> plane) & 0x1) << y);
×
4173
                }
4174
                sixel_put_pixel(output, pix);
×
4175
            }
4176
            status = sixel_put_flash(output);
×
4177
            if (SIXEL_FAILED(status)) {
×
4178
                return status;
4179
            }
4180

4181
            sixel_putc(output->buffer + output->pos, '$');
×
4182
            sixel_advance(output, 1);
×
4183
        }
4184
        return SIXEL_OK;
4185
    }
4186

4187
    for (plane = 0; plane < nplanes; plane++) {
×
4188
        plane_bit = 1 << plane;
×
4189
        first_x = (-1);
×
4190
        first_pix = 0;
×
4191
        column_pixels = band_pixels;
×
4192
        for (x = 0; x < width; x++, column_pixels++) {
×
4193
            pix = 0;
4194
            for (y = 0; y < band_height; y++) {
×
4195
                pix |= (((column_pixels[width * y] >> plane) & 0x1) << y);
×
4196
            }
4197
            if (pix != 0) {
×
4198
                first_x = x;
4199
                first_pix = pix;
4200
                break;
4201
            }
4202
        }
4203
        if (first_x < 0) {
×
4204
            continue;
×
4205
        }
4206

4207
        sixel_putc(output->buffer + output->pos, '#');
×
4208
        sixel_advance(output, 1);
×
4209
        nwrite = sixel_putnum((char *)output->buffer + output->pos,
×
4210
                              plane_bit);
4211
        sixel_advance(output, nwrite);
×
4212

4213
        status = sixel_emit_run(output, '?', first_x);
×
4214
        if (SIXEL_FAILED(status)) {
×
4215
            return status;
4216
        }
4217
        sixel_put_pixel(output, first_pix);
×
4218
        column_pixels = band_pixels + first_x + 1;
×
4219
        for (x = first_x + 1; x < width; x++, column_pixels++) {
×
4220
            pix = 0;
4221
            for (y = 0; y < band_height; y++) {
×
4222
                pix |= (((column_pixels[width * y] >> plane) & 0x1) << y);
×
4223
            }
4224
            sixel_put_pixel(output, pix);
×
4225
        }
4226
        status = sixel_put_flash(output);
×
4227
        if (SIXEL_FAILED(status)) {
×
4228
            return status;
4229
        }
4230

4231
        sixel_putc(output->buffer + output->pos, '$');
×
4232
        sixel_advance(output, 1);
×
4233
    }
4234
    if (band_height == 6) {
×
4235
        sixel_putc(output->buffer + output->pos, '-');
4236
        sixel_advance(output, 1);
4237
    }
4238

4239
    return SIXEL_OK;
4240
}
64✔
4241

4242
SIXEL_INTERNAL_API SIXELSTATUS
4243
sixel_encode_body_ormode(
96✔
4244
    sixel_index_t       /* in */ *pixels,
4245
    int                 /* in */ width,
4246
    int                 /* in */ height,
4247
    unsigned char       /* in */ *palette,
4248
    int                 /* in */ ncolors,
4249
    int                 /* in */ keycolor,
4250
    sixel_output_t      /* in */ *output)
4251
{
4252
    SIXELSTATUS status;
43✔
4253
    int nplanes;
43✔
4254
    int nbands;
43✔
4255
    int band_index;
43✔
4256

4257
    if (pixels == NULL) {
96✔
4258
        return SIXEL_BAD_ARGUMENT;
9✔
4259
    }
4260

4261
    status = sixel_encode_body_ormode_emit_palette(palette,
84✔
4262
                                                   ncolors,
4✔
4263
                                                   keycolor,
4✔
4264
                                                   output);
4✔
4265
    if (SIXEL_FAILED(status)) {
80!
4266
        return status;
4267
    }
4268

4269
    nplanes = sixel_encode_body_ormode_nplanes(ncolors);
80✔
4270
    nbands = (height + 5) / 6;
80✔
4271
    for (band_index = 0; band_index < nbands; band_index++) {
384✔
4272
        status = sixel_encode_body_ormode_band(pixels,
310✔
4273
                                               width,
6✔
4274
                                               height,
6✔
4275
                                               band_index,
6✔
4276
                                               nplanes,
6✔
4277
                                               output);
6✔
4278
        if (SIXEL_FAILED(status)) {
304!
4279
            return status;
4280
        }
4281
    }
6✔
4282

4283
    return SIXEL_OK;
44✔
4284
}
5✔
4285

4286

4287
static SIXELSTATUS
4288
sixel_encode_dither(
33,573✔
4289
    unsigned char   /* in */ *pixels,   /* pixel bytes to be encoded */
4290
    int             /* in */ width,     /* width of source image */
4291
    int             /* in */ height,    /* height of source image */
4292
    sixel_dither_t  /* in */ *dither,   /* dither context */
4293
    sixel_output_t  /* in */ *output)   /* output context */
4294
{
4295
    SIXELSTATUS status = SIXEL_FALSE;
33,573✔
4296
    sixel_index_t *paletted_pixels = NULL;
33,573✔
4297
    sixel_index_t *input_pixels;
14,692✔
4298
    size_t bufsize;
14,692✔
4299
    unsigned char *palette_entries = NULL;
33,573✔
4300
    float *palette_entries_float32 = NULL;
33,573✔
4301
    sixel_palette_t *palette_obj = NULL;
33,573✔
4302
    size_t palette_count = 0U;
33,573✔
4303
    size_t palette_float_count = 0U;
33,573✔
4304
    size_t palette_bytes = 0U;
33,573✔
4305
    size_t palette_float_bytes = 0U;
33,573✔
4306
    size_t palette_channels = 0U;
33,573✔
4307
    size_t palette_index = 0U;
33,573✔
4308
    size_t pixel_count = 0U;
33,573✔
4309
    int palette_source_colorspace;
14,692✔
4310
    int palette_float_pixelformat;
14,692✔
4311
    int output_float_pixelformat;
14,692✔
4312
    int palette_float_depth;
14,692✔
4313
    int pipeline_active;
14,692✔
4314
    int pipeline_threads = 0;  /* set to a deterministic default before use */
33,573✔
4315
    int pipeline_nbands;
14,692✔
4316
    int encoded_width;
14,692✔
4317
    int encoded_height;
14,692✔
4318
    sixel_parallel_dither_config_t dither_parallel;
14,692✔
4319
    sixel_palette_entries_view_t palette_view;
14,692✔
4320
    sixel_palette_float32_entries_view_t palette_float_view;
14,692✔
4321
#if SIXEL_ENABLE_THREADS
4322
    sixel_timeline_logger_t *serial_logger;
10,536✔
4323
    int logger_owned = 0;
25,261✔
4324
#endif  /* SIXEL_ENABLE_THREADS */
4325
    sixel_timeline_logger_t *logger = NULL;
33,573✔
4326

4327
    if (output == NULL || dither == NULL) {
33,573!
4328
        return SIXEL_BAD_ARGUMENT;
4329
    }
4330

4331
#if SIXEL_ENABLE_THREADS
4332
    serial_logger = NULL;
25,261✔
4333
#endif  /* SIXEL_ENABLE_THREADS */
4334
    palette_source_colorspace = SIXEL_COLORSPACE_GAMMA;
33,573✔
4335
    if (width <= 0 || height <= 0 ||
33,573!
4336
        (size_t)width > SIZE_MAX / (size_t)height) {
33,573!
UNCOV
4337
        sixel_helper_set_additional_message(
×
4338
            "sixel_encode_dither: image size overflow.");
UNCOV
4339
        status = SIXEL_BAD_INTEGER_OVERFLOW;
×
4340
        goto end;
×
4341
    }
4342
    if (sixel_output_has_transparent_offset(output) != 0) {
33,573✔
4343
        if (output->ormode != 0) {
40!
UNCOV
4344
            sixel_helper_set_additional_message(
×
4345
                "transparent-offset cannot be used with ormode.");
UNCOV
4346
            status = SIXEL_BAD_ARGUMENT;
×
4347
            goto end;
×
4348
        }
4349
        if (output->encode_policy == SIXEL_ENCODEPOLICY_SIZE) {
40!
UNCOV
4350
            sixel_helper_set_additional_message(
×
4351
                "transparent-offset cannot be used with "
4352
                "encode-policy=size.");
UNCOV
4353
            status = SIXEL_BAD_ARGUMENT;
×
4354
            goto end;
×
4355
        }
4356
        if (output->transparent_policy != SIXEL_TRANSPARENT_POLICY_KEEP) {
40!
UNCOV
4357
            sixel_helper_set_additional_message(
×
4358
                "transparent-offset requires transparent-policy=keep.");
UNCOV
4359
            status = SIXEL_BAD_ARGUMENT;
×
4360
            goto end;
×
4361
        }
4362
    }
3✔
4363
    status = sixel_output_compute_transparent_extent(output,
35,687✔
4364
                                                     width,
2,114✔
4365
                                                     height,
2,114✔
4366
                                                     &encoded_width,
4367
                                                     &encoded_height);
4368
    if (SIXEL_FAILED(status)) {
33,573!
UNCOV
4369
        goto end;
×
4370
    }
4371
    pixel_count = (size_t)width * (size_t)height;
33,573✔
4372
    palette_float_pixelformat =
16,806✔
4373
        sixel_palette_float_pixelformat_for_colorspace(
33,573✔
4374
            palette_source_colorspace);
2,114✔
4375
    palette_float_depth =
16,806✔
4376
        sixel_helper_compute_depth(palette_float_pixelformat);
33,573✔
4377
    output_float_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
33,572✔
4378
    memset(&palette_view, 0, sizeof(palette_view));
33,572!
4379
    memset(&palette_float_view, 0, sizeof(palette_float_view));
33,572✔
4380
    palette_obj = dither->palette;
33,572✔
4381
    if (palette_obj == NULL || palette_obj->vtbl == NULL ||
33,572!
4382
            palette_obj->vtbl->get_entries == NULL ||
33,572!
4383
            palette_obj->vtbl->get_entries_float32 == NULL) {
33,572!
UNCOV
4384
        sixel_helper_set_additional_message(
×
4385
            "sixel_encode_dither: palette acquisition failed.");
UNCOV
4386
        status = SIXEL_BAD_ARGUMENT;
×
4387
        goto end;
×
4388
    }
4389

4390
    pipeline_active = 0;
33,572✔
4391
#if SIXEL_ENABLE_THREADS
4392
    #endif
4393
    dither_parallel.enabled = 0;
33,572✔
4394
    dither_parallel.band_height = 0;
33,572✔
4395
    dither_parallel.overlap = 0;
33,572✔
4396
    dither_parallel.dither_threads = 0;
33,572✔
4397
    dither_parallel.encode_threads = 0;
33,572✔
4398
    /*
4399
     * Normalize the planner-provided pinning request so both palette and
4400
     * encode workers see the same 0/1 flag.
4401
     */
4402
    dither->pipeline_pin_threads =
33,572✔
4403
        dither->pipeline_pin_threads != 0 ? 1 : 0;
33,572✔
4404
    dither_parallel.pin_threads = dither->pipeline_pin_threads;
33,572✔
4405
    switch (dither->pixelformat) {
33,572!
4406
    case SIXEL_PIXELFORMAT_PAL1:
4407
    case SIXEL_PIXELFORMAT_PAL2:
4408
    case SIXEL_PIXELFORMAT_PAL4:
4409
    case SIXEL_PIXELFORMAT_G1:
4410
    case SIXEL_PIXELFORMAT_G2:
4411
    case SIXEL_PIXELFORMAT_G4:
UNCOV
4412
        bufsize = (sizeof(sixel_index_t) * (size_t)width * (size_t)height * 3UL);
×
4413
        paletted_pixels = (sixel_index_t *)sixel_allocator_malloc(dither->allocator, bufsize);
×
4414
        if (paletted_pixels == NULL) {
×
4415
            sixel_helper_set_additional_message(
×
4416
                "sixel_encode_dither: sixel_allocator_malloc() failed.");
UNCOV
4417
            status = SIXEL_BAD_ALLOCATION;
×
4418
            goto end;
×
4419
        }
UNCOV
4420
        status = sixel_helper_normalize_pixelformat(paletted_pixels,
×
4421
                                                    &dither->pixelformat,
4422
                                                    pixels,
4423
                                                    dither->pixelformat,
4424
                                                    width, height);
UNCOV
4425
        if (SIXEL_FAILED(status)) {
×
4426
            goto end;
×
4427
        }
4428
        input_pixels = paletted_pixels;
4429
        break;
4430
    case SIXEL_PIXELFORMAT_PAL8:
1,536✔
4431
    case SIXEL_PIXELFORMAT_G8:
4432
    case SIXEL_PIXELFORMAT_GA88:
4433
    case SIXEL_PIXELFORMAT_AG88:
4434
        input_pixels = pixels;
1,729✔
4435
        break;
1,729✔
4436
    default:
28,577✔
4437
        /* apply palette */
4438
        pipeline_threads = sixel_threads_resolve();
30,498✔
4439
        pipeline_nbands = sixel_count_sixel_bands(encoded_height);
30,499!
4440
        /*
4441
         * Pipeline mode lets PaletteApply produce contiguous index rows while
4442
         * band workers consume completed six-line slices. OR mode now has its
4443
         * own band encoder, so it can share this producer/writer path without
4444
         * dereferencing input_pixels on the caller side.
4445
         */
4446
        if (pipeline_threads > 1 && pipeline_nbands > 1) {
30,499✔
4447
            pipeline_active = 1;
11,167✔
4448
            input_pixels = NULL;
11,167✔
4449
        } else {
1,613✔
4450
            paletted_pixels = sixel_dither_apply_palette(dither, pixels,
11,639✔
4451
                                                         width, height);
308✔
4452
            if (paletted_pixels == NULL) {
11,331!
UNCOV
4453
                status = SIXEL_RUNTIME_ERROR;
×
UNCOV
4454
                goto end;
×
4455
            }
4456
            input_pixels = paletted_pixels;
5,985✔
4457
        }
4458
        break;
17,152✔
4459
    }
4460

4461
    if (pipeline_active) {
26,882✔
4462
        sixel_parallel_dither_configure(height,
20,781✔
4463
                                        dither->ncolors,
1,613✔
4464
                                        pipeline_threads,
1,613✔
4465
                                        dither->pipeline_pin_threads,
1,613✔
4466
                                        &dither_parallel);
4467
        if (dither_parallel.enabled) {
19,168!
4468
            dither->pipeline_parallel_active = 1;
19,168✔
4469
            dither->pipeline_band_height = dither_parallel.band_height;
19,168✔
4470
            dither->pipeline_band_overlap = dither_parallel.overlap;
19,168✔
4471
            dither->pipeline_dither_threads =
19,168✔
4472
                dither_parallel.dither_threads;
19,168✔
4473
            pipeline_threads = dither_parallel.encode_threads;
19,168✔
4474
        }
1,613✔
4475
        if (pipeline_threads <= 1) {
19,168✔
4476
            /*
4477
             * Disable the pipeline when the encode side cannot spawn at
4478
             * least two workers.  A single encode thread cannot consume the
4479
             * six-line jobs that PaletteApply produces, so fall back to the
4480
             * serialized encoder path.
4481
             */
4482
            pipeline_active = 0;
252✔
4483
            dither->pipeline_parallel_active = 0;
252✔
4484
            if (paletted_pixels == NULL) {
252!
4485
                paletted_pixels = sixel_dither_apply_palette(dither, pixels,
273✔
4486
                                                             width, height);
21✔
4487
                if (paletted_pixels == NULL) {
252!
UNCOV
4488
                    status = SIXEL_RUNTIME_ERROR;
×
UNCOV
4489
                    goto end;
×
4490
                }
4491
            }
21✔
4492
            input_pixels = paletted_pixels;
147✔
4493
        }
21✔
4494
    }
1,613✔
4495

4496
#if SIXEL_ENABLE_THREADS
4497
    if (!pipeline_active) {
14,725✔
4498
        logger = dither->pipeline_logger;
6,345✔
4499
        if (logger == NULL) {
6,345!
4500
            sixel_timeline_logger_prepare_default(dither->allocator,
6,345✔
4501
                                                  &serial_logger);
4502
            if (serial_logger != NULL) {
6,345✔
4503
                logger_owned = 1;
24✔
4504
                dither->pipeline_logger = serial_logger;
24✔
4505
                logger = serial_logger;
24✔
4506
            } else {
2✔
4507
                logger = NULL;
3,691✔
4508
            }
4509
        }
522✔
4510
        if (logger != NULL) {
3,715✔
4511
            sixel_timeline_logger_logf(logger,
26✔
4512
                              "controller",
4513
                              "pipeline",
4514
                              "configure",
4515
                              -1,
4516
                              -1,
4517
                              0,
4518
                              height,
2✔
4519
                              0,
4520
                              height,
2✔
4521
                              "serial path threads=1");
4522
        }
2✔
4523
    }
522✔
4524
#endif
4525

4526
    if (output != NULL) {
33,573!
4527
        palette_source_colorspace = output->source_colorspace;
33,573✔
4528
        palette_float_pixelformat =
16,806✔
4529
            sixel_palette_float_pixelformat_for_colorspace(
33,573✔
4530
                palette_source_colorspace);
2,114✔
4531
        palette_float_depth =
16,806✔
4532
            sixel_helper_compute_depth(palette_float_pixelformat);
33,573✔
4533
    }
2,114✔
4534

4535
    status = palette_obj->vtbl->get_entries(palette_obj, &palette_view);
33,573✔
4536
    if (SIXEL_FAILED(status) || palette_view.entries == NULL ||
33,573!
4537
            palette_view.depth != 3 || palette_view.entry_count == 0U) {
33,573!
UNCOV
4538
        sixel_helper_set_additional_message(
×
4539
            "sixel_encode_dither: palette view failed.");
UNCOV
4540
        status = SIXEL_RUNTIME_ERROR;
×
UNCOV
4541
        goto end;
×
4542
    }
4543
    palette_count = palette_view.entry_count;
33,573✔
4544
    palette_bytes = palette_count * 3U;
33,573✔
4545
    palette_entries = (unsigned char *)sixel_allocator_malloc(
33,573✔
4546
        dither->allocator,
2,114✔
4547
        palette_bytes);
2,114✔
4548
    if (palette_entries == NULL) {
33,573!
UNCOV
4549
        sixel_helper_set_additional_message(
×
4550
            "sixel_encode_dither: palette copy allocation failed.");
4551
        status = SIXEL_BAD_ALLOCATION;
×
UNCOV
4552
        goto end;
×
4553
    }
4554
    memcpy(palette_entries, palette_view.entries, palette_bytes);
33,573✔
4555

4556
    status = palette_obj->vtbl->get_entries_float32(palette_obj,
33,573✔
4557
                                                    &palette_float_view);
4558
    if (SIXEL_FAILED(status)) {
33,573!
UNCOV
4559
        goto end;
×
4560
    }
4561
    if (palette_float_view.entries != NULL &&
33,573✔
4562
            palette_float_view.entry_count == palette_count &&
3,568!
4563
            palette_float_view.depth == palette_float_depth) {
3,568!
4564
        palette_float_count = palette_float_view.entry_count;
3,568✔
4565
        palette_float_bytes =
3,568✔
4566
            palette_float_count * (size_t)palette_float_view.depth;
3,568✔
4567
        palette_entries_float32 = (float *)sixel_allocator_malloc(
3,568✔
4568
            dither->allocator,
114✔
4569
            palette_float_bytes);
114✔
4570
        if (palette_entries_float32 == NULL) {
3,568!
UNCOV
4571
            sixel_helper_set_additional_message(
×
4572
                "sixel_encode_dither: float palette copy allocation failed.");
UNCOV
4573
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
4574
            goto end;
×
4575
        }
4576
        memcpy(palette_entries_float32,
3,682✔
4577
               palette_float_view.entries,
3,568✔
4578
               palette_float_bytes);
114✔
4579
    }
114✔
4580
    if (palette_entries != NULL && palette_entries_float32 != NULL
33,573!
4581
            && palette_count == palette_float_count
5,568!
4582
            && palette_count > 0U
3,568!
4583
            && !sixel_palette_float32_matches_u8(
3,568!
4584
                    palette_entries,
114✔
4585
                    palette_entries_float32,
114✔
4586
                    palette_count,
114✔
4587
                    palette_float_pixelformat)) {
114✔
UNCOV
4588
        sixel_palette_sync_float32_from_u8(palette_entries,
×
4589
                                           palette_entries_float32,
4590
                                           palette_count,
4591
                                           palette_float_pixelformat);
4592
    }
4593
    if (palette_entries != NULL && palette_count > 0U
33,573!
4594
            && output != NULL
18,881!
4595
            && output->source_colorspace != output->colorspace) {
33,573✔
4596
        palette_bytes = palette_count * 3U;
1,133✔
4597
        if (palette_entries_float32 != NULL
1,133✔
4598
                && palette_float_count == palette_count) {
723!
4599
            /*
4600
             * Use the higher-precision palette to change color spaces once and
4601
             * then quantize those float channels down to bytes.  The previous
4602
             * implementation converted the 8bit entries before overwriting
4603
             * them from float again, doubling the amount of work and rounding
4604
             * the palette twice.
4605
             */
4606
            palette_float_bytes = palette_bytes * sizeof(float);
316✔
4607
            status = sixel_helper_convert_colorspace(
316✔
4608
                (unsigned char *)palette_entries_float32,
3✔
4609
                palette_float_bytes,
3✔
4610
                palette_float_pixelformat,
3✔
4611
                output->source_colorspace,
3✔
4612
                output->colorspace);
3✔
4613
            if (SIXEL_FAILED(status)) {
316!
UNCOV
4614
                sixel_helper_set_additional_message(
×
4615
                    "sixel_encode_dither: float palette colorspace conversion failed.");
UNCOV
4616
                goto end;
×
4617
            }
4618
            output_float_pixelformat =
158✔
4619
                sixel_palette_float_pixelformat_for_colorspace(
316!
4620
                    output->colorspace);
3✔
4621
            palette_channels = palette_count * 3U;
316✔
4622
            for (palette_index = 0U; palette_index < palette_channels;
206,572✔
4623
                    ++palette_index) {
206,256✔
4624
                int channel;
103,101✔
4625

4626
                channel = (int)(palette_index % 3U);
206,256✔
4627
                palette_entries[palette_index] =
206,256✔
4628
                    sixel_pixelformat_float_channel_to_byte(
206,256✔
4629
                        output_float_pixelformat,
27✔
4630
                        channel,
27✔
4631
                        palette_entries_float32[palette_index]);
206,256✔
4632
            }
27✔
4633
        } else {
3✔
4634
            status = sixel_helper_convert_colorspace(palette_entries,
885✔
4635
                                                     palette_bytes,
68✔
4636
                                                     SIXEL_PIXELFORMAT_RGB888,
4637
                                                     output->source_colorspace,
68✔
4638
                                                     output->colorspace);
68✔
4639
            if (SIXEL_FAILED(status)) {
817!
UNCOV
4640
                sixel_helper_set_additional_message(
×
4641
                    "sixel_encode_dither: palette colorspace "
4642
                    "conversion failed.");
UNCOV
4643
                goto end;
×
4644
            }
4645
        }
4646
    }
71✔
4647
    if (SIXEL_FAILED(status) || palette_entries == NULL) {
18,881!
4648
        sixel_helper_set_additional_message(
4649
            "sixel_encode_dither: palette copy failed.");
4650
        goto end;
4651
    }
4652

4653
    if (input_pixels != NULL &&
33,573!
4654
            dither->pipeline_accumulation_result_enabled != 0) {
14,657✔
4655
        status = sixel_dither_set_pipeline_accumulation_result_rgb(
228✔
4656
            dither,
13✔
4657
            input_pixels,
13✔
4658
            pixel_count,
13✔
4659
            palette_entries,
13✔
4660
            palette_count);
13✔
4661
        if (SIXEL_FAILED(status)) {
228!
UNCOV
4662
            goto end;
×
4663
        }
4664
    }
13✔
4665

4666
    status = sixel_encode_header(encoded_width,
35,687✔
4667
                                 encoded_height,
2,114✔
4668
                                 dither->keycolor,
2,114✔
4669
                                 output);
2,114✔
4670
    if (SIXEL_FAILED(status)) {
33,573!
UNCOV
4671
        goto end;
×
4672
    }
4673

4674
    if (pipeline_active) {
33,573!
4675
        if (output->ormode) {
18,916!
4676
            status = sixel_encode_body_ormode_pipeline(pixels,
65✔
4677
                                                       width,
5✔
4678
                                                       height,
5✔
4679
                                                       palette_entries,
5✔
4680
                                                       dither,
5✔
4681
                                                       output,
5✔
4682
                                                       pipeline_threads);
5✔
4683
        } else {
5✔
4684
            status = sixel_encode_body_pipeline(pixels,
20,443✔
4685
                                                width,
1,587✔
4686
                                                height,
1,587✔
4687
                                                palette_entries,
1,587✔
4688
                                                palette_entries_float32,
1,587✔
4689
                                                dither,
1,587✔
4690
                                                output,
1,587✔
4691
                                                pipeline_threads);
1,587✔
4692
        }
4693
    } else if (output->ormode) {
16,249!
4694
        status = sixel_encode_body_ormode(input_pixels,
20✔
4695
                                          width,
4696
                                          height,
4697
                                          palette_entries,
4698
                                          dither->ncolors,
4699
                                          dither->keycolor,
4700
                                          output);
4701
    } else {
4702
        status = sixel_encode_body(input_pixels,
15,159✔
4703
                                   width,
522✔
4704
                                   height,
522✔
4705
                                   palette_entries,
522✔
4706
                                   palette_entries_float32,
522✔
4707
                                   dither->ncolors,
522✔
4708
                                   dither->keycolor,
522✔
4709
                                   dither->bodyonly,
522✔
4710
                                   output,
522✔
4711
                                   NULL,
4712
                                   dither->allocator,
522✔
4713
                                   dither->pipeline_pin_threads,
522✔
4714
                                   logger != NULL ?
522✔
4715
                                       logger :
2✔
4716
                                       NULL);
4717
    }
4718

4719
    if (SIXEL_FAILED(status)) {
33,573!
UNCOV
4720
        goto end;
×
4721
    }
4722

4723
    status = sixel_encode_footer(output);
33,573✔
4724
    if (SIXEL_FAILED(status)) {
33,573!
4725
        goto end;
4726
    }
4727

4728
end:
31,459✔
4729
#if SIXEL_ENABLE_THREADS
4730
    if (logger_owned) {
25,261✔
4731
        dither->pipeline_logger = NULL;
24✔
4732
        sixel_timeline_logger_unref(serial_logger);
24✔
4733
    }
2✔
4734
#endif
4735
    if (palette_entries != NULL) {
33,573!
4736
        sixel_allocator_free(dither->allocator, palette_entries);
33,573✔
4737
    }
2,114✔
4738
    if (palette_entries_float32 != NULL) {
33,573!
4739
        sixel_allocator_free(dither->allocator, palette_entries_float32);
3,568✔
4740
    }
114✔
4741
    sixel_allocator_free(dither->allocator, paletted_pixels);
33,573✔
4742

4743
    return status;
33,573✔
4744
}
2,114✔
4745

4746
SIXEL_INTERNAL_API SIXELSTATUS
4747
sixel_encoder_core_encode_dispatch(
33,957✔
4748
    sixel_encoder_core_encode_request_t const *request)
4749
{
4750
    SIXELSTATUS status;
14,860✔
4751

4752
    if (request == NULL || request->pixels == NULL ||
33,957!
4753
        request->dither == NULL || request->output == NULL) {
33,893!
4754
        return SIXEL_BAD_ARGUMENT;
36✔
4755
    }
4756
    if (request->width < 1 || request->height < 1) {
33,893!
4757
        return SIXEL_BAD_INPUT;
4758
    }
4759
    if (sixel_output_has_transparent_offset(request->output) != 0 &&
33,893✔
4760
        request->dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
40!
UNCOV
4761
        sixel_helper_set_additional_message(
×
4762
            "transparent-offset cannot be used with high-color output.");
UNCOV
4763
        return SIXEL_BAD_ARGUMENT;
×
4764
    }
4765

4766
    (void)request->depth;
16,966✔
4767
    if (request->dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
33,893✔
4768
        status = sixel_encode_highcolor(request->pixels,
340✔
4769
                                        request->width,
180✔
4770
                                        request->height,
180✔
4771
                                        request->dither,
180✔
4772
                                        request->output);
180✔
4773
    } else {
20✔
4774
        status = sixel_encode_dither(request->pixels,
35,687✔
4775
                                     request->width,
18,881✔
4776
                                     request->height,
18,881✔
4777
                                     request->dither,
18,881✔
4778
                                     request->output);
18,881✔
4779
    }
4780

4781
    return status;
19,061✔
4782
}
2,138✔
4783

4784
SIXELAPI SIXELSTATUS
4785
sixel_encode(
33,877✔
4786
    unsigned char  /* in */ *pixels,   /* pixel bytes */
4787
    int            /* in */ width,     /* image width */
4788
    int            /* in */ height,    /* image height */
4789
    int const      /* in */ depth,     /* color depth */
4790
    sixel_dither_t /* in */ *dither,   /* dither context */
4791
    sixel_output_t /* in */ *output)   /* output context */
4792
{
4793
    SIXELSTATUS status = SIXEL_FALSE;
33,877✔
4794
    sixel_encoder_core_t *core;
14,825✔
4795
    sixel_encoder_core_encode_request_t request;
14,825✔
4796

4797
    if (pixels == NULL) {
33,877!
UNCOV
4798
        sixel_helper_set_additional_message(
×
4799
            "sixel_encode: bad pixels parameter."
4800
            " (pixels == NULL)");
UNCOV
4801
        return SIXEL_BAD_ARGUMENT;
×
4802
    }
4803

4804
    if (dither == NULL) {
33,877!
UNCOV
4805
        sixel_helper_set_additional_message(
×
4806
            "sixel_encode: bad dither parameter."
4807
            " (dither == NULL)");
UNCOV
4808
        return SIXEL_BAD_ARGUMENT;
×
4809
    }
4810

4811
    if (output == NULL) {
33,877!
UNCOV
4812
        sixel_helper_set_additional_message(
×
4813
            "sixel_encode: bad output parameter."
4814
            " (output == NULL)");
UNCOV
4815
        return SIXEL_BAD_ARGUMENT;
×
4816
    }
4817

4818
    /* TODO: reference counting should be thread-safe */
4819
    sixel_dither_ref(dither);
33,877✔
4820
    sixel_output_ref(output);
33,877✔
4821

4822
    if (width < 1) {
33,877!
UNCOV
4823
        sixel_helper_set_additional_message(
×
4824
            "sixel_encode: bad width parameter."
4825
            " (width < 1)");
UNCOV
4826
        status = SIXEL_BAD_INPUT;
×
UNCOV
4827
        goto end;
×
4828
    }
4829

4830
    if (height < 1) {
33,877!
UNCOV
4831
        sixel_helper_set_additional_message(
×
4832
            "sixel_encode: bad height parameter."
4833
            " (height < 1)");
UNCOV
4834
        status = SIXEL_BAD_INPUT;
×
UNCOV
4835
        goto end;
×
4836
    }
4837

4838
    core = sixel_output_as_encoder_core(output);
33,877✔
4839
    if (core == NULL || core->vtbl == NULL ||
33,877!
4840
        core->vtbl->encode == NULL) {
33,877!
UNCOV
4841
        status = SIXEL_BAD_ARGUMENT;
×
UNCOV
4842
        goto end;
×
4843
    }
4844
    request.pixels = pixels;
33,877✔
4845
    request.width = width;
33,877✔
4846
    request.height = height;
33,877✔
4847
    request.depth = depth;
33,877✔
4848
    request.dither = dither;
33,877✔
4849
    request.output = output;
33,877✔
4850
    status = core->vtbl->encode(core, &request);
33,877✔
4851

4852
end:
31,744✔
4853
    sixel_output_unref(output);
33,877✔
4854
    sixel_dither_unref(dither);
33,877✔
4855

4856
    return status;
33,877✔
4857
}
2,133✔
4858

4859

4860
/* emacs Local Variables:      */
4861
/* emacs mode: c               */
4862
/* emacs tab-width: 4          */
4863
/* emacs indent-tabs-mode: nil */
4864
/* emacs c-basic-offset: 4     */
4865
/* emacs End:                  */
4866
/* vim: set expandtab ts=4 sts=4 sw=4 : */
4867
/* 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