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

saitoha / libsixel / 19776286632

29 Nov 2025 12:20AM UTC coverage: 41.017% (-0.3%) from 41.338%
19776286632

push

github

saitoha
build: remove unused status in reversible snap test

9964 of 36344 branches covered (27.42%)

13002 of 31699 relevant lines covered (41.02%)

1178071.22 hits per line

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

60.56
/src/tosixel.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
 * useing, 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
#include "config.h"
45

46
/* STDC_HEADERS */
47
#include <stdio.h>
48
#include <stdlib.h>
49
#include <errno.h>
50

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

61
#include <sixel.h>
62
#include "output.h"
63
#include "dither.h"
64
#include "pixelformat.h"
65
#include "assessment.h"
66
#include "logger.h"
67
#include "palette-common-snap.h"
68
#include "sixel_threads_config.h"
69
#if SIXEL_ENABLE_THREADS
70
# include "sixel_atomic.h"
71
# include "sixel_threading.h"
72
# include "threadpool.h"
73
#endif
74

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

87
enum {
88
    PALETTE_HIT    = 1,
89
    PALETTE_CHANGE = 2
90
};
91

92
typedef struct sixel_parallel_dither_config {
93
    int enabled;
94
    int band_height;
95
    int overlap;
96
    int dither_threads;
97
    int encode_threads;
98
    int dither_env_override;
99
} sixel_parallel_dither_config_t;
100

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

113
typedef struct sixel_band_state {
114
    int row_in_band;
115
    int fillable;
116
    int active_color_count;
117
} sixel_band_state_t;
118

119
typedef struct sixel_encode_metrics {
120
    int encode_probe_active;
121
    double compose_span_started_at;
122
    double compose_queue_started_at;
123
    double compose_span_duration;
124
    double compose_scan_duration;
125
    double compose_queue_duration;
126
    double compose_scan_probes;
127
    double compose_queue_nodes;
128
    double emit_cells;
129
    double *emit_cells_ptr;
130
} sixel_encode_metrics_t;
131

132
#if SIXEL_ENABLE_THREADS
133
/*
134
 * Parallel execution relies on dedicated buffers per band and reusable
135
 * per-worker scratch spaces.  The main thread prepares the context and
136
 * pushes one job for each six-line band:
137
 *
138
 *   +------------------------+      enqueue jobs      +------------------+
139
 *   | main thread (producer) | ---------------------> | threadpool queue |
140
 *   +------------------------+                        +------------------+
141
 *            |                                                       |
142
 *            | allocate band buffers                                 |
143
 *            v                                                       v
144
 *   +------------------------+      execute callbacks      +-----------------+
145
 *   | per-worker workspace   | <-------------------------- | worker threads  |
146
 *   +------------------------+                             +-----------------+
147
 *            |
148
 *            | build SIXEL fragments
149
 *            v
150
 *   +------------------------+  ordered combination after join  +-----------+
151
 *   | band buffer array      | -------------------------------> | final I/O |
152
 *   +------------------------+                                  +-----------+
153
 */
154
typedef struct sixel_parallel_band_buffer {
155
    unsigned char *data;
156
    size_t size;
157
    size_t used;
158
    SIXELSTATUS status;
159
    int ready;
160
    int dispatched;
161
} sixel_parallel_band_buffer_t;
162

163
struct sixel_parallel_context;
164
typedef struct sixel_parallel_worker_state
165
    sixel_parallel_worker_state_t;
166

167
typedef struct sixel_parallel_context {
168
    sixel_index_t *pixels;
169
    int width;
170
    int height;
171
    int ncolors;
172
    int keycolor;
173
    unsigned char *palstate;
174
    int encode_policy;
175
    sixel_allocator_t *allocator;
176
    sixel_output_t *output;
177
    int encode_probe_active;
178
    int thread_count;
179
    int band_count;
180
    sixel_parallel_band_buffer_t *bands;
181
    sixel_parallel_worker_state_t **workers;
182
    int worker_capacity;
183
    int worker_registered;
184
    threadpool_t *pool;
185
    sixel_logger_t *logger;
186
    sixel_mutex_t mutex;
187
    int mutex_ready;
188
    sixel_cond_t cond_band_ready;
189
    int cond_ready;
190
    sixel_thread_t writer_thread;
191
    int writer_started;
192
    int next_band_to_flush;
193
    int writer_should_stop;
194
    SIXELSTATUS writer_error;
195
    int queue_capacity;
196
} sixel_parallel_context_t;
197

198
typedef struct sixel_parallel_row_notifier {
199
    sixel_parallel_context_t *context;
200
    sixel_logger_t *logger;
201
    int band_height;
202
    int image_height;
203
} sixel_parallel_row_notifier_t;
204

205
static void sixel_parallel_writer_stop(sixel_parallel_context_t *ctx,
206
                                       int force_abort);
207
static int sixel_parallel_writer_main(void *arg);
208
#endif
209

210
#if SIXEL_ENABLE_THREADS
211
static int sixel_parallel_jobs_allowed(sixel_parallel_context_t *ctx);
212
static void sixel_parallel_context_abort_locked(sixel_parallel_context_t *ctx,
213
                                               SIXELSTATUS status);
214
#endif
215

216
#if SIXEL_ENABLE_THREADS
217
struct sixel_parallel_worker_state {
218
    int initialized;
219
    int index;
220
    SIXELSTATUS writer_error;
221
    sixel_parallel_band_buffer_t *band_buffer;
222
    sixel_parallel_context_t *context;
223
    sixel_output_t *output;
224
    sixel_encode_work_t work;
225
    sixel_band_state_t band;
226
    sixel_encode_metrics_t metrics;
227
};
228
#endif
229

230
static void sixel_encode_work_init(sixel_encode_work_t *work);
231
static SIXELSTATUS sixel_encode_work_allocate(sixel_encode_work_t *work,
232
                                              int width,
233
                                              int ncolors,
234
                                              sixel_allocator_t *allocator);
235
static void sixel_encode_work_cleanup(sixel_encode_work_t *work,
236
                                      sixel_allocator_t *allocator);
237
static void sixel_band_state_reset(sixel_band_state_t *state);
238
static void sixel_band_finish(sixel_encode_work_t *work,
239
                              sixel_band_state_t *state);
240
static void sixel_band_clear_map(sixel_encode_work_t *work);
241
static void sixel_encode_metrics_init(sixel_encode_metrics_t *metrics);
242
static SIXELSTATUS sixel_band_classify_row(sixel_encode_work_t *work,
243
                                           sixel_band_state_t *state,
244
                                           sixel_index_t *pixels,
245
                                           int width,
246
                                           int absolute_row,
247
                                           int ncolors,
248
                                           int keycolor,
249
                                           unsigned char *palstate,
250
                                           int encode_policy);
251
static SIXELSTATUS sixel_band_compose(sixel_encode_work_t *work,
252
                                      sixel_band_state_t *state,
253
                                      sixel_output_t *output,
254
                                      int width,
255
                                      int ncolors,
256
                                      int keycolor,
257
                                      sixel_allocator_t *allocator,
258
                                      sixel_encode_metrics_t *metrics);
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
                                   sixel_encode_metrics_t *metrics);
266
static SIXELSTATUS sixel_put_flash(sixel_output_t *const output);
267
static void sixel_advance(sixel_output_t *output, int nwrite);
268

269
#if SIXEL_ENABLE_THREADS
270
static void
271
sixel_logger_prepare(sixel_logger_t *logger)
453✔
272
{
273
    if (logger == NULL) {
453!
274
        return;
275
    }
276

277
    sixel_logger_init(logger);
453✔
278
    (void)sixel_logger_prepare_env(logger);
453✔
279
}
280
#endif
281

282
static void
283
sixel_parallel_dither_configure(int height,
×
284
                                int ncolors,
285
                                int pipeline_threads,
286
                                sixel_parallel_dither_config_t *config)
287
{
288
    char const *text;
289
    long parsed;
290
    char *endptr;
291
    int band_height;
292
    int overlap;
293
    int dither_threads;
294
    int encode_threads;
295
    int dither_env_override;
296

297
    if (config == NULL) {
×
298
        return;
×
299
    }
300

301
    config->enabled = 0;
×
302
    config->band_height = 0;
×
303
    config->overlap = 0;
×
304
    config->dither_threads = 0;
×
305
    config->encode_threads = pipeline_threads;
×
306

307
    if (pipeline_threads <= 1 || height <= 0) {
×
308
        return;
×
309
    }
310

311
    dither_env_override = 0;
×
312
    dither_threads = (pipeline_threads * 7 + 9) / 10;
×
313
    text = getenv("SIXEL_DITHER_PARALLEL_THREADS_MAX");
×
314
    if (text != NULL && text[0] != '\0') {
×
315
        errno = 0;
×
316
        parsed = strtol(text, &endptr, 10);
×
317
        if (endptr != text && errno != ERANGE && parsed > 0) {
×
318
            if (parsed > INT_MAX) {
×
319
                parsed = INT_MAX;
×
320
            }
321
            dither_threads = (int)parsed;
×
322
            dither_env_override = 1;
×
323
        }
324
    }
325
    if (dither_threads < 1) {
×
326
        dither_threads = 1;
×
327
    }
328
    if (dither_threads > pipeline_threads) {
×
329
        dither_threads = pipeline_threads;
×
330
    }
331

332
    if (!dither_env_override && pipeline_threads >= 4 && dither_threads < 2) {
×
333
        /*
334
         * When the total budget is ample, keep at least two dither workers so
335
         * the banded producer can feed the encoder fast enough to pipeline.
336
         */
337
        dither_threads = pipeline_threads - 2;
×
338
    }
339

340
    encode_threads = pipeline_threads - dither_threads;
×
341
    if (encode_threads < 2 && pipeline_threads > 2) {
×
342
        /*
343
         * Preserve a minimal pair of encoder workers to keep the pipeline
344
         * alive while leaving the rest to dithering. Small budgets fall back
345
         * to the serial encoder path later in the caller.
346
         */
347
        encode_threads = 2;
×
348
        dither_threads = pipeline_threads - encode_threads;
×
349
    }
350
    if (encode_threads < 1) {
×
351
        encode_threads = 1;
×
352
        dither_threads = pipeline_threads - encode_threads;
×
353
    }
354
    if (dither_threads < 1) {
×
355
        return;
×
356
    }
357

358
    /*
359
     * Choose the band height from the environment when present. Otherwise
360
     * split the image across the initial dither workers so each thread starts
361
     * with a single band. The result is rounded to a six-line multiple to
362
     * stay aligned with the encoder's natural cadence.
363
     */
364
    band_height = 0;
×
365
    text = getenv("SIXEL_DITHER_PARALLEL_BAND_WIDTH");
×
366
    if (text != NULL && text[0] != '\0') {
×
367
        errno = 0;
×
368
        parsed = strtol(text, &endptr, 10);
×
369
        if (endptr != text && errno != ERANGE && parsed > 0) {
×
370
            if (parsed > INT_MAX) {
×
371
                parsed = INT_MAX;
×
372
            }
373
            band_height = (int)parsed;
×
374
        }
375
    }
376
    if (band_height <= 0) {
×
377
        band_height = (height + dither_threads - 1) / dither_threads;
×
378
    }
379
    if (band_height < 6) {
×
380
        band_height = 6;
×
381
    }
382
    if ((band_height % 6) != 0) {
×
383
        band_height = ((band_height + 5) / 6) * 6;
×
384
    }
385

386
    text = getenv("SIXEL_DITHER_PARALLEL_BAND_OVERWRAP");
×
387
    /*
388
     * Default overlap favors quality for small palettes and speed when
389
     * colors are plentiful. The environment can override this policy.
390
     */
391
    if (ncolors <= 32) {
×
392
        overlap = 6;
×
393
    } else {
394
        overlap = 0;
×
395
    }
396
    if (text != NULL && text[0] != '\0') {
×
397
        errno = 0;
×
398
        parsed = strtol(text, &endptr, 10);
×
399
        if (endptr != text && errno != ERANGE && parsed >= 0) {
×
400
            if (parsed > INT_MAX) {
×
401
                parsed = INT_MAX;
×
402
            }
403
            overlap = (int)parsed;
×
404
        }
405
    }
406
    if (overlap < 0) {
×
407
        overlap = 0;
×
408
    }
409
    if (overlap > band_height / 2) {
×
410
        overlap = band_height / 2;
×
411
    }
412

413
    config->enabled = 1;
×
414
    config->band_height = band_height;
×
415
    config->overlap = overlap;
×
416
    config->dither_threads = dither_threads;
×
417
    config->encode_threads = encode_threads;
×
418
}
419

420
#if SIXEL_ENABLE_THREADS
421
static int sixel_parallel_band_writer(char *data, int size, void *priv);
422
static int sixel_parallel_worker_main(tp_job_t job,
423
                                      void *userdata,
424
                                      void *workspace);
425
static SIXELSTATUS
426
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
427
                             sixel_index_t *pixels,
428
                             int width,
429
                             int height,
430
                             int ncolors,
431
                             int keycolor,
432
                             unsigned char *palstate,
433
                             sixel_output_t *output,
434
                             sixel_allocator_t *allocator,
435
                             int requested_threads,
436
                             int worker_capacity,
437
                             int queue_capacity,
438
                             sixel_logger_t *logger);
439
static SIXELSTATUS sixel_parallel_context_grow(sixel_parallel_context_t *ctx,
440
                                              int target_threads);
441
static void sixel_parallel_submit_band(sixel_parallel_context_t *ctx,
442
                                       int band_index);
443
static SIXELSTATUS sixel_parallel_context_wait(sixel_parallel_context_t *ctx,
444
                                               int force_abort);
445
static void sixel_parallel_palette_row_ready(void *priv, int row_index);
446
static SIXELSTATUS sixel_encode_emit_palette(int bodyonly,
447
                          int ncolors,
448
                          int keycolor,
449
                          unsigned char const *palette,
450
                          float const *palette_float,
451
                          sixel_output_t *output,
452
                          int encode_probe_active);
453

454
static void
455
sixel_parallel_context_init(sixel_parallel_context_t *ctx)
456
{
457
    ctx->pixels = NULL;
458
    ctx->width = 0;
459
    ctx->height = 0;
460
    ctx->ncolors = 0;
461
    ctx->keycolor = (-1);
462
    ctx->palstate = NULL;
463
    ctx->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
464
    ctx->allocator = NULL;
465
    ctx->output = NULL;
466
    ctx->encode_probe_active = 0;
467
    ctx->thread_count = 0;
468
    ctx->band_count = 0;
469
    ctx->bands = NULL;
470
    ctx->workers = NULL;
471
    ctx->worker_capacity = 0;
472
    ctx->worker_registered = 0;
473
    ctx->pool = NULL;
474
    ctx->logger = NULL;
475
    ctx->mutex_ready = 0;
476
    ctx->cond_ready = 0;
477
    ctx->writer_started = 0;
478
    ctx->next_band_to_flush = 0;
479
    ctx->writer_should_stop = 0;
480
    ctx->writer_error = SIXEL_OK;
481
    ctx->queue_capacity = 0;
482
}
483

484
static void
485
sixel_parallel_worker_release_nodes(sixel_parallel_worker_state_t *state,
486
                                    sixel_allocator_t *allocator)
487
{
488
    sixel_node_t *np;
489

490
    if (state == NULL || state->output == NULL) {
×
491
        return;
492
    }
493

494
    while ((np = state->output->node_free) != NULL) {
×
495
        state->output->node_free = np->next;
496
        sixel_allocator_free(allocator, np);
497
    }
498
    state->output->node_top = NULL;
499
}
500

501
static void
502
sixel_parallel_worker_cleanup(sixel_parallel_worker_state_t *state,
503
                              sixel_allocator_t *allocator)
504
{
505
    if (state == NULL) {
×
506
        return;
507
    }
508
    sixel_parallel_worker_release_nodes(state, allocator);
509
    if (state->output != NULL) {
×
510
        sixel_output_unref(state->output);
511
        state->output = NULL;
512
    }
513
    sixel_encode_work_cleanup(&state->work, allocator);
514
    sixel_band_state_reset(&state->band);
515
    sixel_encode_metrics_init(&state->metrics);
516
    state->initialized = 0;
517
    state->index = 0;
518
    state->writer_error = SIXEL_OK;
519
    state->band_buffer = NULL;
520
    state->context = NULL;
521
}
522

523
static void
524
sixel_parallel_context_cleanup(sixel_parallel_context_t *ctx)
525
{
526
    int i;
527

528
    if (ctx->workers != NULL) {
×
529
        for (i = 0; i < ctx->worker_capacity; i++) {
×
530
            sixel_parallel_worker_cleanup(ctx->workers[i], ctx->allocator);
531
        }
532
        free(ctx->workers);
533
        ctx->workers = NULL;
534
    }
535
    sixel_parallel_writer_stop(ctx, 1);
536
    if (ctx->bands != NULL) {
×
537
        for (i = 0; i < ctx->band_count; i++) {
×
538
            free(ctx->bands[i].data);
539
        }
540
        free(ctx->bands);
541
        ctx->bands = NULL;
542
    }
543
    if (ctx->pool != NULL) {
×
544
        threadpool_destroy(ctx->pool);
545
        ctx->pool = NULL;
546
    }
547
    if (ctx->cond_ready) {
×
548
        sixel_cond_destroy(&ctx->cond_band_ready);
549
        ctx->cond_ready = 0;
550
    }
551
    if (ctx->mutex_ready) {
×
552
        sixel_mutex_destroy(&ctx->mutex);
553
        ctx->mutex_ready = 0;
554
    }
555
}
556

557
/*
558
 * Abort the pipeline when either a worker or the writer encounters an error.
559
 * The helper normalizes the `writer_error` bookkeeping so later callers see a
560
 * consistent stop request regardless of whether the mutex has been
561
 * initialized.
562
 */
563
static void
564
sixel_parallel_context_abort_locked(sixel_parallel_context_t *ctx,
565
                                    SIXELSTATUS status)
566
{
567
    if (ctx == NULL) {
×
568
        return;
569
    }
570
    if (!ctx->mutex_ready) {
×
571
        if (ctx->writer_error == SIXEL_OK) {
×
572
            ctx->writer_error = status;
573
        }
574
        ctx->writer_should_stop = 1;
575
        return;
576
    }
577

578
    sixel_mutex_lock(&ctx->mutex);
579
    if (ctx->writer_error == SIXEL_OK) {
×
580
        ctx->writer_error = status;
581
    }
582
    ctx->writer_should_stop = 1;
583
    sixel_cond_broadcast(&ctx->cond_band_ready);
584
    sixel_mutex_unlock(&ctx->mutex);
585
}
586

587
/*
588
 * Determine whether additional bands should be queued or executed.  The
589
 * producer and workers call this guard to avoid redundant work once the writer
590
 * decides to shut the pipeline down.
591
 */
592
static int
593
sixel_parallel_jobs_allowed(sixel_parallel_context_t *ctx)
594
{
595
    int accept;
596

597
    if (ctx == NULL) {
×
598
        return 0;
599
    }
600
    if (!ctx->mutex_ready) {
×
601
        if (ctx->writer_should_stop || ctx->writer_error != SIXEL_OK) {
×
602
            return 0;
603
        }
604
        return 1;
605
    }
606

607
    sixel_mutex_lock(&ctx->mutex);
608
    accept = (!ctx->writer_should_stop && ctx->writer_error == SIXEL_OK);
×
609
    sixel_mutex_unlock(&ctx->mutex);
610
    return accept;
611
}
612

613
static void
614
sixel_parallel_worker_reset(sixel_parallel_worker_state_t *state)
615
{
616
    if (state == NULL || state->output == NULL) {
×
617
        return;
618
    }
619

620
    sixel_band_state_reset(&state->band);
621
    sixel_band_clear_map(&state->work);
622
    sixel_encode_metrics_init(&state->metrics);
623
    /* Parallel workers avoid touching the global assessment recorder. */
624
    state->metrics.encode_probe_active = 0;
625
    state->metrics.emit_cells_ptr = NULL;
626
    state->writer_error = SIXEL_OK;
627
    state->output->pos = 0;
628
    state->output->save_count = 0;
629
    state->output->save_pixel = 0;
630
    state->output->active_palette = (-1);
631
    state->output->node_top = NULL;
632
    state->output->node_free = NULL;
633
}
634

635
static SIXELSTATUS
636
sixel_parallel_worker_prepare(sixel_parallel_worker_state_t *state,
637
                              sixel_parallel_context_t *ctx)
638
{
639
    SIXELSTATUS status;
640

641
    if (state->initialized) {
×
642
        return SIXEL_OK;
643
    }
644

645
    sixel_encode_work_init(&state->work);
646
    sixel_band_state_reset(&state->band);
647
    sixel_encode_metrics_init(&state->metrics);
648
    state->metrics.encode_probe_active = 0;
649
    state->metrics.emit_cells_ptr = NULL;
650
    state->writer_error = SIXEL_OK;
651
    state->band_buffer = NULL;
652
    state->context = ctx;
653

654
    status = sixel_encode_work_allocate(&state->work,
655
                                        ctx->width,
656
                                        ctx->ncolors,
657
                                        ctx->allocator);
658
    if (SIXEL_FAILED(status)) {
×
659
        return status;
660
    }
661

662
    status = sixel_output_new(&state->output,
663
                              sixel_parallel_band_writer,
664
                              state,
665
                              ctx->allocator);
666
    if (SIXEL_FAILED(status)) {
×
667
        sixel_encode_work_cleanup(&state->work, ctx->allocator);
668
        return status;
669
    }
670

671
    state->output->has_8bit_control = ctx->output->has_8bit_control;
672
    state->output->has_sixel_scrolling = ctx->output->has_sixel_scrolling;
673
    state->output->has_sdm_glitch = ctx->output->has_sdm_glitch;
674
    state->output->has_gri_arg_limit = ctx->output->has_gri_arg_limit;
675
    state->output->skip_dcs_envelope = 1;
676
    state->output->skip_header = 1;
677
    state->output->palette_type = ctx->output->palette_type;
678
    state->output->colorspace = ctx->output->colorspace;
679
    state->output->source_colorspace = ctx->output->source_colorspace;
680
    state->output->pixelformat = ctx->output->pixelformat;
681
    state->output->penetrate_multiplexer =
682
        ctx->output->penetrate_multiplexer;
683
    state->output->encode_policy = ctx->output->encode_policy;
684
    state->output->ormode = ctx->output->ormode;
685

686
    state->initialized = 1;
687
    state->index = (-1);
688

689
    if (ctx->mutex_ready) {
×
690
        sixel_mutex_lock(&ctx->mutex);
691
    }
692
    if (ctx->worker_registered < ctx->worker_capacity) {
×
693
        state->index = ctx->worker_registered;
694
        ctx->workers[state->index] = state;
695
        ctx->worker_registered += 1;
696
    }
697
    if (ctx->mutex_ready) {
×
698
        sixel_mutex_unlock(&ctx->mutex);
699
    }
700

701
    if (state->index < 0) {
×
702
        sixel_parallel_worker_cleanup(state, ctx->allocator);
703
        return SIXEL_RUNTIME_ERROR;
704
    }
705

706
    return SIXEL_OK;
707
}
708

709
static SIXELSTATUS
710
sixel_parallel_context_grow(sixel_parallel_context_t *ctx, int target_threads)
711
{
712
    int capped_target;
713
    int delta;
714
    int status;
715

716
    if (ctx == NULL || ctx->pool == NULL) {
×
717
        return SIXEL_BAD_ARGUMENT;
718
    }
719

720
    capped_target = target_threads;
721
    if (capped_target > ctx->worker_capacity) {
×
722
        capped_target = ctx->worker_capacity;
723
    }
724
    if (ctx->band_count > 0 && capped_target > ctx->band_count) {
×
725
        capped_target = ctx->band_count;
726
    }
727
    if (capped_target <= ctx->thread_count) {
×
728
        return SIXEL_OK;
729
    }
730

731
    delta = capped_target - ctx->thread_count;
732
    status = threadpool_grow(ctx->pool, delta);
733
    if (SIXEL_FAILED(status)) {
×
734
        return status;
735
    }
736
    ctx->thread_count += delta;
737

738
    if (ctx->logger != NULL) {
×
739
        sixel_logger_logf(ctx->logger,
740
                          "controller",
741
                          "encode",
742
                          "grow_workers",
743
                          -1,
744
                          -1,
745
                          0,
746
                          ctx->height,
747
                          0,
748
                          ctx->height,
749
                          "threads=%d target=%d delta=%d",
750
                          ctx->thread_count,
751
                          capped_target,
752
                          delta);
753
    }
754

755
    return SIXEL_OK;
756
}
757

758
static int
759
sixel_parallel_band_writer(char *data, int size, void *priv)
760
{
761
    sixel_parallel_worker_state_t *state;
762
    sixel_parallel_band_buffer_t *band;
763
    size_t required;
764
    size_t capacity;
765
    size_t new_capacity;
766
    unsigned char *tmp;
767

768
    state = (sixel_parallel_worker_state_t *)priv;
769
    if (state == NULL || data == NULL || size <= 0) {
×
770
        return size;
771
    }
772
    band = state->band_buffer;
773
    if (band == NULL) {
×
774
        state->writer_error = SIXEL_RUNTIME_ERROR;
775
        return size;
776
    }
777
    if (state->writer_error != SIXEL_OK) {
×
778
        return size;
779
    }
780

781
    required = band->used + (size_t)size;
782
    if (required < band->used) {
×
783
        state->writer_error = SIXEL_BAD_INTEGER_OVERFLOW;
784
        return size;
785
    }
786
    capacity = band->size;
787
    if (required > capacity) {
×
788
        if (capacity == 0) {
×
789
            new_capacity = (size_t)SIXEL_OUTPUT_PACKET_SIZE;
790
        } else {
791
            new_capacity = capacity;
792
        }
793
        while (new_capacity < required) {
×
794
            if (new_capacity > SIZE_MAX / 2) {
×
795
                new_capacity = required;
796
                break;
797
            }
798
            new_capacity *= 2;
799
        }
800
        tmp = (unsigned char *)realloc(band->data, new_capacity);
801
        if (tmp == NULL) {
×
802
            state->writer_error = SIXEL_BAD_ALLOCATION;
803
            return size;
804
        }
805
        band->data = tmp;
806
        band->size = new_capacity;
807
    }
808

809
    memcpy(band->data + band->used, data, (size_t)size);
810
    band->used += (size_t)size;
811

812
    return size;
813
}
814

815
static SIXELSTATUS
816
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
817
                             sixel_index_t *pixels,
818
                             int width,
819
                             int height,
820
                             int ncolors,
821
                             int keycolor,
822
                             unsigned char *palstate,
823
                             sixel_output_t *output,
