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

saitoha / libsixel / 29185764356

12 Jul 2026 08:17AM UTC coverage: 85.335% (+0.02%) from 85.312%
29185764356

push

github

saitoha
fix: boost persistent sticky accents

79277 of 143950 branches covered (55.07%)

53 of 65 new or added lines in 1 file covered. (81.54%)

1263 existing lines in 7 files now uncovered.

141324 of 165611 relevant lines covered (85.33%)

6086992.77 hits per line

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

92.03
/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,
28,007✔
284
                                      sixel_timeline_logger_t **logger)
285
{
286
    if (logger == NULL) {
28,007!
287
        return;
288
    }
289

290
    *logger = NULL;
28,007✔
291
    (void)sixel_timeline_logger_prepare_env(allocator, logger);
28,007✔
292
}
2,343✔
293
#endif
294

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

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

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

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

326
    dither_env_override = 0;
19,250✔
327
    dither_threads = (pipeline_threads * 7 + 9) / 10;
19,250✔
328
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_THREADS_MAX");
19,250✔
329
    if (text != NULL && text[0] != '\0') {
19,250!
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,209!
341
        dither_threads = 1;
342
    }
343
    if (dither_threads > pipeline_threads) {
19,250!
344
        dither_threads = pipeline_threads;
345
    }
346

347
    if (!dither_env_override && pipeline_threads >= 4 && dither_threads < 2) {
19,250!
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,250✔
356
    if (encode_threads < 2 && pipeline_threads > 2) {
19,250✔
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,926✔
363
        dither_threads = pipeline_threads - encode_threads;
18,926✔
364
    }
1,592✔
365
    if (encode_threads < 1) {
19,250✔
366
        encode_threads = 1;
252✔
367
        dither_threads = pipeline_threads - encode_threads;
252✔
368
    }
21✔
369
    if (dither_threads < 1) {
12,871!
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,250✔
380
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_WIDTH");
19,250✔
381
    if (text != NULL && text[0] != '\0') {
19,250!
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,221✔
392
        band_height = (height + dither_threads - 1) / dither_threads;
19,178✔
393
    }
1,613✔
394
    if (band_height < 6) {
19,250✔
395
        band_height = 6;
1,624✔
396
    }
232✔
397
    if ((band_height % 6) != 0) {
18,092✔
398
        band_height = ((band_height + 5) / 6) * 6;
13,633✔
399
    }
1,150✔
400

401
    text = sixel_compat_getenv("SIXEL_DITHER_PARALLEL_BAND_OVERWRAP");
19,250✔
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,250✔
407
        overlap = 6;
6,430✔
408
    } else {
922✔
409
        overlap = 0;
8,229✔
410
    }
411
    if (text != NULL && text[0] != '\0') {
19,250!
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,209!
422
        overlap = 0;
423
    }
424
    if (overlap > band_height / 2) {
19,250✔
425
        overlap = band_height / 2;
2,954✔
426
    }
422✔
427

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

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

442
    return output->transparent_offset_left != 0 ||
184,893✔
443
           output->transparent_offset_top != 0;
89,285!
444
}
6,422✔
445

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

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

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

460
    return bands;
47,017✔
461
}
6,213✔
462

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

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

478
    left = output->transparent_offset_left;
93,711✔
479
    top = output->transparent_offset_top;
93,711✔
480
    if (left < 0 || top < 0 ||
93,711!
481
        left > INT_MAX - width || top > INT_MAX - height) {
93,711!
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,711✔
488
    *encoded_height = height + top;
93,711✔
489
    return SIXEL_OK;
93,711✔
490
}
6,406✔
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,556✔
534
{
535
    memset(ctx, 0, sizeof(*ctx));
21,556✔
536
    ctx->pixels = NULL;
21,556✔
537
    ctx->keycolor = (-1);
21,556✔
538
    ctx->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
21,556✔
539
    ctx->writer_error = SIXEL_OK;
21,556✔
540
}
14,323✔
541

542
static int
543
sixel_parallel_context_is_ormode(sixel_parallel_context_t const *ctx)
252,312✔
544
{
545
    if (ctx == NULL || ctx->output == NULL) {
252,077!
546
        return 0;
4✔
547
    }
548
    return ctx->output->ormode != 0 ? 1 : 0;
234,339✔
549
}
25,942✔
550

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

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

561
    while ((np = state->output->node_free) != NULL) {
6,937,574✔
562
        state->output->node_free = np->next;
6,762,174✔
563
        sixel_allocator_free(allocator, np);
6,762,174✔
564
    }
565
    state->output->node_top = NULL;
175,400✔
566
}
14,808✔
567

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

589
static void
590
sixel_parallel_context_cleanup(sixel_parallel_context_t *ctx)
21,556✔
591
{
592
    int i;
9,002✔
593

594
    if (ctx->workers != NULL) {
21,556!
595
        for (i = 0; i < ctx->worker_capacity; i++) {
87,229✔
596
            sixel_parallel_worker_cleanup(ctx->workers[i], ctx->allocator);
65,673✔
597
        }
5,533✔
598
        free(ctx->workers);
21,556✔
599
        ctx->workers = NULL;
21,556✔
600
    }
1,812✔
601
    sixel_parallel_writer_stop(ctx, 1);
21,556✔
602
    if (ctx->bands != NULL) {
21,556!
603
        if (ctx->band_count < 0) {
21,556!
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,514✔
611
            free(ctx->bands[i].data);
131,958✔
612
            ctx->bands[i].data = NULL;
131,958✔
613
        }
11,136✔
614
#if defined(_MSC_VER)
615
#pragma warning(pop)
616
#endif
617
        free(ctx->bands);
21,556✔
618
        ctx->bands = NULL;
21,556✔
619
    }
1,812✔
620
    free(ctx->band_source_rows_total);
21,556✔
621
    ctx->band_source_rows_total = NULL;
21,556✔
622
    free(ctx->band_source_rows_ready);
21,556✔
623
    ctx->band_source_rows_ready = NULL;
21,556✔
624
    ctx->band_count = 0;
21,556✔
625
    if (ctx->pool != NULL) {
21,556!
626
        ctx->pool->vtbl->unref(ctx->pool);
21,556✔
627
        ctx->pool = NULL;
21,556✔
628
    }
1,812✔
629
    if (ctx->cond_ready) {
21,556!
630
        sixel_cond_destroy(&ctx->cond_band_ready);
21,556✔
631
        ctx->cond_ready = 0;
21,556✔
632
    }
1,812✔
633
    if (ctx->mutex_ready) {
21,556!
634
        sixel_mutex_destroy(&ctx->mutex);
21,556✔
635
        ctx->mutex_ready = 0;
21,556✔
636
    }
1,812✔
637
}
21,556✔
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,948✔
676
{
677
    int accept;
55,225✔
678

679
    if (ctx == NULL) {
131,948✔
680
        return 0;
681
    }
682
    if (!ctx->mutex_ready) {
131,948!
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,948✔
690
    accept = (!ctx->writer_should_stop && ctx->writer_error == SIXEL_OK);
131,958!
691
    sixel_mutex_unlock(&ctx->mutex);
131,958✔
692
    return accept;
131,958✔
693
}
11,136✔
694

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

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

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

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

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

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

742
    status = sixel_encoder_core_create_output_from_factory(&state->output,
47,118✔
743
                                              sixel_parallel_band_writer,
744
                                              state,
3,673✔
745
                                              ctx->allocator);
3,673✔
746
    if (SIXEL_FAILED(status)) {
43,440!
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,440✔
754
    state->output->has_sixel_scrolling = ctx->output->has_sixel_scrolling;
43,440✔
755
    state->output->has_sdm_glitch = ctx->output->has_sdm_glitch;
43,440✔
756
    state->output->has_gri_arg_limit = ctx->output->has_gri_arg_limit;
43,440✔
757
    state->output->skip_dcs_envelope = 1;
43,440✔
758
    state->output->skip_header = 1;
43,440✔
759
    state->output->palette_type = ctx->output->palette_type;
43,440✔
760
    state->output->colorspace = ctx->output->colorspace;
43,440✔
761
    state->output->source_colorspace = ctx->output->source_colorspace;
43,440✔
762
    state->output->pixelformat = ctx->output->pixelformat;
43,440✔
763
    state->output->penetrate_multiplexer =
43,440✔
764
        ctx->output->penetrate_multiplexer;
43,440✔
765
    state->output->encode_policy = ctx->output->encode_policy;
43,440✔
766
    state->output->ormode = ctx->output->ormode;
43,440✔
767

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

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

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

788
    return SIXEL_OK;
25,711✔
789
}
11,136✔
790

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

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

802
    capped_target = target_threads;
19,010✔
803
    if (capped_target > ctx->worker_capacity) {
19,010✔
804
        capped_target = ctx->worker_capacity;
7,546✔
805
    }
1,078✔
806
    if (ctx->band_count > 0 && capped_target > ctx->band_count) {
19,010!
807
        capped_target = ctx->band_count;
7,941✔
808
    }
809
    if (capped_target <= ctx->thread_count) {
19,010✔
810
        return SIXEL_OK;
3,122✔
811
    }
812

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

820
    if (ctx->logger != NULL) {
13,665✔
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,599✔
830

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

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

854
    required = band->used + (size_t)size;
131,331✔
855
    if (required < band->used) {
131,331!
856
        state->writer_error = SIXEL_BAD_INTEGER_OVERFLOW;
857
        return size;
858
    }
859
    capacity = band->size;
131,331✔
860
    if (required > capacity) {
131,331!
861
        if (capacity == 0) {
131,335!
862
            new_capacity = (size_t)SIXEL_OUTPUT_PACKET_SIZE;
109,615✔
863
        } else {
11,080✔
864
            new_capacity = capacity;
6✔
865
        }
866
        while (new_capacity < required) {
131,335!
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,335✔
874
        if (tmp == NULL) {
131,335!
875
            state->writer_error = SIXEL_BAD_ALLOCATION;
876
            return size;
877
        }
878
        band->data = tmp;
131,335✔
879
        band->size = new_capacity;
131,335✔
880
    }
11,086✔
881

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

885
    return size;
131,331✔
886
}
11,086✔
887

888
static SIXELSTATUS
889
sixel_parallel_worker_flush_output(sixel_parallel_worker_state_t *state)
131,948✔
890
{
891
    if (state == NULL || state->output == NULL) {
131,948!
892
        return SIXEL_BAD_ARGUMENT;
3✔
893
    }
894
    if (state->output->pos > 0) {
131,947✔
895
        state->writer_error = sixel_output_write_bytes(
251,609✔
896
            state->output,
11,085✔
897
            (char *)state->output->buffer,
131,339✔
898
            state->output->pos);
76,369✔
899
        state->output->pos = 0;
131,355✔
900
    }
11,085✔
901
    if (state->writer_error != SIXEL_OK) {
131,963!
902
        return state->writer_error;
903
    }
904
    return SIXEL_OK;
76,733✔
905
}
11,135✔
906

907
static SIXELSTATUS
908
sixel_parallel_create_pool(sixel_thread_pool_t **pool,
21,556✔
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;
9,002✔
916
    sixel_thread_pool_create_request_t request;
9,002✔
917
    void *service_object;
9,002✔
918
    SIXELSTATUS status;
9,002✔
919

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

927
    service = NULL;
21,556✔
928
    service_object = NULL;
21,556✔
929
    status = sixel_components_getservice("services/threadpool",
21,556✔
930
                                         &service_object);
931
    if (SIXEL_FAILED(status)) {
21,556!
932
        return status;
933
    }
934
    service = (sixel_threadpool_service_t *)service_object;
21,556✔
935
    if (service == NULL || service->vtbl == NULL ||
21,556!
936
        service->vtbl->create_pool == NULL) {
21,556!
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,556✔
945
    request.queue_size = queue_depth;
21,556✔
946
    request.workspace_size = workspace_size;
21,556✔
947
    request.worker = worker;
21,556✔
948
    request.userdata = userdata;
21,556✔
949
    request.workspace_cleanup = NULL;
21,556✔
950
    status = service->vtbl->create_pool(service, &request, pool);
21,556✔
951
    if (service->vtbl->unref != NULL) {
21,556!
952
        service->vtbl->unref(service);
21,556✔
953
    }
1,812✔
954

955
    return status;
12,554✔
956
}
1,812✔
957

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

967
    if (ctx == NULL || ctx->band_source_rows_total == NULL ||
21,556!
968
            ctx->band_source_rows_ready == NULL) {
21,556✔
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,514✔
980
        band_start = band_index * 6;
131,958✔
981
        band_end = ctx->encoded_height;
131,958✔
982
        if (band_start <= ctx->encoded_height - 6) {
131,958✔
983
            band_end = band_start + 6;
114,663✔
984
        }
9,680✔
985

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

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

1006
static void
1007
sixel_parallel_submit_source_empty_bands(sixel_parallel_context_t *ctx)
19,010✔
1008
{
1009
    int band_index;
7,941✔
1010

1011
    if (ctx == NULL || ctx->band_source_rows_total == NULL) {
19,010!
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,321✔
1021
        if (ctx->band_source_rows_total[band_index] == 0) {
114,311!
1022
            sixel_parallel_submit_band(ctx, band_index);
1023
        }
1024
    }
9,663✔
1025
}
1,599✔
1026

1027
static SIXELSTATUS
1028
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
21,556✔
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;
9,002✔
1044
    int nbands;
9,002✔
1045
    int encoded_width;
9,002✔
1046
    int encoded_height;
9,002✔
1047
    int threads;
9,002✔
1048
    int i;
9,002✔
1049

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

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

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

1104
    if (logger != NULL) {
21,556✔
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,556✔
1113
                                                        sizeof(*ctx->bands));
1114
    if (ctx->bands == NULL) {
21,556!
1115
        return SIXEL_BAD_ALLOCATION;
1116
    }
1117
    for (i = 0; i < nbands; ++i) {
153,514✔
1118
        ctx->bands[i].data = NULL;
131,958✔
1119
        ctx->bands[i].size = 0;
131,958✔
1120
        ctx->bands[i].used = 0;
131,958✔
1121
        ctx->bands[i].status = SIXEL_OK;
131,958✔
1122
        ctx->bands[i].ready = 0;
131,958✔
1123
        ctx->bands[i].dispatched = 0;
131,958✔
1124
    }
11,136✔
1125
    ctx->band_count = nbands;
21,556✔
1126

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

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

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

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

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

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

1176
    ctx->pool->vtbl->set_affinity(ctx->pool, ctx->pin_threads);
21,556✔
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,556✔
1183
    ctx->next_band_to_flush = 0;
21,556✔
1184
    ctx->writer_should_stop = 0;
21,556✔
1185
    ctx->writer_error = SIXEL_OK;
21,556✔
1186

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

1197
    return SIXEL_OK;
21,556✔
1198
}
1,812✔
1199

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

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

1214
    dispatch = 0;
131,957✔
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,957!
1221
        sixel_mutex_lock(&ctx->mutex);
131,957✔
1222
        if (!ctx->bands[band_index].dispatched
131,958!
1223
                && !ctx->writer_should_stop
121,221!
1224
                && ctx->writer_error == SIXEL_OK) {
131,958!
1225
            ctx->bands[band_index].dispatched = 1;
131,958✔
1226
            dispatch = 1;
131,958✔
1227
        }
11,136✔
1228
        sixel_mutex_unlock(&ctx->mutex);
131,958✔
1229
    } else {
11,136✔
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,958!
1238
        return;
1239
    }
1240

1241
    sixel_fence_release();
131,958✔
1242
    if (ctx->logger != NULL) {
131,958✔
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,958✔
1250
    status = ctx->pool->vtbl->push(ctx->pool, job);
131,958✔
1251
    if (SIXEL_FAILED(status)) {
131,958!
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,136✔
1261

1262
static SIXELSTATUS
1263
sixel_parallel_context_wait(sixel_parallel_context_t *ctx, int force_abort)
21,556✔
1264
{
1265
    int pool_error;
9,002✔
1266

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

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

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

1282
    return SIXEL_OK;
12,554✔
1283
}
1,812✔
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)
645,189✔
1292
{
1293
    sixel_parallel_row_notifier_t *notifier;
270,239✔
1294
    sixel_parallel_context_t *ctx;
270,239✔
1295
    sixel_timeline_logger_t *logger;
270,239✔
1296
    int band_height;
270,239✔
1297
    int band_index;
270,239✔
1298
    int ready_row;
270,239✔
1299
    int should_dispatch;
270,239✔
1300
    int rows_ready;
270,239✔
1301
    int rows_total;
270,239✔
1302

1303
    notifier = (sixel_parallel_row_notifier_t *)priv;
645,189✔
1304
    if (notifier == NULL) {
645,189✔
1305
        return;
1306
    }
1307
    ctx = notifier->context;
645,189✔
1308
    logger = notifier->logger;
645,189✔
1309
    if (ctx == NULL || ctx->band_count <= 0 || ctx->height <= 0) {
645,189!
1310
        return;
3✔
1311
    }
1312
    if (row_index < 0 || row_index >= ctx->height) {
645,198!
1313
        return;
7✔
1314
    }
1315
    band_height = notifier->band_height;
645,197✔
1316
    if (band_height < 1) {
645,197!
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;
645,197✔
1328
    band_index = ready_row / band_height;
645,197✔
1329
    if (band_index >= ctx->band_count) {
645,197!
1330
        band_index = ctx->band_count - 1;
1331
    }
1332
    if (band_index < 0) {
645,197!
1333
        return;
1334
    }
1335

1336
    should_dispatch = 0;
645,197✔
1337
    if (ctx->band_source_rows_total != NULL &&
645,197!
1338
            ctx->band_source_rows_ready != NULL) {
645,197!
1339
        if (ctx->mutex_ready) {
645,197!
1340
            sixel_mutex_lock(&ctx->mutex);
645,197✔
1341
        }
54,554✔
1342
        rows_total = ctx->band_source_rows_total[band_index];
645,217✔
1343
        rows_ready = ctx->band_source_rows_ready[band_index];
645,217✔
1344
        if (rows_total > 0 && rows_ready < rows_total) {
645,217!
1345
            rows_ready += 1;
645,218✔
1346
            ctx->band_source_rows_ready[band_index] = rows_ready;
645,218✔
1347
            if (rows_ready == rows_total) {
645,218✔
1348
                should_dispatch = 1;
336,688✔
1349
            }
9,663✔
1350
        }
54,555✔
1351
        if (ctx->mutex_ready) {
645,217!
1352
            sixel_mutex_unlock(&ctx->mutex);
645,218✔
1353
        }
54,555✔
1354
    } else if ((ready_row % band_height) == band_height - 1 ||
54,558!
1355
            row_index == ctx->height - 1) {
×
1356
        should_dispatch = 1;
1357
    }
1358

1359
    if (!should_dispatch) {
645,215✔
1360
        return;
396,230✔
1361
    }
1362

1363
    if (logger != NULL) {
114,310✔
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,310✔
1372
}
54,558✔
1373

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

1381
    band = &ctx->bands[band_index];
131,958✔
1382
    if (ctx->logger != NULL) {
131,958✔
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,138✔
1390
    while (offset < band->used) {
265,116✔
1391
        chunk = band->used - offset;
133,158✔
1392
        if (chunk > (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos)) {
133,158✔
1393
            chunk = (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos);
1,050✔
1394
        }
150✔
1395
        memcpy(ctx->output->buffer + ctx->output->pos,
155,630✔
1396
               band->data + offset,
133,158✔
1397
               chunk);
11,236✔
1398
        sixel_advance(ctx->output, (int)chunk);
133,158✔
1399
        offset += chunk;
133,158✔
1400
    }
1401
    return SIXEL_OK;
131,958✔
1402
}
1403

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

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

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

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

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

1453
    sixel_fence_acquire();
131,953✔
1454

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

1460
    if (!sixel_parallel_jobs_allowed(ctx)) {
131,956!
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,956✔
1478
    sixel_parallel_worker_reset(state);
131,956✔
1479

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

1489
    if (ctx->logger != NULL) {
131,927✔
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)) {
187,145!
1498
        nplanes = sixel_encode_body_ormode_nplanes(ctx->ncolors);
982✔
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++) {
874,635✔
1516
        absolute_row = band_start + row_index;
743,388✔
1517
        status = sixel_band_classify_row(&state->work,
806,136✔
1518
                                         &state->band,
62,748✔
1519
                                         ctx->pixels,
62,748✔
1520
                                         ctx->width,
62,748✔
1521
                                         ctx->height,
62,748✔
1522
                                         ctx->encoded_width,
62,748✔
1523
                                         absolute_row,
62,748✔
1524
                                         ctx->offset_left,
62,748✔
1525
                                         ctx->offset_top,
62,748✔
1526
                                         ctx->ncolors,
62,748✔
1527
                                         ctx->keycolor,
62,748✔
1528
                                         ctx->palstate,
62,748✔
1529
                                         ctx->encode_policy);
62,748✔
1530
        if (SIXEL_FAILED(status)) {
743,394!
1531
            goto cleanup;
1532
        }
1533
    }
62,748✔
1534

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

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

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

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

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

1571
cleanup:
120,819✔
1572
    sixel_parallel_worker_release_nodes(state, ctx->allocator);
131,955✔
1573
    if (band != NULL && ctx->mutex_ready && ctx->cond_ready) {
131,956!
1574
        sixel_fence_release();
131,953✔
1575
        sixel_mutex_lock(&ctx->mutex);
131,953✔
1576
        band->status = status;
131,957✔
1577
        band->ready = 1;
131,957✔
1578
        sixel_cond_broadcast(&ctx->cond_band_ready);
131,957✔
1579
        sixel_mutex_unlock(&ctx->mutex);
131,957✔
1580
    }
11,135✔
1581
    if (ctx->logger != NULL) {
131,956✔
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,957!
1589
        return status;
1590
    }
1591
    return SIXEL_OK;
76,727✔
1592
}
11,136✔
1593

1594
static void
1595
sixel_parallel_writer_stop(sixel_parallel_context_t *ctx, int force_abort)
43,112✔
1596
{
1597
    int should_signal;
18,004✔
1598

1599
    if (ctx == NULL || !ctx->writer_started) {
43,112!
1600
        return;
12,554✔
1601
    }
1602

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

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

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

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

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

1648
    for (;;) {
77,178✔
1649
        sixel_mutex_lock(&ctx->mutex);
153,514✔
1650
        while (!ctx->writer_should_stop &&
271,464!
1651
               ctx->next_band_to_flush < ctx->band_count) {
250,799✔
1652
    band_index = ctx->next_band_to_flush;
229,243✔
1653
    band = &ctx->bands[band_index];
229,243✔
1654
    if (band->ready) {
229,243✔
1655
        break;
76,730✔
1656
    }
1657
            sixel_cond_wait(&ctx->cond_band_ready, &ctx->mutex);
97,285✔
1658
        }
1659

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

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

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

1680
        sixel_fence_acquire();
131,958✔
1681
        status = band->status;
131,958✔
1682
        if (ctx->logger != NULL) {
131,958✔
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,958!
1690
            status = sixel_parallel_flush_band(ctx, band_index);
131,958✔
1691
        }
11,136✔
1692
        if (SIXEL_FAILED(status)) {
131,958!
1693
            sixel_parallel_context_abort_locked(ctx, status);
1694
            break;
1695
        }
1696
    }
1697

1698
    return SIXEL_OK;
12,554✔
1699
}
1,812✔
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,193✔
1778
        sixel_parallel_submit_band(&ctx, i);
17,647✔
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,938✔
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,911✔
1812
    SIXELSTATUS wait_status;
7,911✔
1813
    sixel_parallel_context_t ctx = {0};
18,938✔
1814
    sixel_index_t *indexes;
7,911✔
1815
    sixel_index_t *result;
7,911✔
1816
    sixel_allocator_t *allocator;
7,911✔
1817
    size_t pixel_count;
7,911✔
1818
    size_t buffer_size;
7,911✔
1819
    int threads;
7,911✔
1820
    int nbands;
7,911✔
1821
    int queue_depth;
7,911✔
1822
    int waited;
7,911✔
1823
    int dither_threads_budget;
7,911✔
1824
    int worker_capacity;
7,911✔
1825
    int boost_target;
7,911✔
1826
    int encoded_width;
7,911✔
1827
    int encoded_height;
7,911✔
1828
    sixel_timeline_logger_t *logger;
7,911✔
1829
    int owns_logger;
7,911✔
1830
    sixel_parallel_row_notifier_t notifier;
7,911✔
1831

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

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

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

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

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

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

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

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

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

1948
    result = sixel_dither_apply_palette(dither, pixels, width, height);
18,938✔
1949
    if (result == NULL) {
18,938!
1950
        status = SIXEL_RUNTIME_ERROR;
1951
        goto cleanup;
1952
    }
1953
    if (result != indexes) {
18,938!
1954
        status = SIXEL_RUNTIME_ERROR;
1955
        goto cleanup;
1956
    }
1957
    if (dither->pipeline_accumulation_result_enabled != 0) {
18,938✔
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,938✔
1975
    status = sixel_parallel_context_grow(&ctx, boost_target);
18,938✔
1976
    if (SIXEL_FAILED(status)) {
18,938!
1977
        goto cleanup;
1978
    }
1979

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

1986
cleanup:
17,345✔
1987
    dither->pipeline_row_callback = NULL;
18,938✔
1988
    dither->pipeline_row_priv = NULL;
18,938✔
1989
    dither->pipeline_index_buffer = NULL;
18,938✔
1990
    dither->pipeline_index_size = 0;
18,938✔
1991
    dither->pipeline_image_width = 0;
18,938✔
1992
    dither->pipeline_image_height = 0;
18,938✔
1993
    dither->pipeline_transparent_mask = NULL;
18,938✔
1994
    dither->pipeline_transparent_mask_size = 0;
18,938✔
1995
    dither->pipeline_transparent_keycolor = (-1);
18,938✔
1996
    dither->pipeline_accumulation_result_enabled = 0;
18,938✔
1997
    if (!waited && ctx.pool != NULL) {
18,938!
1998
        wait_status = sixel_parallel_context_wait(&ctx, status != SIXEL_OK);
1999
        if (status == SIXEL_OK) {
×
2000
            status = wait_status;
7,911✔
2001
        }
2002
    }
2003
    sixel_parallel_context_cleanup(&ctx);
18,938✔
2004
    if (owns_logger) {
18,938✔
2005
        sixel_timeline_logger_unref(logger);
132✔
2006
    }
11✔
2007
    if (indexes != NULL) {
18,938!
2008
        sixel_allocator_free(allocator, indexes);
18,938✔
2009
    }
1,593✔
2010
    return status;
14,159✔
2011
}
1,593✔
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)
96,202,478✔
2255
{
2256
    if ((output->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) {
96,202,478✔
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
}
96,202,478✔
2265

2266

2267
static void
2268
sixel_putc(unsigned char *buffer, unsigned char value)
27,039,905✔
2269
{
2270
    *buffer = value;
27,039,905✔
2271
}
15,153,229✔
2272

2273

2274
static void
2275
sixel_puts(unsigned char *buffer, char const *value, int size)
2,671,403✔
2276
{
2277
    memcpy(buffer, (void *)value, (size_t)size);
2,671,403✔
2278
}
1,822,524✔
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,465,824✔
2287
                          unsigned char value,
2288
                          int count)
2289
{
2290
    int chunk;
17,743,839✔
2291

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

2296
    while (count > 0) {
80,951,329✔
2297
        chunk = SIXEL_OUTPUT_PACKET_SIZE - output->pos;
40,469,928✔
2298
        if (chunk > count) {
40,469,928✔
2299
            chunk = count;
22,730,893✔
2300
        }
2,552,342✔
2301
        memset(output->buffer + output->pos, value, (size_t)chunk);
40,469,928✔
2302
        sixel_advance(output, chunk);
40,469,928✔
2303
        count -= chunk;
40,485,505✔
2304
    }
2305
}
2,551,007✔
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,474,252✔
2314
                             int width,
2315
                             int start)
2316
{
2317
    int idx;
6,808,111✔
2318
    size_t chunk_size;
6,808,111✔
2319
    unsigned char const *cursor;
6,808,111✔
2320
    unsigned long block;
6,808,111✔
2321

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

2326
    while ((width - idx) >= (int)chunk_size) {
167,107,486✔
2327
        memcpy(&block, cursor, chunk_size);
161,035,121✔
2328
        if (block != 0UL) {
161,035,121✔
2329
            break;
5,265,931✔
2330
        }
2331
        idx += (int)chunk_size;
151,633,234✔
2332
        cursor += chunk_size;
151,633,234✔
2333
    }
2334

2335
    while (idx < width) {
50,544,749✔
2336
        if (*cursor != 0) {
44,617,237✔
2337
            break;
5,347,831✔
2338
        }
2339
        idx += 1;
35,070,497✔
2340
        cursor += 1;
35,070,497✔
2341
    }
2342

2343
    return idx;
15,474,252✔
2344
}
2345

2346

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

2359
    gap = 0;
16,737,114✔
2360
    *reached_end = 0;
16,737,114✔
2361
    if (start >= width) {
16,737,114✔
2362
        *reached_end = 1;
207,577✔
2363
        return gap;
207,577✔
2364
    }
2365

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

2370
    while (remaining >= (int)chunk_size) {
99,912,098✔
2371
        memcpy(&block, cursor, chunk_size);
93,800,116✔
2372
        if (block != 0UL) {
93,800,116✔
2373
            break;
5,861,224✔
2374
        }
2375
        gap += (int)chunk_size;
83,382,561✔
2376
        cursor += chunk_size;
83,382,561✔
2377
        remaining -= (int)chunk_size;
83,382,561✔
2378
    }
2379

2380
    while (remaining > 0) {
46,296,392✔
2381
        if (*cursor != 0) {
40,572,583✔
2382
            return gap;
6,079,901✔
2383
        }
2384
        gap += 1;
29,766,855✔
2385
        cursor += 1;
29,766,855✔
2386
        remaining -= 1;
29,766,855✔
2387
    }
2388

2389
    *reached_end = 1;
5,723,809✔
2390
    return gap;
5,723,809✔
2391
}
1,064,541✔
2392

2393

2394
#if HAVE_LDIV
2395
static int
2396
sixel_putnum_impl(char *buffer, long value, int pos)
49,085,004✔
2397
{
2398
    ldiv_t r;
21,578,140✔
2399

2400
    r = ldiv(value, 10);
49,085,004✔
2401
    if (r.quot > 0) {
49,087,738✔
2402
        pos = sixel_putnum_impl(buffer, r.quot, pos);
24,204,791✔
2403
    }
1,541,014✔
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,092,898✔
2409
    return pos + 1;
49,092,898✔
2410
}
2411
#endif  /* HAVE_LDIV */
2412

2413

2414
static int
2415
sixel_putnum(char *buffer, int value)
24,898,197✔
2416
{
2417
    int pos;
10,946,349✔
2418

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

2425
    return pos;
24,901,476✔
2426
}
2427

2428

2429
static SIXELSTATUS
2430
sixel_put_flash(sixel_output_t *const output)
45,371,299✔
2431
{
2432
    int nwrite;
19,883,970✔
2433

2434
    if (output->save_count <= 0) {
45,371,299✔
2435
        return SIXEL_OK;
76,307✔
2436
    }
2437

2438
    if (output->has_gri_arg_limit) {  /* VT240 Max 255 ? */
45,240,072✔
2439
            while (output->save_count > 255) {
62,696!
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;
44,792✔
2447
            }
2448
        }
1,130✔
2449

2450
    if (output->save_count > 3) {
45,284,864✔
2451
        /* DECGRI Graphics Repeat Introducer ! Pn Ch */
2452
        sixel_putc(output->buffer + output->pos, '!');
4,812,222✔
2453
        sixel_advance(output, 1);
4,811,883✔
2454
        nwrite = sixel_putnum((char *)output->buffer + output->pos, output->save_count);
4,812,184✔
2455
        sixel_advance(output, nwrite);
4,812,776✔
2456
        sixel_putc(output->buffer + output->pos,
5,118,332✔
2457
                   (unsigned char)output->save_pixel);
4,812,391✔
2458
        sixel_advance(output, 1);
4,812,184✔
2459
    } else {
305,941✔
2460
        sixel_output_emit_literal(output,
43,023,497✔
2461
                                  (unsigned char)output->save_pixel,
40,472,642✔
2462
                                  output->save_count);
2,550,855✔
2463
    }
2464

2465
    output->save_pixel = 0;
45,249,792✔
2466
    output->save_count = 0;
45,249,792✔
2467

2468
    return SIXEL_OK;
45,249,792✔
2469
}
2,867,872✔
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,297,743✔
2479
{
2480
    SIXELSTATUS status = SIXEL_FALSE;
45,297,743✔
2481

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

2486
    if (output->save_count > 0) {
45,297,727✔
2487
        if (output->save_pixel == symbol) {
35,806,470✔
2488
            output->save_count += count;
46,219✔
2489
            return SIXEL_OK;
46,219✔
2490
        }
2491

2492
        status = sixel_put_flash(output);
35,760,251✔
2493
        if (SIXEL_FAILED(status)) {
35,738,440!
2494
            return status;
2495
        }
2496
    }
2,250,274✔
2497

2498
    output->save_pixel = symbol;
45,232,343✔
2499
    output->save_count = count;
45,232,343✔
2500

2501
    return SIXEL_OK;
45,232,343✔
2502
}
2,853,577✔
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,556,504✔
2511
                         unsigned char const *map,
2512
                         int length)
2513
{
2514
    SIXELSTATUS status = SIXEL_FALSE;
9,556,504✔
2515
    int index;
4,198,921✔
2516
    int run_length;
4,198,921✔
2517
    unsigned char value;
4,198,921✔
2518
    size_t chunk_size;
4,198,921✔
2519
    unsigned long pattern;
4,198,921✔
2520
    unsigned long block;
4,198,921✔
2521
    int chunk_mismatch;
4,198,921✔
2522
    int remain;
4,198,921✔
2523
    int byte_index;
4,198,921✔
2524

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

2529
    for (index = 0; index < length; index += run_length) {
51,014,239✔
2530
        value = map[index];
41,481,671✔
2531
        if (value > '?') {
41,481,671!
2532
            value = 0;
×
2533
        }
2534

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

2542
            while (remain >= (int)chunk_size) {
44,018,487✔
2543
                memcpy(&block,
27,859,333✔
2544
                       map + index + run_length,
26,096,475✔
2545
                       chunk_size);
1,762,858✔
2546
                block ^= pattern;
24,333,617✔
2547
                if (block != 0UL) {
24,333,617✔
2548
                    for (byte_index = 0;
12,764,651✔
2549
                         byte_index < (int)chunk_size;
30,975,994!
2550
                         byte_index++) {
9,191,047✔
2551
                        if ((block & 0xffUL) != 0UL) {
30,980,640✔
2552
                            chunk_mismatch = 1;
12,231,945✔
2553
                            break;
12,231,945✔
2554
                        }
2555
                        block >>= 8;
9,191,047✔
2556
                        run_length += 1;
9,191,047✔
2557
                    }
537,352✔
2558
                    break;
12,227,305✔
2559
                }
2560
                run_length += (int)chunk_size;
2,548,670✔
2561
                remain -= (int)chunk_size;
2,548,670✔
2562
            }
2563
        }
2,615,989✔
2564

2565
        if (!chunk_mismatch) {
41,481,677✔
2566
            while (index + run_length < length) {
25,135,117✔
2567
                unsigned char next;
6,816,384✔
2568

2569
                next = map[index + run_length];
15,597,767✔
2570
                if (next > '?') {
15,597,767!
2571
                    next = 0;
×
2572
                }
2573
                if (next != value) {
15,597,767✔
2574
                    break;
5,772,360✔
2575
                }
2576
                run_length += 1;
5,332,776✔
2577
            }
2578
        }
1,095,585✔
2579

2580
        status = sixel_emit_run(output,
44,081,727✔
2581
                                 (int)value + '?',
23,287,223✔
2582
                                 run_length);
2,610,676✔
2583
        if (SIXEL_FAILED(status)) {
41,457,735!
2584
            return status;
2585
        }
2586
    }
2,610,676✔
2587

2588
    return SIXEL_OK;
5,334,153✔
2589
}
605,490✔
2590