824
                             sixel_allocator_t *allocator,
825
                             int requested_threads,
826
                             int worker_capacity,
827
                             int queue_capacity,
828
                             sixel_logger_t *logger)
829
{
830
    SIXELSTATUS status;
831
    int nbands;
832
    int threads;
833
    int i;
834

835
    if (ctx == NULL || pixels == NULL || output == NULL) {
×
836
        return SIXEL_BAD_ARGUMENT;
837
    }
838

839
    ctx->pixels = pixels;
840
    ctx->width = width;
841
    ctx->height = height;
842
    ctx->ncolors = ncolors;
843
    ctx->keycolor = keycolor;
844
    ctx->palstate = palstate;
845
    ctx->encode_policy = output->encode_policy;
846
    ctx->allocator = allocator;
847
    ctx->output = output;
848
    ctx->encode_probe_active = 0;
849
    ctx->logger = logger;
850

851
    nbands = (height + 5) / 6;
852
    if (nbands <= 0) {
×
853
        return SIXEL_OK;
854
    }
855
    ctx->band_count = nbands;
856

857
    threads = requested_threads;
858
    if (threads > nbands) {
×
859
        threads = nbands;
860
    }
861
    if (threads < 1) {
×
862
        threads = 1;
863
    }
864
    ctx->thread_count = threads;
865
    if (worker_capacity < threads) {
×
866
        worker_capacity = threads;
867
    }
868
    if (worker_capacity > nbands) {
×
869
        worker_capacity = nbands;
870
    }
871
    ctx->worker_capacity = worker_capacity;
872
    sixel_assessment_set_encode_parallelism(threads);
873

874
    if (logger != NULL) {
×
875
        sixel_logger_logf(logger,
876
                          "controller",
877
                          "encode",
878
                          "context_begin",
879
                          -1,
880
                          -1,
881
                          0,
882
                          height,
883
                          0,
884
                          height,
885
                          "threads=%d queue=%d bands=%d",
886
                          threads,
887
                          queue_capacity,
888
                          nbands);
889
    }
890

891
    ctx->bands = (sixel_parallel_band_buffer_t *)calloc((size_t)nbands,
892
                                                        sizeof(*ctx->bands));
893
    if (ctx->bands == NULL) {
×
894
        return SIXEL_BAD_ALLOCATION;
895
    }
896

897
    ctx->workers = (sixel_parallel_worker_state_t **)
898
        calloc((size_t)ctx->worker_capacity, sizeof(*ctx->workers));
899
    if (ctx->workers == NULL) {
×
900
        return SIXEL_BAD_ALLOCATION;
901
    }
902

903
    status = sixel_mutex_init(&ctx->mutex);
904
    if (status != SIXEL_OK) {
×
905
        return status;
906
    }
907
    ctx->mutex_ready = 1;
908

909
    status = sixel_cond_init(&ctx->cond_band_ready);
910
    if (status != SIXEL_OK) {
×
911
        return status;
912
    }
913
    ctx->cond_ready = 1;
914

915
    ctx->queue_capacity = queue_capacity;
916
    if (ctx->queue_capacity < 1) {
×
917
        ctx->queue_capacity = nbands;
918
    }
919
    if (ctx->queue_capacity > nbands) {
×
920
        ctx->queue_capacity = nbands;
921
    }
922

923
    ctx->pool = threadpool_create(threads,
924
                                  ctx->queue_capacity,
925
                                  sizeof(sixel_parallel_worker_state_t),
926
                                  sixel_parallel_worker_main,
927
                                  ctx);
928
    if (ctx->pool == NULL) {
×
929
        return SIXEL_RUNTIME_ERROR;
930
    }
931

932
    status = sixel_thread_create(&ctx->writer_thread,
933
                                 sixel_parallel_writer_main,
934
                                 ctx);
935
    if (SIXEL_FAILED(status)) {
×
936
        return status;
937
    }
938
    ctx->writer_started = 1;
939
    ctx->next_band_to_flush = 0;
940
    ctx->writer_should_stop = 0;
941
    ctx->writer_error = SIXEL_OK;
942

943
    for (i = 0; i < nbands; ++i) {
×
944
        ctx->bands[i].used = 0;
945
        ctx->bands[i].status = SIXEL_OK;
946
        ctx->bands[i].ready = 0;
947
        ctx->bands[i].dispatched = 0;
948
    }
949

950
    return SIXEL_OK;
951
}
952

953
static void
954
sixel_parallel_submit_band(sixel_parallel_context_t *ctx, int band_index)
955
{
956
    tp_job_t job;
957
    int dispatch;
958

959
    if (ctx == NULL || ctx->pool == NULL) {
×
960
        return;
961
    }
962
    if (band_index < 0 || band_index >= ctx->band_count) {
×
963
        return;
964
    }
965

966
    dispatch = 0;
967
    /*
968
     * Multiple producers may notify the same band when PaletteApply runs in
969
     * parallel.  Guard the dispatched flag so only the first notifier pushes
970
     * work into the encoder queue.
971
     */
972
    if (ctx->mutex_ready) {
×
973
        sixel_mutex_lock(&ctx->mutex);
974
        if (!ctx->bands[band_index].dispatched
×
975
                && !ctx->writer_should_stop
×
976
                && ctx->writer_error == SIXEL_OK) {
×
977
            ctx->bands[band_index].dispatched = 1;
978
            dispatch = 1;
979
        }
980
        sixel_mutex_unlock(&ctx->mutex);
981
    } else {
982
        if (!ctx->bands[band_index].dispatched
×
983
                && sixel_parallel_jobs_allowed(ctx)) {
×
984
            ctx->bands[band_index].dispatched = 1;
985
            dispatch = 1;
986
        }
987
    }
988

989
    if (!dispatch) {
×
990
        return;
991
    }
992

993
    sixel_fence_release();
994
    if (ctx->logger != NULL) {
×
995
        int y0;
996
        int y1;
997

998
        y0 = band_index * 6;
999
        y1 = y0 + 6;
1000
        if (ctx->height > 0 && y1 > ctx->height) {
×
1001
            y1 = ctx->height;
1002
        }
1003
        sixel_logger_logf(ctx->logger,
1004
                          "controller",
1005
                          "encode",
1006
                          "dispatch",
1007
                          band_index,
1008
                          y1 - 1,
1009
                          y0,
1010
                          y1,
1011
                          y0,
1012
                          y1,
1013
                          "enqueue band");
1014
    }
1015
    job.band_index = band_index;
1016
    threadpool_push(ctx->pool, job);
1017
}
1018

1019
static SIXELSTATUS
1020
sixel_parallel_context_wait(sixel_parallel_context_t *ctx, int force_abort)
1021
{
1022
    int pool_error;
1023

1024
    if (ctx == NULL || ctx->pool == NULL) {
×
1025
        return SIXEL_BAD_ARGUMENT;
1026
    }
1027

1028
    threadpool_finish(ctx->pool);
1029
    pool_error = threadpool_get_error(ctx->pool);
1030
    sixel_parallel_writer_stop(ctx, force_abort || pool_error != SIXEL_OK);
×
1031

1032
    if (pool_error != SIXEL_OK) {
×
1033
        return pool_error;
1034
    }
1035
    if (ctx->writer_error != SIXEL_OK) {
×
1036
        return ctx->writer_error;
1037
    }
1038

1039
    return SIXEL_OK;
1040
}
1041

1042
/*
1043
 * Producer callback invoked after PaletteApply finishes a scanline.  The
1044
 * helper promotes every sixth row (or the final partial band) into the job
1045
 * queue so workers can begin encoding while dithering continues.
1046
 */
1047
static void
1048
sixel_parallel_palette_row_ready(void *priv, int row_index)
1049
{
1050
    sixel_parallel_row_notifier_t *notifier;
1051
    sixel_parallel_context_t *ctx;
1052
    sixel_logger_t *logger;
1053
    int band_height;
1054
    int band_index;
1055
    int y0;
1056
    int y1;
1057

1058
    notifier = (sixel_parallel_row_notifier_t *)priv;
1059
    if (notifier == NULL) {
×
1060
        return;
1061
    }
1062
    ctx = notifier->context;
1063
    logger = notifier->logger;
1064
    if (ctx == NULL || ctx->band_count <= 0 || ctx->height <= 0) {
×
1065
        return;
1066
    }
1067
    if (row_index < 0) {
×
1068
        return;
1069
    }
1070
    band_height = notifier->band_height;
1071
    if (band_height < 1) {
×
1072
        band_height = 6;
1073
    }
1074
    if ((row_index % band_height) != band_height - 1
×
1075
            && row_index != ctx->height - 1) {
×
1076
        return;
1077
    }
1078

1079
    band_index = row_index / band_height;
1080
    if (band_index >= ctx->band_count) {
×
1081
        band_index = ctx->band_count - 1;
1082
    }
1083
    if (band_index < 0) {
×
1084
        return;
1085
    }
1086

1087
    y0 = band_index * band_height;
1088
    y1 = y0 + band_height;
1089
    if (notifier->image_height > 0 && y1 > notifier->image_height) {
×
1090
        y1 = notifier->image_height;
1091
    }
1092
    if (logger != NULL) {
×
1093
        sixel_logger_logf(logger,
1094
                          "controller",
1095
                          "encode",
1096
                          "row_gate",
1097
                          band_index,
1098
                          row_index,
1099
                          y0,
1100
                          y1,
1101
                          y0,
1102
                          y1,
1103
                          "trigger band enqueue");
1104
    }
1105

1106
    sixel_parallel_submit_band(ctx, band_index);
1107
}
1108

1109
static SIXELSTATUS
1110
sixel_parallel_flush_band(sixel_parallel_context_t *ctx, int band_index)
1111
{
1112
    sixel_parallel_band_buffer_t *band;
1113
    size_t offset;
1114
    size_t chunk;
1115

1116
    band = &ctx->bands[band_index];
1117
    if (ctx->logger != NULL) {
×
1118
        int y0;
1119
        int y1;
1120

1121
        y0 = band_index * 6;
1122
        y1 = y0 + 6;
1123
        if (ctx->height > 0 && y1 > ctx->height) {
×
1124
            y1 = ctx->height;
1125
        }
1126
        sixel_logger_logf(ctx->logger,
1127
                          "worker",
1128
                          "encode",
1129
                          "writer_flush",
1130
                          band_index,
1131
                          y1 - 1,
1132
                          y0,
1133
                          y1,
1134
                          y0,
1135
                          y1,
1136
                          "bytes=%zu",
1137
                          band->used);
1138
    }
1139
    offset = 0;
1140
    while (offset < band->used) {
×
1141
        chunk = band->used - offset;
1142
        if (chunk > (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos)) {
×
1143
            chunk = (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos);
1144
        }
1145
        memcpy(ctx->output->buffer + ctx->output->pos,
1146
               band->data + offset,
1147
               chunk);
1148
        sixel_advance(ctx->output, (int)chunk);
1149
        offset += chunk;
1150
    }
1151
    return SIXEL_OK;
1152
}
1153

1154
static int
1155
sixel_parallel_worker_main(tp_job_t job, void *userdata, void *workspace)
1156
{
1157
    sixel_parallel_context_t *ctx;
1158
    sixel_parallel_worker_state_t *state;
1159
    sixel_parallel_band_buffer_t *band;
1160
    SIXELSTATUS status;
1161
    int band_index;
1162
    int band_start;
1163
    int band_height;
1164
    int row_index;
1165
    int absolute_row;
1166
    int last_row_index;
1167
    size_t emitted_bytes;
1168

1169
    ctx = (sixel_parallel_context_t *)userdata;
1170
    state = (sixel_parallel_worker_state_t *)workspace;
1171

1172
    if (ctx == NULL || state == NULL) {
×
1173
        return SIXEL_BAD_ARGUMENT;
1174
    }
1175

1176
    band = NULL;
1177
    status = SIXEL_OK;
1178
    band_index = job.band_index;
1179
    band_start = 0;
1180
    band_height = 0;
1181
    last_row_index = -1;
1182
    emitted_bytes = 0U;
1183
    if (band_index < 0 || band_index >= ctx->band_count) {
×
1184
        status = SIXEL_BAD_ARGUMENT;
1185
        goto cleanup;
1186
    }
1187

1188
    band = &ctx->bands[band_index];
1189
    band->used = 0;
1190
    band->status = SIXEL_OK;
1191
    band->ready = 0;
1192

1193
    sixel_fence_acquire();
1194

1195
    status = sixel_parallel_worker_prepare(state, ctx);
1196
    if (SIXEL_FAILED(status)) {
×
1197
        goto cleanup;
1198
    }
1199

1200
    if (!sixel_parallel_jobs_allowed(ctx)) {
×
1201
        if (ctx->mutex_ready) {
×
1202
            sixel_mutex_lock(&ctx->mutex);
1203
            if (ctx->writer_error != SIXEL_OK) {
×
1204
                status = ctx->writer_error;
1205
            } else {
1206
                status = SIXEL_RUNTIME_ERROR;
1207
            }
1208
            sixel_mutex_unlock(&ctx->mutex);
1209
        } else if (ctx->writer_error != SIXEL_OK) {
×
1210
            status = ctx->writer_error;
1211
        } else {
1212
            status = SIXEL_RUNTIME_ERROR;
1213
        }
1214
        goto cleanup;
1215
    }
1216

1217
    state->band_buffer = band;
1218
    sixel_parallel_worker_reset(state);
1219

1220
    band_start = band_index * 6;
1221
    band_height = ctx->height - band_start;
1222
    if (band_height > 6) {
×
1223
        band_height = 6;
1224
    }
1225
    if (band_height <= 0) {
×
1226
        goto cleanup;
1227
    }
1228

1229
    if (ctx->logger != NULL) {
×
1230
        sixel_logger_logf(ctx->logger,
1231
                          "worker",
1232
                          "encode",
1233
                          "worker_start",
1234
                          band_index,
1235
                          band_start,
1236
                          band_start,
1237
                          band_start + band_height,
1238
                          band_start,
1239
                          band_start + band_height,
1240
                          "worker=%d",
1241
                          state->index);
1242
    }
1243

1244
    for (row_index = 0; row_index < band_height; row_index++) {
×
1245
        absolute_row = band_start + row_index;
1246
        status = sixel_band_classify_row(&state->work,
1247
                                         &state->band,
1248
                                         ctx->pixels,
1249
                                         ctx->width,
1250
                                         absolute_row,
1251
                                         ctx->ncolors,
1252
                                         ctx->keycolor,
1253
                                         ctx->palstate,
1254
                                         ctx->encode_policy);
1255
        if (SIXEL_FAILED(status)) {
×
1256
            goto cleanup;
1257
        }
1258
    }
1259

1260
    status = sixel_band_compose(&state->work,
1261
                                &state->band,
1262
                                state->output,
1263
                                ctx->width,
1264
                                ctx->ncolors,
1265
                                ctx->keycolor,
1266
                                ctx->allocator,
1267
                                &state->metrics);
1268
    if (SIXEL_FAILED(status)) {
×
1269
        goto cleanup;
1270
    }
1271

1272
    last_row_index = band_start + band_height - 1;
1273
    status = sixel_band_emit(&state->work,
1274
                             &state->band,
1275
                             state->output,
1276
                             ctx->ncolors,
1277
                             ctx->keycolor,
1278
                             last_row_index,
1279
                             &state->metrics);
1280
    if (SIXEL_FAILED(status)) {
×
1281
        goto cleanup;
1282
    }
1283

1284
    status = sixel_put_flash(state->output);
1285
    if (SIXEL_FAILED(status)) {
×
1286
        goto cleanup;
1287
    }
1288

1289
    if (state->output->pos > 0) {
×
1290
        state->output->fn_write((char *)state->output->buffer,
1291
                                state->output->pos,
1292
                                state);
1293
        state->output->pos = 0;
1294
    }
1295
    emitted_bytes = band->used;
1296

1297
    if (state->writer_error != SIXEL_OK) {
×
1298
        status = state->writer_error;
1299
        goto cleanup;
1300
    }
1301

1302
    sixel_band_finish(&state->work, &state->band);
1303
    status = SIXEL_OK;
1304

1305
cleanup:
1306
    sixel_parallel_worker_release_nodes(state, ctx->allocator);
1307
    if (band != NULL && ctx->mutex_ready && ctx->cond_ready) {
×
1308
        sixel_fence_release();
1309
        sixel_mutex_lock(&ctx->mutex);
1310
        band->status = status;
1311
        band->ready = 1;
1312
        sixel_cond_broadcast(&ctx->cond_band_ready);
1313
        sixel_mutex_unlock(&ctx->mutex);
1314
    }
1315
    if (ctx->logger != NULL) {
×
1316
        sixel_logger_logf(ctx->logger,
1317
                          "worker",
1318
                          "encode",
1319
                          "worker_done",
1320
                          band_index,
1321
                          last_row_index,
1322
                          band_start,
1323
                          band_start + band_height,
1324
                          band_start,
1325
                          band_start + band_height,
1326
                          "status=%d bytes=%zu",
1327
                          status,
1328
                          emitted_bytes);
1329
    }
1330
    if (SIXEL_FAILED(status)) {
×
1331
        return status;
1332
    }
1333
    return SIXEL_OK;
1334
}
1335

1336
static void
1337
sixel_parallel_writer_stop(sixel_parallel_context_t *ctx, int force_abort)
1338
{
1339
    int should_signal;
1340

1341
    if (ctx == NULL || !ctx->writer_started) {
×
1342
        return;
1343
    }
1344

1345
    should_signal = ctx->mutex_ready && ctx->cond_ready;
×
1346
    if (should_signal) {
×
1347
        sixel_mutex_lock(&ctx->mutex);
1348
        if (force_abort) {
×
1349
            ctx->writer_should_stop = 1;
1350
        }
1351
        sixel_cond_broadcast(&ctx->cond_band_ready);
1352
        sixel_mutex_unlock(&ctx->mutex);
1353
    } else if (force_abort) {
×
1354
        ctx->writer_should_stop = 1;
1355
    }
1356

1357
    sixel_thread_join(&ctx->writer_thread);
1358
    ctx->writer_started = 0;
1359
    ctx->writer_should_stop = 0;
1360
    if (ctx->logger != NULL) {
×
1361
        sixel_logger_logf(ctx->logger,
1362
                          "writer",
1363
                          "encode",
1364
                          "writer_stop",
1365
                          -1,
1366
                          -1,
1367
                          0,
1368
                          ctx->height,
1369
                          0,
1370
                          ctx->height,
1371
                          "force=%d",
1372
                          force_abort);
1373
    }
1374
}
1375

1376
static int
1377
sixel_parallel_writer_main(void *arg)
1378
{
1379
    sixel_parallel_context_t *ctx;
1380
    sixel_parallel_band_buffer_t *band;
1381
    SIXELSTATUS status;
1382
    int band_index;
1383

1384
    ctx = (sixel_parallel_context_t *)arg;
1385
    if (ctx == NULL) {
×
1386
        return SIXEL_BAD_ARGUMENT;
1387
    }
1388

1389
    if (ctx->logger != NULL) {
×
1390
        sixel_logger_logf(ctx->logger,
1391
                                   "writer",
1392
                                   "encode",
1393
                                   "writer_start",
1394
                                   -1,
1395
                                   -1,
1396
                                   0,
1397
                                   ctx->height,
1398
                                   0,
1399
                                   ctx->height,
1400
                                   "bands=%d",
1401
                                   ctx->band_count);
1402
    }
1403

1404
    for (;;) {
1405
        sixel_mutex_lock(&ctx->mutex);
1406
        while (!ctx->writer_should_stop &&
×
1407
               ctx->next_band_to_flush < ctx->band_count) {
×
1408
    band_index = ctx->next_band_to_flush;
1409
    band = &ctx->bands[band_index];
1410
    if (band->ready) {
×
1411
        break;
1412
    }
1413
            sixel_cond_wait(&ctx->cond_band_ready, &ctx->mutex);
1414
        }
1415

1416
        if (ctx->writer_should_stop) {
×
1417
            sixel_mutex_unlock(&ctx->mutex);
1418
            break;
1419
        }
1420

1421
        if (ctx->next_band_to_flush >= ctx->band_count) {
×
1422
            sixel_mutex_unlock(&ctx->mutex);
1423
            break;
1424
        }
1425

1426
        band_index = ctx->next_band_to_flush;
1427
        band = &ctx->bands[band_index];
1428
        if (!band->ready) {
×
1429
            sixel_mutex_unlock(&ctx->mutex);
1430
            continue;
1431
        }
1432
        band->ready = 0;
1433
        ctx->next_band_to_flush += 1;
1434
        sixel_mutex_unlock(&ctx->mutex);
1435

1436
        sixel_fence_acquire();
1437
        status = band->status;
1438
        if (ctx->logger != NULL) {
×
1439
            int y0;
1440
            int y1;
1441

1442
            y0 = band_index * 6;
1443
            y1 = y0 + 6;
1444
            if (ctx->height > 0 && y1 > ctx->height) {
×
1445
                y1 = ctx->height;
1446
            }
1447
            sixel_logger_logf(ctx->logger,
1448
                              "writer",
1449
                              "encode",
1450
                              "writer_dequeue",
1451
                              band_index,
1452
                              y1 - 1,
1453
                              y0,
1454
                              y1,
1455
                              y0,
1456
                              y1,
1457
                              "bytes=%zu",
1458
                              band->used);
1459
        }
1460
        if (SIXEL_SUCCEEDED(status)) {
×
1461
            status = sixel_parallel_flush_band(ctx, band_index);
1462
        }
1463
        if (SIXEL_FAILED(status)) {
×
1464
            sixel_parallel_context_abort_locked(ctx, status);
1465
            break;
1466
        }
1467
    }
1468

1469
    return SIXEL_OK;
1470
}
1471

1472
static SIXELSTATUS
1473
sixel_encode_body_parallel(sixel_index_t *pixels,
1474
                           int width,
1475
                           int height,
1476
                           int ncolors,
1477
                           int keycolor,
1478
                           sixel_output_t *output,
1479
                           unsigned char *palstate,
1480
                           sixel_allocator_t *allocator,
1481
                           int requested_threads)
1482
{
1483
    sixel_parallel_context_t ctx;
1484
    SIXELSTATUS status;
1485
    int nbands;
1486
    int threads;
1487
    int i;
1488
    int queue_depth;
1489
    sixel_logger_t logger;
1490

1491
    sixel_parallel_context_init(&ctx);
1492
    sixel_logger_prepare(&logger);
1493
    nbands = (height + 5) / 6;
1494
    ctx.band_count = nbands;
1495
    if (nbands <= 0) {
×
1496
        sixel_logger_close(&logger);
1497
        return SIXEL_OK;
1498
    }
1499

1500
    threads = requested_threads;
1501
    if (threads > nbands) {
×
1502
        threads = nbands;
1503
    }
1504
    if (threads < 1) {
×
1505
        threads = 1;
1506
    }
1507
    sixel_assessment_set_encode_parallelism(threads);
1508
    ctx.thread_count = threads;
1509
    queue_depth = threads * 3;
1510
    if (queue_depth > nbands) {
×
1511
        queue_depth = nbands;
1512
    }
1513
    if (queue_depth < 1) {
×
1514
        queue_depth = 1;
1515
    }
1516

1517
    status = sixel_parallel_context_begin(&ctx,
1518
                                          pixels,
1519
                                          width,
1520
                                          height,
1521
                                          ncolors,
1522
                                          keycolor,
1523
                                          palstate,
1524
                                          output,
1525
                                          allocator,
1526
                                          threads,
1527
                                          threads,
1528
                                          queue_depth,
1529
                                          &logger);
1530
    if (SIXEL_FAILED(status)) {
×
1531
        sixel_parallel_context_cleanup(&ctx);
1532
        sixel_logger_close(&logger);
1533
        return status;
1534
    }
1535

1536
    for (i = 0; i < nbands; i++) {
×
1537
        sixel_parallel_submit_band(&ctx, i);
1538
    }
1539

1540
    status = sixel_parallel_context_wait(&ctx, 0);
1541
    if (SIXEL_FAILED(status)) {
×
1542
        sixel_parallel_context_cleanup(&ctx);
1543
        sixel_logger_close(&logger);
1544
        return status;
1545
    }
1546

1547
    sixel_parallel_context_cleanup(&ctx);
1548
    sixel_logger_close(&logger);
1549
    return SIXEL_OK;
1550
}
1551
#endif
1552

1553
#if SIXEL_ENABLE_THREADS
1554
/*
1555
 * Execute PaletteApply, band encoding, and output emission as a pipeline.
1556
 * The producer owns the dithered index buffer and enqueues bands once every
1557
 * six rows have been produced.  Worker threads encode in parallel while the
1558
 * writer emits completed bands in-order to preserve deterministic output.
1559
 */
1560
static SIXELSTATUS
1561
sixel_encode_body_pipeline(unsigned char *pixels,
1562
                           int width,
1563
                           int height,
1564
                           unsigned char const *palette,
1565
                           float const *palette_float,
1566
                           sixel_dither_t *dither,
1567
                           sixel_output_t *output,
1568
                           int encode_threads)