2591

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

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

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

2607
    *np = (sixel_node_t *)sixel_allocator_malloc(allocator,
7,445,753✔
2608
                                                 sizeof(sixel_node_t));
2609
    if (np == NULL) {
7,447,095!
2610
        sixel_helper_set_additional_message(
80✔
2611
            "sixel_node_new: sixel_allocator_malloc() failed.");
2612
        status = SIXEL_BAD_ALLOCATION;
2613
        goto end;
2614
    }
2615

2616
    status = SIXEL_OK;
4,284,799✔
2617

2618
end:
6,855,340✔
2619
    return status;
7,447,015✔
2620
}
2621

2622
static void
2623
sixel_node_del(sixel_output_t *output, sixel_node_t *np)
9,544,028✔
2624
{
2625
    sixel_node_t *tp;
4,198,533✔
2626

2627
    if ((tp = output->node_top) == np) {
9,544,028✔
2628
        output->node_top = np->next;
2,329,588✔
2629
    } else {
146,148✔
2630
        while (tp->next != NULL) {
330,404,436!
2631
            if (tp->next == np) {
330,409,384✔
2632
                tp->next = np->next;
7,219,388✔
2633
                break;
7,219,388✔
2634
            }
2635
            tp = tp->next;
181,661,932✔
2636
        }
2637
    }
2638

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

2643

2644
static SIXELSTATUS
2645
sixel_put_node(
9,546,173✔
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,546,173✔
2653
    int nwrite;
4,198,446✔
2654

2655
    if (ncolors != 2 || keycolor == (-1)) {
9,546,173✔
2656
        /* designate palette index */
2657
        if (output->active_palette != np->pal) {
9,543,275✔
2658
            sixel_putc(output->buffer + output->pos, '#');
9,401,266✔
2659
            sixel_advance(output, 1);
9,400,758✔
2660
            nwrite = sixel_putnum((char *)output->buffer + output->pos, np->pal);
9,401,039✔
2661
            sixel_advance(output, nwrite);
9,402,097✔
2662
            output->active_palette = np->pal;
9,401,658✔
2663
        }
597,249✔
2664
    }
605,634✔
2665

2666
    if (*x < np->sx) {
9,546,565✔
2667
        int span;
1,582,746✔
2668

2669
        span = np->sx - *x;
3,602,074✔
2670
        status = sixel_emit_run(output, '?', span);
3,602,074✔
2671
        if (SIXEL_FAILED(status)) {
3,601,846!
2672
            goto end;
2673
        }
2674
        *x = np->sx;
3,601,846✔
2675
    }
229,190✔
2676

2677
    if (*x < np->mx) {
9,546,103!
2678
        int span;
4,199,193✔
2679

2680
        span = np->mx - *x;
9,546,567✔
2681
        status = sixel_emit_span_from_map(output,
14,350,425✔
2682
                                          (unsigned char const *)np->map + *x,
9,546,567✔
2683
                                          span);
605,438✔
2684
        if (SIXEL_FAILED(status)) {
9,544,566!
2685
            goto end;
2686
        }
2687
        *x = np->mx;
9,544,566✔
2688
    }
605,438✔
2689

2690
    status = sixel_put_flash(output);
9,544,102✔
2691
    if (SIXEL_FAILED(status)) {
9,543,022!
2692
        goto end;
2693
    }
2694

2695
end:
8,937,505✔
2696
    return status;
9,542,864✔
2697
}
2698

2699

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

2708
    if (output->ormode) {
34,140✔
2709
        p[0] = 7;
80✔
2710
        p[1] = 5;
80✔
2711
    } else if (sixel_output_has_transparent_offset(output)) {
34,065✔
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) {
34,023✔
2718
        if (output->transparent_policy == SIXEL_TRANSPARENT_POLICY_KEEP) {
2,704✔
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,494✔
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,464✔
2732
        }
154✔
2733
    }
169✔
2734

2735
    output->pos = 0;
34,140✔
2736

2737
    if (pcount == 3 && p[2] == 0) {
34,140!
2738
        pcount--;
31,676✔
2739
        if (p[1] == 0) {
31,676✔
2740
            pcount--;
31,332✔
2741
            if (p[0] == 0) {
31,332!
2742
                pcount--;
31,332✔
2743
            }
1,973✔
2744
        }
1,973✔
2745
    }
1,995✔
2746

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

2756
    return status;
34,140✔
2757
}
2758

2759

2760
static int
2761
sixel_palette_float_pixelformat_for_colorspace(int colorspace)
53,153✔
2762
{
2763
    switch (colorspace) {
53,153✔
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,340✔
2773
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
37,527✔
2774
    }
2775
}
4,261✔
2776

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

2788
    if (palette == NULL || palette_float == NULL || count == 0U) {
3,567!
2789
        return 1;
2790
    }
2791
    if (count > SIZE_MAX / 3U) {
3,567!
2792
        return 0;
2793
    }
2794
    limit = count * 3U;
3,567✔
2795
    for (index = 0U; index < limit; ++index) {
699,975✔
2796
        channel = (int)(index % 3U);
696,408✔
2797
        expected = sixel_pixelformat_float_channel_to_byte(
1,042,356✔
2798
            float_pixelformat,
2,232✔
2799
            channel,
2,232✔
2800
            palette_float[index]);
696,408✔
2801
        if (palette[index] != expected) {
696,408!
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,904,670✔
2836
                                    float const *palette_float,
2837
                                    int n,
2838
                                    int channel)
2839
{
2840
    size_t index;
3,475,266✔
2841
    float value;
3,475,266✔
2842
    int percent;
3,475,266✔
2843

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

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

2865
    return 0;
2866
}
501,084✔
2867

2868
static double
2869
sixel_output_palette_channel_to_float(unsigned char const *palette,
109,539✔
2870
                                      float const *palette_float,
2871
                                      int n,
2872
                                      int channel)
2873
{
2874
    size_t index;
47,493✔
2875
    double value;
47,493✔
2876

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

2890
    return value;
109,539✔
2891
}
2892

2893
static SIXELSTATUS
2894
output_rgb_palette_definition(
2,636,778✔
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,636,778✔
2903
    int nwrite;
1,159,248✔
2904

2905
    if (n != keycolor) {
2,636,778✔
2906
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
2907
        sixel_putc(output->buffer + output->pos, '#');
2,634,890✔
2908
        sixel_advance(output, 1);
2,634,890✔
2909
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
2,634,890✔
2910
        sixel_advance(output, nwrite);
2,634,890✔
2911
        sixel_puts(output->buffer + output->pos, ";2;", 3);
2,634,890✔
2912
        sixel_advance(output, 3);
2,634,890✔
2913
        nwrite = sixel_putnum(
3,793,312✔
2914
            (char *)output->buffer + output->pos,
2,634,890✔
2915
            sixel_output_palette_channel_to_pct(palette,
334,056✔
2916
                                                palette_float,
167,028✔
2917
                                                n,
167,028✔
2918
                                                0));
2919
        sixel_advance(output, nwrite);
2,634,890✔
2920
        sixel_putc(output->buffer + output->pos, ';');
2,634,890✔
2921
        sixel_advance(output, 1);
2,634,890✔
2922
        nwrite = sixel_putnum(
3,793,312✔
2923
            (char *)output->buffer + output->pos,
2,634,890✔
2924
            sixel_output_palette_channel_to_pct(palette,
334,056✔
2925
                                                palette_float,
167,028✔
2926
                                                n,
167,028✔
2927
                                                1));
2928
        sixel_advance(output, nwrite);
2,634,890✔
2929
        sixel_putc(output->buffer + output->pos, ';');
2,634,890✔
2930
        sixel_advance(output, 1);
2,634,890✔
2931
        nwrite = sixel_putnum(
3,793,312✔
2932
            (char *)output->buffer + output->pos,
2,634,890✔
2933
            sixel_output_palette_channel_to_pct(palette,
334,056✔
2934
                                                palette_float,
167,028✔
2935
                                                n,
167,028✔
2936
                                                2));
2937
        sixel_advance(output, nwrite);
2,634,890✔
2938
    }
167,028✔
2939

2940
    status = SIXEL_OK;
2,636,778✔
2941

2942
    return status;
2,636,778✔
2943
}
2944

2945

2946
static SIXELSTATUS
2947
output_hls_palette_definition(
36,513✔
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,513✔
2956
    double r;
15,831✔
2957
    double g;
15,831✔
2958
    double b;
15,831✔
2959
    double maxc;
15,831✔
2960
    double minc;
15,831✔
2961
    double lightness;
15,831✔
2962
    double saturation;
15,831✔
2963
    double hue;
15,831✔
2964
    double diff;
15,831✔
2965
    int h;
15,831✔
2966
    int l;
15,831✔
2967
    int s;
15,831✔
2968
    int nwrite;
15,831✔
2969

2970
    if (n != keycolor) {
36,513!
2971
        r = sixel_output_palette_channel_to_float(palette,
38,811✔
2972
                                                  palette_float,
2,298✔
2973
                                                  n,
2,298✔
2974
                                                  0);
2975
        g = sixel_output_palette_channel_to_float(palette,
38,811✔
2976
                                                  palette_float,
2,298✔
2977
                                                  n,
2,298✔
2978
                                                  1);
2979
        b = sixel_output_palette_channel_to_float(palette,
38,811✔
2980
                                                  palette_float,
2,298✔
2981
                                                  n,
2,298✔
2982
                                                  2);
2983
        maxc = r > g ? (r > b ? r : b) : (g > b ? g : b);
36,513✔
2984
        minc = r < g ? (r < b ? r : b) : (g < b ? g : b);
36,513✔
2985
        lightness = (maxc + minc) * 0.5;
36,513✔
2986
        saturation = 0.0;
36,513✔
2987
        hue = 0.0;
36,513✔
2988
        diff = maxc - minc;
36,513✔
2989
        if (diff <= 0.0) {
36,513✔
2990
            h = 0;
2,241✔
2991
            s = 0;
2,241✔
2992
        } else {
249✔
2993
            if (lightness < 0.5) {
32,778✔
2994
                saturation = diff / (maxc + minc);
27,876✔
2995
            } else {
1,741✔
2996
                saturation = diff / (2.0 - maxc - minc);
4,902✔
2997
            }
2998
            if (maxc == r) {
32,778✔
2999
                hue = (g - b) / diff;
22,377✔
3000
            } else if (maxc == g) {
11,800✔
3001
                hue = 2.0 + (b - r) / diff;
10,102✔
3002
            } else {
631✔
3003
                hue = 4.0 + (r - g) / diff;
299✔
3004
            }
3005
            hue *= 60.0;
32,778✔
3006
            if (hue < 0.0) {
32,778✔
3007
                hue += 360.0;
15✔
3008
            }
1✔
3009
            if (hue >= 360.0) {
32,778!
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,778✔
3019
            while (hue >= 360.0) {
32,836✔
3020
                hue -= 360.0;
58✔
3021
            }
3022
            while (hue < 0.0) {
32,778!
3023
                hue += 360.0;
×
3024
            }
3025
            s = (int)(saturation * 100.0 + 0.5);
32,778✔
3026
            if (s < 0) {
32,778!
3027
                s = 0;
3028
            } else if (s > 100) {
32,778!
3029
                s = 100;
3030
            }
3031
            h = (int)(hue + 0.5);
32,778✔
3032
            if (h >= 360) {
32,778!
3033
                h -= 360;
×
3034
            } else if (h < 0) {
32,778!
3035
                h = 0;
3036
            }
3037
        }
3038
        l = (int)(lightness * 100.0 + 0.5);
36,513✔
3039
        if (l < 0) {
36,513!
3040
            l = 0;
3041
        } else if (l > 100) {
36,513!
3042
            l = 100;
3043
        }
3044
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
3045
        sixel_putc(output->buffer + output->pos, '#');
36,513✔
3046
        sixel_advance(output, 1);
36,513✔
3047
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
36,513✔
3048
        sixel_advance(output, nwrite);
36,513✔
3049
        sixel_puts(output->buffer + output->pos, ";1;", 3);
36,513✔
3050
        sixel_advance(output, 3);
36,513✔
3051
        nwrite = sixel_putnum((char *)output->buffer + output->pos, h);
36,513✔
3052
        sixel_advance(output, nwrite);
36,513✔
3053
        sixel_putc(output->buffer + output->pos, ';');
36,513✔
3054
        sixel_advance(output, 1);
36,513✔
3055
        nwrite = sixel_putnum((char *)output->buffer + output->pos, l);
36,513✔
3056
        sixel_advance(output, nwrite);
36,513✔
3057
        sixel_putc(output->buffer + output->pos, ';');
36,513✔
3058
        sixel_advance(output, 1);
36,513✔
3059
        nwrite = sixel_putnum((char *)output->buffer + output->pos, s);
36,513✔
3060
        sixel_advance(output, nwrite);
36,513✔
3061
    }
2,298✔
3062

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

3067

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

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

3094
    len = ncolors * width;
57,596✔
3095
    work->map = (char *)sixel_allocator_calloc(allocator,
61,704✔
3096
                                               (size_t)len,
4,108✔
3097
                                               sizeof(char));
3098
    if (work->map == NULL && len > 0) {
57,611!
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,611✔
3105

3106
    columns_size = sizeof(sixel_node_t *) * (size_t)width;
57,611✔
3107
    if (width > 0) {
57,611!
3108
        work->columns = (sixel_node_t **)sixel_allocator_malloc(
57,611✔
3109
            allocator,
4,108✔
3110
            columns_size);
4,108✔
3111
        if (work->columns == NULL) {
57,609!
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,606✔
3118
        work->columns_size = columns_size;
57,606✔
3119
    } else {
4,108✔
3120
        work->columns = NULL;
1✔
3121
        work->columns_size = 0;
1✔
3122
    }
3123

3124
    active_colors_size = (size_t)ncolors;
57,606✔
3125
    if (active_colors_size > 0) {
57,606!
3126
        work->active_colors =
81,981✔
3127
            (unsigned char *)sixel_allocator_malloc(allocator,
61,715✔
3128
                                                    active_colors_size);
4,108✔
3129
        if (work->active_colors == NULL) {
57,612!
3130
            sixel_helper_set_additional_message(
2✔
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,610✔
3136
        work->active_colors_size = active_colors_size;
57,610✔
3137
    } else {
4,108✔
3138
        work->active_colors = NULL;
3139
        work->active_colors_size = 0;
3140
    }
3141

3142
    active_color_index_size = sizeof(int) * (size_t)ncolors;
57,609✔
3143
    if (active_color_index_size > 0) {
57,609!
3144
        work->active_color_index = (int *)sixel_allocator_malloc(
57,608✔
3145
            allocator,
4,108✔
3146
            active_color_index_size);
4,108✔
3147
        if (work->active_color_index == NULL) {
57,613!
3148
            sixel_helper_set_additional_message(
×
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,613✔
3154
        work->active_color_index_size = active_color_index_size;
57,613✔
3155
    } else {
4,108✔
3156
        work->active_color_index = NULL;
3✔
3157
        work->active_color_index_size = 0;
3✔
3158
    }
3159

3160
    status = SIXEL_OK;
33,240✔
3161

3162
end:
29,132✔
3163
    if (SIXEL_FAILED(status)) {
47,587!
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,614✔
3187
}
3188

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

3215
static void
3216
sixel_band_state_reset(sixel_band_state_t *state)
281,797✔
3217
{
3218
    state->row_in_band = 0;
281,797✔
3219
    state->fillable = 0;
281,797✔
3220
    state->active_color_count = 0;
279,407!
3221
}
174,185✔
3222

3223
static void
3224
sixel_band_finish(sixel_encode_work_t *work, sixel_band_state_t *state)
201,747✔
3225
{
3226
    int color_index;
88,471✔
3227
    int c;
88,471✔
3228

3229
    if (work->active_colors == NULL
201,747!
3230
        || work->active_color_index == NULL) {
201,747!
3231
        state->active_color_count = 0;
1✔
3232
        return;
1✔
3233
    }
3234

3235
    for (color_index = 0;
520,192✔
3236
         color_index < state->active_color_count;
6,588,080✔
3237
         color_index++) {
6,386,334✔
3238
        c = work->active_color_index[color_index];
6,386,336✔
3239
        if (c >= 0
6,386,336!
3240
            && (size_t)c < work->active_colors_size) {
6,386,323!
3241
            work->active_colors[c] = 0;
6,386,327✔
3242
        }
406,912✔
3243
    }
406,919✔
3244
    state->active_color_count = 0;
201,744✔
3245
}
12,770✔
3246

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

3255
static SIXELSTATUS
3256
sixel_encode_emit_palette(int bodyonly,
35,788✔
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,788✔
3264
    int n;
15,665✔
3265

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

3270
    if (palette == NULL && palette_float == NULL) {
34,972!
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,972✔
3277
        for (n = 0; n < ncolors; n++) {
36,656✔
3278
            status = output_hls_palette_definition(output,
38,811✔
3279
                                                   palette,
2,298✔
3280
                                                   palette_float,
2,298✔
3281
                                                   n,
2,298✔
3282
                                                   keycolor);
2,298✔
3283
            if (SIXEL_FAILED(status)) {
36,513!
3284
                goto end;
3285
            }
3286
        }
2,298✔
3287
    } else {
9✔
3288
        for (n = 0; n < ncolors; n++) {
2,666,455✔
3289
            status = output_rgb_palette_definition(output,
2,798,448✔
3290
                                                   palette,
166,822✔
3291
                                                   palette_float,
166,822✔
3292
                                                   n,
166,822✔
3293
                                                   keycolor);
166,822✔
3294
            if (SIXEL_FAILED(status)) {
2,631,626!
3295
                goto end;
3296
            }
3297
        }
166,822✔
3298
    }
3299

3300
    status = SIXEL_OK;
19,664✔
3301

3302
end:
33,128✔
3303
    return status;
19,664✔
3304
}
2,252✔
3305

3306
static SIXELSTATUS
3307
sixel_band_classify_row(sixel_encode_work_t *work,
1,127,429✔
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,127,429✔
3322
    int row_bit;
494,451✔
3323
    int band_start;
494,451✔
3324
    int source_row;
494,451✔
3325
    int source_band_start;
494,451✔
3326
    int target_x;
494,451✔
3327
    int pix;
494,451✔
3328
    int x;
494,451✔
3329
    int check_integer_overflow;
494,451✔
3330
    int source_index_base;
494,451✔
3331
    char *map;
494,451✔
3332
    unsigned char *active_colors;
494,451✔
3333
    int *active_color_index;
494,451✔
3334

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

3341
    if (row_bit == 0) {
1,127,429✔
3342
        if (encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
201,743✔
3343
            state->fillable = 0;
201,569✔
3344
        } else if (palstate) {
12,935!
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;
174✔
3363
        }
3364
        state->active_color_count = 0;
201,743✔
3365
    }
12,772✔
3366

3367
    source_row = absolute_row - offset_top;
1,127,429✔
3368
    if (source_row < 0 || source_row >= height) {
1,127,429!
3369
        state->row_in_band += 1;
164✔
3370
        return SIXEL_OK;
164✔
3371
    }
3372
    if (source_row > INT_MAX / width) {
1,127,313!
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,127,313✔
3380

3381
    for (x = 0; x < width; x++) {
135,835,107✔
3382
        target_x = offset_left + x;
134,725,888✔
3383
        if (target_x < 0 || target_x >= map_width) {
134,725,888!
3384
            sixel_helper_set_additional_message(
73,838✔
3385
                "sixel_encode_body: transparent offset is out of range.");
3386
            status = SIXEL_BAD_INTEGER_OVERFLOW;
54,894✔
3387
            goto end;
54,894✔
3388
        }
3389
        check_integer_overflow = source_index_base;
134,707,816✔
3390
        if (check_integer_overflow > INT_MAX - x) {
134,707,816!
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,707,816✔
3398
        if (pix >= 0 && pix < ncolors && pix != keycolor) {
134,707,816!
3399
            if (pix > INT_MAX / map_width) {
114,559,720!
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,559,720✔
3407
            if (check_integer_overflow > INT_MAX - target_x) {
114,559,720!
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,559,720✔
3415
            if (active_colors != NULL && active_colors[pix] == 0) {
114,559,720!
3416
                active_colors[pix] = 1;
6,374,229✔
3417
                if (state->active_color_count < ncolors
6,374,229!
3418
                    && active_color_index != NULL) {
6,383,049!
3419
                    active_color_index[state->active_color_count] = pix;
6,383,044✔
3420
                    state->active_color_count += 1;
6,383,044✔
3421
                }
406,618✔
3422
            }
406,713✔
3423
        } else if (!palstate) {
27,281,127✔
3424
            state->fillable = 0;
15,433,852✔
3425
        }
962,555✔
3426
    }
8,395,196✔
3427

3428
    state->row_in_band += 1;
1,109,219✔
3429
    status = SIXEL_OK;
1,109,219✔
3430

3431
end:
828,744✔
3432
    return status;
614,833✔
3433
}
71,422✔
3434

3435
static SIXELSTATUS
3436
sixel_band_compose(sixel_encode_work_t *work,
201,751✔
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;
201,751✔
3445
    int color_index;
88,472✔
3446
    int c;
88,472✔
3447
    unsigned char *row;
88,472✔
3448
    int sx;
88,472✔
3449
    int mx;
88,472✔
3450
    int gap;
88,472✔
3451
    int gap_reached_end;
88,472✔
3452
    sixel_node_t *np;
88,472✔
3453
    sixel_node_t *column_head;
88,472✔
3454
    sixel_node_t *column_tail;
88,472✔
3455
    sixel_node_t top;
88,472✔
3456

3457
    (void)ncolors;
101,246✔
3458
    (void)keycolor;
101,246✔
3459
    row = NULL;
201,751✔
3460
    gap_reached_end = 0;
201,751✔
3461
    np = NULL;
201,751✔
3462
    column_head = NULL;
201,751✔
3463
    column_tail = NULL;
201,751✔
3464
    top.next = NULL;
201,751✔
3465

3466
    if (work->columns != NULL) {
201,751!
3467
        memset(work->columns, 0, work->columns_size);
201,747✔
3468
    }
12,773✔
3469
    output->node_top = NULL;
201,751✔
3470

3471
    for (color_index = 0;
608,475✔
3472
         color_index < state->active_color_count;
6,588,808✔
3473
         color_index++) {
6,387,059✔
3474
        c = work->active_color_index[color_index];
6,384,323✔
3475
        row = (unsigned char *)(work->map + c * width);
6,384,323✔
3476
        sx = 0;
6,384,323✔
3477
        while (sx < width) {
15,934,279✔
3478
            sx = sixel_compose_find_run_start(
15,481,693✔
3479
                row,
984,213✔
3480
                width,
984,213✔
3481
                sx);
984,213✔
3482
            if (sx >= width) {
15,481,738✔
3483
                break;
3,321,617✔
3484
            }
3485

3486
            mx = sx + 1;
9,547,265✔
3487
            while (mx < width) {
54,046,015✔
3488
                if (row[mx] != 0) {
53,596,055✔
3489
                    mx += 1;
36,836,433✔
3490
                    continue;
36,836,433✔
3491
                }
3492

3493
                gap = sixel_compose_measure_gap(
16,759,622✔
3494
                    row,
1,064,621✔
3495
                    width,
1,064,621✔
3496
                    mx + 1,
1,064,621✔
3497
                    &gap_reached_end);
3498
                if (gap >= 9 || gap_reached_end) {
16,755,701✔
3499
                    break;
577,127✔
3500
                }
3501
                mx += gap + 1;
7,662,317✔
3502
            }
3503

3504
            if ((np = output->node_free) != NULL) {
9,543,344✔
3505
                output->node_free = np->next;
2,099,991✔
3506
            } else {
14,483✔
3507
                status = sixel_node_new(&np, allocator);
7,443,353✔
3508
                if (SIXEL_FAILED(status)) {
7,446,667!
3509
                    goto end;
3510
                }
3511
            }
3512

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

3519
            if (work->columns != NULL) {
9,549,956!
3520
                column_head = work->columns[sx];
9,549,637✔
3521
                if (column_head == NULL
9,549,637✔
3522
                    || column_head->mx <= np->mx) {
4,600,814✔
3523
                    np->next = column_head;
7,498,658✔
3524
                    work->columns[sx] = np;
7,498,658✔
3525
                } else {
474,300✔
3526
                    column_tail = column_head;
1,147,863✔
3527
                    while (column_tail->next != NULL
2,505,499✔
3528
                           && column_tail->next->mx > np->mx) {
2,744,848✔
3529
                        column_tail = column_tail->next;
388,408✔
3530
                    }
3531
                    np->next = column_tail->next;
2,050,979✔
3532
                    column_tail->next = np;
2,050,979✔
3533
                }
3534
            } else {
606,121✔
3535
                top.next = output->node_top;
319✔
3536
                column_tail = &top;
319✔
3537

3538
                while (column_tail->next != NULL) {
319!
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;
319✔
3549
                column_tail->next = np;
319✔
3550
                output->node_top = top.next;
319✔
3551
            }
3552

3553
            sx = mx;
7,786,105✔
3554
        }
3555
    }
406,724✔
3556

3557
    if (work->columns != NULL) {
204,485!
3558
        top.next = NULL;
201,746✔
3559
        column_tail = &top;
201,746✔
3560
        for (sx = 0; sx < width; sx++) {
23,369,336✔
3561
            column_head = work->columns[sx];
23,167,590✔
3562
            if (column_head == NULL) {
23,167,590✔
3563
                continue;
17,891,599✔
3564
            }
3565
            column_tail->next = column_head;
5,275,991✔
3566
            while (column_tail->next != NULL) {
14,818,381✔
3567
                column_tail = column_tail->next;
5,348,549✔
3568
            }
3569
            work->columns[sx] = NULL;
5,275,991✔
3570
        }
332,803✔
3571
        output->node_top = top.next;
201,746✔
3572
    }
12,772✔
3573

3574
    status = SIXEL_OK;
204,485✔
3575

3576
end:
191,713✔
3577
    return status;
204,485✔
3578
}
3579

3580
static SIXELSTATUS
3581
sixel_band_emit(sixel_encode_work_t *work,
201,744✔
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;
201,744✔
3589
    sixel_node_t *np;
88,469✔
3590
    sixel_node_t *next;
88,469✔
3591
    int x;
88,469✔
3592
    int emit_next_line;
88,469✔
3593

3594
    emit_next_line = (last_row_index >= 6);
201,744✔
3595
    if (emit_next_line) {
201,744✔
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,962✔
3601
        sixel_advance(output, 1);
165,962✔
3602
    }
10,521✔
3603

3604
    for (x = 0; (np = output->node_top) != NULL;) {
1,494,842✔
3605
        if (x > np->sx) {
1,293,199✔
3606
            output->buffer[output->pos] = '$';
1,098,176✔
3607
            sixel_advance(output, 1);
1,098,176✔
3608
            x = 0;
1,098,151✔
3609
        }
70,263✔
3610

3611
        if (state->fillable) {
1,293,174✔
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,375,788✔
3617
                                &x,
3618
                                np,
82,614✔
3619
                                ncolors,
82,614✔
3620
                                keycolor);
82,614✔
3621
        if (SIXEL_FAILED(status)) {
1,293,131!
3622
            goto end;
3623
        }
3624
        next = np->next;
1,293,131✔
3625
        sixel_node_del(output, np);
1,293,131✔
3626
        np = next;
1,293,133✔
3627

3628
        while (np != NULL) {
50,711,050✔
3629
            if (np->sx < x) {
49,417,943✔
3630
                np = np->next;
41,164,413✔
3631
                continue;
41,164,413✔
3632
            }
3633

3634
            if (state->fillable) {
8,253,530!
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,776,437✔
3640
                                    &x,
3641
                                    np,
522,907✔
3642
                                    ncolors,
522,907✔
3643
                                    keycolor);
522,907✔
3644
            if (SIXEL_FAILED(status)) {
8,251,677!
3645
                goto end;
3646
            }
3647
            next = np->next;
8,251,677✔
3648
            sixel_node_del(output, np);
8,251,677✔
3649
            np = next;
8,252,646✔
3650
        }
3651

3652
        state->fillable = 0;
1,293,106✔
3653
    }
3654

3655
    status = SIXEL_OK;
113,183✔
3656

3657
end:
188,874✔
3658
    (void)work;
101,229✔
3659
    return status;
201,643✔
3660
}
3661

3662

3663
SIXELSTATUS
3664
sixel_encode_body(
16,850✔
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,850✔
3680
    int band_start;
7,754✔
3681
    int band_height;
7,754✔
3682
    int row_index;
7,754✔
3683
    int absolute_row;
7,754✔
3684
    int last_row_index;
7,754✔
3685
    int encoded_width;
7,754✔
3686
    int encoded_height;
7,754✔
3687
    int offset_left;
7,754✔
3688
    int offset_top;
7,754✔
3689
    sixel_node_t *np;
7,754✔
3690
    sixel_encode_work_t work;
7,754✔
3691
    sixel_band_state_t band;
7,754✔
3692
    int logging_active;
7,754✔
3693
    int job_index;
7,754✔
3694

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

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

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

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

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

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

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

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

3740
        nbands = sixel_count_sixel_bands(encoded_height);
7,986!
3741
        threads = work.requested_threads;
7,986✔
3742
        if (nbands > 1 && threads > 1) {
7,986✔
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,304!
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,304✔
3770
                                        encoded_width,
446✔
3771
                                        ncolors,
446✔
3772
                                        allocator);
446✔
3773
    if (SIXEL_FAILED(status)) {
14,304!
3774
        goto cleanup;
×
3775
    }
3776

3777
    band_start = 0;
7,611✔
3778
    while (band_start < encoded_height) {
84,796✔
3779
        band_height = encoded_height - band_start;
70,492✔
3780
        if (band_height > 6) {
70,492✔
3781
            band_height = 6;
29,345✔
3782
        }
1,249✔
3783

3784
        band.row_in_band = 0;
70,492✔
3785
        band.fillable = 0;
70,492✔
3786
        band.active_color_count = 0;
70,492✔
3787

3788
        if (logging_active) {
70,492!
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++) {
454,545✔
3797
            absolute_row = band_start + row_index;
384,053✔
3798
            status = sixel_band_classify_row(&work,
384,053✔
3799
                                             &band,
3800
                                             pixels,
8,670✔
3801
                                             width,
8,670✔
3802
                                             height,
8,670✔
3803
                                             encoded_width,
8,670✔
3804
                                             absolute_row,
8,670✔
3805
                                             offset_left,
8,670✔
3806
                                             offset_top,
8,670✔
3807
                                             ncolors,
8,670✔
3808
                                             keycolor,
8,670✔
3809
                                             palstate,
8,670✔
3810
                                             output->encode_policy);
8,670✔
3811
            if (SIXEL_FAILED(status)) {
384,053!
3812
                goto cleanup;
×
3813
            }
3814
        }
8,670✔
3815

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

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

3838
        sixel_band_finish(&work, &band);
70,492✔
3839

3840
        sixel_band_clear_map(&work);
70,492✔
3841

3842
        if (logging_active) {
70,492!
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;
70,492✔
3851
        sixel_band_state_reset(&band);
70,492✔
3852
        job_index += 1;
70,492✔
3853
    }
3854

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

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

3864
cleanup:
18,333✔
3865
    while ((np = output->node_free) != NULL) {
704,657✔
3866
        output->node_free = np->next;
687,807✔
3867
        sixel_allocator_free(allocator, np);
687,807✔
3868
    }
3869
    output->node_top = NULL;
16,850✔
3870

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

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

3880
    if (output->pos > 0) {
34,140✔
3881
        status = sixel_output_write_bytes(output,
51,089✔
3882
                                          (char *)output->buffer,
34,044✔
3883
                                          output->pos);
2,143✔
3884
        if (SIXEL_FAILED(status)) {
34,044✔
3885
            return status;
3886
        }
3887
        output->pos = 0;
34,044✔
3888
    }
2,143✔
3889

3890
    status = sixel_output_end_image(output);
34,140✔
3891

3892
    return status;
34,140✔
3893
}
2,149✔
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,647✔
3997
                nwrite = sixel_putnum((char *)output->buffer + output->pos,
3,878✔
3998
                                      1 << plane);
230✔
3999
                sixel_advance(output, nwrite);
3,647✔
4000

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

4015
                sixel_putc(output->buffer + output->pos, '$');
3,647✔
4016
                sixel_advance(output, 1);
3,647✔
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,821✔
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,821✔
4296
    sixel_index_t *paletted_pixels = NULL;
33,821✔
4297
    sixel_index_t *input_pixels;
14,805✔
4298
    size_t bufsize;
14,805✔
4299
    unsigned char *palette_entries = NULL;
33,821✔
4300
    float *palette_entries_float32 = NULL;
33,821✔
4301
    sixel_palette_t *palette_obj = NULL;
33,821✔
4302
    size_t palette_count = 0U;
33,821✔
4303
    size_t palette_float_count = 0U;
33,821✔
4304
    size_t palette_bytes = 0U;
33,821✔
4305
    size_t palette_float_bytes = 0U;
33,821✔
4306
    size_t palette_channels = 0U;
33,821✔
4307
    size_t palette_index = 0U;
33,821✔
4308
    size_t pixel_count = 0U;
33,821✔
4309
    int palette_source_colorspace;
14,805✔
4310
    int palette_float_pixelformat;
14,805✔
4311
    int output_float_pixelformat;
14,805✔
4312
    int palette_float_depth;
14,805✔
4313
    int pipeline_active;
14,805✔
4314
    int pipeline_threads = 0;  /* set to a deterministic default before use */
33,821✔
4315
    int pipeline_nbands;
14,805✔
4316
    int encoded_width;
14,805✔
4317
    int encoded_height;
14,805✔
4318
    sixel_parallel_dither_config_t dither_parallel;
14,805✔
4319
    sixel_palette_entries_view_t palette_view;
14,805✔
4320
    sixel_palette_float32_entries_view_t palette_float_view;
14,805✔
4321
#if SIXEL_ENABLE_THREADS
4322
    sixel_timeline_logger_t *serial_logger;
10,619✔
4323
    int logger_owned = 0;
25,449✔
4324
#endif  /* SIXEL_ENABLE_THREADS */
4325
    sixel_timeline_logger_t *logger = NULL;
33,821✔
4326

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

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

4390
    pipeline_active = 0;
33,821✔
4391
#if SIXEL_ENABLE_THREADS
4392
    #endif
4393
    dither_parallel.enabled = 0;
33,821✔
4394
    dither_parallel.band_height = 0;
33,821✔
4395
    dither_parallel.overlap = 0;
33,821✔
4396
    dither_parallel.dither_threads = 0;
33,821✔
4397
    dither_parallel.encode_threads = 0;
33,821✔
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,821✔
4403
        dither->pipeline_pin_threads != 0 ? 1 : 0;
33,821✔
4404
    dither_parallel.pin_threads = dither->pipeline_pin_threads;
33,821✔
4405
    switch (dither->pixelformat) {
33,821!
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:
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.");
4417
            status = SIXEL_BAD_ALLOCATION;
×
4418
            goto end;
×
4419
        }
4420
        status = sixel_helper_normalize_pixelformat(paletted_pixels,
×
4421
                                                    &dither->pixelformat,
4422
                                                    pixels,
4423
                                                    dither->pixelformat,
4424
                                                    width, height);
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,810✔
4437
        /* apply palette */
4438
        pipeline_threads = sixel_threads_resolve();
30,746✔
4439
        pipeline_nbands = sixel_count_sixel_bands(encoded_height);
30,746!
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,746✔
4447
            pipeline_active = 1;
11,209✔
4448
            input_pixels = NULL;
11,209✔
4449
        } else {
1,619✔
4450
            paletted_pixels = sixel_dither_apply_palette(dither, pixels,
11,813✔
4451
                                                         width, height);
317✔
4452
            if (paletted_pixels == NULL) {
11,496!
4453
                status = SIXEL_RUNTIME_ERROR;
×
4454
                goto end;
×
4455
            }
4456
            input_pixels = paletted_pixels;
6,078✔
4457
        }
4458
        break;
17,287✔
4459
    }
4460

4461
    if (pipeline_active) {
27,057✔
4462
        sixel_parallel_dither_configure(height,
20,869✔
4463
                                        dither->ncolors,
1,619✔
4464
                                        pipeline_threads,
1,619✔
4465
                                        dither->pipeline_pin_threads,
1,619✔
4466
                                        &dither_parallel);
4467
        if (dither_parallel.enabled) {
19,250!
4468
            dither->pipeline_parallel_active = 1;
19,250✔
4469
            dither->pipeline_band_height = dither_parallel.band_height;
19,250✔
4470
            dither->pipeline_band_overlap = dither_parallel.overlap;
19,250✔
4471
            dither->pipeline_dither_threads =
19,250✔
4472
                dither_parallel.dither_threads;
19,250✔
4473
            pipeline_threads = dither_parallel.encode_threads;
19,250✔
4474
        }
1,619✔
4475
        if (pipeline_threads <= 1) {
19,250✔
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!
4488
                    status = SIXEL_RUNTIME_ERROR;
×
4489
                    goto end;
×
4490
                }
4491
            }
21✔
4492
            input_pixels = paletted_pixels;
147✔
4493
        }
21✔
4494
    }
1,619✔
4495

4496
#if SIXEL_ENABLE_THREADS
4497
    if (!pipeline_active) {
14,830✔
4498
        logger = dither->pipeline_logger;
6,451✔
4499
        if (logger == NULL) {
6,451!
4500
            sixel_timeline_logger_prepare_default(dither->allocator,
6,451✔
4501
                                                  &serial_logger);
4502
            if (serial_logger != NULL) {
6,451✔
4503
                logger_owned = 1;
24✔
4504
                dither->pipeline_logger = serial_logger;
24✔
4505
                logger = serial_logger;
24✔
4506
            } else {
2✔
4507
                logger = NULL;
3,754✔
4508
            }
4509
        }
531✔
4510
        if (logger != NULL) {
3,778✔
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
    }
531✔
4524
#endif
4525

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

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

4556
    status = palette_obj->vtbl->get_entries_float32(palette_obj,
33,821✔
4557
                                                    &palette_float_view);
4558
    if (SIXEL_FAILED(status)) {
33,821!
4559
        goto end;
×
4560
    }
4561
    if (palette_float_view.entries != NULL &&
33,821✔
4562
            palette_float_view.entry_count == palette_count &&
3,567!
4563
            palette_float_view.depth == palette_float_depth) {
3,567!
4564
        palette_float_count = palette_float_view.entry_count;
3,567✔
4565
        palette_float_bytes =
3,567✔
4566
            palette_float_count * (size_t)palette_float_view.depth;
3,567✔
4567
        palette_entries_float32 = (float *)sixel_allocator_malloc(
3,567✔
4568
            dither->allocator,
114✔
4569
            palette_float_bytes);
114✔
4570
        if (palette_entries_float32 == NULL) {
3,567!
4571
            sixel_helper_set_additional_message(
×
4572
                "sixel_encode_dither: float palette copy allocation failed.");
4573
            status = SIXEL_BAD_ALLOCATION;
×
4574
            goto end;
×
4575
        }
4576
        memcpy(palette_entries_float32,
3,681✔
4577
               palette_float_view.entries,
3,567✔
4578
               palette_float_bytes);
114✔
4579
    }
114✔
4580
    if (palette_entries != NULL && palette_entries_float32 != NULL
33,821!
4581
            && palette_count == palette_float_count
5,582!
4582
            && palette_count > 0U
3,567!
4583
            && !sixel_palette_float32_matches_u8(
3,567!
4584
                    palette_entries,
114✔
4585
                    palette_entries_float32,
114✔
4586
                    palette_count,
114✔
4587
                    palette_float_pixelformat)) {
114✔
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,821!
4594
            && output != NULL
19,016!
4595
            && output->source_colorspace != output->colorspace) {
33,821✔
4596
        palette_bytes = palette_count * 3U;
1,136✔
4597
        if (palette_entries_float32 != NULL
1,136✔
4598
                && palette_float_count == palette_count) {
726!
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!
4614
                sixel_helper_set_additional_message(
×
4615
                    "sixel_encode_dither: float palette colorspace conversion failed.");
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,
888✔
4635
                                                     palette_bytes,
68✔
4636
                                                     SIXEL_PIXELFORMAT_RGB888,
4637
                                                     output->source_colorspace,
68✔
4638
                                                     output->colorspace);
68✔
4639
            if (SIXEL_FAILED(status)) {
820!
4640
                sixel_helper_set_additional_message(
×
4641
                    "sixel_encode_dither: palette colorspace "
4642
                    "conversion failed.");
4643
                goto end;
×
4644
            }
4645
        }
4646
    }
71✔
4647
    if (SIXEL_FAILED(status) || palette_entries == NULL) {
19,016!
4648
        sixel_helper_set_additional_message(
4649
            "sixel_encode_dither: palette copy failed.");
4650
        goto end;
4651
    }
4652

4653
    if (input_pixels != NULL &&
33,821!
4654
            dither->pipeline_accumulation_result_enabled != 0) {
14,823✔
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!
4662
            goto end;
×
4663
        }
4664
    }
13✔
4665

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

4674
    if (pipeline_active) {
33,821!
4675
        if (output->ormode) {
18,998!
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,531✔
4685
                                                width,
1,593✔
4686
                                                height,
1,593✔
4687
                                                palette_entries,
1,593✔
4688
                                                palette_entries_float32,
1,593✔
4689
                                                dither,
1,593✔
4690
                                                output,
1,593✔
4691
                                                pipeline_threads);
1,593✔
4692
        }
4693
    } else if (output->ormode) {
16,421!
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,334✔
4703
                                   width,
531✔
4704
                                   height,
531✔
4705
                                   palette_entries,
531✔
4706
                                   palette_entries_float32,
531✔
4707
                                   dither->ncolors,
531✔
4708
                                   dither->keycolor,
531✔
4709
                                   dither->bodyonly,
531✔
4710
                                   output,
531✔
4711
                                   NULL,
4712
                                   dither->allocator,
531✔
4713
                                   dither->pipeline_pin_threads,
531✔
4714
                                   logger != NULL ?
531✔
4715
                                       logger :
2✔
4716
                                       NULL);
4717
    }
4718

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

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

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

4743
    return status;
33,821✔
4744
}
2,129✔
4745

4746
SIXEL_INTERNAL_API SIXELSTATUS
4747
sixel_encoder_core_encode_dispatch(
34,204✔
4748
    sixel_encoder_core_encode_request_t const *request)
4749
{
4750
    SIXELSTATUS status;
14,972✔
4751

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

4766
    (void)request->depth;
17,093✔
4767
    if (request->dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
34,140✔
4768
        status = sixel_encode_highcolor(request->pixels,
339✔
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,950✔
4775
                                     request->width,
19,016✔
4776
                                     request->height,
19,016✔
4777
                                     request->dither,
19,016✔
4778
                                     request->output);
19,016✔
4779
    }
4780

4781
    return status;
19,196✔
4782
}
2,153✔
4783

4784
SIXELAPI SIXELSTATUS
4785
sixel_encode(
34,124✔
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;
34,124✔
4794
    sixel_encoder_core_t *core;
14,937✔
4795
    sixel_encoder_core_encode_request_t request;
14,937✔
4796

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

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

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

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

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

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

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

4852
end:
31,976✔
4853
    sixel_output_unref(output);
34,124✔
4854
    sixel_dither_unref(dither);
34,124✔
4855

4856
    return status;
34,124✔
4857
}
2,148✔
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