1569
{
1570
    SIXELSTATUS status;
1571
    SIXELSTATUS wait_status;
1572
    sixel_parallel_context_t ctx;
1573
    sixel_index_t *indexes;
1574
    sixel_index_t *result;
1575
    sixel_allocator_t *allocator;
1576
    size_t pixel_count;
1577
    size_t buffer_size;
1578
    int threads;
1579
    int nbands;
1580
    int queue_depth;
1581
    int encode_probe_active;
1582
    int waited;
1583
    int saved_optimize_palette;
1584
    int dither_threads_budget;
1585
    int worker_capacity;
1586
    int boost_target;
1587
    sixel_logger_t logger_storage;
1588
    sixel_logger_t *logger;
1589
    int owns_logger;
1590
    sixel_parallel_row_notifier_t notifier;
1591

1592
    if (pixels == NULL
×
1593
            || (palette == NULL && palette_float == NULL)
×
1594
            || dither == NULL
×
1595
            || output == NULL) {
×
1596
        return SIXEL_BAD_ARGUMENT;
1597
    }
1598

1599
    threads = encode_threads;
1600
    nbands = (height + 5) / 6;
1601
    if (threads <= 1 || nbands <= 1) {
×
1602
        return SIXEL_RUNTIME_ERROR;
1603
    }
1604

1605
    pixel_count = (size_t)width * (size_t)height;
1606
    if (height != 0 && pixel_count / (size_t)height != (size_t)width) {
×
1607
        return SIXEL_BAD_INTEGER_OVERFLOW;
1608
    }
1609
    buffer_size = pixel_count * sizeof(*indexes);
1610
    allocator = dither->allocator;
1611
    indexes = (sixel_index_t *)sixel_allocator_malloc(allocator, buffer_size);
1612
    if (indexes == NULL) {
×
1613
        return SIXEL_BAD_ALLOCATION;
1614
    }
1615

1616
    sixel_parallel_context_init(&ctx);
1617
    logger = dither->pipeline_logger;
1618
    owns_logger = 0;
1619
    if (logger == NULL || !logger->active) {
×
1620
        sixel_logger_prepare(&logger_storage);
1621
        if (logger_storage.active) {
×
1622
            logger = &logger_storage;
1623
            owns_logger = 1;
1624
        }
1625
    }
1626
    notifier.context = &ctx;
1627
    notifier.logger = logger;
1628
    notifier.band_height = 6;
1629
    notifier.image_height = height;
1630
    waited = 0;
1631
    status = SIXEL_OK;
1632

1633
    encode_probe_active = sixel_assessment_encode_probe_enabled();
1634
    status = sixel_encode_emit_palette(dither->bodyonly,
1635
                                       dither->ncolors,
1636
                                       dither->keycolor,
1637
                                       palette,
1638
                                       palette_float,
1639
                                       output,
1640
                                       encode_probe_active);
1641
    if (SIXEL_FAILED(status)) {
×
1642
        goto cleanup;
1643
    }
1644

1645
    queue_depth = threads * 3;
1646
    if (queue_depth > nbands) {
×
1647
        queue_depth = nbands;
1648
    }
1649
    if (queue_depth < 1) {
×
1650
        queue_depth = 1;
1651
    }
1652

1653
    dither_threads_budget = dither->pipeline_dither_threads;
1654
    worker_capacity = threads + dither_threads_budget;
1655
    if (worker_capacity < threads) {
×
1656
        worker_capacity = threads;
1657
    }
1658
    if (worker_capacity > nbands) {
×
1659
        worker_capacity = nbands;
1660
    }
1661

1662
    dither->pipeline_index_buffer = indexes;
1663
    dither->pipeline_index_size = buffer_size;
1664
    dither->pipeline_row_callback = sixel_parallel_palette_row_ready;
1665
    dither->pipeline_row_priv = &notifier;
1666
    dither->pipeline_logger = logger;
1667
    dither->pipeline_image_height = height;
1668

1669
    if (logger != NULL && logger->active) {
×
1670
        /*
1671
         * Record the thread split and band geometry before spawning workers.
1672
         * This clarifies why only a subset of hardware threads might appear
1673
         * in the log when the encoder side is clamped to keep the pipeline
1674
         * draining.
1675
         */
1676
        sixel_logger_logf(logger,
1677
                          "controller",
1678
                          "pipeline",
1679
                          "configure",
1680
                          -1,
1681
                          -1,
1682
                          0,
1683
                          height,
1684
                          0,
1685
                          height,
1686
                          "encode_threads=%d dither_threads=%d "
1687
                          "band=%d overlap=%d",
1688
                          threads,
1689
                          dither->pipeline_dither_threads,
1690
                          dither->pipeline_band_height,
1691
                          dither->pipeline_band_overlap);
1692
    }
1693

1694
    status = sixel_parallel_context_begin(&ctx,
1695
                                          indexes,
1696
                                          width,
1697
                                          height,
1698
                                          dither->ncolors,
1699
                                          dither->keycolor,
1700
                                          NULL,
1701
                                          output,
1702
                                          allocator,
1703
                                          threads,
1704
                                          worker_capacity,
1705
                                          queue_depth,
1706
                                          logger);
1707
    if (SIXEL_FAILED(status)) {
×
1708
        goto cleanup;
1709
    }
1710

1711
    saved_optimize_palette = dither->optimize_palette;
1712
    if (saved_optimize_palette != 0) {
×
1713
        /*
1714
         * Palette optimization reorders palette entries after dithering.  The
1715
         * pipeline emits the palette up-front, so workers must see the exact
1716
         * order that we already sent to the output stream.  Disable palette
1717
         * reordering while the pipeline is active and restore the caller
1718
         * preference afterwards.
1719
         */
1720
        dither->optimize_palette = 0;
1721
    }
1722
    result = sixel_dither_apply_palette(dither, pixels, width, height);
1723
    dither->optimize_palette = saved_optimize_palette;
1724
    if (result == NULL) {
×
1725
        status = SIXEL_RUNTIME_ERROR;
1726
        goto cleanup;
1727
    }
1728
    if (result != indexes) {
×
1729
        status = SIXEL_RUNTIME_ERROR;
1730
        goto cleanup;
1731
    }
1732

1733
    /*
1734
     * All dithering work has finished at this point.  Reclaim the idle dither
1735
     * workers for encoding so the tail of the pipeline drains with additional
1736
     * parallelism instead of leaving those CPU resources unused.
1737
     */
1738
    boost_target = threads + dither_threads_budget;
1739
    status = sixel_parallel_context_grow(&ctx, boost_target);
1740
    if (SIXEL_FAILED(status)) {
×
1741
        goto cleanup;
1742
    }
1743

1744
    status = sixel_parallel_context_wait(&ctx, 0);
1745
    waited = 1;
1746
    if (SIXEL_FAILED(status)) {
×
1747
        goto cleanup;
1748
    }
1749

1750
cleanup:
1751
    dither->pipeline_row_callback = NULL;
1752
    dither->pipeline_row_priv = NULL;
1753
    dither->pipeline_index_buffer = NULL;
1754
    dither->pipeline_index_size = 0;
1755
    if (!waited && ctx.pool != NULL) {
×
1756
        wait_status = sixel_parallel_context_wait(&ctx, status != SIXEL_OK);
1757
        if (status == SIXEL_OK) {
×
1758
            status = wait_status;
1759
        }
1760
    }
1761
    sixel_parallel_context_cleanup(&ctx);
1762
    if (owns_logger) {
×
1763
        sixel_logger_close(&logger_storage);
1764
    }
1765
    if (indexes != NULL) {
×
1766
        sixel_allocator_free(allocator, indexes);
1767
    }
1768
    return status;
1769
}
1770
#else
1771
static SIXELSTATUS
1772
sixel_encode_body_pipeline(unsigned char *pixels,
1773
                           int width,
1774
                           int height,
1775
                           unsigned char const *palette,
1776
                           float const *palette_float,
1777
                           sixel_dither_t *dither,
1778
                           sixel_output_t *output,
1779
                           int encode_threads)
1780
{
1781
    (void)pixels;
1782
    (void)width;
1783
    (void)height;
1784
    (void)palette;
1785
    (void)palette_float;
1786
    (void)dither;
1787
    (void)output;
1788
    (void)encode_threads;
1789
    return SIXEL_RUNTIME_ERROR;
1790
}
1791
#endif
1792

1793
/* implementation */
1794

1795
/* GNU Screen penetration */
1796
static void
1797
sixel_penetrate(
16✔
1798
    sixel_output_t  /* in */    *output,        /* output context */
1799
    int             /* in */    nwrite,         /* output size */
1800
    char const      /* in */    *dcs_start,     /* DCS introducer */
1801
    char const      /* in */    *dcs_end,       /* DCS terminator */
1802
    int const       /* in */    dcs_start_size, /* size of DCS introducer */
1803
    int const       /* in */    dcs_end_size)   /* size of DCS terminator */
1804
{
1805
    int pos;
1806
    int const splitsize = SCREEN_PACKET_SIZE
16✔
1807
                        - dcs_start_size - dcs_end_size;
16✔
1808

1809
    for (pos = 0; pos < nwrite; pos += splitsize) {
732✔
1810
        output->fn_write((char *)dcs_start, dcs_start_size, output->priv);
716✔
1811
        output->fn_write(((char *)output->buffer) + pos,
716✔
1812
                          nwrite - pos < splitsize ? nwrite - pos: splitsize,
716✔
1813
                          output->priv);
1814
        output->fn_write((char *)dcs_end, dcs_end_size, output->priv);
716✔
1815
    }
1816
}
16✔
1817

1818

1819
static void
1820
sixel_advance(sixel_output_t *output, int nwrite)
50,612,944✔
1821
{
1822
    if ((output->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) {
50,612,944✔
1823
        if (output->penetrate_multiplexer) {
3,860✔
1824
            sixel_penetrate(output,
8✔
1825
                            SIXEL_OUTPUT_PACKET_SIZE,
1826
                            DCS_START_7BIT,
1827
                            DCS_END_7BIT,
1828
                            DCS_START_7BIT_SIZE,
1829
                            DCS_END_7BIT_SIZE);
1830
        } else {
1831
            output->fn_write((char *)output->buffer,
3,852✔
1832
                             SIXEL_OUTPUT_PACKET_SIZE, output->priv);
1833
        }
1834
        memcpy(output->buffer,
3,860✔
1835
               output->buffer + SIXEL_OUTPUT_PACKET_SIZE,
3,860✔
1836
               (size_t)(output->pos -= SIXEL_OUTPUT_PACKET_SIZE));
3,860✔
1837
    }
1838
}
50,612,944✔
1839

1840

1841
static void
1842
sixel_putc(unsigned char *buffer, unsigned char value)
10,772,440✔
1843
{
1844
    *buffer = value;
10,772,440✔
1845
}
10,772,440✔
1846

1847

1848
static void
1849
sixel_puts(unsigned char *buffer, char const *value, int size)
380,492✔
1850
{
1851
    memcpy(buffer, (void *)value, (size_t)size);
380,492✔
1852
}
380,492✔
1853

1854
/*
1855
 * Append a literal byte several times while respecting the output packet
1856
 * boundary.  The helper keeps `sixel_advance` responsible for flushing and
1857
 * preserves the repeating logic used by DECGRI sequences.
1858
 */
1859
static void
1860
sixel_output_emit_literal(sixel_output_t *output,
30,858,100✔
1861
                          unsigned char value,
1862
                          int count)
1863
{
1864
    int chunk;
1865

1866
    if (count <= 0) {
30,858,100!
1867
        return;
×
1868
    }
1869

1870
    while (count > 0) {
61,716,548✔
1871
        chunk = SIXEL_OUTPUT_PACKET_SIZE - output->pos;
30,858,448✔
1872
        if (chunk > count) {
30,858,448✔
1873
            chunk = count;
30,856,348✔
1874
        }
1875
        memset(output->buffer + output->pos, value, (size_t)chunk);
30,858,448✔
1876
        sixel_advance(output, chunk);
30,858,448✔
1877
        count -= chunk;
30,858,448✔
1878
    }
1879
}
1880

1881
static double
1882
sixel_encode_span_start(int probe_active)
203,092✔
1883
{
1884
    if (probe_active) {
203,092!
1885
        return sixel_assessment_timer_now();
×
1886
    }
1887
    return 0.0;
203,092✔
1888
}
1889

1890
static void
1891
sixel_encode_span_commit(int probe_active,
180,736✔
1892
                         sixel_assessment_stage_t stage,
1893
                         double started_at)
1894
{
1895
    double duration;
1896

1897
    if (!probe_active) {
180,736!
1898
        return;
180,736✔
1899
    }
1900
    duration = sixel_assessment_timer_now() - started_at;
×
1901
    if (duration < 0.0) {
×
1902
        duration = 0.0;
×
1903
    }
1904
    sixel_assessment_record_encode_span(stage, duration);
×
1905
}
1906

1907

1908
/*
1909
 * Compose helpers accelerate palette sweeps by skipping zero columns in
1910
 * word-sized chunks.  Each helper mirrors the original probe counters so
1911
 * the performance report still reflects how many cells were inspected.
1912
 */
1913

1914
static int
1915
sixel_compose_find_run_start(unsigned char const *row,
5,520,728✔
1916
                             int width,
1917
                             int start,
1918
                             int encode_probe_active,
1919
                             double *compose_scan_probes)
1920
{
1921
    int idx;
1922
    size_t chunk_size;
1923
    unsigned char const *cursor;
1924
    unsigned long block;
1925

1926
    idx = start;
5,520,728✔
1927
    chunk_size = sizeof(unsigned long);
5,520,728✔
1928
    cursor = row + start;
5,520,728✔
1929

1930
    while ((width - idx) >= (int)chunk_size) {
155,450,700✔
1931
        memcpy(&block, cursor, chunk_size);
154,230,044✔
1932
        if (block != 0UL) {
154,230,044✔
1933
            break;
4,300,072✔
1934
        }
1935
        if (encode_probe_active) {
149,929,972!
1936
            *compose_scan_probes += (double)chunk_size;
×
1937
        }
1938
        idx += (int)chunk_size;
149,929,972✔
1939
        cursor += chunk_size;
149,929,972✔
1940
    }
1941

1942
    while (idx < width) {
24,438,568✔
1943
        if (encode_probe_active) {
23,230,892!
1944
            *compose_scan_probes += 1.0;
×
1945
        }
1946
        if (*cursor != 0) {
23,230,892✔
1947
            break;
4,313,052✔
1948
        }
1949
        idx += 1;
18,917,840✔
1950
        cursor += 1;
18,917,840✔
1951
    }
1952

1953
    return idx;
5,520,728✔
1954
}
1955

1956

1957
static int
1958
sixel_compose_measure_gap(unsigned char const *row,
10,076,340✔
1959
                          int width,
1960
                          int start,
1961
                          int encode_probe_active,
1962
                          double *compose_scan_probes,
1963
                          int *reached_end)
1964
{
1965
    int gap;
1966
    size_t chunk_size;
1967
    unsigned char const *cursor;
1968
    unsigned long block;
1969
    int remaining;
1970

1971
    gap = 0;
10,076,340✔
1972
    *reached_end = 0;
10,076,340✔
1973
    if (start >= width) {
10,076,340✔
1974
        *reached_end = 1;
10,228✔
1975
        return gap;
10,228✔
1976
    }
1977

1978
    chunk_size = sizeof(unsigned long);
10,066,112✔
1979
    cursor = row + start;
10,066,112✔
1980
    remaining = width - start;
10,066,112✔
1981

1982
    while (remaining >= (int)chunk_size) {
110,951,268✔
1983
        memcpy(&block, cursor, chunk_size);
109,709,420✔
1984
        if (block != 0UL) {
109,709,420✔
1985
            break;
8,824,264✔
1986
        }
1987
        gap += (int)chunk_size;
100,885,156✔
1988
        if (encode_probe_active) {
100,885,156!
1989
            *compose_scan_probes += (double)chunk_size;
×
1990
        }
1991
        cursor += chunk_size;
100,885,156✔
1992
        remaining -= (int)chunk_size;
100,885,156✔
1993
    }
1994

1995
    while (remaining > 0) {
32,077,860✔
1996
        if (encode_probe_active) {
30,880,412!
1997
            *compose_scan_probes += 1.0;
×
1998
        }
1999
        if (*cursor != 0) {
30,880,412✔
2000
            return gap;
8,868,664✔
2001
        }
2002
        gap += 1;
22,011,748✔
2003
        cursor += 1;
22,011,748✔
2004
        remaining -= 1;
22,011,748✔
2005
    }
2006

2007
    *reached_end = 1;
1,197,448✔
2008
    return gap;
1,197,448✔
2009
}
2010

2011

2012
#if HAVE_LDIV
2013
static int
2014
sixel_putnum_impl(char *buffer, long value, int pos)
17,615,261✔
2015
{
2016
    ldiv_t r;
2017

2018
    r = ldiv(value, 10);
17,615,261✔
2019
    if (r.quot > 0) {
17,615,261✔
2020
        pos = sixel_putnum_impl(buffer, r.quot, pos);
9,159,505✔
2021
    }
2022
    *(buffer + pos) = '0' + r.rem;
17,615,261✔
2023
    return pos + 1;
17,615,261✔
2024
}
2025
#endif  /* HAVE_LDIV */
2026

2027

2028
static int
2029
sixel_putnum(char *buffer, int value)
8,455,756✔
2030
{
2031
    int pos;
2032

2033
#if HAVE_LDIV
2034
    pos = sixel_putnum_impl(buffer, value, 0);
8,455,756✔
2035
#else
2036
    pos = sprintf(buffer, "%d", value);
2037
#endif  /* HAVE_LDIV */
2038

2039
    return pos;
8,455,756✔
2040
}
2041

2042

2043
static SIXELSTATUS
2044
sixel_put_flash(sixel_output_t *const output)
33,553,328✔
2045
{
2046
    int nwrite;
2047

2048
    if (output->save_count <= 0) {
33,553,328!
2049
        return SIXEL_OK;
×
2050
    }
2051

2052
    if (output->has_gri_arg_limit) {  /* VT240 Max 255 ? */
33,553,328!
2053
        while (output->save_count > 255) {
×
2054
            /* argument of DECGRI('!') is limitted to 255 in real VT */
2055
            sixel_puts(output->buffer + output->pos, "!255", 4);
×
2056
            sixel_advance(output, 4);
×
2057
            sixel_putc(output->buffer + output->pos, output->save_pixel);
×
2058
            sixel_advance(output, 1);
×
2059
            output->save_count -= 255;
×
2060
        }
2061
    }
2062

2063
    if (output->save_count > 3) {
33,553,328✔
2064
        /* DECGRI Graphics Repeat Introducer ! Pn Ch */
2065
        sixel_putc(output->buffer + output->pos, '!');
2,695,228✔
2066
        sixel_advance(output, 1);
2,695,228✔
2067
        nwrite = sixel_putnum((char *)output->buffer + output->pos, output->save_count);
2,695,228✔
2068
        sixel_advance(output, nwrite);
2,695,228✔
2069
        sixel_putc(output->buffer + output->pos, output->save_pixel);
2,695,228✔
2070
        sixel_advance(output, 1);
2,695,228✔
2071
    } else {
2072
        sixel_output_emit_literal(output,
30,858,100✔
2073
                                  (unsigned char)output->save_pixel,
30,858,100✔
2074
                                  output->save_count);
2075
    }
2076

2077
    output->save_pixel = 0;
33,553,328✔
2078
    output->save_count = 0;
33,553,328✔
2079

2080
    return SIXEL_OK;
33,553,328✔
2081
}
2082

2083

2084
/*
2085
 * Emit a run of identical SIXEL cells while keeping the existing repeat
2086
 * accumulator intact.  The helper extends the current run when possible and
2087
 * falls back to flushing through DECGRI before starting a new symbol.
2088
 */
2089
static SIXELSTATUS
2090
sixel_emit_run(sixel_output_t *output, int symbol, int count)
33,553,328✔
2091
{
2092
    SIXELSTATUS status = SIXEL_FALSE;
33,553,328✔
2093

2094
    if (count <= 0) {
33,553,328!
2095
        return SIXEL_OK;
×
2096
    }
2097

2098
    if (output->save_count > 0) {
33,553,328✔
2099
        if (output->save_pixel == symbol) {
29,240,276!
2100
            output->save_count += count;
×
2101
            return SIXEL_OK;
×
2102
        }
2103

2104
        status = sixel_put_flash(output);
29,240,276✔
2105
        if (SIXEL_FAILED(status)) {
29,240,276!
2106
            return status;
×
2107
        }
2108
    }
2109

2110
    output->save_pixel = symbol;
33,553,328✔
2111
    output->save_count = count;
33,553,328✔
2112

2113
    return SIXEL_OK;
33,553,328✔
2114
}
2115

2116

2117
/*
2118
 * Walk a composed node and coalesce identical columns into runs so the
2119
 * emitter touches the repeat accumulator only once per symbol.
2120
 */
2121
static SIXELSTATUS
2122
sixel_emit_span_from_map(sixel_output_t *output,
4,313,052✔
2123
                         unsigned char const *map,
2124
                         int length)
2125
{
2126
    SIXELSTATUS status = SIXEL_FALSE;
4,313,052✔
2127
    int index;
2128
    int run_length;
2129
    unsigned char value;
2130
    size_t chunk_size;
2131
    unsigned long pattern;
2132
    unsigned long block;
2133
    int chunk_mismatch;
2134
    int remain;
2135
    int byte_index;
2136

2137
    if (length <= 0) {
4,313,052!
2138
        return SIXEL_OK;
×
2139
    }
2140

2141
    for (index = 0; index < length; index += run_length) {
35,558,508✔
2142
        value = map[index];
31,245,456✔
2143
        if (value > '?') {
31,245,456!
2144
            value = 0;
×
2145
        }
2146

2147
        run_length = 1;
31,245,456✔
2148
        chunk_size = sizeof(unsigned long);
31,245,456✔
2149
        chunk_mismatch = 0;
31,245,456✔
2150
        if (chunk_size > 1) {
31,245,456!
2151
            remain = length - (index + run_length);
31,245,456✔
2152
            pattern = (~0UL / 0xffUL) * (unsigned long)value;
31,245,456✔
2153

2154
            while (remain >= (int)chunk_size) {
31,849,664✔
2155
                memcpy(&block,
18,038,924✔
2156
                       map + index + run_length,
18,038,924✔
2157
                       chunk_size);
2158
                block ^= pattern;
18,038,924✔
2159
                if (block != 0UL) {
18,038,924✔
2160
                    for (byte_index = 0;
17,434,716!
2161
                         byte_index < (int)chunk_size;
23,767,420!
2162
                         byte_index++) {
6,332,704✔
2163
                        if ((block & 0xffUL) != 0UL) {
23,767,420✔
2164
                            chunk_mismatch = 1;
17,434,716✔
2165
                            break;
17,434,716✔
2166
                        }
2167
                        block >>= 8;
6,332,704✔
2168
                        run_length += 1;
6,332,704✔
2169
                    }
2170
                    break;
17,434,716✔
2171
                }
2172
                run_length += (int)chunk_size;
604,208✔
2173
                remain -= (int)chunk_size;
604,208✔
2174
            }
2175
        }
2176

2177
        if (!chunk_mismatch) {
31,245,456✔
2178
            while (index + run_length < length) {
17,341,472✔
2179
                unsigned char next;
2180

2181
                next = map[index + run_length];
13,028,420✔
2182
                if (next > '?') {
13,028,420!
2183
                    next = 0;
×
2184
                }
2185
                if (next != value) {
13,028,420✔
2186
                    break;
9,497,688✔
2187
                }
2188
                run_length += 1;
3,530,732✔
2189
            }
2190
        }
2191

2192
        status = sixel_emit_run(output,
31,245,456✔
2193
                                 (int)value + '?',
31,245,456✔
2194
                                 run_length);
2195
        if (SIXEL_FAILED(status)) {
31,245,456!
2196
            return status;
×
2197
        }
2198
    }
2199

2200
    return SIXEL_OK;
4,313,052✔
2201
}
2202

2203

2204
static SIXELSTATUS
2205
sixel_put_pixel(sixel_output_t *const output, int pix, double *emit_cells)
×
2206
{
2207
    if (pix < 0 || pix > '?') {
×
2208
        pix = 0;
×
2209
    }
2210

2211
    if (emit_cells != NULL) {
×
2212
        *emit_cells += 1.0;
×
2213
    }
2214

2215
    return sixel_emit_run(output, pix + '?', 1);
×
2216
}
2217

2218
static SIXELSTATUS
2219
sixel_node_new(sixel_node_t **np, sixel_allocator_t *allocator)
591,060✔
2220
{
2221
    SIXELSTATUS status = SIXEL_FALSE;
591,060✔
2222

2223
    *np = (sixel_node_t *)sixel_allocator_malloc(allocator,
591,060✔
2224
                                                 sizeof(sixel_node_t));
2225
    if (np == NULL) {
591,060!
2226
        sixel_helper_set_additional_message(
×
2227
            "sixel_node_new: sixel_allocator_malloc() failed.");
2228
        status = SIXEL_BAD_ALLOCATION;
×
2229
        goto end;
×
2230
    }
2231

2232
    status = SIXEL_OK;
591,060✔
2233

2234
end:
591,060✔
2235
    return status;
591,060✔
2236
}
2237

2238
static void
2239
sixel_node_del(sixel_output_t *output, sixel_node_t *np)
4,313,052✔
2240
{
2241
    sixel_node_t *tp;
2242

2243
    if ((tp = output->node_top) == np) {
4,313,052✔
2244
        output->node_top = np->next;
265,456✔
2245
    } else {
2246
        while (tp->next != NULL) {
885,192,148!
2247
            if (tp->next == np) {
885,192,148✔
2248
                tp->next = np->next;
4,047,596✔
2249
                break;
4,047,596✔
2250
            }
2251
            tp = tp->next;
881,144,552✔
2252
        }
2253
    }
2254

2255
    np->next = output->node_free;
4,313,052✔
2256
    output->node_free = np;
4,313,052✔
2257
}
4,313,052✔
2258

2259

2260
static SIXELSTATUS
2261
sixel_put_node(
4,313,052✔
2262
    sixel_output_t /* in */     *output,  /* output context */
2263
    int            /* in/out */ *x,       /* header position */
2264
    sixel_node_t   /* in */     *np,      /* node object */
2265
    int            /* in */     ncolors,  /* number of palette colors */
2266
    int            /* in */     keycolor, /* transparent color number */
2267
    double         /* in/out */ *emit_cells) /* emitted cell accumulator */
2268
{
2269
    SIXELSTATUS status = SIXEL_FALSE;
4,313,052✔
2270
    int nwrite;
2271

2272
    if (ncolors != 2 || keycolor == (-1)) {
4,313,052✔
2273
        /* designate palette index */
2274
        if (output->active_palette != np->pal) {
4,311,812✔
2275
            sixel_putc(output->buffer + output->pos, '#');
4,245,048✔
2276
            sixel_advance(output, 1);
4,245,048✔
2277
            nwrite = sixel_putnum((char *)output->buffer + output->pos, np->pal);
4,245,048✔
2278
            sixel_advance(output, nwrite);
4,245,048✔
2279
            output->active_palette = np->pal;
4,245,048✔
2280
        }
2281
    }
2282

2283
    if (*x < np->sx) {
4,313,052✔
2284
        int span;
2285

2286
        span = np->sx - *x;
2,307,872✔
2287
        status = sixel_emit_run(output, '?', span);
2,307,872✔
2288
        if (SIXEL_FAILED(status)) {
2,307,872!
2289
            goto end;
×
2290
        }
2291
        if (emit_cells != NULL) {
2,307,872!
2292
            *emit_cells += (double)span;
×
2293
        }
2294
        *x = np->sx;
2,307,872✔
2295
    }
2296

2297
    if (*x < np->mx) {
4,313,052!
2298
        int span;
2299

2300
        span = np->mx - *x;
4,313,052✔
2301
        status = sixel_emit_span_from_map(output,
4,313,052✔
2302
                                          (unsigned char const *)np->map + *x,
4,313,052✔
2303
                                          span);
2304
        if (SIXEL_FAILED(status)) {
4,313,052!
2305
            goto end;
×
2306
        }
2307
        if (emit_cells != NULL) {
4,313,052!
2308
            *emit_cells += (double)span;
×
2309
        }
2310
        *x = np->mx;
4,313,052✔
2311
    }
2312

2313
    status = sixel_put_flash(output);
4,313,052✔
2314
    if (SIXEL_FAILED(status)) {
4,313,052!
2315
        goto end;
×
2316
    }
2317

2318
end:
4,313,052✔
2319
    return status;
4,313,052✔
2320
}
2321

2322

2323
static SIXELSTATUS
2324
sixel_encode_header(int width, int height, sixel_output_t *output)
652✔
2325
{
2326
    SIXELSTATUS status = SIXEL_FALSE;
652✔
2327
    int nwrite;
2328
    int p[3] = {0, 0, 0};
652✔
2329
    int pcount = 3;
652✔
2330
    int use_raster_attributes = 1;
652✔
2331

2332
    if (output->ormode) {
652!
2333
        p[0] = 7;
×
2334
        p[1] = 5;
×
2335
    }
2336

2337
    output->pos = 0;
652✔
2338

2339
    if (! output->skip_dcs_envelope) {
652!
2340
        if (output->has_8bit_control) {
652✔
2341
            sixel_puts(output->buffer + output->pos,
20✔
2342
                       DCS_START_8BIT,
2343
                       DCS_START_8BIT_SIZE);
2344
            sixel_advance(output, DCS_START_8BIT_SIZE);
20✔
2345
        } else {
2346
            sixel_puts(output->buffer + output->pos,
632✔
2347
                       DCS_START_7BIT,
2348
                       DCS_START_7BIT_SIZE);
2349
            sixel_advance(output, DCS_START_7BIT_SIZE);
632✔
2350
        }
2351
    }
2352

2353
    if (output->skip_header) {
652!
2354
        goto laster_attr;
×
2355
    }
2356

2357
    if (p[2] == 0) {
652!
2358
        pcount--;
652✔
2359
        if (p[1] == 0) {
652!
2360
            pcount--;
652✔
2361
            if (p[0] == 0) {
652!
2362
                pcount--;
652✔
2363
            }
2364
        }
2365
    }
2366

2367
    if (pcount > 0) {
652!
2368
        nwrite = sixel_putnum((char *)output->buffer + output->pos, p[0]);
×
2369
        sixel_advance(output, nwrite);
×
2370
        if (pcount > 1) {
×
2371
            sixel_putc(output->buffer + output->pos, ';');
×
2372
            sixel_advance(output, 1);
×
2373
            nwrite = sixel_putnum((char *)output->buffer + output->pos, p[1]);
×
2374
            sixel_advance(output, nwrite);
×
2375
            if (pcount > 2) {
×
2376
                sixel_putc(output->buffer + output->pos, ';');
×
2377
                sixel_advance(output, 1);
×
2378
                nwrite = sixel_putnum((char *)output->buffer + output->pos, p[2]);
×
2379
                sixel_advance(output, nwrite);
×
2380
            }
2381
        }
2382
    }
2383

2384
    sixel_putc(output->buffer + output->pos, 'q');
652✔
2385
    sixel_advance(output, 1);
652✔
2386

2387
laster_attr:
652✔
2388
    if (use_raster_attributes) {
652!
2389
        sixel_puts(output->buffer + output->pos, "\"1;1;", 5);
652✔
2390
        sixel_advance(output, 5);
652✔
2391
        nwrite = sixel_putnum((char *)output->buffer + output->pos, width);
652✔
2392
        sixel_advance(output, nwrite);
652✔
2393
        sixel_putc(output->buffer + output->pos, ';');
652✔
2394
        sixel_advance(output, 1);
652✔
2395
        nwrite = sixel_putnum((char *)output->buffer + output->pos, height);
652✔
2396
        sixel_advance(output, nwrite);
652✔
2397
    }
2398

2399
    status = SIXEL_OK;
652✔
2400

2401
    return status;
652✔
2402
}
2403

2404

2405
static int
2406
sixel_palette_float_pixelformat_for_colorspace(int colorspace)
1,208✔
2407
{
2408
    switch (colorspace) {
1,208!
2409
    case SIXEL_COLORSPACE_LINEAR:
×
2410
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
×
2411
    case SIXEL_COLORSPACE_OKLAB:
×
2412
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
×
2413
    case SIXEL_COLORSPACE_CIELAB:
×
2414
        return SIXEL_PIXELFORMAT_CIELABFLOAT32;
×
2415
    case SIXEL_COLORSPACE_DIN99D:
×
2416
        return SIXEL_PIXELFORMAT_DIN99DFLOAT32;
×
2417
    default:
1,208✔
2418
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
1,208✔
2419
    }
2420
}
2421

2422
static int
2423
sixel_palette_float32_matches_u8(unsigned char const *palette,
×
2424
                                 float const *palette_float,
2425
                                 size_t count,
2426
                                 int float_pixelformat)
2427
{
2428
    size_t index;
2429
    size_t limit;
2430
    unsigned char expected;
2431
    int channel;
2432

2433
    if (palette == NULL || palette_float == NULL || count == 0U) {
×
2434
        return 1;
×
2435
    }
2436
    if (count > SIZE_MAX / 3U) {
×
2437
        return 0;
×
2438
    }
2439
    limit = count * 3U;
×
2440
    for (index = 0U; index < limit; ++index) {
×
2441
        channel = (int)(index % 3U);
×
2442
        expected = sixel_pixelformat_float_channel_to_byte(
×
2443
            float_pixelformat,
2444
            channel,
2445
            palette_float[index]);
×
2446
        if (palette[index] != expected) {
×
2447
            return 0;
×
2448
        }
2449
    }
2450
    return 1;
×
2451
}
2452

2453
static void
2454
sixel_palette_sync_float32_from_u8(unsigned char const *palette,
×
2455
                                   float *palette_float,
2456
                                   size_t count,
2457
                                   int float_pixelformat)
2458
{
2459
    size_t index;
2460
    size_t limit;
2461
    int channel;
2462

2463
    if (palette == NULL || palette_float == NULL || count == 0U) {
×
2464
        return;
×
2465
    }
2466
    if (count > SIZE_MAX / 3U) {
×
2467
        return;
×
2468
    }
2469
    limit = count * 3U;
×
2470
    for (index = 0U; index < limit; ++index) {
×
2471
        channel = (int)(index % 3U);
×
2472
        palette_float[index] = sixel_pixelformat_byte_to_float(
×
2473
            float_pixelformat,
2474
            channel,
2475
            palette[index]);
×
2476
    }
2477
}
2478

2479
static int
2480
sixel_output_palette_channel_to_pct(unsigned char const *palette,
841,824✔
2481
                                    float const *palette_float,
2482
                                    int n,
2483
                                    int channel)
2484
{
2485
    size_t index;
2486
    float value;
2487
    int percent;
2488

2489
    index = (size_t)n * 3U + (size_t)channel;
841,824✔
2490
    if (palette_float != NULL) {
841,824!
2491
        value = palette_float[index];
×
2492
        if (value < 0.0f) {
×
2493
            value = 0.0f;
×
2494
        } else if (value > 1.0f) {
×
2495
            value = 1.0f;
×
2496
        }
2497
        percent = (int)(value * 100.0f + 0.5f);
×
2498
        if (percent < 0) {
×
2499
            percent = 0;
×
2500
        } else if (percent > 100) {
×
2501
            percent = 100;
×
2502
        }
2503
        return percent;
×
2504
    }
2505

2506
    if (palette != NULL) {
841,824!
2507
        return (palette[index] * 100 + 127) / 255;
841,824✔
2508
    }
2509

2510
    return 0;
×
2511
}
2512

2513
static double
2514
sixel_output_palette_channel_to_float(unsigned char const *palette,
293,808✔
2515
                                      float const *palette_float,
2516
                                      int n,
2517
                                      int channel)
2518
{
2519
    size_t index;
2520
    double value;
2521

2522
    index = (size_t)n * 3U + (size_t)channel;
293,808✔
2523
    value = 0.0;
293,808✔
2524
    if (palette_float != NULL) {
293,808!
2525
        value = palette_float[index];
×
2526
    } else if (palette != NULL) {
293,808!
2527
        value = (double)palette[index] / 255.0;
293,808✔
2528
    }
2529
    if (value < 0.0) {
293,808!
2530
        value = 0.0;
×
2531
    } else if (value > 1.0) {
293,808!
2532
        value = 1.0;
×
2533
    }
2534

2535
    return value;
293,808✔
2536
}
2537

2538
static SIXELSTATUS
2539
output_rgb_palette_definition(
280,608✔
2540
    sixel_output_t /* in */ *output,
2541
    unsigned char const /* in */ *palette,
2542
    float const /* in */ *palette_float,
2543
    int            /* in */ n,
2544
    int            /* in */ keycolor
2545
)
2546
{
2547
    SIXELSTATUS status = SIXEL_FALSE;
280,608✔
2548
    int nwrite;
2549

2550
    if (n != keycolor) {
280,608!
2551
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
2552
        sixel_putc(output->buffer + output->pos, '#');
280,608✔
2553
        sixel_advance(output, 1);
280,608✔
2554
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
280,608✔
2555
        sixel_advance(output, nwrite);
280,608✔
2556
        sixel_puts(output->buffer + output->pos, ";2;", 3);
280,608✔
2557
        sixel_advance(output, 3);
280,608✔
2558
        nwrite = sixel_putnum(
280,608✔
2559
            (char *)output->buffer + output->pos,
280,608✔
2560
            sixel_output_palette_channel_to_pct(palette,
2561
                                                palette_float,
2562
                                                n,
2563
                                                0));
2564
        sixel_advance(output, nwrite);
280,608✔
2565
        sixel_putc(output->buffer + output->pos, ';');
280,608✔
2566
        sixel_advance(output, 1);
280,608✔
2567
        nwrite = sixel_putnum(
280,608✔
2568
            (char *)output->buffer + output->pos,
280,608✔
2569
            sixel_output_palette_channel_to_pct(palette,
2570
                                                palette_float,
2571
                                                n,
2572
                                                1));
2573
        sixel_advance(output, nwrite);
280,608✔
2574
        sixel_putc(output->buffer + output->pos, ';');
280,608✔
2575
        sixel_advance(output, 1);
280,608✔
2576
        nwrite = sixel_putnum(
280,608✔
2577
            (char *)output->buffer + output->pos,
280,608✔
2578
            sixel_output_palette_channel_to_pct(palette,
2579
                                                palette_float,
2580
                                                n,
2581
                                                2));
2582
        sixel_advance(output, nwrite);
280,608✔
2583
    }
2584

2585
    status = SIXEL_OK;
280,608✔
2586

2587
    return status;
280,608✔
2588
}
2589

2590

2591
static SIXELSTATUS
2592
output_hls_palette_definition(
97,936✔
2593
    sixel_output_t /* in */ *output,
2594
    unsigned char const /* in */ *palette,
2595
    float const /* in */ *palette_float,
2596
    int            /* in */ n,
2597
    int            /* in */ keycolor
2598
)
2599
{
2600
    SIXELSTATUS status = SIXEL_FALSE;
97,936✔
2601
    double r;
2602
    double g;
2603
    double b;
2604
    double maxc;
2605
    double minc;
2606
    double lightness;
2607
    double saturation;
2608
    double hue;
2609
    double diff;
2610
    int h;
2611
    int l;
2612
    int s;
2613
    int nwrite;
2614

2615
    if (n != keycolor) {
97,936!
2616
        r = sixel_output_palette_channel_to_float(palette,
97,936✔
2617
                                                  palette_float,
2618
                                                  n,
2619
                                                  0);
2620
        g = sixel_output_palette_channel_to_float(palette,
97,936✔
2621
                                                  palette_float,
2622
                                                  n,
2623
                                                  1);
2624
        b = sixel_output_palette_channel_to_float(palette,
97,936✔
2625
                                                  palette_float,
2626
                                                  n,
2627
                                                  2);
2628
        maxc = r > g ? (r > b ? r : b) : (g > b ? g : b);
97,936✔
2629
        minc = r < g ? (r < b ? r : b) : (g < b ? g : b);
97,936✔
2630
        lightness = (maxc + minc) * 0.5;
97,936✔
2631
        saturation = 0.0;
97,936✔
2632
        hue = 0.0;
97,936✔
2633
        diff = maxc - minc;
97,936✔
2634
        if (diff <= 0.0) {
97,936✔
2635
            h = 0;
44✔
2636
            s = 0;
44✔
2637
        } else {
2638
            if (lightness < 0.5) {
97,892✔
2639
                saturation = diff / (maxc + minc);
79,747✔
2640
            } else {
2641
                saturation = diff / (2.0 - maxc - minc);
18,145✔
2642
            }
2643
            if (maxc == r) {
97,892✔
2644
                hue = (g - b) / diff;
59,170✔
2645
            } else if (maxc == g) {
38,722✔
2646
                hue = 2.0 + (b - r) / diff;
35,742✔
2647
            } else {
2648
                hue = 4.0 + (r - g) / diff;
2,980✔
2649
            }
2650
            hue *= 60.0;
97,892✔
2651
            if (hue < 0.0) {
97,892✔
2652
                hue += 360.0;
397✔
2653
            }
2654
            if (hue >= 360.0) {
97,892!
2655
                hue -= 360.0;
×
2656
            }
2657
            /*
2658
             * The DEC HLS color wheel used by DECGCI considers
2659
             * hue==0 to be blue instead of red.  Rotate the hue by
2660
             * +120 degrees so that RGB primaries continue to match
2661
             * the historical img2sixel output and VT340 behavior.
2662
             */
2663
            hue += 120.0;
97,892✔
2664
            while (hue >= 360.0) {
99,409✔
2665
                hue -= 360.0;
1,517✔
2666
            }
2667
            while (hue < 0.0) {
97,892!
2668
                hue += 360.0;
×
2669
            }
2670
            s = (int)(saturation * 100.0 + 0.5);
97,892✔
2671
            if (s < 0) {
97,892!
2672
                s = 0;
×
2673
            } else if (s > 100) {
97,892!
2674
                s = 100;
×
2675
            }
2676
            h = (int)(hue + 0.5);
97,892✔
2677
            if (h >= 360) {
97,892!
2678
                h -= 360;
×
2679
            } else if (h < 0) {
97,892!
2680
                h = 0;
×
2681
            }
2682
        }
2683
        l = (int)(lightness * 100.0 + 0.5);
97,936✔
2684
        if (l < 0) {
97,936!
2685
            l = 0;
×
2686
        } else if (l > 100) {
97,936!
2687
            l = 100;
×
2688
        }
2689
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
2690
        sixel_putc(output->buffer + output->pos, '#');
97,936✔
2691
        sixel_advance(output, 1);
97,936✔
2692
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
97,936✔
2693
        sixel_advance(output, nwrite);
97,936✔
2694
        sixel_puts(output->buffer + output->pos, ";1;", 3);
97,936✔
2695
        sixel_advance(output, 3);
97,936✔
2696
        nwrite = sixel_putnum((char *)output->buffer + output->pos, h);
97,936✔
2697
        sixel_advance(output, nwrite);
97,936✔
2698
        sixel_putc(output->buffer + output->pos, ';');
97,936✔
2699
        sixel_advance(output, 1);
97,936✔
2700
        nwrite = sixel_putnum((char *)output->buffer + output->pos, l);
97,936✔
2701
        sixel_advance(output, nwrite);
97,936✔
2702
        sixel_putc(output->buffer + output->pos, ';');
97,936✔
2703
        sixel_advance(output, 1);
97,936✔
2704
        nwrite = sixel_putnum((char *)output->buffer + output->pos, s);
97,936✔
2705
        sixel_advance(output, nwrite);
97,936✔
2706
    }
2707

2708
    status = SIXEL_OK;
97,936✔
2709
    return status;
97,936✔
2710
}
2711

2712

2713
static void
2714
sixel_encode_work_init(sixel_encode_work_t *work)
1,888✔
2715
{
2716
    work->map = NULL;
1,888✔
2717
    work->map_size = 0;
1,888✔
2718
    work->columns = NULL;
1,888✔
2719
    work->columns_size = 0;
1,888✔
2720
    work->active_colors = NULL;
1,888✔
2721
    work->active_colors_size = 0;
1,888✔
2722
    work->active_color_index = NULL;
1,888✔
2723
    work->active_color_index_size = 0;
1,888✔
2724
    work->requested_threads = 1;
1,888✔
2725
}
1,888✔
2726

2727
static SIXELSTATUS
2728
sixel_encode_work_allocate(sixel_encode_work_t *work,
1,888✔
2729
                           int width,
2730
                           int ncolors,
2731
                           sixel_allocator_t *allocator)
2732
{
2733
    SIXELSTATUS status = SIXEL_FALSE;
1,888✔
2734
    int len;
2735
    size_t columns_size;
2736
    size_t active_colors_size;
2737
    size_t active_color_index_size;
2738

2739
    len = ncolors * width;
1,888✔
2740
    work->map = (char *)sixel_allocator_calloc(allocator,
1,888✔
2741
                                               (size_t)len,
2742
                                               sizeof(char));
2743
    if (work->map == NULL && len > 0) {
1,888!
2744
        sixel_helper_set_additional_message(
×
2745
            "sixel_encode_body: sixel_allocator_calloc() failed.");
2746
        status = SIXEL_BAD_ALLOCATION;
×
2747
        goto end;
×
2748
    }
2749
    work->map_size = (size_t)len;
1,888✔
2750

2751
    columns_size = sizeof(sixel_node_t *) * (size_t)width;
1,888✔
2752
    if (width > 0) {
1,888!
2753
        work->columns = (sixel_node_t **)sixel_allocator_malloc(
1,888✔
2754
            allocator,
2755
            columns_size);
2756
        if (work->columns == NULL) {
1,888!
2757
            sixel_helper_set_additional_message(
×
2758
                "sixel_encode_body: sixel_allocator_malloc() failed.");
2759
            status = SIXEL_BAD_ALLOCATION;
×
2760
            goto end;
×
2761
        }
2762
        memset(work->columns, 0, columns_size);
1,888✔
2763
        work->columns_size = columns_size;
1,888✔
2764
    } else {
2765
        work->columns = NULL;
×
2766
        work->columns_size = 0;
×
2767
    }
2768

2769
    active_colors_size = (size_t)ncolors;
1,888✔
2770
    if (active_colors_size > 0) {
1,888!
2771
        work->active_colors =
1,888✔
2772
            (unsigned char *)sixel_allocator_malloc(allocator,
1,888✔
2773
                                                    active_colors_size);
2774
        if (work->active_colors == NULL) {
1,888!
2775
            sixel_helper_set_additional_message(
×
2776
                "sixel_encode_body: sixel_allocator_malloc() failed.");
2777
            status = SIXEL_BAD_ALLOCATION;
×
2778
            goto end;
×
2779
        }
2780
        memset(work->active_colors, 0, active_colors_size);
1,888✔
2781
        work->active_colors_size = active_colors_size;
1,888✔
2782
    } else {
2783
        work->active_colors = NULL;
×
2784
        work->active_colors_size = 0;
×
2785
    }
2786

2787
    active_color_index_size = sizeof(int) * (size_t)ncolors;
1,888✔
2788
    if (active_color_index_size > 0) {
1,888!
2789
        work->active_color_index = (int *)sixel_allocator_malloc(
1,888✔
2790
            allocator,
2791
            active_color_index_size);
2792
        if (work->active_color_index == NULL) {
1,888!
2793
            sixel_helper_set_additional_message(
×
2794
                "sixel_encode_body: sixel_allocator_malloc() failed.");
2795
            status = SIXEL_BAD_ALLOCATION;
×
2796
            goto end;
×
2797
        }
2798
        memset(work->active_color_index, 0, active_color_index_size);
1,888✔
2799
        work->active_color_index_size = active_color_index_size;
1,888✔
2800
    } else {
2801
        work->active_color_index = NULL;
×
2802
        work->active_color_index_size = 0;
×
2803
    }
2804

2805
    status = SIXEL_OK;
1,888✔
2806

2807
end:
1,888✔
2808
    if (SIXEL_FAILED(status)) {
1,888!
2809
        if (work->active_color_index != NULL) {
×
2810
            sixel_allocator_free(allocator, work->active_color_index);
×
2811
            work->active_color_index = NULL;
×
2812
        }
2813
        work->active_color_index_size = 0;
×
2814
        if (work->active_colors != NULL) {
×
2815
            sixel_allocator_free(allocator, work->active_colors);
×
2816
            work->active_colors = NULL;
×
2817
        }
2818
        work->active_colors_size = 0;
×
2819
        if (work->columns != NULL) {
×
2820
            sixel_allocator_free(allocator, work->columns);
×
2821
            work->columns = NULL;
×
2822
        }
2823
        work->columns_size = 0;
×
2824
        if (work->map != NULL) {
×
2825
            sixel_allocator_free(allocator, work->map);
×
2826
            work->map = NULL;
×
2827
        }
2828
        work->map_size = 0;
×
2829
    }
2830

2831
    return status;
1,888✔
2832
}
2833

2834
static void
2835
sixel_encode_work_cleanup(sixel_encode_work_t *work,
1,888✔
2836
                          sixel_allocator_t *allocator)
2837
{
2838
    if (work->active_color_index != NULL) {
1,888!
2839
        sixel_allocator_free(allocator, work->active_color_index);
1,888✔
2840
        work->active_color_index = NULL;
1,888✔
2841
    }
2842
    work->active_color_index_size = 0;
1,888✔
2843
    if (work->active_colors != NULL) {
1,888!
2844
        sixel_allocator_free(allocator, work->active_colors);
1,888✔
2845
        work->active_colors = NULL;
1,888✔
2846
    }
2847
    work->active_colors_size = 0;
1,888✔
2848
    if (work->columns != NULL) {
1,888!
2849
        sixel_allocator_free(allocator, work->columns);
1,888✔
2850
        work->columns = NULL;
1,888✔
2851
    }
2852
    work->columns_size = 0;
1,888✔
2853
    if (work->map != NULL) {
1,888!
2854
        sixel_allocator_free(allocator, work->map);
1,888✔
2855
        work->map = NULL;
1,888✔
2856
    }
2857
    work->map_size = 0;
1,888✔
2858
}
1,888✔
2859

2860
static void
2861
sixel_band_state_reset(sixel_band_state_t *state)
24,244✔
2862
{
2863
    state->row_in_band = 0;
24,244✔
2864
    state->fillable = 0;
24,244✔
2865
    state->active_color_count = 0;
24,244✔
2866
}
24,244✔
2867

2868
static void
2869
sixel_band_finish(sixel_encode_work_t *work, sixel_band_state_t *state)
22,356✔
2870
{
2871
    int color_index;
2872
    int c;
2873

2874
    if (work->active_colors == NULL
22,356!
2875
        || work->active_color_index == NULL) {
22,356!
2876
        state->active_color_count = 0;
×
2877
        return;
×
2878
    }
2879

2880
    for (color_index = 0;
22,356✔
2881
         color_index < state->active_color_count;
1,269,524✔
2882
         color_index++) {
1,247,168✔
2883
        c = work->active_color_index[color_index];
1,247,168✔
2884
        if (c >= 0
1,247,168!
2885
            && (size_t)c < work->active_colors_size) {
1,247,168!
2886
            work->active_colors[c] = 0;
1,247,168✔
2887
        }
2888
    }
2889
    state->active_color_count = 0;
22,356✔
2890
}
2891

2892
static void
2893
sixel_band_clear_map(sixel_encode_work_t *work)
22,356✔
2894
{
2895
    if (work->map != NULL && work->map_size > 0) {
22,356!
2896
        memset(work->map, 0, work->map_size);
22,356✔
2897
    }
2898
}
22,356✔
2899

2900
static void
2901
sixel_encode_metrics_init(sixel_encode_metrics_t *metrics)
1,888✔
2902
{
2903
    metrics->encode_probe_active = 0;
1,888✔
2904
    metrics->compose_span_started_at = 0.0;
1,888✔
2905
    metrics->compose_queue_started_at = 0.0;
1,888✔
2906
    metrics->compose_span_duration = 0.0;
1,888✔
2907
    metrics->compose_scan_duration = 0.0;
1,888✔
2908
    metrics->compose_queue_duration = 0.0;
1,888✔
2909
    metrics->compose_scan_probes = 0.0;
1,888✔
2910
    metrics->compose_queue_nodes = 0.0;
1,888✔
2911
    metrics->emit_cells = 0.0;
1,888✔
2912
    metrics->emit_cells_ptr = NULL;
1,888✔
2913
}
1,888✔
2914

2915
static SIXELSTATUS
2916
sixel_encode_emit_palette(int bodyonly,
1,888✔
2917
                          int ncolors,
2918
                          int keycolor,
2919
                          unsigned char const *palette,
2920
                          float const *palette_float,
2921
                          sixel_output_t *output,
2922
                          int encode_probe_active)
2923
{
2924
    SIXELSTATUS status = SIXEL_FALSE;
1,888✔
2925
    double span_started_at;
2926
    int n;
2927

2928
    if (bodyonly || (ncolors == 2 && keycolor != (-1))) {
1,888!
2929
        return SIXEL_OK;
16✔
2930
    }
2931

2932
    if (palette == NULL && palette_float == NULL) {
1,872!
2933
        sixel_helper_set_additional_message(
×
2934
            "sixel_encode_emit_palette: missing palette data.");
2935
        return SIXEL_BAD_ARGUMENT;
×
2936
    }
2937

2938
    span_started_at = sixel_encode_span_start(encode_probe_active);
1,872✔
2939
    if (output->palette_type == SIXEL_PALETTETYPE_HLS) {
1,872✔
2940
        for (n = 0; n < ncolors; n++) {
98,328✔
2941
            status = output_hls_palette_definition(output,
97,936✔
2942
                                                   palette,
2943
                                                   palette_float,
2944
                                                   n,
2945
                                                   keycolor);
2946
            if (SIXEL_FAILED(status)) {
97,936!
2947
                goto end;
×
2948
            }
2949
        }
2950
    } else {
2951
        for (n = 0; n < ncolors; n++) {
282,088✔
2952
            status = output_rgb_palette_definition(output,
280,608✔
2953
                                                   palette,
2954
                                                   palette_float,
2955
                                                   n,
2956
                                                   keycolor);
2957
            if (SIXEL_FAILED(status)) {
280,608!
2958
                goto end;
×
2959
            }
2960
        }
2961
    }
2962

2963
    sixel_encode_span_commit(encode_probe_active,
1,872✔
2964
                             SIXEL_ASSESSMENT_STAGE_ENCODE_PREPARE,
2965
                             span_started_at);
2966

2967
    status = SIXEL_OK;
1,872✔
2968

2969
end:
1,872✔
2970
    return status;
1,872✔
2971
}
2972

2973
static SIXELSTATUS
2974
sixel_band_classify_row(sixel_encode_work_t *work,
132,868✔
2975
                        sixel_band_state_t *state,
2976
                        sixel_index_t *pixels,
2977
                        int width,
2978
                        int absolute_row,
2979
                        int ncolors,
2980
                        int keycolor,
2981
                        unsigned char *palstate,
2982
                        int encode_policy)
2983
{
2984
    SIXELSTATUS status = SIXEL_FALSE;
132,868✔
2985
    int row_bit;
2986
    int band_start;
2987
    int pix;
2988
    int x;
2989
    int check_integer_overflow;
2990
    char *map;
2991
    unsigned char *active_colors;
2992
    int *active_color_index;
2993

2994
    map = work->map;
132,868✔
2995
    active_colors = work->active_colors;
132,868✔
2996
    active_color_index = work->active_color_index;
132,868✔
2997
    row_bit = state->row_in_band;
132,868✔
2998
    band_start = absolute_row - row_bit;
132,868✔
2999

3000
    if (row_bit == 0) {
132,868✔
3001
        if (encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
22,356✔
3002
            state->fillable = 0;
22,056✔
3003
        } else if (palstate) {
300!
3004
            if (width > 0) {
×
3005
                pix = pixels[band_start * width];
×
3006
                if (pix >= ncolors) {
×
3007
                    state->fillable = 0;
×
3008
                } else {
3009
                    state->fillable = 1;
×
3010
                }
3011
            } else {
3012
                state->fillable = 0;
×
3013
            }
3014
        } else {
3015
            state->fillable = 1;
300✔
3016
        }
3017
        state->active_color_count = 0;
22,356✔
3018
    }
3019

3020
    for (x = 0; x < width; x++) {
81,590,100✔
3021
        if (absolute_row > INT_MAX / width) {
81,457,232!
3022
            sixel_helper_set_additional_message(
×
3023
                "sixel_encode_body: integer overflow detected."
3024
                " (y > INT_MAX)");
3025
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3026
            goto end;
×
3027
        }
3028
        check_integer_overflow = absolute_row * width;
81,457,232✔
3029
        if (check_integer_overflow > INT_MAX - x) {
81,457,232!
3030
            sixel_helper_set_additional_message(
×
3031
                "sixel_encode_body: integer overflow detected."
3032
                " (y * width > INT_MAX - x)");
3033
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3034
            goto end;
×
3035
        }
3036
        pix = pixels[check_integer_overflow + x];
81,457,232✔
3037
        if (pix >= 0 && pix < ncolors && pix != keycolor) {
81,457,232!
3038
            if (pix > INT_MAX / width) {
75,310,852!
3039
                sixel_helper_set_additional_message(
×
3040
                    "sixel_encode_body: integer overflow detected."
3041
                    " (pix > INT_MAX / width)");
3042
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3043
                goto end;
×
3044
            }
3045
            check_integer_overflow = pix * width;
75,310,852✔
3046
            if (check_integer_overflow > INT_MAX - x) {
75,310,852!
3047
                sixel_helper_set_additional_message(
×
3048
                    "sixel_encode_body: integer overflow detected."
3049
                    " (pix * width > INT_MAX - x)");
3050
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3051
                goto end;
×
3052
            }
3053
            map[pix * width + x] |= (1 << row_bit);
75,310,852✔
3054
            if (active_colors != NULL && active_colors[pix] == 0) {
75,310,852!
3055
                active_colors[pix] = 1;
1,247,168✔
3056
                if (state->active_color_count < ncolors
1,247,168!
3057
                    && active_color_index != NULL) {
1,247,168!
3058
                    active_color_index[state->active_color_count] = pix;
1,247,168✔
3059
                    state->active_color_count += 1;
1,247,168✔
3060
                }
3061
            }
3062
        } else if (!palstate) {
6,146,380✔
3063
            state->fillable = 0;
1,696,780✔
3064
        }
3065
    }
3066

3067
    state->row_in_band += 1;
132,868✔
3068
    status = SIXEL_OK;
132,868✔
3069

3070
end:
132,868✔
3071
    return status;
132,868✔
3072
}
3073

3074
static SIXELSTATUS
3075
sixel_band_compose(sixel_encode_work_t *work,
22,356✔
3076
                   sixel_band_state_t *state,
3077
                   sixel_output_t *output,
3078
                   int width,
3079
                   int ncolors,
3080
                   int keycolor,
3081
                   sixel_allocator_t *allocator,
3082
                   sixel_encode_metrics_t *metrics)
3083
{
3084
    SIXELSTATUS status = SIXEL_FALSE;
22,356✔
3085
    int color_index;
3086
    int c;
3087
    unsigned char *row;
3088
    int sx;
3089
    int mx;
3090
    int gap;
3091
    int gap_reached_end;
3092
    double now;
3093
    sixel_node_t *np;
3094
    sixel_node_t *column_head;
3095
    sixel_node_t *column_tail;
3096
    sixel_node_t top;
3097

3098
    (void)ncolors;
3099
    (void)keycolor;
3100
    row = NULL;
22,356✔
3101
    gap_reached_end = 0;
22,356✔
3102
    now = 0.0;
22,356✔
3103
    np = NULL;
22,356✔
3104
    column_head = NULL;
22,356✔
3105
    column_tail = NULL;
22,356✔
3106
    top.next = NULL;
22,356✔
3107

3108
    if (work->columns != NULL) {
22,356!
3109
        memset(work->columns, 0, work->columns_size);
22,356✔
3110
    }
3111
    output->node_top = NULL;
22,356✔
3112

3113
    metrics->compose_span_started_at =
22,356✔
3114
        sixel_encode_span_start(metrics->encode_probe_active);
22,356✔
3115
    metrics->compose_queue_started_at = 0.0;
22,356✔
3116
    metrics->compose_span_duration = 0.0;
22,356✔
3117
    metrics->compose_queue_duration = 0.0;
22,356✔
3118
    metrics->compose_scan_duration = 0.0;
22,356✔
3119
    metrics->compose_scan_probes = 0.0;
22,356✔
3120
    metrics->compose_queue_nodes = 0.0;
22,356✔
3121

3122
    for (color_index = 0;
22,356✔
3123
         color_index < state->active_color_count;
1,269,524✔
3124
         color_index++) {
1,247,168✔
3125
        c = work->active_color_index[color_index];
1,247,168✔
3126
        row = (unsigned char *)(work->map + c * width);
1,247,168✔
3127
        sx = 0;
1,247,168✔
3128
        while (sx < width) {
5,560,220✔
3129
            sx = sixel_compose_find_run_start(
5,520,728✔
3130
                row,
3131
                width,
3132
                sx,
3133
                metrics->encode_probe_active,
3134
                &metrics->compose_scan_probes);
3135
            if (sx >= width) {
5,520,728✔
3136
                break;
1,207,676✔
3137
            }
3138

3139
            if (metrics->encode_probe_active) {
4,313,052!
3140
                now = sixel_assessment_timer_now();
×
3141
                metrics->compose_queue_started_at = now;
×
3142
                metrics->compose_queue_nodes += 1.0;
×
3143
            }
3144

3145
            mx = sx + 1;
4,313,052✔
3146
            while (mx < width) {
37,937,876✔
3147
                if (metrics->encode_probe_active) {
37,898,384!
3148
                    metrics->compose_scan_probes += 1.0;
×
3149
                }
3150
                if (row[mx] != 0) {
37,898,384✔
3151
                    mx += 1;
27,822,044✔
3152
                    continue;
27,822,044✔
3153
                }
3154

3155
                gap = sixel_compose_measure_gap(
10,076,340✔
3156
                    row,
3157
                    width,
3158
                    mx + 1,
3159
                    metrics->encode_probe_active,
3160
                    &metrics->compose_scan_probes,
3161
                    &gap_reached_end);
3162
                if (gap >= 9 || gap_reached_end) {
10,076,340✔
3163
                    break;
3164
                }
3165
                mx += gap + 1;
5,802,780✔
3166
            }
3167

3168
            if ((np = output->node_free) != NULL) {
4,313,052✔
3169
                output->node_free = np->next;
3,721,992✔
3170
            } else {
3171
                status = sixel_node_new(&np, allocator);
591,060✔
3172
                if (SIXEL_FAILED(status)) {
591,060!
3173
                    goto end;
×
3174
                }
3175
            }
3176

3177
            np->pal = c;
4,313,052✔
3178
            np->sx = sx;
4,313,052✔
3179
            np->mx = mx;
4,313,052✔
3180
            np->map = (char *)row;
4,313,052✔
3181
            np->next = NULL;
4,313,052✔
3182

3183
            if (work->columns != NULL) {
4,313,052!
3184
                column_head = work->columns[sx];
4,313,052✔
3185
                if (column_head == NULL
4,313,052✔
3186
                    || column_head->mx <= np->mx) {
1,442,080✔
3187
                    np->next = column_head;
3,628,088✔
3188
                    work->columns[sx] = np;
3,628,088✔
3189
                } else {
3190
                    column_tail = column_head;
684,964✔
3191
                    while (column_tail->next != NULL
684,964✔
3192
                           && column_tail->next->mx > np->mx) {
830,096✔
3193
                        column_tail = column_tail->next;
145,132✔
3194
                    }
3195
                    np->next = column_tail->next;
684,964✔
3196
                    column_tail->next = np;
684,964✔
3197
                }
3198
            } else {
3199
                top.next = output->node_top;
×
3200
                column_tail = &top;
×
3201

3202
                while (column_tail->next != NULL) {
×
3203
                    if (np->sx < column_tail->next->sx) {
×
3204
                        break;
×
3205
                    } else if (np->sx == column_tail->next->sx
×
3206
                               && np->mx > column_tail->next->mx) {
×
3207
                        break;
×
3208
                    }
3209
                    column_tail = column_tail->next;
×
3210
                }
3211

3212
                np->next = column_tail->next;
×
3213
                column_tail->next = np;
×
3214
                output->node_top = top.next;
×
3215
            }
3216

3217
            if (metrics->encode_probe_active) {
4,313,052!
3218
                now = sixel_assessment_timer_now();
×
3219
                metrics->compose_queue_duration +=
×
3220
                    now - metrics->compose_queue_started_at;
×
3221
            }
3222

3223
            sx = mx;
4,313,052✔
3224
        }
3225
    }
3226

3227
    if (work->columns != NULL) {
22,356!
3228
        if (metrics->encode_probe_active) {
22,356!
3229
            metrics->compose_queue_started_at =
×
3230
                sixel_assessment_timer_now();
×
3231
        }
3232
        top.next = NULL;
22,356✔
3233
        column_tail = &top;
22,356✔
3234
        for (sx = 0; sx < width; sx++) {
13,625,848✔
3235
            column_head = work->columns[sx];
13,603,492✔
3236
            if (column_head == NULL) {
13,603,492✔
3237
                continue;
10,732,520✔
3238
            }
3239
            column_tail->next = column_head;
2,870,972✔
3240
            while (column_tail->next != NULL) {
7,184,024✔
3241
                column_tail = column_tail->next;
4,313,052✔
3242
            }
3243
            work->columns[sx] = NULL;
2,870,972✔
3244
        }
3245
        output->node_top = top.next;
22,356✔
3246
        if (metrics->encode_probe_active) {
22,356!
3247
            now = sixel_assessment_timer_now();
×
3248
            metrics->compose_queue_duration +=
×
3249
                now - metrics->compose_queue_started_at;
×
3250
        }
3251
    }
3252

3253
    if (metrics->encode_probe_active) {
22,356!
3254
        now = sixel_assessment_timer_now();
×
3255
        metrics->compose_span_duration =
×
3256
            now - metrics->compose_span_started_at;
×
3257
        metrics->compose_scan_duration =
×
3258
            metrics->compose_span_duration
×
3259
            - metrics->compose_queue_duration;
×
3260
        if (metrics->compose_scan_duration < 0.0) {
×
3261
            metrics->compose_scan_duration = 0.0;
×
3262
        }
3263
        if (metrics->compose_queue_duration < 0.0) {
×
3264
            metrics->compose_queue_duration = 0.0;
×
3265
        }
3266
        if (metrics->compose_span_duration < 0.0) {
×
3267
            metrics->compose_span_duration = 0.0;
×
3268
        }
3269
        sixel_assessment_record_encode_span(
×
3270
            SIXEL_ASSESSMENT_STAGE_ENCODE_COMPOSE_SCAN,
3271
            metrics->compose_scan_duration);
3272
        sixel_assessment_record_encode_span(
×
3273
            SIXEL_ASSESSMENT_STAGE_ENCODE_COMPOSE_QUEUE,
3274
            metrics->compose_queue_duration);
3275
        sixel_assessment_record_encode_span(
×
3276
            SIXEL_ASSESSMENT_STAGE_ENCODE_COMPOSE,
3277
            metrics->compose_span_duration);
3278
        sixel_assessment_record_encode_work(
×
3279
            SIXEL_ASSESSMENT_STAGE_ENCODE_COMPOSE_SCAN,
3280
            metrics->compose_scan_probes);
3281
        sixel_assessment_record_encode_work(
×
3282
            SIXEL_ASSESSMENT_STAGE_ENCODE_COMPOSE_QUEUE,
3283
            metrics->compose_queue_nodes);
3284
        sixel_assessment_record_encode_work(
×
3285
            SIXEL_ASSESSMENT_STAGE_ENCODE_COMPOSE,
3286
            metrics->compose_queue_nodes);
3287
    }
3288

3289
    status = SIXEL_OK;
22,356✔
3290

3291
end:
22,356✔
3292
    return status;
22,356✔
3293
}
3294

3295
static SIXELSTATUS
3296
sixel_band_emit(sixel_encode_work_t *work,
22,356✔
3297
                sixel_band_state_t *state,
3298
                sixel_output_t *output,
3299
                int ncolors,
3300
                int keycolor,
3301
                int last_row_index,
3302
                sixel_encode_metrics_t *metrics)
3303
{
3304
    SIXELSTATUS status = SIXEL_FALSE;
22,356✔
3305
    double span_started_at;
3306
    sixel_node_t *np;
3307
    sixel_node_t *next;
3308
    int x;
3309

3310
    span_started_at = sixel_encode_span_start(metrics->encode_probe_active);
22,356✔
3311
    if (last_row_index != 5) {
22,356✔
3312
        output->buffer[output->pos] = '-';
20,468✔
3313
        sixel_advance(output, 1);
20,468✔
3314
    }
3315

3316
    for (x = 0; (np = output->node_top) != NULL;) {
168,764✔
3317
        if (x > np->sx) {
146,408✔
3318
            output->buffer[output->pos] = '$';
124,056✔
3319
            sixel_advance(output, 1);
124,056✔
3320
            x = 0;
124,056✔
3321
        }
3322

3323
        if (state->fillable) {
146,408✔
3324
            memset(np->map + np->sx,
300✔
3325
                   (1 << state->row_in_band) - 1,
300✔
3326
                   (size_t)(np->mx - np->sx));
300✔
3327
        }
3328
        status = sixel_put_node(output,
146,408✔
3329
                                &x,
3330
                                np,
3331
                                ncolors,
3332
                                keycolor,
3333
                                metrics->emit_cells_ptr);
3334
        if (SIXEL_FAILED(status)) {
146,408!
3335
            goto end;
×
3336
        }
3337
        next = np->next;
146,408✔
3338
        sixel_node_del(output, np);
146,408✔
3339
        np = next;
146,408✔
3340

3341
        while (np != NULL) {
19,362,256✔
3342
            if (np->sx < x) {
19,215,848✔
3343
                np = np->next;
15,049,204✔
3344
                continue;
15,049,204✔
3345
            }
3346

3347
            if (state->fillable) {
4,166,644✔
3348
                memset(np->map + np->sx,
308✔
3349
                       (1 << state->row_in_band) - 1,
308✔
3350
                       (size_t)(np->mx - np->sx));
308✔
3351
            }
3352
            status = sixel_put_node(output,
4,166,644✔
3353
                                    &x,
3354
                                    np,
3355
                                    ncolors,
3356
                                    keycolor,
3357
                                    metrics->emit_cells_ptr);
3358
            if (SIXEL_FAILED(status)) {
4,166,644!
3359
                goto end;
×
3360
            }
3361
            next = np->next;
4,166,644✔
3362
            sixel_node_del(output, np);
4,166,644✔
3363
            np = next;
4,166,644✔
3364
        }
3365

3366
        state->fillable = 0;
146,408✔
3367
    }
3368

3369
    sixel_encode_span_commit(metrics->encode_probe_active,
22,356✔
3370
                             SIXEL_ASSESSMENT_STAGE_ENCODE_EMIT,
3371
                             span_started_at);
3372

3373
    status = SIXEL_OK;
22,356✔
3374

3375
end:
22,356✔
3376
    (void)work;
3377
    return status;
22,356✔
3378
}
3379

3380

3381
static SIXELSTATUS
3382
sixel_encode_body(
1,888✔
3383
    sixel_index_t       /* in */ *pixels,
3384
    int                 /* in */ width,
3385
    int                 /* in */ height,
3386
    unsigned char       /* in */ *palette,
3387
    float const         /* in */ *palette_float,
3388
    int                 /* in */ ncolors,
3389
    int                 /* in */ keycolor,
3390
    int                 /* in */ bodyonly,
3391
    sixel_output_t      /* in */ *output,
3392
    unsigned char       /* in */ *palstate,
3393
    sixel_allocator_t   /* in */ *allocator,
3394
    sixel_logger_t      /* in */ *logger)
3395
{
3396
    SIXELSTATUS status = SIXEL_FALSE;
1,888✔
3397
    int encode_probe_active;
3398
    double span_started_at;
3399
    int band_start;
3400
    int band_height;
3401
    int row_index;
3402
    int absolute_row;
3403
    int last_row_index;
3404
    sixel_node_t *np;
3405
    sixel_encode_work_t work;
3406
    sixel_band_state_t band;
3407
    sixel_encode_metrics_t metrics;
3408
    int logging_active;
3409
    int job_index;
3410

3411
    sixel_encode_work_init(&work);
1,888✔
3412
    sixel_band_state_reset(&band);
1,888✔
3413
    sixel_encode_metrics_init(&metrics);
1,888✔
3414

3415
    /* Record the caller/environment preference even before we fan out. */
3416
    work.requested_threads = sixel_threads_resolve();
1,888✔
3417

3418
    if (ncolors < 1) {
1,888!
3419
        status = SIXEL_BAD_ARGUMENT;
×
3420
        goto cleanup;
×
3421
    }
3422
    output->active_palette = (-1);
1,888✔
3423

3424
    encode_probe_active = sixel_assessment_encode_probe_enabled();
1,888✔
3425
    logging_active = logger != NULL && logger->active;
1,888!
3426
    job_index = 0;
1,888✔
3427
    metrics.encode_probe_active = encode_probe_active;
1,888✔
3428
    if (encode_probe_active != 0) {
1,888!
3429
        metrics.emit_cells_ptr = &metrics.emit_cells;
×
3430
    } else {
3431
        metrics.emit_cells_ptr = NULL;
1,888✔
3432
    }
3433

3434
    sixel_assessment_set_encode_parallelism(1);
1,888✔
3435

3436
    status = sixel_encode_emit_palette(bodyonly,
1,888✔
3437
                                       ncolors,
3438
                                       keycolor,
3439
                                       palette,
3440
                                       palette_float,
3441
                                       output,
3442
                                       encode_probe_active);
3443
    if (SIXEL_FAILED(status)) {
1,888!
3444
        goto cleanup;
×
3445
    }
3446

3447
#if SIXEL_ENABLE_THREADS
3448
    {
3449
        int nbands;
3450
        int threads;
3451

3452
        nbands = (height + 5) / 6;
1,416✔
3453
        threads = work.requested_threads;
1,416✔
3454
        if (nbands > 1 && threads > 1) {
1,416!
3455
            status = sixel_encode_body_parallel(pixels,
3456
                                                width,
3457
                                                height,
3458
                                                ncolors,
3459
                                                keycolor,
3460
                                                output,
3461
                                                palstate,
3462
                                                allocator,
3463
                                                threads);
3464
            if (SIXEL_FAILED(status)) {
×
3465
                goto cleanup;
3466
            }
3467
            goto finalize;
3468
        }
3469
    }
3470
#endif
3471

3472
    if (logging_active) {
1,888!
3473
        sixel_logger_logf(logger,
×
3474
                          "controller",
3475
                          "encode",
3476
                          "configure",
3477
                          -1,
3478
                          -1,
3479
                          0,
3480
                          height,
3481
                          0,
3482
                          height,
3483
                          "serial encoder bands=%d",
3484
                          (height + 5) / 6);
×
3485
    }
3486

3487
    status = sixel_encode_work_allocate(&work,
1,888✔
3488
                                        width,
3489
                                        ncolors,
3490
                                        allocator);
3491
    if (SIXEL_FAILED(status)) {
1,888!
3492
        goto cleanup;
×
3493
    }
3494

3495
    band_start = 0;
1,888✔
3496
    while (band_start < height) {
24,244✔
3497
        band_height = height - band_start;
22,356✔
3498
        if (band_height > 6) {
22,356✔
3499
            band_height = 6;
20,468✔
3500
        }
3501

3502
        band.row_in_band = 0;
22,356✔
3503
        band.fillable = 0;
22,356✔
3504
        band.active_color_count = 0;
22,356✔
3505

3506
        if (logging_active) {
22,356!
3507
            sixel_logger_logf(logger,
×
3508
                              "worker",
3509
                              "encode",
3510
                              "start",
3511
                              job_index,
3512
                              band_start,
3513
                              band_start,
3514
                              band_start + band_height,
3515
                              band_start,
3516
                              band_start + band_height,
3517
                              "serial band start");
3518
        }
3519

3520
        for (row_index = 0; row_index < band_height; row_index++) {
155,224✔
3521
            absolute_row = band_start + row_index;
132,868✔
3522
            span_started_at =
3523
                sixel_encode_span_start(encode_probe_active);
132,868✔
3524
            status = sixel_band_classify_row(&work,
132,868✔
3525
                                             &band,
3526
                                             pixels,
3527
                                             width,
3528
                                             absolute_row,
3529
                                             ncolors,
3530
                                             keycolor,
3531
                                             palstate,
3532
                                             output->encode_policy);
3533
            if (SIXEL_FAILED(status)) {
132,868!
3534
                goto cleanup;
×
3535
            }
3536
            sixel_encode_span_commit(
132,868✔
3537
                encode_probe_active,
3538
                SIXEL_ASSESSMENT_STAGE_ENCODE_CLASSIFY,
3539
                span_started_at);
3540
        }
3541

3542
        status = sixel_band_compose(&work,
22,356✔
3543
                                    &band,
3544
                                    output,
3545
                                    width,
3546
                                    ncolors,
3547
                                    keycolor,
3548
                                    allocator,
3549
                                    &metrics);
3550
        if (SIXEL_FAILED(status)) {
22,356!
3551
            goto cleanup;
×
3552
        }
3553

3554
        last_row_index = band_start + band_height - 1;
22,356✔
3555
        status = sixel_band_emit(&work,
22,356✔
3556
                                 &band,
3557
                                 output,
3558
                                 ncolors,
3559
                                 keycolor,
3560
                                 last_row_index,
3561
                                 &metrics);
3562
        if (SIXEL_FAILED(status)) {
22,356!
3563
            goto cleanup;
×
3564
        }
3565

3566
        sixel_band_finish(&work, &band);
22,356✔
3567

3568
        span_started_at =
3569
            sixel_encode_span_start(encode_probe_active);
22,356✔
3570
        sixel_band_clear_map(&work);
22,356✔
3571
        sixel_encode_span_commit(
22,356✔
3572
            encode_probe_active,
3573
            SIXEL_ASSESSMENT_STAGE_ENCODE_PREPARE,
3574
            span_started_at);
3575

3576
        if (logging_active) {
22,356!
3577
            sixel_logger_logf(logger,
×
3578
                              "worker",
3579
                              "encode",
3580
                              "finish",
3581
                              job_index,
3582
                              band_start + band_height - 1,
×
3583
                              band_start,
3584
                              band_start + band_height,
3585
                              band_start,
3586
                              band_start + band_height,
3587
                              "serial band done");
3588
        }
3589

3590
        band_start += band_height;
22,356✔
3591
        sixel_band_state_reset(&band);
22,356✔
3592
        job_index += 1;
22,356✔
3593
    }
3594

3595
    status = SIXEL_OK;
1,888✔
3596
    goto finalize;
1,888✔
3597

3598
finalize:
1,888✔
3599
    if (palstate) {
1,888✔
3600
        span_started_at = sixel_encode_span_start(encode_probe_active);
1,284✔
3601
        output->buffer[output->pos] = '$';
1,284✔
3602
        sixel_advance(output, 1);
1,284✔
3603
        sixel_encode_span_commit(encode_probe_active,
1,284✔
3604
                                 SIXEL_ASSESSMENT_STAGE_ENCODE_EMIT,
3605
                                 span_started_at);
3606
    }
3607

3608
    if (encode_probe_active) {
1,888!
3609
        sixel_assessment_record_encode_work(
×
3610
            SIXEL_ASSESSMENT_STAGE_ENCODE_EMIT,
3611
            metrics.emit_cells);
3612
    }
3613

3614
cleanup:
1,888✔
3615
    while ((np = output->node_free) != NULL) {
592,948✔
3616
        output->node_free = np->next;
591,060✔
3617
        sixel_allocator_free(allocator, np);
591,060✔
3618
    }
3619
    output->node_top = NULL;
1,888✔
3620

3621
    sixel_encode_work_cleanup(&work, allocator);
1,888✔
3622

3623
    return status;
1,888✔
3624
}
3625
static SIXELSTATUS
3626
sixel_encode_footer(sixel_output_t *output)
652✔
3627
{
3628
    SIXELSTATUS status = SIXEL_FALSE;
652✔
3629

3630
    if (!output->skip_dcs_envelope && !output->penetrate_multiplexer) {
652!
3631
        if (output->has_8bit_control) {
644✔
3632
            sixel_puts(output->buffer + output->pos,
20✔
3633
                       DCS_END_8BIT, DCS_END_8BIT_SIZE);
3634
            sixel_advance(output, DCS_END_8BIT_SIZE);
20✔
3635
        } else {
3636
            sixel_puts(output->buffer + output->pos,
624✔
3637
                       DCS_END_7BIT, DCS_END_7BIT_SIZE);
3638
            sixel_advance(output, DCS_END_7BIT_SIZE);
624✔
3639
        }
3640
    }
3641

3642
    if (output->pos > 0) {
652!
3643
        if (output->penetrate_multiplexer) {
652✔
3644
            sixel_penetrate(output,
8✔
3645
                            output->pos,
3646
                            DCS_START_7BIT,
3647
                            DCS_END_7BIT,
3648
                            DCS_START_7BIT_SIZE,
3649
                            DCS_END_7BIT_SIZE);
3650
            output->fn_write((char *)DCS_7BIT("\033") DCS_7BIT("\\"),
8✔
3651
                             (DCS_START_7BIT_SIZE + 1 +
3652
                              DCS_END_7BIT_SIZE) * 2,
3653
                             output->priv);
3654
        } else {
3655
            output->fn_write((char *)output->buffer,
644✔
3656
                             output->pos,
3657
                             output->priv);
3658
        }
3659
    }
3660

3661
    status = SIXEL_OK;
652✔
3662

3663
    return status;
652✔
3664
}
3665

3666
static SIXELSTATUS
3667
sixel_encode_body_ormode(
×
3668
    uint8_t             /* in */ *pixels,
3669
    int                 /* in */ width,
3670
    int                 /* in */ height,
3671
    unsigned char       /* in */ *palette,
3672
    int                 /* in */ ncolors,
3673
    int                 /* in */ keycolor,
3674
    sixel_output_t      /* in */ *output)
3675
{
3676
    SIXELSTATUS status;
3677
    int n;
3678
    int nplanes;
3679
    uint8_t *buf = pixels;
×
3680
    uint8_t *buf_p;
3681
    int x;
3682
    int cur_h;
3683
    int nwrite;
3684
    int plane;
3685

3686
    for (n = 0; n < ncolors; n++) {
×
3687
        status = output_rgb_palette_definition(output, palette, NULL, n, keycolor);
×
3688
        if (SIXEL_FAILED(status)) {
×
3689
            return status;
×
3690
        }
3691
    }
3692

3693
    for (nplanes = 8; nplanes > 1; nplanes--) {
×
3694
        if (ncolors > (1 << (nplanes - 1))) {
×
3695
            break;
×
3696
        }
3697
    }
3698

3699
    for (cur_h = 6; cur_h <= height; cur_h += 6) {
×
3700
        for (plane = 0; plane < nplanes; plane++) {
×
3701
            sixel_putc(output->buffer + output->pos, '#');
×
3702
            sixel_advance(output, 1);
×
3703
            nwrite = sixel_putnum((char *)output->buffer + output->pos, 1 << plane);
×
3704
            sixel_advance(output, nwrite);
×
3705

3706
            buf_p = buf;
×
3707
            for (x = 0; x < width; x++, buf_p++) {
×
3708
                sixel_put_pixel(output,
×
3709
                                ((buf_p[0] >> plane) & 0x1) |
×
3710
                                (((buf_p[width] >> plane) << 1) & 0x2) |
×
3711
                                (((buf_p[width * 2] >> plane) << 2) & 0x4) |
×
3712
                                (((buf_p[width * 3] >> plane) << 3) & 0x8) |
×
3713
                                (((buf_p[width * 4] >> plane) << 4) & 0x10) |
×
3714
                                (((buf_p[width * 5] >> plane) << 5) & 0x20),
×
3715
                                NULL);
3716
            }
3717
            status = sixel_put_flash(output);
×
3718
            if (SIXEL_FAILED(status)) {
×
3719
                return status;
×
3720
            }
3721
            sixel_putc(output->buffer + output->pos, '$');
×
3722
            sixel_advance(output, 1);
×
3723
        }
3724
        sixel_putc(output->buffer + output->pos, '-');
×
3725
        sixel_advance(output, 1);
×
3726
        buf += (width * 6);
×
3727
        }
3728

3729
    if (cur_h - height < 6) {
×
3730
        for (plane = 0; plane < nplanes; plane++) {
×
3731
            sixel_putc(output->buffer + output->pos, '#');
×
3732
            sixel_advance(output, 1);
×
3733
            nwrite = sixel_putnum((char *)output->buffer + output->pos, 1 << plane);
×
3734
            sixel_advance(output, nwrite);
×
3735

3736
            buf_p = buf;
×
3737
            for (x = 0; x < width; x++, buf_p++) {
×
3738
                int pix = ((buf_p[0] >> plane) & 0x1);
×
3739

3740
                switch(cur_h - height) {
×
3741
                case 1:
×
3742
                    pix |= (((buf_p[width * 4] >> plane) << 4) & 0x10);
×
3743
                    /* Fall through */
3744
                case 2:
×
3745
                    pix |= (((buf_p[width * 3] >> plane) << 3) & 0x8);
×
3746
                    /* Fall through */
3747
                case 3:
×
3748
                    pix |= (((buf_p[width * 2] >> plane) << 2) & 0x4);
×
3749
                    /* Fall through */
3750
                case 4:
×
3751
                    pix |= (((buf_p[width] >> plane) << 1) & 0x2);
×
3752
                    /* Fall through */
3753
                default:
×
3754
                    break;
×
3755
                }
3756

3757
                sixel_put_pixel(output, pix, NULL);
×
3758
            }
3759
            status = sixel_put_flash(output);
×
3760
            if (SIXEL_FAILED(status)) {
×
3761
                return status;
×
3762
            }
3763

3764
            sixel_putc(output->buffer + output->pos, '$');
×
3765
            sixel_advance(output, 1);
×
3766
        }
3767
    }
3768

3769
    return 0;
×
3770
}
3771

3772

3773
static SIXELSTATUS
3774
sixel_encode_dither(
604✔
3775
    unsigned char   /* in */ *pixels,   /* pixel bytes to be encoded */
3776
    int             /* in */ width,     /* width of source image */
3777
    int             /* in */ height,    /* height of source image */
3778
    sixel_dither_t  /* in */ *dither,   /* dither context */
3779
    sixel_output_t  /* in */ *output)   /* output context */
3780
{
3781
    SIXELSTATUS status = SIXEL_FALSE;
604✔
3782
    sixel_index_t *paletted_pixels = NULL;
604✔
3783
    sixel_index_t *input_pixels;
3784
    size_t bufsize;
3785
    unsigned char *palette_entries = NULL;
604✔
3786
    float *palette_entries_float32 = NULL;
604✔
3787
    sixel_palette_t *palette_obj = NULL;
604✔
3788
    size_t palette_count = 0U;
604✔
3789
    size_t palette_float_count = 0U;
604✔
3790
    size_t palette_bytes = 0U;
604✔
3791
    size_t palette_float_bytes = 0U;
604✔
3792
    size_t palette_channels = 0U;
604✔
3793
    size_t palette_index = 0U;
604✔
3794
    int palette_source_colorspace;
3795
    int palette_float_pixelformat;
3796
    int output_float_pixelformat;
3797
    int pipeline_active;
3798
    int pipeline_threads;
3799
    int pipeline_nbands;
3800
    sixel_parallel_dither_config_t dither_parallel;
3801
    char const *band_env_text;
3802
#if SIXEL_ENABLE_THREADS
3803
    sixel_logger_t serial_logger;
3804
    int logger_owned = 0;
453✔
3805
#endif  /* SIXEL_ENABLE_THREADS */
3806
    sixel_logger_t *logger = NULL;
604✔
3807

3808
    palette_source_colorspace = SIXEL_COLORSPACE_GAMMA;
604✔
3809
    palette_float_pixelformat =
3810
        sixel_palette_float_pixelformat_for_colorspace(
604✔
3811
            palette_source_colorspace);
3812
    output_float_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
604✔
3813
    status = sixel_dither_get_quantized_palette(dither, &palette_obj);
604✔
3814
    if (SIXEL_FAILED(status)) {
604!
3815
        sixel_helper_set_additional_message(
×
3816
            "sixel_encode_dither: palette acquisition failed.");
3817
        goto end;
×
3818
    }
3819

3820
    pipeline_active = 0;
604✔
3821
#if SIXEL_ENABLE_THREADS
3822
    sixel_logger_init(&serial_logger);
453✔
3823
#endif
3824
    dither_parallel.enabled = 0;
604✔
3825
    dither_parallel.band_height = 0;
604✔
3826
    dither_parallel.overlap = 0;
604✔
3827
    dither_parallel.dither_threads = 0;
604✔
3828
    dither_parallel.encode_threads = 0;
604✔
3829
    switch (dither->pixelformat) {
604!
3830
    case SIXEL_PIXELFORMAT_PAL1:
×
3831
    case SIXEL_PIXELFORMAT_PAL2:
3832
    case SIXEL_PIXELFORMAT_PAL4:
3833
    case SIXEL_PIXELFORMAT_G1:
3834
    case SIXEL_PIXELFORMAT_G2:
3835
    case SIXEL_PIXELFORMAT_G4:
3836
        bufsize = (sizeof(sixel_index_t) * (size_t)width * (size_t)height * 3UL);
×
3837
        paletted_pixels = (sixel_index_t *)sixel_allocator_malloc(dither->allocator, bufsize);
×
3838
        if (paletted_pixels == NULL) {
×
3839
            sixel_helper_set_additional_message(
×
3840
                "sixel_encode_dither: sixel_allocator_malloc() failed.");
3841
            status = SIXEL_BAD_ALLOCATION;
×
3842
            goto end;
×
3843
        }
3844
        status = sixel_helper_normalize_pixelformat(paletted_pixels,
×
3845
                                                    &dither->pixelformat,
3846
                                                    pixels,
3847
                                                    dither->pixelformat,
3848
                                                    width, height);
3849
        if (SIXEL_FAILED(status)) {
×
3850
            goto end;
×
3851
        }
3852
        input_pixels = paletted_pixels;
×
3853
        break;
×
3854
    case SIXEL_PIXELFORMAT_PAL8:
268✔
3855
    case SIXEL_PIXELFORMAT_G8:
3856
    case SIXEL_PIXELFORMAT_GA88:
3857
    case SIXEL_PIXELFORMAT_AG88:
3858
        input_pixels = pixels;
268✔
3859
        break;
268✔
3860
    default:
336✔
3861
        /* apply palette */
3862
        pipeline_threads = sixel_threads_resolve();
336✔
3863
        band_env_text = getenv("SIXEL_DITHER_PARALLEL_BAND_WIDTH");
336✔
3864
        if (pipeline_threads <= 1 && band_env_text != NULL
336!
3865
                && band_env_text[0] != '\0') {
×
3866
            /*
3867
             * Parallel band dithering was explicitly requested via the
3868
             * environment.  When SIXEL_THREADS is absent, prefer hardware
3869
             * concurrency instead of silently running a single worker so
3870
             * that multiple dither jobs appear in the log.
3871
             */
3872
            pipeline_threads = sixel_threads_normalize(0);
×
3873
        }
3874
        pipeline_nbands = (height + 5) / 6;
336✔
3875
        if (pipeline_threads > 1 && pipeline_nbands > 1) {
336!
3876
            pipeline_active = 1;
×
3877
            input_pixels = NULL;
×
3878
        } else {
3879
            paletted_pixels = sixel_dither_apply_palette(dither, pixels,
336✔
3880
                                                         width, height);
3881
            if (paletted_pixels == NULL) {
336!
3882
                status = SIXEL_RUNTIME_ERROR;
×
3883
                goto end;
×
3884
            }
3885
            input_pixels = paletted_pixels;
336✔
3886
        }
3887
        break;
336✔
3888
    }
3889

3890
    if (pipeline_active) {
604!
3891
        sixel_parallel_dither_configure(height,
×
3892
                                        dither->ncolors,
3893
                                        pipeline_threads,
3894
                                        &dither_parallel);
3895
        if (dither_parallel.enabled) {
×
3896
            dither->pipeline_parallel_active = 1;
×
3897
            dither->pipeline_band_height = dither_parallel.band_height;
×
3898
            dither->pipeline_band_overlap = dither_parallel.overlap;
×
3899
            dither->pipeline_dither_threads =
×
3900
                dither_parallel.dither_threads;
×
3901
            pipeline_threads = dither_parallel.encode_threads;
×
3902
        }
3903
        if (pipeline_threads <= 1) {
×
3904
            /*
3905
             * Disable the pipeline when the encode side cannot spawn at
3906
             * least two workers.  A single encode thread cannot consume the
3907
             * six-line jobs that PaletteApply produces, so fall back to the
3908
             * serialized encoder path.
3909
             */
3910
            pipeline_active = 0;
×
3911
            dither->pipeline_parallel_active = 0;
×
3912
            if (paletted_pixels == NULL) {
×
3913
                paletted_pixels = sixel_dither_apply_palette(dither, pixels,
×
3914
                                                             width, height);
3915
                if (paletted_pixels == NULL) {
×
3916
                    status = SIXEL_RUNTIME_ERROR;
×
3917
                    goto end;
×
3918
                }
3919
            }
3920
            input_pixels = paletted_pixels;
×
3921
        }
3922
    }
3923
#if SIXEL_ENABLE_THREADS
3924
    if (!pipeline_active) {
453!
3925
        logger = dither->pipeline_logger;
453✔
3926
        if (logger == NULL || !logger->active) {
453!
3927
            sixel_logger_prepare(&serial_logger);
453✔
3928
            if (serial_logger.active) {
453!
3929
                logger_owned = 1;
3930
                dither->pipeline_logger = &serial_logger;
3931
                logger = &serial_logger;
3932
            }
3933
        }
3934
        if (logger != NULL && logger->active) {
453!
3935
            sixel_logger_logf(logger,
3936
                              "controller",
3937
                              "pipeline",
3938
                              "configure",
3939
                              -1,
3940
                              -1,
3941
                              0,
3942
                              height,
3943
                              0,
3944
                              height,
3945
                              "serial path threads=1");
3946
        }
3947
    }
3948
#endif
3949

3950
    if (output != NULL) {
604!
3951
        palette_source_colorspace = output->source_colorspace;
604✔
3952
        palette_float_pixelformat =
3953
            sixel_palette_float_pixelformat_for_colorspace(
604✔
3954
                palette_source_colorspace);
3955
    }
3956

3957
    status = sixel_palette_copy_entries_8bit(
604✔
3958
        palette_obj,
3959
        &palette_entries,
3960
        &palette_count,
3961
        SIXEL_PIXELFORMAT_RGB888,
3962
        dither->allocator);
3963

3964
    if (SIXEL_SUCCEEDED(status)) {
604!
3965
        status = sixel_palette_copy_entries_float32(
604✔
3966
            palette_obj,
3967
            &palette_entries_float32,
3968
            &palette_float_count,
3969
            SIXEL_PIXELFORMAT_RGBFLOAT32,
3970
            dither->allocator);
3971
    }
3972

3973
    (void)palette_float_count;
3974

3975
    sixel_palette_unref(palette_obj);
604✔
3976
    palette_obj = NULL;
604✔
3977
    if (palette_entries != NULL && palette_entries_float32 != NULL
604!
3978
            && palette_count == palette_float_count
×
3979
            && palette_count > 0U
×
3980
            && !sixel_palette_float32_matches_u8(
×
3981
                    palette_entries,
3982
                    palette_entries_float32,
3983
                    palette_count,
3984
                    palette_float_pixelformat)) {
3985
        sixel_palette_sync_float32_from_u8(palette_entries,
×
3986
                                           palette_entries_float32,
3987
                                           palette_count,
3988
                                           palette_float_pixelformat);
3989
    }
3990
    if (palette_entries != NULL && palette_count > 0U
604!
3991
            && output != NULL
604!
3992
            && output->source_colorspace != output->colorspace) {
604!
3993
        palette_bytes = palette_count * 3U;
132✔
3994
        if (palette_entries_float32 != NULL
132!
3995
                && palette_float_count == palette_count) {
×
3996
            /*
3997
             * Use the higher-precision palette to change color spaces once and
3998
             * then quantize those float channels down to bytes.  The previous
3999
             * implementation converted the 8bit entries before overwriting
4000
             * them from float again, doubling the amount of work and rounding
4001
             * the palette twice.
4002
             */
4003
            palette_float_bytes = palette_bytes * sizeof(float);
×
4004
            status = sixel_helper_convert_colorspace(
×
4005
                (unsigned char *)palette_entries_float32,
4006
                palette_float_bytes,
4007
                palette_float_pixelformat,
4008
                output->source_colorspace,
4009
                output->colorspace);
4010
            if (SIXEL_FAILED(status)) {
×
4011
                sixel_helper_set_additional_message(
×
4012
                    "sixel_encode_dither: float palette colorspace conversion failed.");
4013
                goto end;
×
4014
            }
4015
            output_float_pixelformat =
4016
                sixel_palette_float_pixelformat_for_colorspace(
×
4017
                    output->colorspace);
4018
            palette_channels = palette_count * 3U;
×
4019
            for (palette_index = 0U; palette_index < palette_channels;
×
4020
                    ++palette_index) {
×
4021
                int channel;
4022

4023
                channel = (int)(palette_index % 3U);
×
4024
                palette_entries[palette_index] =
×
4025
                    sixel_pixelformat_float_channel_to_byte(
×
4026
                        output_float_pixelformat,
4027
                        channel,
4028
                        palette_entries_float32[palette_index]);
×
4029
            }
4030
        } else {
4031
            status = sixel_helper_convert_colorspace(palette_entries,
132✔
4032
                                                     palette_bytes,
4033
                                                     SIXEL_PIXELFORMAT_RGB888,
4034
                                                     output->source_colorspace,
4035
                                                     output->colorspace);
4036
            if (SIXEL_FAILED(status)) {
132!
4037
                sixel_helper_set_additional_message(
×
4038
                    "sixel_encode_dither: palette colorspace "
4039
                    "conversion failed.");
4040
                goto end;
×
4041
            }
4042
        }
4043
    }
4044
    if (SIXEL_FAILED(status) || palette_entries == NULL) {
604!
4045
        sixel_helper_set_additional_message(
×
4046
            "sixel_encode_dither: palette copy failed.");
4047
        goto end;
×
4048
    }
4049

4050
    if (dither->sixel_reversible && palette_count > 0U) {
604!
4051
        float *reversible_float = palette_entries_float32;
×
4052

4053
        if (reversible_float == NULL && palette_entries != NULL) {
×
4054
            palette_float_bytes = palette_count * 3U * sizeof(float);
×
4055
            reversible_float = (float *)sixel_allocator_malloc(
×
4056
                dither->allocator, palette_float_bytes);
4057
            if (reversible_float != NULL) {
×
4058
                palette_entries_float32 = reversible_float;
×
4059
                palette_float_count = palette_count;
×
4060
                for (palette_index = 0U; palette_index < palette_count * 3U;
×
4061
                        ++palette_index) {
×
4062
                    reversible_float[palette_index]
×
4063
                        = sixel_pixelformat_byte_to_float(
×
4064
                            output_float_pixelformat,
4065
                            (int)(palette_index % 3U),
×
4066
                            palette_entries[palette_index]);
×
4067
                }
4068
            }
4069
        }
4070
        if (reversible_float != NULL) {
×
4071
            sixel_palette_reversible_palette_float(
×
4072
                reversible_float,
4073
                palette_count,
4074
                output_float_pixelformat);
4075
            if (palette_entries != NULL) {
×
4076
                palette_channels = palette_count * 3U;
×
4077
                for (palette_index = 0U; palette_index < palette_channels;
×
4078
                        ++palette_index) {
×
4079
                    palette_entries[palette_index]
×
4080
                        = sixel_pixelformat_float_channel_to_byte(
×
4081
                            output_float_pixelformat,
4082
                            (int)(palette_index % 3U),
×
4083
                            reversible_float[palette_index]);
×
4084
                }
4085
            }
4086
        } else if (palette_entries != NULL) {
×
4087
            sixel_palette_reversible_palette(palette_entries,
×
4088
                                             palette_count,
4089
                                             SIXEL_PIXELFORMAT_RGB888);
4090
        }
4091
    }
4092

4093
    status = sixel_encode_header(width, height, output);
604✔
4094
    if (SIXEL_FAILED(status)) {
604!
4095
        goto end;
×
4096
    }
4097

4098
    if (output->ormode) {
604!
4099
        status = sixel_encode_body_ormode(input_pixels,
×
4100
                                          width,
4101
                                          height,
4102
                                          palette_entries,
4103
                                          dither->ncolors,
4104
                                          dither->keycolor,
4105
                                          output);
4106
    } else if (pipeline_active) {
604!
4107
        status = sixel_encode_body_pipeline(pixels,
×
4108
                                            width,
4109
                                            height,
4110
                                            palette_entries,
4111
                                            palette_entries_float32,
4112
                                            dither,
4113
                                            output,
4114
                                            pipeline_threads);
4115
    } else {
4116
        status = sixel_encode_body(input_pixels,
604!
4117
                                   width,
4118
                                   height,
4119
                                   palette_entries,
4120
                                   palette_entries_float32,
4121
                                   dither->ncolors,
4122
                                   dither->keycolor,
4123
                                   dither->bodyonly,
4124
                                   output,
4125
                                   NULL,
4126
                                   dither->allocator,
4127
                                   logger != NULL && logger->active ?
×
4128
                                       logger :
4129
                                       NULL);
4130
    }
4131

4132
    if (SIXEL_FAILED(status)) {
604!
4133
        goto end;
×
4134
    }
4135

4136
    status = sixel_encode_footer(output);
604✔
4137
    if (SIXEL_FAILED(status)) {
604!
4138
        goto end;
×
4139
    }
4140

4141
end:
604✔
4142
#if SIXEL_ENABLE_THREADS
4143
    if (logger_owned) {
453!
4144
        dither->pipeline_logger = NULL;
4145
        sixel_logger_close(&serial_logger);
4146
    }
4147
#endif
4148
    if (palette_obj != NULL) {
604!
4149
        sixel_palette_unref(palette_obj);
×
4150
    }
4151
    if (palette_entries != NULL) {
604!
4152
        sixel_allocator_free(dither->allocator, palette_entries);
604✔
4153
    }
4154
    if (palette_entries_float32 != NULL) {
604!
4155
        sixel_allocator_free(dither->allocator, palette_entries_float32);
×
4156
    }
4157
    sixel_allocator_free(dither->allocator, paletted_pixels);
604✔
4158

4159
    return status;
604✔
4160
}
4161

4162
static void
4163
dither_func_none(unsigned char *data, int width)
52,000✔
4164
{
4165
    (void) data;  /* unused */
4166
    (void) width; /* unused */
4167
}
52,000✔
4168

4169

4170
static void
4171
dither_func_fs(unsigned char *data, int width)
5,194,320✔
4172
{
4173
    int r, g, b;
4174
    int error_r = data[0] & 0x7;
5,194,320✔
4175
    int error_g = data[1] & 0x7;
5,194,320✔
4176
    int error_b = data[2] & 0x7;
5,194,320✔
4177

4178
    /* Floyd Steinberg Method
4179
     *          curr    7/16
4180
     *  3/16    5/48    1/16
4181
     */
4182
    r = (data[3 + 0] + (error_r * 5 >> 4));
5,194,320✔
4183
    g = (data[3 + 1] + (error_g * 5 >> 4));
5,194,320✔
4184
    b = (data[3 + 2] + (error_b * 5 >> 4));
5,194,320✔
4185
    data[3 + 0] = r > 0xff ? 0xff: r;
5,194,320✔
4186
    data[3 + 1] = g > 0xff ? 0xff: g;
5,194,320✔
4187
    data[3 + 2] = b > 0xff ? 0xff: b;
5,194,320✔
4188
    r = data[width * 3 - 3 + 0] + (error_r * 3 >> 4);
5,194,320✔
4189
    g = data[width * 3 - 3 + 1] + (error_g * 3 >> 4);
5,194,320✔
4190
    b = data[width * 3 - 3 + 2] + (error_b * 3 >> 4);
5,194,320✔
4191
    data[width * 3 - 3 + 0] = r > 0xff ? 0xff: r;
5,194,320✔
4192
    data[width * 3 - 3 + 1] = g > 0xff ? 0xff: g;
5,194,320✔
4193
    data[width * 3 - 3 + 2] = b > 0xff ? 0xff: b;
5,194,320✔
4194
    r = data[width * 3 + 0] + (error_r * 5 >> 4);
5,194,320✔
4195
    g = data[width * 3 + 1] + (error_g * 5 >> 4);
5,194,320✔
4196
    b = data[width * 3 + 2] + (error_b * 5 >> 4);
5,194,320✔
4197
    data[width * 3 + 0] = r > 0xff ? 0xff: r;
5,194,320✔
4198
    data[width * 3 + 1] = g > 0xff ? 0xff: g;
5,194,320✔
4199
    data[width * 3 + 2] = b > 0xff ? 0xff: b;
5,194,320✔
4200
}
5,194,320✔
4201

4202

4203
static void
4204
dither_func_atkinson(unsigned char *data, int width)
1,071,616✔
4205
{
4206
    int r, g, b;
4207
    int error_r = data[0] & 0x7;
1,071,616✔
4208
    int error_g = data[1] & 0x7;
1,071,616✔
4209
    int error_b = data[2] & 0x7;
1,071,616✔
4210

4211
    error_r += 4;
1,071,616✔
4212
    error_g += 4;
1,071,616✔
4213
    error_b += 4;
1,071,616✔
4214

4215
    /* Atkinson's Method
4216
     *          curr    1/8    1/8
4217
     *   1/8     1/8    1/8
4218
     *           1/8
4219
     */
4220
    r = data[(width * 0 + 1) * 3 + 0] + (error_r >> 3);
1,071,616✔
4221
    g = data[(width * 0 + 1) * 3 + 1] + (error_g >> 3);
1,071,616✔
4222
    b = data[(width * 0 + 1) * 3 + 2] + (error_b >> 3);
1,071,616✔
4223
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,071,616✔
4224
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,071,616✔
4225
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,071,616✔
4226
    r = data[(width * 0 + 2) * 3 + 0] + (error_r >> 3);
1,071,616✔
4227
    g = data[(width * 0 + 2) * 3 + 1] + (error_g >> 3);
1,071,616✔
4228
    b = data[(width * 0 + 2) * 3 + 2] + (error_b >> 3);
1,071,616✔
4229
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,071,616✔
4230
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,071,616✔
4231
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,071,616✔
4232
    r = data[(width * 1 - 1) * 3 + 0] + (error_r >> 3);
1,071,616✔
4233
    g = data[(width * 1 - 1) * 3 + 1] + (error_g >> 3);
1,071,616✔
4234
    b = data[(width * 1 - 1) * 3 + 2] + (error_b >> 3);
1,071,616✔
4235
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,071,616✔
4236
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,071,616✔
4237
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,071,616✔
4238
    r = data[(width * 1 + 0) * 3 + 0] + (error_r >> 3);
1,071,616✔
4239
    g = data[(width * 1 + 0) * 3 + 1] + (error_g >> 3);
1,071,616✔
4240
    b = data[(width * 1 + 0) * 3 + 2] + (error_b >> 3);
1,071,616✔
4241
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
1,071,616✔
4242
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
1,071,616✔
4243
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
1,071,616✔
4244
    r = (data[(width * 1 + 1) * 3 + 0] + (error_r >> 3));
1,071,616✔
4245
    g = (data[(width * 1 + 1) * 3 + 1] + (error_g >> 3));
1,071,616✔
4246
    b = (data[(width * 1 + 1) * 3 + 2] + (error_b >> 3));
1,071,616✔
4247
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,071,616✔
4248
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,071,616✔
4249
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,071,616✔
4250
    r = (data[(width * 2 + 0) * 3 + 0] + (error_r >> 3));
1,071,616✔
4251
    g = (data[(width * 2 + 0) * 3 + 1] + (error_g >> 3));
1,071,616✔
4252
    b = (data[(width * 2 + 0) * 3 + 2] + (error_b >> 3));
1,071,616✔
4253
    data[(width * 2 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
1,071,616✔
4254
    data[(width * 2 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
1,071,616✔
4255
    data[(width * 2 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
1,071,616✔
4256
}
1,071,616✔
4257

4258

4259
static void
4260
dither_func_jajuni(unsigned char *data, int width)
1,361,148✔
4261
{
4262
    int r, g, b;
4263
    int error_r = data[0] & 0x7;
1,361,148✔
4264
    int error_g = data[1] & 0x7;
1,361,148✔
4265
    int error_b = data[2] & 0x7;
1,361,148✔
4266

4267
    error_r += 4;
1,361,148✔
4268
    error_g += 4;
1,361,148✔
4269
    error_b += 4;
1,361,148✔
4270

4271
    /* Jarvis, Judice & Ninke Method
4272
     *                  curr    7/48    5/48
4273
     *  3/48    5/48    7/48    5/48    3/48
4274
     *  1/48    3/48    5/48    3/48    1/48
4275
     */
4276
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 7 / 48);
1,361,148✔
4277
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 7 / 48);
1,361,148✔
4278
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 7 / 48);
1,361,148✔
4279
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,361,148✔
4280
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,361,148✔
4281
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,361,148✔
4282
    r = data[(width * 0 + 2) * 3 + 0] + (error_r * 5 / 48);
1,361,148✔
4283
    g = data[(width * 0 + 2) * 3 + 1] + (error_g * 5 / 48);
1,361,148✔
4284
    b = data[(width * 0 + 2) * 3 + 2] + (error_b * 5 / 48);
1,361,148✔
4285
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,361,148✔
4286
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,361,148✔
4287
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,361,148✔
4288
    r = data[(width * 1 - 2) * 3 + 0] + (error_r * 3 / 48);
1,361,148✔
4289
    g = data[(width * 1 - 2) * 3 + 1] + (error_g * 3 / 48);
1,361,148✔
4290
    b = data[(width * 1 - 2) * 3 + 2] + (error_b * 3 / 48);
1,361,148✔
4291
    data[(width * 1 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,361,148!
4292
    data[(width * 1 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,361,148!
4293
    data[(width * 1 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,361,148!
4294
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 5 / 48);
1,361,148✔
4295
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 5 / 48);
1,361,148✔
4296
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 5 / 48);
1,361,148✔
4297
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,361,148✔
4298
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,361,148✔
4299
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,361,148✔
4300
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 7 / 48);
1,361,148✔
4301
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 7 / 48);
1,361,148✔
4302
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 7 / 48);
1,361,148✔
4303
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
1,361,148✔
4304
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
1,361,148✔
4305
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
1,361,148✔
4306
    r = data[(width * 1 + 1) * 3 + 0] + (error_r * 5 / 48);
1,361,148✔
4307
    g = data[(width * 1 + 1) * 3 + 1] + (error_g * 5 / 48);
1,361,148✔
4308
    b = data[(width * 1 + 1) * 3 + 2] + (error_b * 5 / 48);
1,361,148✔
4309
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,361,148!
4310
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,361,148✔
4311
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,361,148✔
4312
    r = data[(width * 1 + 2) * 3 + 0] + (error_r * 3 / 48);
1,361,148✔
4313
    g = data[(width * 1 + 2) * 3 + 1] + (error_g * 3 / 48);
1,361,148✔
4314
    b = data[(width * 1 + 2) * 3 + 2] + (error_b * 3 / 48);
1,361,148✔
4315
    data[(width * 1 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,361,148!
4316
    data[(width * 1 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,361,148!
4317
    data[(width * 1 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,361,148!
4318
    r = data[(width * 2 - 2) * 3 + 0] + (error_r * 1 / 48);
1,361,148✔
4319
    g = data[(width * 2 - 2) * 3 + 1] + (error_g * 1 / 48);
1,361,148✔
4320
    b = data[(width * 2 - 2) * 3 + 2] + (error_b * 1 / 48);
1,361,148✔
4321
    data[(width * 2 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,361,148!
4322
    data[(width * 2 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,361,148!
4323
    data[(width * 2 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,361,148!
4324
    r = data[(width * 2 - 1) * 3 + 0] + (error_r * 3 / 48);
1,361,148✔
4325
    g = data[(width * 2 - 1) * 3 + 1] + (error_g * 3 / 48);
1,361,148✔
4326
    b = data[(width * 2 - 1) * 3 + 2] + (error_b * 3 / 48);
1,361,148✔
4327
    data[(width * 2 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,361,148!
4328
    data[(width * 2 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,361,148!
4329
    data[(width * 2 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,361,148!
4330
    r = data[(width * 2 + 0) * 3 + 0] + (error_r * 5 / 48);
1,361,148✔
4331
    g = data[(width * 2 + 0) * 3 + 1] + (error_g * 5 / 48);
1,361,148✔
4332
    b = data[(width * 2 + 0) * 3 + 2] + (error_b * 5 / 48);
1,361,148✔
4333
    data[(width * 2 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
1,361,148!
4334
    data[(width * 2 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
1,361,148✔
4335
    data[(width * 2 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
1,361,148✔
4336
    r = data[(width * 2 + 1) * 3 + 0] + (error_r * 3 / 48);
1,361,148✔
4337
    g = data[(width * 2 + 1) * 3 + 1] + (error_g * 3 / 48);
1,361,148✔
4338
    b = data[(width * 2 + 1) * 3 + 2] + (error_b * 3 / 48);
1,361,148✔
4339
    data[(width * 2 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,361,148!
4340
    data[(width * 2 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,361,148!
4341
    data[(width * 2 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,361,148!
4342
    r = data[(width * 2 + 2) * 3 + 0] + (error_r * 1 / 48);
1,361,148✔
4343
    g = data[(width * 2 + 2) * 3 + 1] + (error_g * 1 / 48);
1,361,148✔
4344
    b = data[(width * 2 + 2) * 3 + 2] + (error_b * 1 / 48);
1,361,148✔
4345
    data[(width * 2 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,361,148!
4346
    data[(width * 2 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,361,148!
4347
    data[(width * 2 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,361,148!
4348
}
1,361,148✔
4349

4350

4351
static void
4352
dither_func_stucki(unsigned char *data, int width)
1,333,412✔
4353
{
4354
    int r, g, b;
4355
    int error_r = data[0] & 0x7;
1,333,412✔
4356
    int error_g = data[1] & 0x7;
1,333,412✔
4357
    int error_b = data[2] & 0x7;
1,333,412✔
4358

4359
    error_r += 4;
1,333,412✔
4360
    error_g += 4;
1,333,412✔
4361
    error_b += 4;
1,333,412✔
4362

4363
    /* Stucki's Method
4364
     *                  curr    8/48    4/48
4365
     *  2/48    4/48    8/48    4/48    2/48
4366
     *  1/48    2/48    4/48    2/48    1/48
4367
     */
4368
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 8 / 48);
1,333,412✔
4369
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 8 / 48);
1,333,412✔
4370
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 8 / 48);
1,333,412✔
4371
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,333,412✔
4372
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,333,412✔
4373
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,333,412✔
4374
    r = data[(width * 0 + 2) * 3 + 0] + (error_r * 4 / 48);
1,333,412✔
4375
    g = data[(width * 0 + 2) * 3 + 1] + (error_g * 4 / 48);
1,333,412✔
4376
    b = data[(width * 0 + 2) * 3 + 2] + (error_b * 4 / 48);
1,333,412✔
4377
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,333,412!
4378
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,333,412!
4379
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,333,412!
4380
    r = data[(width * 1 - 2) * 3 + 0] + (error_r * 2 / 48);
1,333,412✔
4381
    g = data[(width * 1 - 2) * 3 + 1] + (error_g * 2 / 48);
1,333,412✔
4382
    b = data[(width * 1 - 2) * 3 + 2] + (error_b * 2 / 48);
1,333,412✔
4383
    data[(width * 1 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,333,412!
4384
    data[(width * 1 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,333,412!
4385
    data[(width * 1 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,333,412!
4386
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 4 / 48);
1,333,412✔
4387
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 4 / 48);
1,333,412✔
4388
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 4 / 48);
1,333,412✔
4389
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,333,412!
4390
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,333,412!
4391
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,333,412!
4392
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 8 / 48);
1,333,412✔
4393
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 8 / 48);
1,333,412✔
4394
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 8 / 48);
1,333,412✔
4395
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
1,333,412!
4396
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
1,333,412✔
4397
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
1,333,412✔
4398
    r = data[(width * 1 + 1) * 3 + 0] + (error_r * 4 / 48);
1,333,412✔
4399
    g = data[(width * 1 + 1) * 3 + 1] + (error_g * 4 / 48);
1,333,412✔
4400
    b = data[(width * 1 + 1) * 3 + 2] + (error_b * 4 / 48);
1,333,412✔
4401
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,333,412!
4402
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,333,412!
4403
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,333,412!
4404
    r = data[(width * 1 + 2) * 3 + 0] + (error_r * 2 / 48);
1,333,412✔
4405
    g = data[(width * 1 + 2) * 3 + 1] + (error_g * 2 / 48);
1,333,412✔
4406
    b = data[(width * 1 + 2) * 3 + 2] + (error_b * 2 / 48);
1,333,412✔
4407
    data[(width * 1 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,333,412!
4408
    data[(width * 1 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,333,412!
4409
    data[(width * 1 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,333,412!
4410
    r = data[(width * 2 - 2) * 3 + 0] + (error_r * 1 / 48);
1,333,412✔
4411
    g = data[(width * 2 - 2) * 3 + 1] + (error_g * 1 / 48);
1,333,412✔
4412
    b = data[(width * 2 - 2) * 3 + 2] + (error_b * 1 / 48);
1,333,412✔
4413
    data[(width * 2 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,333,412!
4414
    data[(width * 2 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,333,412!
4415
    data[(width * 2 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,333,412!
4416
    r = data[(width * 2 - 1) * 3 + 0] + (error_r * 2 / 48);
1,333,412✔
4417
    g = data[(width * 2 - 1) * 3 + 1] + (error_g * 2 / 48);
1,333,412✔
4418
    b = data[(width * 2 - 1) * 3 + 2] + (error_b * 2 / 48);
1,333,412✔
4419
    data[(width * 2 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,333,412!
4420
    data[(width * 2 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,333,412!
4421
    data[(width * 2 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,333,412!
4422
    r = data[(width * 2 + 0) * 3 + 0] + (error_r * 4 / 48);
1,333,412✔
4423
    g = data[(width * 2 + 0) * 3 + 1] + (error_g * 4 / 48);
1,333,412✔
4424
    b = data[(width * 2 + 0) * 3 + 2] + (error_b * 4 / 48);
1,333,412✔
4425
    data[(width * 2 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
1,333,412!
4426
    data[(width * 2 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
1,333,412!
4427
    data[(width * 2 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
1,333,412!
4428
    r = data[(width * 2 + 1) * 3 + 0] + (error_r * 2 / 48);
1,333,412✔
4429
    g = data[(width * 2 + 1) * 3 + 1] + (error_g * 2 / 48);
1,333,412✔
4430
    b = data[(width * 2 + 1) * 3 + 2] + (error_b * 2 / 48);
1,333,412✔
4431
    data[(width * 2 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,333,412!
4432
    data[(width * 2 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,333,412!
4433
    data[(width * 2 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,333,412!
4434
    r = data[(width * 2 + 2) * 3 + 0] + (error_r * 1 / 48);
1,333,412✔
4435
    g = data[(width * 2 + 2) * 3 + 1] + (error_g * 1 / 48);
1,333,412✔
4436
    b = data[(width * 2 + 2) * 3 + 2] + (error_b * 1 / 48);
1,333,412✔
4437
    data[(width * 2 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,333,412!
4438
    data[(width * 2 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,333,412!
4439
    data[(width * 2 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,333,412!
4440
}
1,333,412✔
4441

4442

4443
static void
4444
dither_func_burkes(unsigned char *data, int width)
1,394,652✔
4445
{
4446
    int r, g, b;
4447
    int error_r = data[0] & 0x7;
1,394,652✔
4448
    int error_g = data[1] & 0x7;
1,394,652✔
4449
    int error_b = data[2] & 0x7;
1,394,652✔
4450

4451
    error_r += 2;
1,394,652✔
4452
    error_g += 2;
1,394,652✔
4453
    error_b += 2;
1,394,652✔
4454

4455
    /* Burkes' Method
4456
     *                  curr    4/16    2/16
4457
     *  1/16    2/16    4/16    2/16    1/16
4458
     */
4459
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 4 / 16);
1,394,652✔
4460
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 4 / 16);
1,394,652✔
4461
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 4 / 16);
1,394,652✔
4462
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,394,652✔
4463
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,394,652✔
4464
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,394,652✔
4465
    r = data[(width * 0 + 2) * 3 + 0] + (error_r * 2 / 16);
1,394,652✔
4466
    g = data[(width * 0 + 2) * 3 + 1] + (error_g * 2 / 16);
1,394,652✔
4467
    b = data[(width * 0 + 2) * 3 + 2] + (error_b * 2 / 16);
1,394,652✔
4468
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,394,652✔
4469
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,394,652✔
4470
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,394,652✔
4471
    r = data[(width * 1 - 2) * 3 + 0] + (error_r * 1 / 16);
1,394,652✔
4472
    g = data[(width * 1 - 2) * 3 + 1] + (error_g * 1 / 16);
1,394,652✔
4473
    b = data[(width * 1 - 2) * 3 + 2] + (error_b * 1 / 16);
1,394,652✔
4474
    data[(width * 1 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,394,652!
4475
    data[(width * 1 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,394,652!
4476
    data[(width * 1 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,394,652!
4477
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 2 / 16);
1,394,652✔
4478
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 2 / 16);
1,394,652✔
4479
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 2 / 16);
1,394,652✔
4480
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,394,652✔
4481
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,394,652✔
4482
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,394,652✔
4483
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 4 / 16);
1,394,652✔
4484
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 4 / 16);
1,394,652✔
4485
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 4 / 16);
1,394,652✔
4486
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
1,394,652✔
4487
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
1,394,652✔
4488
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
1,394,652✔
4489
    r = data[(width * 1 + 1) * 3 + 0] + (error_r * 2 / 16);
1,394,652✔
4490
    g = data[(width * 1 + 1) * 3 + 1] + (error_g * 2 / 16);
1,394,652✔
4491
    b = data[(width * 1 + 1) * 3 + 2] + (error_b * 2 / 16);
1,394,652✔
4492
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,394,652✔
4493
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,394,652✔
4494
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,394,652✔
4495
    r = data[(width * 1 + 2) * 3 + 0] + (error_r * 1 / 16);
1,394,652✔
4496
    g = data[(width * 1 + 2) * 3 + 1] + (error_g * 1 / 16);
1,394,652✔
4497
    b = data[(width * 1 + 2) * 3 + 2] + (error_b * 1 / 16);
1,394,652✔
4498
    data[(width * 1 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,394,652!
4499
    data[(width * 1 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,394,652!
4500
    data[(width * 1 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,394,652!
4501
}
1,394,652✔
4502

4503

4504
static void
4505
dither_func_sierra1(unsigned char *data, int width)
×
4506
{
4507
    int r, g, b;
4508
    int error_r = data[0] & 0x7;
×
4509
    int error_g = data[1] & 0x7;
×
4510
    int error_b = data[2] & 0x7;
×
4511

4512
    error_r += 2;
×
4513
    error_g += 2;
×
4514
    error_b += 2;
×
4515

4516
    /* Sierra Lite Method
4517
     *          curr    2/4
4518
     *  1/4     1/4
4519
     */
4520
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 2 / 4);
×
4521
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 2 / 4);
×
4522
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 2 / 4);
×
4523
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4524
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4525
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4526
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 1 / 4);
×
4527
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 1 / 4);
×
4528
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 1 / 4);
×
4529
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4530
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4531
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4532
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 1 / 4);
×
4533
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 1 / 4);
×
4534
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 1 / 4);
×
4535
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
×
4536
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
×
4537
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
×
4538
}
×
4539

4540

4541
static void
4542
dither_func_sierra2(unsigned char *data, int width)
×
4543
{
4544
    int r, g, b;
4545
    int error_r = data[0] & 0x7;
×
4546
    int error_g = data[1] & 0x7;
×
4547
    int error_b = data[2] & 0x7;
×
4548

4549
    error_r += 4;
×
4550
    error_g += 4;
×
4551
    error_b += 4;
×
4552

4553
    /* Sierra Two-row Method
4554
     *                  curr    4/32    3/32
4555
     *  1/32    2/32    3/32    2/32    1/32
4556
     *                  2/32    3/32    2/32
4557
     */
4558
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 4 / 32);
×
4559
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 4 / 32);
×
4560
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 4 / 32);
×
4561
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4562
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4563
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4564
    r = data[(width * 0 + 2) * 3 + 0] + (error_r * 3 / 32);
×
4565
    g = data[(width * 0 + 2) * 3 + 1] + (error_g * 3 / 32);
×
4566
    b = data[(width * 0 + 2) * 3 + 2] + (error_b * 3 / 32);
×
4567
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4568
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4569
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4570
    r = data[(width * 1 - 2) * 3 + 0] + (error_r * 1 / 32);
×
4571
    g = data[(width * 1 - 2) * 3 + 1] + (error_g * 1 / 32);
×
4572
    b = data[(width * 1 - 2) * 3 + 2] + (error_b * 1 / 32);
×
4573
    data[(width * 1 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4574
    data[(width * 1 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4575
    data[(width * 1 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4576
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 2 / 32);
×
4577
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 2 / 32);
×
4578
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 2 / 32);
×
4579
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4580
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4581
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4582
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 3 / 32);
×
4583
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 3 / 32);
×
4584
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 3 / 32);
×
4585
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
×
4586
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
×
4587
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
×
4588
    r = data[(width * 1 + 1) * 3 + 0] + (error_r * 2 / 32);
×
4589
    g = data[(width * 1 + 1) * 3 + 1] + (error_g * 2 / 32);
×
4590
    b = data[(width * 1 + 1) * 3 + 2] + (error_b * 2 / 32);
×
4591
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4592
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4593
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4594
    r = data[(width * 1 + 2) * 3 + 0] + (error_r * 1 / 32);
×
4595
    g = data[(width * 1 + 2) * 3 + 1] + (error_g * 1 / 32);
×
4596
    b = data[(width * 1 + 2) * 3 + 2] + (error_b * 1 / 32);
×
4597
    data[(width * 1 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4598
    data[(width * 1 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4599
    data[(width * 1 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4600
    r = data[(width * 2 - 1) * 3 + 0] + (error_r * 2 / 32);
×
4601
    g = data[(width * 2 - 1) * 3 + 1] + (error_g * 2 / 32);
×
4602
    b = data[(width * 2 - 1) * 3 + 2] + (error_b * 2 / 32);
×
4603
    data[(width * 2 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4604
    data[(width * 2 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4605
    data[(width * 2 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4606
    r = data[(width * 2 + 0) * 3 + 0] + (error_r * 3 / 32);
×
4607
    g = data[(width * 2 + 0) * 3 + 1] + (error_g * 3 / 32);
×
4608
    b = data[(width * 2 + 0) * 3 + 2] + (error_b * 3 / 32);
×
4609
    data[(width * 2 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
×
4610
    data[(width * 2 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
×
4611
    data[(width * 2 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
×
4612
    r = data[(width * 2 + 1) * 3 + 0] + (error_r * 2 / 32);
×
4613
    g = data[(width * 2 + 1) * 3 + 1] + (error_g * 2 / 32);
×
4614
    b = data[(width * 2 + 1) * 3 + 2] + (error_b * 2 / 32);
×
4615
    data[(width * 2 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4616
    data[(width * 2 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4617
    data[(width * 2 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4618
    r = data[(width * 2 + 2) * 3 + 0] + (error_r * 1 / 32);
×
4619
    g = data[(width * 2 + 2) * 3 + 1] + (error_g * 1 / 32);
×
4620
    b = data[(width * 2 + 2) * 3 + 2] + (error_b * 1 / 32);
×
4621
    data[(width * 2 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4622
    data[(width * 2 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4623
    data[(width * 2 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4624
}
×
4625

4626

4627
static void
4628
dither_func_sierra3(unsigned char *data, int width)
×
4629
{
4630
    int r, g, b;
4631
    int error_r = data[0] & 0x7;
×
4632
    int error_g = data[1] & 0x7;
×
4633
    int error_b = data[2] & 0x7;
×
4634

4635
    error_r += 4;
×
4636
    error_g += 4;
×
4637
    error_b += 4;
×
4638

4639
    /* Sierra-3 Method
4640
     *                  curr    5/32    3/32
4641
     *  2/32    4/32    5/32    4/32    2/32
4642
     *                  2/32    3/32    2/32
4643
     */
4644
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 5 / 32);
×
4645
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 5 / 32);
×
4646
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 5 / 32);
×
4647
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4648
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4649
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4650
    r = data[(width * 0 + 2) * 3 + 0] + (error_r * 3 / 32);
×
4651
    g = data[(width * 0 + 2) * 3 + 1] + (error_g * 3 / 32);
×
4652
    b = data[(width * 0 + 2) * 3 + 2] + (error_b * 3 / 32);
×
4653
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4654
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4655
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4656
    r = data[(width * 1 - 2) * 3 + 0] + (error_r * 2 / 32);
×
4657
    g = data[(width * 1 - 2) * 3 + 1] + (error_g * 2 / 32);
×
4658
    b = data[(width * 1 - 2) * 3 + 2] + (error_b * 2 / 32);
×
4659
    data[(width * 1 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4660
    data[(width * 1 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4661
    data[(width * 1 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4662
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 4 / 32);
×
4663
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 4 / 32);
×
4664
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 4 / 32);
×
4665
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4666
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4667
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4668
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 5 / 32);
×
4669
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 5 / 32);
×
4670
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 5 / 32);
×
4671
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
×
4672
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
×
4673
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
×
4674
    r = data[(width * 1 + 1) * 3 + 0] + (error_r * 4 / 32);
×
4675
    g = data[(width * 1 + 1) * 3 + 1] + (error_g * 4 / 32);
×
4676
    b = data[(width * 1 + 1) * 3 + 2] + (error_b * 4 / 32);
×
4677
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4678
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4679
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4680
    r = data[(width * 1 + 2) * 3 + 0] + (error_r * 2 / 32);
×
4681
    g = data[(width * 1 + 2) * 3 + 1] + (error_g * 2 / 32);
×
4682
    b = data[(width * 1 + 2) * 3 + 2] + (error_b * 2 / 32);
×
4683
    data[(width * 1 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4684
    data[(width * 1 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4685
    data[(width * 1 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4686
    r = data[(width * 2 - 1) * 3 + 0] + (error_r * 2 / 32);
×
4687
    g = data[(width * 2 - 1) * 3 + 1] + (error_g * 2 / 32);
×
4688
    b = data[(width * 2 - 1) * 3 + 2] + (error_b * 2 / 32);
×
4689
    data[(width * 2 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4690
    data[(width * 2 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4691
    data[(width * 2 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4692
    r = data[(width * 2 + 0) * 3 + 0] + (error_r * 3 / 32);
×
4693
    g = data[(width * 2 + 0) * 3 + 1] + (error_g * 3 / 32);
×
4694
    b = data[(width * 2 + 0) * 3 + 2] + (error_b * 3 / 32);
×
4695
    data[(width * 2 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
×
4696
    data[(width * 2 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
×
4697
    data[(width * 2 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
×
4698
    r = data[(width * 2 + 1) * 3 + 0] + (error_r * 2 / 32);
×
4699
    g = data[(width * 2 + 1) * 3 + 1] + (error_g * 2 / 32);
×
4700
    b = data[(width * 2 + 1) * 3 + 2] + (error_b * 2 / 32);
×
4701
    data[(width * 2 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4702
    data[(width * 2 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4703
    data[(width * 2 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4704
}
×
4705

4706

4707
static void
4708
dither_func_a_dither(unsigned char *data, int width, int x, int y)
30,000✔
4709
{
4710
    int c;
4711
    float value;
4712
    float mask;
4713

4714
    (void) width; /* unused */
4715

4716
    for (c = 0; c < 3; c ++) {
120,000✔
4717
        mask = (((x + c * 17) + y * 236) * 119) & 255;
90,000✔
4718
        mask = ((mask - 128) / 256.0f) ;
90,000✔
4719
        value = data[c] + mask;
90,000✔
4720
        if (value < 0) {
90,000!
4721
            value = 0;
×
4722
        }
4723
        value = value > 255 ? 255 : value;
90,000!
4724
        data[c] = value;
90,000✔
4725
    }
4726
}
30,000✔
4727

4728

4729
static void
4730
dither_func_x_dither(unsigned char *data, int width, int x, int y)
53,200✔
4731
{
4732
    int c;
4733
    float value;
4734
    float mask;
4735

4736
    (void) width;  /* unused */
4737

4738
    for (c = 0; c < 3; c ++) {
212,800✔
4739
        mask = (((x + c * 17) ^ y * 236) * 1234) & 511;
159,600✔
4740
        mask = ((mask - 128) / 512.0f) ;
159,600✔
4741
        value = data[c] + mask;
159,600✔
4742
        if (value < 0) {
159,600✔
4743
            value = 0;
264✔
4744
        }
4745
        value = value > 255 ? 255 : value;
159,600!
4746
        data[c] = value;
159,600✔
4747
    }
4748
}
53,200✔
4749

4750

4751
static void
4752
sixel_apply_15bpp_dither(
10,547,792✔
4753
    unsigned char *pixels,
4754
    int x, int y, int width, int height,
4755
    int method_for_diffuse)
4756
{
4757
    /* apply floyd steinberg dithering */
4758
    switch (method_for_diffuse) {
10,547,792!
4759
    case SIXEL_DIFFUSE_FS:
5,216,384✔
4760
        if (x < width - 1 && y < height - 1) {
5,216,384✔
4761
            dither_func_fs(pixels, width);
5,194,320✔
4762
        }
4763
        break;
5,216,384✔
4764
    case SIXEL_DIFFUSE_ATKINSON:
1,080,000✔
4765
        if (x < width - 2 && y < height - 2) {
1,080,000✔
4766
            dither_func_atkinson(pixels, width);
1,071,616✔
4767
        }
4768
        break;
1,080,000✔
4769
    case SIXEL_DIFFUSE_JAJUNI:
1,370,992✔
4770
        if (x < width - 2 && y < height - 2) {
1,370,992✔
4771
            dither_func_jajuni(pixels, width);
1,361,148✔
4772
        }
4773
        break;
1,370,992✔
4774
    case SIXEL_DIFFUSE_STUCKI:
1,343,052✔
4775
        if (x < width - 2 && y < height - 2) {
1,343,052✔
4776
            dither_func_stucki(pixels, width);
1,333,412✔
4777
        }
4778
        break;
1,343,052✔
4779
    case SIXEL_DIFFUSE_BURKES:
1,402,164✔
4780
        if (x < width - 2 && y < height - 1) {
1,402,164✔
4781
            dither_func_burkes(pixels, width);
1,394,652✔
4782
        }
4783
        break;
1,402,164✔
4784
    case SIXEL_DIFFUSE_SIERRA1:
×
4785
        if (x < width - 1 && y < height - 1) {
×
4786
            dither_func_sierra1(pixels, width);
×
4787
        }
4788
        break;
×
4789
    case SIXEL_DIFFUSE_SIERRA2:
×
4790
        if (x < width - 2 && y < height - 2) {
×
4791
            dither_func_sierra2(pixels, width);
×
4792
        }
4793
        break;
×
4794
    case SIXEL_DIFFUSE_SIERRA3:
×
4795
        if (x < width - 2 && y < height - 2) {
×
4796
            dither_func_sierra3(pixels, width);
×
4797
        }
4798
        break;
×
4799
    case SIXEL_DIFFUSE_A_DITHER:
30,000✔
4800
        dither_func_a_dither(pixels, width, x, y);
30,000✔
4801
        break;
30,000✔
4802
    case SIXEL_DIFFUSE_X_DITHER:
53,200✔
4803
        dither_func_x_dither(pixels, width, x, y);
53,200✔
4804
        break;
53,200✔
4805
    case SIXEL_DIFFUSE_NONE:
52,000✔
4806
    default:
4807
        dither_func_none(pixels, width);
52,000✔
4808
        break;
52,000✔
4809
    }
4810
}
10,547,792✔
4811

4812

4813
static SIXELSTATUS
4814
sixel_encode_highcolor(
48✔
4815
        unsigned char *pixels, int width, int height,
4816
        sixel_dither_t *dither, sixel_output_t *output
4817
        )
4818
{
4819
    SIXELSTATUS status = SIXEL_FALSE;
48✔
4820
    sixel_index_t *paletted_pixels = NULL;
48✔
4821
    unsigned char *normalized_pixels = NULL;
48✔
4822
    /* Mark sixel line pixels which have been already drawn. */
4823
    unsigned char *marks;
4824
    unsigned char *rgbhit;
4825
    unsigned char *rgb2pal;
4826
    unsigned char palhitcount[SIXEL_PALETTE_MAX];
4827
    unsigned char palstate[SIXEL_PALETTE_MAX];
4828
    int output_count;
4829
    int const maxcolors = 1 << 15;
48✔
4830
    int whole_size = width * height  /* for paletted_pixels */
48✔
4831
                   + maxcolors       /* for rgbhit */
48✔
4832
                   + maxcolors       /* for rgb2pal */
48✔
4833
                   + width * 6;      /* for marks */
48✔
4834
    int x, y;
4835
    unsigned char *dst;
4836
    unsigned char *mptr;
4837
    int dirty;
4838
    int mod_y;
4839
    int nextpal;
4840
    int threshold;
4841
    int pix;
4842
    int orig_height;
4843
    unsigned char *pal;
4844
    unsigned char *palette_entries = NULL;
48✔
4845
    sixel_palette_t *palette_obj = NULL;
48✔
4846
    size_t palette_count = 0U;
48✔
4847

4848
    if (dither->pixelformat != SIXEL_PIXELFORMAT_RGB888) {
48!
4849
        /* normalize pixelfromat */
4850
        normalized_pixels = (unsigned char *)sixel_allocator_malloc(dither->allocator,
×
4851
                                                                    (size_t)(width * height * 3));
×
4852
        if (normalized_pixels == NULL) {
×
4853
            goto error;
×
4854
        }
4855
        status = sixel_helper_normalize_pixelformat(normalized_pixels,
×
4856
                                                    &dither->pixelformat,
4857
                                                    pixels,
4858
                                                    dither->pixelformat,
4859
                                                    width, height);
4860
        if (SIXEL_FAILED(status)) {
×
4861
            goto error;
×
4862
        }
4863
        pixels = normalized_pixels;
×
4864
    }
4865

4866
    palette_entries = NULL;
48✔
4867
    palette_obj = NULL;
48✔
4868
    palette_count = 0U;
48✔
4869
    status = sixel_dither_get_quantized_palette(dither, &palette_obj);
48✔
4870
    if (SIXEL_FAILED(status)) {
48!
4871
        goto error;
×
4872
    }
4873
    status = sixel_palette_copy_entries_8bit(
48✔
4874
        palette_obj,
4875
        &palette_entries,
4876
        &palette_count,
4877
        SIXEL_PIXELFORMAT_RGB888,
4878
        dither->allocator);
4879
    sixel_palette_unref(palette_obj);
48✔
4880
    palette_obj = NULL;
48✔
4881
    if (SIXEL_FAILED(status) || palette_entries == NULL) {
48!
4882
        goto error;
×
4883
    }
4884

4885
    paletted_pixels = (sixel_index_t *)sixel_allocator_malloc(dither->allocator,
48✔
4886
                                                              (size_t)whole_size);
4887
    if (paletted_pixels == NULL) {
48!
4888
        goto error;
×
4889
    }
4890
    rgbhit = paletted_pixels + width * height;
48✔
4891
    memset(rgbhit, 0, (size_t)(maxcolors * 2 + width * 6));
48✔
4892
    rgb2pal = rgbhit + maxcolors;
48✔
4893
    marks = rgb2pal + maxcolors;
48✔
4894
    output_count = 0;
48✔
4895

4896
next:
1,284✔
4897
    dst = paletted_pixels;
1,284✔
4898
    nextpal = 0;
1,284✔
4899
    threshold = 1;
1,284✔
4900
    dirty = 0;
1,284✔
4901
    mptr = marks;
1,284✔
4902
    memset(palstate, 0, sizeof(palstate));
1,284✔
4903
    y = mod_y = 0;
1,284✔
4904

4905
    while (1) {
4906
        for (x = 0; x < width; x++, mptr++, dst++, pixels += 3) {
14,144,636✔
4907
            if (*mptr) {
14,120,800✔
4908
                *dst = 255;
3,573,008✔
4909
            } else {
4910
                sixel_apply_15bpp_dither(pixels,
10,547,792✔
4911
                                         x, y, width, height,
4912
                                         dither->method_for_diffuse);
4913
                pix = ((pixels[0] & 0xf8) << 7) |
10,547,792✔
4914
                      ((pixels[1] & 0xf8) << 2) |
10,547,792✔
4915
                      ((pixels[2] >> 3) & 0x1f);
10,547,792✔
4916

4917
                if (!rgbhit[pix]) {
10,547,792✔
4918
                    while (1) {
4919
                        if (nextpal >= 255) {
1,830,387✔
4920
                            if (threshold >= 255) {
879,077✔
4921
                                break;
876,592✔
4922
                            } else {
4923
                                threshold = (threshold == 1) ? 9: 255;
2,485✔
4924
                                nextpal = 0;
2,485✔
4925
                            }
4926
                        } else if (palstate[nextpal] ||
951,310✔
4927
                                 palhitcount[nextpal] > threshold) {
520,882✔
4928
                            nextpal++;
674,238✔
4929
                        } else {
4930
                            break;
4931
                        }
4932
                    }
4933

4934
                    if (nextpal >= 255) {
1,153,664✔
4935
                        dirty = 1;
876,592✔
4936
                        *dst = 255;
876,592✔
4937
                    } else {
4938
                        pal = palette_entries + (nextpal * 3);
277,072✔
4939

4940
                        rgbhit[pix] = 1;
277,072✔
4941
                        if (output_count > 0) {
277,072✔
4942
                            rgbhit[((pal[0] & 0xf8) << 7) |
272,576✔
4943
                                   ((pal[1] & 0xf8) << 2) |
272,576✔
4944
                                   ((pal[2] >> 3) & 0x1f)] = 0;
272,576✔
4945
                        }
4946
                        *dst = rgb2pal[pix] = nextpal++;
277,072✔
4947
                        *mptr = 1;
277,072✔
4948
                        palstate[*dst] = PALETTE_CHANGE;
277,072✔
4949
                        palhitcount[*dst] = 1;
277,072✔
4950
                        *(pal++) = pixels[0];
277,072✔
4951
                        *(pal++) = pixels[1];
277,072✔
4952
                        *(pal++) = pixels[2];
277,072✔
4953
                    }
4954
                } else {
4955
                    *dst = rgb2pal[pix];
9,394,128✔
4956
                    *mptr = 1;
9,394,128✔
4957
                    if (!palstate[*dst]) {
9,394,128✔
4958
                        palstate[*dst] = PALETTE_HIT;
40,472✔
4959
                    }
4960
                    if (palhitcount[*dst] < 255) {
9,394,128✔
4961
                        palhitcount[*dst]++;
2,243,902✔
4962
                    }
4963
                }
4964
            }
4965
        }
4966

4967
        if (++y >= height) {
23,836✔
4968
            if (dirty) {
48!
4969
                mod_y = 5;
×
4970
            } else {
4971
                goto end;
48✔
4972
            }
4973
        }
4974
        if (dirty && (mod_y == 5 || y >= height)) {
23,788!
4975
            orig_height = height;
1,236✔
4976

4977
            if (output_count++ == 0) {
1,236✔
4978
                status = sixel_encode_header(width, height, output);
16✔
4979
                if (SIXEL_FAILED(status)) {
16!
4980
                    goto error;
×
4981
                }
4982
            }
4983
            height = y;
1,236✔
4984
            status = sixel_encode_body(paletted_pixels,
1,236✔
4985
                                       width,
4986
                                       height,
4987
                                       palette_entries,
4988
                                       NULL,
4989
                                       255,
4990
                                       255,
4991
                                       dither->bodyonly,
4992
                                       output,
4993
                                       palstate,
4994
                                       dither->allocator,
4995
                                       NULL);
4996
            if (SIXEL_FAILED(status)) {
1,236!
4997
                goto error;
×
4998
            }
4999
            if (y >= orig_height) {
1,236!
5000
              goto end;
×
5001
            }
5002
            pixels -= (6 * width * 3);
1,236✔
5003
            height = orig_height - height + 6;
1,236✔
5004
            goto next;
1,236✔
5005
        }
5006

5007
        if (++mod_y == 6) {
22,552✔
5008
            mptr = (unsigned char *)memset(marks, 0, (size_t)(width * 6));
2,696✔
5009
            mod_y = 0;
2,696✔
5010
        }
5011
    }
5012

5013
    goto next;
5014

5015
end:
48✔
5016
    if (output_count == 0) {
48✔
5017
        status = sixel_encode_header(width, height, output);
32✔
5018
        if (SIXEL_FAILED(status)) {
32!
5019
            goto error;
×
5020
        }
5021
    }
5022
    status = sixel_encode_body(paletted_pixels,
48✔
5023
                               width,
5024
                               height,
5025
                               palette_entries,
5026
                               NULL,
5027
                               255,
5028
                               255,
5029
                               dither->bodyonly,
5030
                               output,
5031
                               palstate,
5032
                               dither->allocator,
5033
                               NULL);
5034
    if (SIXEL_FAILED(status)) {
48!
5035
        goto error;
×
5036
    }
5037

5038
    status = sixel_encode_footer(output);
48✔
5039
    if (SIXEL_FAILED(status)) {
48!
5040
        goto error;
×
5041
    }
5042

5043
error:
48✔
5044
    if (palette_entries != NULL) {
48!
5045
        sixel_allocator_free(dither->allocator, palette_entries);
48✔
5046
    }
5047
    sixel_allocator_free(dither->allocator, paletted_pixels);
48✔
5048
    sixel_allocator_free(dither->allocator, normalized_pixels);
48✔
5049

5050
    return status;
48✔
5051
}
5052

5053

5054
SIXELAPI SIXELSTATUS
5055
sixel_encode(
652✔
5056
    unsigned char  /* in */ *pixels,   /* pixel bytes */
5057
    int            /* in */ width,     /* image width */
5058
    int            /* in */ height,    /* image height */
5059
    int const      /* in */ depth,     /* color depth */
5060
    sixel_dither_t /* in */ *dither,   /* dither context */
5061
    sixel_output_t /* in */ *output)   /* output context */
5062
{
5063
    SIXELSTATUS status = SIXEL_FALSE;
652✔
5064

5065
    (void) depth;
5066

5067
    /* TODO: reference counting should be thread-safe */
5068
    sixel_dither_ref(dither);
652✔
5069
    sixel_output_ref(output);
652✔
5070

5071
    if (width < 1) {
652!
5072
        sixel_helper_set_additional_message(
×
5073
            "sixel_encode: bad width parameter."
5074
            " (width < 1)");
5075
        status = SIXEL_BAD_INPUT;
×
5076
        goto end;
×
5077
    }
5078

5079
    if (height < 1) {
652!
5080
        sixel_helper_set_additional_message(
×
5081
            "sixel_encode: bad height parameter."
5082
            " (height < 1)");
5083
        status = SIXEL_BAD_INPUT;
×
5084
        goto end;
×
5085
    }
5086

5087
    if (dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
652✔
5088
        status = sixel_encode_highcolor(pixels, width, height,
48✔
5089
                                        dither, output);
5090
    } else {
5091
        status = sixel_encode_dither(pixels, width, height,
604✔
5092
                                     dither, output);
5093
    }
5094

5095
end:
652✔
5096
    sixel_output_unref(output);
652✔
5097
    sixel_dither_unref(dither);
652✔
5098

5099
    return status;
652✔
5100
}
5101

5102
#if HAVE_TESTS
5103

5104
typedef struct sixel_tosixel_capture {
5105
    char buffer[256];
5106
} sixel_tosixel_capture_t;
5107

5108
static int
5109
sixel_tosixel_capture_write(char *data, int size, void *priv)
×
5110
{
5111
    sixel_tosixel_capture_t *capture;
5112

5113
    (void)data;
5114
    (void)size;
5115
    capture = (sixel_tosixel_capture_t *)priv;
×
5116
    if (capture != NULL && size >= 0) {
×
5117
        capture->buffer[0] = '\0';
×
5118
    }
5119

5120
    return size;
×
5121
}
5122

5123
static int
5124
test_emit_palette_prefers_float32(void)
×
5125
{
5126
    sixel_output_t *output;
5127
    sixel_tosixel_capture_t capture;
5128
    SIXELSTATUS status;
5129
    unsigned char palette_bytes[3] = { 0, 0, 0 };
×
5130
    float palette_float[3] = { 0.123f, 0.456f, 0.789f };
×
5131

5132
    output = NULL;
×
5133
    memset(&capture, 0, sizeof(capture));
×
5134

5135
    status = sixel_output_new(&output,
×
5136
                              sixel_tosixel_capture_write,
5137
                              &capture,
5138
                              NULL);
5139
    if (SIXEL_FAILED(status) || output == NULL) {
×
5140
        goto error;
×
5141
    }
5142

5143
    output->palette_type = SIXEL_PALETTETYPE_RGB;
×
5144
    output->pos = 0;
×
5145
    status = sixel_encode_emit_palette(0,
×
5146
                                       1,
5147
                                       (-1),
5148
                                       palette_bytes,
5149
                                       palette_float,
5150
                                       output,
5151
                                       0);
5152
    if (SIXEL_FAILED(status)) {
×
5153
        goto error;
×
5154
    }
5155

5156
    output->buffer[output->pos] = '\0';
×
5157
    if (strstr((char *)output->buffer, "#0;2;12;46;79") == NULL) {
×
5158
        goto error;
×
5159
    }
5160

5161
    sixel_output_unref(output);
×
5162
    return EXIT_SUCCESS;
×
5163

5164
error:
×
5165
    sixel_output_unref(output);
×
5166
    return EXIT_FAILURE;
×
5167
}
5168

5169
static int
5170
test_emit_palette_hls_from_float32(void)
×
5171
{
5172
    sixel_output_t *output;
5173
    sixel_tosixel_capture_t capture;
5174
    SIXELSTATUS status;
5175
    unsigned char palette_bytes[3] = { 0, 0, 0 };
×
5176
    float palette_float[3] = { 1.0f, 0.0f, 0.0f };
×
5177

5178
    output = NULL;
×
5179
    memset(&capture, 0, sizeof(capture));
×
5180

5181
    status = sixel_output_new(&output,
×
5182
                              sixel_tosixel_capture_write,
5183
                              &capture,
5184
                              NULL);
5185
    if (SIXEL_FAILED(status) || output == NULL) {
×
5186
        goto error;
×
5187
    }
5188

5189
    output->palette_type = SIXEL_PALETTETYPE_HLS;
×
5190
    output->pos = 0;
×
5191
    status = sixel_encode_emit_palette(0,
×
5192
                                       1,
5193
                                       (-1),
5194
                                       palette_bytes,
5195
                                       palette_float,
5196
                                       output,
5197
                                       0);
5198
    if (SIXEL_FAILED(status)) {
×
5199
        goto error;
×
5200
    }
5201

5202
    output->buffer[output->pos] = '\0';
×
5203
    if (strstr((char *)output->buffer, "#0;1;120;50;100") == NULL) {
×
5204
        goto error;
×
5205
    }
5206

5207
    sixel_output_unref(output);
×
5208
    return EXIT_SUCCESS;
×
5209

5210
error:
×
5211
    sixel_output_unref(output);
×
5212
    return EXIT_FAILURE;
×
5213
}
5214

5215
static int
5216
test_emit_palette_hls_from_bytes(void)
×
5217
{
5218
    sixel_output_t *output;
5219
    sixel_tosixel_capture_t capture;
5220
    SIXELSTATUS status;
5221
    unsigned char palette_bytes[3] = { 255, 0, 0 };
×
5222

5223
    output = NULL;
×
5224
    memset(&capture, 0, sizeof(capture));
×
5225

5226
    status = sixel_output_new(&output,
×
5227
                              sixel_tosixel_capture_write,
5228
                              &capture,
5229
                              NULL);
5230
    if (SIXEL_FAILED(status) || output == NULL) {
×
5231
        goto error;
×
5232
    }
5233

5234
    output->palette_type = SIXEL_PALETTETYPE_HLS;
×
5235
    output->pos = 0;
×
5236
    status = sixel_encode_emit_palette(0,
×
5237
                                       1,
5238
                                       (-1),
5239
                                       palette_bytes,
5240
                                       NULL,
5241
                                       output,
5242
                                       0);
5243
    if (SIXEL_FAILED(status)) {
×
5244
        goto error;
×
5245
    }
5246

5247
    output->buffer[output->pos] = '\0';
×
5248
    if (strstr((char *)output->buffer, "#0;1;120;50;100") == NULL) {
×
5249
        goto error;
×
5250
    }
5251

5252
    sixel_output_unref(output);
×
5253
    return EXIT_SUCCESS;
×
5254

5255
error:
×
5256
    sixel_output_unref(output);
×
5257
    return EXIT_FAILURE;
×
5258
}
5259

5260
static int
5261
test_reversible_snap_srgb_idempotent(void)
×
5262
{
5263
    unsigned char palette[6];
5264
    unsigned char expected[6];
5265
    size_t index;
5266

5267
    palette[0] = 13;
×
5268
    palette[1] = 42;
×
5269
    palette[2] = 254;
×
5270
    palette[3] = 200;
×
5271
    palette[4] = 100;
×
5272
    palette[5] = 1;
×
5273
    for (index = 0U; index < sizeof(palette); ++index) {
×
5274
        expected[index] = sixel_palette_reversible_value(palette[index]);
×
5275
    }
5276

5277
    sixel_palette_reversible_palette(palette, 2U, SIXEL_PIXELFORMAT_RGB888);
×
5278
    if (memcmp(palette, expected, sizeof(palette)) != 0) {
×
5279
        return EXIT_FAILURE;
×
5280
    }
5281
    sixel_palette_reversible_palette(palette, 2U, SIXEL_PIXELFORMAT_RGB888);
×
5282
    if (memcmp(palette, expected, sizeof(palette)) != 0) {
×
5283
        return EXIT_FAILURE;
×
5284
    }
5285

5286
    return EXIT_SUCCESS;
×
5287
}
5288

5289
static int
5290
test_reversible_snap_oklab_roundtrip(void)
×
5291
{
5292
    SIXELSTATUS status;
5293
    unsigned char palette_bytes[3];
5294
    unsigned char expected_bytes[3];
5295
    float oklab_palette[3];
5296
    float gamma_palette[3];
5297
    size_t index;
5298
    int channel;
5299

5300
    status = SIXEL_OK;
×
5301
    palette_bytes[0] = 25;
×
5302
    palette_bytes[1] = 180;
×
5303
    palette_bytes[2] = 90;
×
5304
    for (index = 0U; index < 3U; ++index) {
×
5305
        expected_bytes[index] = sixel_palette_reversible_value(
×
5306
            palette_bytes[index]);
5307
    }
5308

5309
    for (index = 0U; index < 3U; ++index) {
×
5310
        gamma_palette[index] = sixel_pixelformat_byte_to_float(
×
5311
            SIXEL_PIXELFORMAT_RGBFLOAT32,
5312
            (int)index,
5313
            palette_bytes[index]);
×
5314
    }
5315
    memcpy(oklab_palette, gamma_palette, sizeof(oklab_palette));
×
5316
    status = sixel_helper_convert_colorspace((unsigned char *)oklab_palette,
×
5317
                                             sizeof(oklab_palette),
5318
                                             SIXEL_PIXELFORMAT_RGBFLOAT32,
5319
                                             SIXEL_COLORSPACE_GAMMA,
5320
                                             SIXEL_COLORSPACE_OKLAB);
5321
    if (SIXEL_FAILED(status)) {
×
5322
        return EXIT_FAILURE;
×
5323
    }
5324
    for (index = 0U; index < 3U; ++index) {
×
5325
        channel = (int)index;
×
5326
        palette_bytes[index] = sixel_pixelformat_float_channel_to_byte(
×
5327
            SIXEL_PIXELFORMAT_OKLABFLOAT32,
5328
            channel,
5329
            oklab_palette[index]);
5330
    }
5331

5332
    sixel_palette_reversible_palette_float(oklab_palette,
×
5333
                                           1U,
5334
                                           SIXEL_PIXELFORMAT_OKLABFLOAT32);
5335
    for (index = 0U; index < 3U; ++index) {
×
5336
        channel = (int)index;
×
5337
        palette_bytes[index] = sixel_pixelformat_float_channel_to_byte(
×
5338
            SIXEL_PIXELFORMAT_OKLABFLOAT32,
5339
            channel,
5340
            oklab_palette[index]);
5341
    }
5342
    memcpy(gamma_palette, oklab_palette, sizeof(gamma_palette));
×
5343
    status = sixel_helper_convert_colorspace((unsigned char *)gamma_palette,
×
5344
                                             sizeof(gamma_palette),
5345
                                             SIXEL_PIXELFORMAT_RGBFLOAT32,
5346
                                             SIXEL_COLORSPACE_OKLAB,
5347
                                             SIXEL_COLORSPACE_GAMMA);
5348
    if (SIXEL_FAILED(status)) {
×
5349
        return EXIT_FAILURE;
×
5350
    }
5351
    for (index = 0U; index < 3U; ++index) {
×
5352
        channel = (int)index;
×
5353
        if (sixel_pixelformat_float_channel_to_byte(
×
5354
                SIXEL_PIXELFORMAT_RGBFLOAT32,
5355
                channel,
5356
                gamma_palette[index]) != expected_bytes[index]) {
×
5357
            return EXIT_FAILURE;
×
5358
        }
5359
    }
5360

5361
    memcpy(expected_bytes, palette_bytes, sizeof(expected_bytes));
×
5362
    sixel_palette_reversible_palette_float(oklab_palette,
×
5363
                                           1U,
5364
                                           SIXEL_PIXELFORMAT_OKLABFLOAT32);
5365
    for (index = 0U; index < 3U; ++index) {
×
5366
        channel = (int)index;
×
5367
        palette_bytes[index] = sixel_pixelformat_float_channel_to_byte(
×
5368
            SIXEL_PIXELFORMAT_OKLABFLOAT32,
5369
            channel,
5370
            oklab_palette[index]);
5371
    }
5372
    if (memcmp(palette_bytes, expected_bytes, sizeof(palette_bytes)) != 0) {
×
5373
        return EXIT_FAILURE;
×
5374
    }
5375

5376
    return EXIT_SUCCESS;
×
5377
}
5378

5379
SIXELAPI int
5380
sixel_tosixel_tests_main(void)
×
5381
{
5382
    int nret;
5383

5384
    nret = test_emit_palette_prefers_float32();
×
5385
    if (nret != EXIT_SUCCESS) {
×
5386
        return nret;
×
5387
    }
5388

5389
    nret = test_emit_palette_hls_from_float32();
×
5390
    if (nret != EXIT_SUCCESS) {
×
5391
        return nret;
×
5392
    }
5393

5394
    nret = test_emit_palette_hls_from_bytes();
×
5395
    if (nret != EXIT_SUCCESS) {
×
5396
        return nret;
×
5397
    }
5398

5399
    nret = test_reversible_snap_srgb_idempotent();
×
5400
    if (nret != EXIT_SUCCESS) {
×
5401
        return nret;
×
5402
    }
5403

5404
    nret = test_reversible_snap_oklab_roundtrip();
×
5405
    if (nret != EXIT_SUCCESS) {
×
5406
        return nret;
×
5407
    }
5408

5409
    return EXIT_SUCCESS;
×
5410
}
5411

5412
#endif  /* HAVE_TESTS */
5413

5414
/* emacs Local Variables:      */
5415
/* emacs mode: c               */
5416
/* emacs tab-width: 4          */
5417
/* emacs indent-tabs-mode: nil */
5418
/* emacs c-basic-offset: 4     */
5419
/* emacs End:                  */
5420
/* vim: set expandtab ts=4 sts=4 sw=4 : */
5421
/* 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