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

saitoha / libsixel / 19541344273

20 Nov 2025 03:02PM UTC coverage: 40.773% (-0.4%) from 41.21%
19541344273

push

github

saitoha
feat: initial prototyping for parallel dithering

9711 of 33880 branches covered (28.66%)

55 of 483 new or added lines in 10 files covered. (11.39%)

12 existing lines in 4 files now uncovered.

12720 of 31197 relevant lines covered (40.77%)

656879.66 hits per line

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

47.48
/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 "parallel-log.h"
67
#include "sixel_threads_config.h"
68
#if SIXEL_ENABLE_THREADS
69
# include "sixel_atomic.h"
70
# include "sixel_threading.h"
71
# include "threadpool.h"
72
#endif
73

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

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

91
/*
92
 * Thread configuration keeps the precedence rules centralized:
93
 *   1. Library callers may override via `sixel_set_threads`.
94
 *   2. Otherwise, `SIXEL_THREADS` from the environment is honored.
95
 *   3. Fallback defaults to single threaded execution.
96
 */
97
typedef struct sixel_thread_config_state {
98
    int requested_threads;
99
    int override_active;
100
    int env_threads;
101
    int env_valid;
102
    int env_checked;
103
} sixel_thread_config_state_t;
104

105
static sixel_thread_config_state_t g_thread_config = {
106
    1,
107
    0,
108
    1,
109
    0,
110
    0
111
};
112

113
typedef struct sixel_parallel_dither_config {
114
    int enabled;
115
    int band_height;
116
    int overlap;
117
    int dither_threads;
118
    int encode_threads;
119
    int dither_env_override;
120
} sixel_parallel_dither_config_t;
121

122
typedef struct sixel_encode_work {
123
    char *map;
124
    size_t map_size;
125
    sixel_node_t **columns;
126
    size_t columns_size;
127
    unsigned char *active_colors;
128
    size_t active_colors_size;
129
    int *active_color_index;
130
    size_t active_color_index_size;
131
    int requested_threads;
132
} sixel_encode_work_t;
133

134
typedef struct sixel_band_state {
135
    int row_in_band;
136
    int fillable;
137
    int active_color_count;
138
} sixel_band_state_t;
139

140
typedef struct sixel_encode_metrics {
141
    int encode_probe_active;
142
    double compose_span_started_at;
143
    double compose_queue_started_at;
144
    double compose_span_duration;
145
    double compose_scan_duration;
146
    double compose_queue_duration;
147
    double compose_scan_probes;
148
    double compose_queue_nodes;
149
    double emit_cells;
150
    double *emit_cells_ptr;
151
} sixel_encode_metrics_t;
152

153
#if SIXEL_ENABLE_THREADS
154
/*
155
 * Parallel execution relies on dedicated buffers per band and reusable
156
 * per-worker scratch spaces.  The main thread prepares the context and
157
 * pushes one job for each six-line band:
158
 *
159
 *   +------------------------+      enqueue jobs      +------------------+
160
 *   | main thread (producer) | ---------------------> | threadpool queue |
161
 *   +------------------------+                        +------------------+
162
 *            |                                                       |
163
 *            | allocate band buffers                                 |
164
 *            v                                                       v
165
 *   +------------------------+      execute callbacks      +-----------------+
166
 *   | per-worker workspace   | <-------------------------- | worker threads  |
167
 *   +------------------------+                             +-----------------+
168
 *            |
169
 *            | build SIXEL fragments
170
 *            v
171
 *   +------------------------+  ordered combination after join  +-----------+
172
 *   | band buffer array      | -------------------------------> | final I/O |
173
 *   +------------------------+                                  +-----------+
174
 */
175
typedef struct sixel_parallel_band_buffer {
176
    unsigned char *data;
177
    size_t size;
178
    size_t used;
179
    SIXELSTATUS status;
180
    int ready;
181
    int dispatched;
182
} sixel_parallel_band_buffer_t;
183

184
struct sixel_parallel_context;
185
typedef struct sixel_parallel_worker_state
186
    sixel_parallel_worker_state_t;
187

188
typedef struct sixel_parallel_context {
189
    sixel_index_t *pixels;
190
    int width;
191
    int height;
192
    int ncolors;
193
    int keycolor;
194
    unsigned char *palstate;
195
    int encode_policy;
196
    sixel_allocator_t *allocator;
197
    sixel_output_t *output;
198
    int encode_probe_active;
199
    int thread_count;
200
    int band_count;
201
    sixel_parallel_band_buffer_t *bands;
202
    sixel_parallel_worker_state_t **workers;
203
    int worker_capacity;
204
    int worker_registered;
205
    threadpool_t *pool;
206
    sixel_parallel_logger_t *logger;
207
    sixel_mutex_t mutex;
208
    int mutex_ready;
209
    sixel_cond_t cond_band_ready;
210
    int cond_ready;
211
    sixel_thread_t writer_thread;
212
    int writer_started;
213
    int next_band_to_flush;
214
    int writer_should_stop;
215
    SIXELSTATUS writer_error;
216
    int queue_capacity;
217
} sixel_parallel_context_t;
218

219
typedef struct sixel_parallel_row_notifier {
220
    sixel_parallel_context_t *context;
221
    sixel_parallel_logger_t *logger;
222
    int band_height;
223
    int image_height;
224
} sixel_parallel_row_notifier_t;
225

226
static void sixel_parallel_writer_stop(sixel_parallel_context_t *ctx,
227
                                       int force_abort);
228
static int sixel_parallel_writer_main(void *arg);
229
#endif
230

231
#if SIXEL_ENABLE_THREADS
232
static int sixel_parallel_jobs_allowed(sixel_parallel_context_t *ctx);
233
static void sixel_parallel_context_abort_locked(sixel_parallel_context_t *ctx,
234
                                               SIXELSTATUS status);
235
#endif
236

237
#if SIXEL_ENABLE_THREADS
238
struct sixel_parallel_worker_state {
239
    int initialized;
240
    int index;
241
    SIXELSTATUS writer_error;
242
    sixel_parallel_band_buffer_t *band_buffer;
243
    sixel_parallel_context_t *context;
244
    sixel_output_t *output;
245
    sixel_encode_work_t work;
246
    sixel_band_state_t band;
247
    sixel_encode_metrics_t metrics;
248
};
249
#endif
250

251
static void sixel_encode_work_init(sixel_encode_work_t *work);
252
static SIXELSTATUS sixel_encode_work_allocate(sixel_encode_work_t *work,
253
                                              int width,
254
                                              int ncolors,
255
                                              sixel_allocator_t *allocator);
256
static void sixel_encode_work_cleanup(sixel_encode_work_t *work,
257
                                      sixel_allocator_t *allocator);
258
static void sixel_band_state_reset(sixel_band_state_t *state);
259
static void sixel_band_finish(sixel_encode_work_t *work,
260
                              sixel_band_state_t *state);
261
static void sixel_band_clear_map(sixel_encode_work_t *work);
262
static void sixel_encode_metrics_init(sixel_encode_metrics_t *metrics);
263
static SIXELSTATUS sixel_band_classify_row(sixel_encode_work_t *work,
264
                                           sixel_band_state_t *state,
265
                                           sixel_index_t *pixels,
266
                                           int width,
267
                                           int absolute_row,
268
                                           int ncolors,
269
                                           int keycolor,
270
                                           unsigned char *palstate,
271
                                           int encode_policy);
272
static SIXELSTATUS sixel_band_compose(sixel_encode_work_t *work,
273
                                      sixel_band_state_t *state,
274
                                      sixel_output_t *output,
275
                                      int width,
276
                                      int ncolors,
277
                                      int keycolor,
278
                                      sixel_allocator_t *allocator,
279
                                      sixel_encode_metrics_t *metrics);
280
static SIXELSTATUS sixel_band_emit(sixel_encode_work_t *work,
281
                                   sixel_band_state_t *state,
282
                                   sixel_output_t *output,
283
                                   int ncolors,
284
                                   int keycolor,
285
                                   int last_row_index,
286
                                   sixel_encode_metrics_t *metrics);
287
static SIXELSTATUS sixel_put_flash(sixel_output_t *const output);
288
static void sixel_advance(sixel_output_t *output, int nwrite);
289

290
static int
291
sixel_threads_token_is_auto(char const *text)
×
292
{
293
    if (text == NULL) {
×
294
        return 0;
×
295
    }
296

297
    if ((text[0] == 'a' || text[0] == 'A') &&
×
298
        (text[1] == 'u' || text[1] == 'U') &&
×
299
        (text[2] == 't' || text[2] == 'T') &&
×
300
        (text[3] == 'o' || text[3] == 'O') &&
×
301
        text[4] == '\0') {
×
302
        return 1;
×
303
    }
304

305
    return 0;
×
306
}
307

308
static int
309
sixel_threads_normalize(int requested)
×
310
{
311
    int normalized;
312

313
#if SIXEL_ENABLE_THREADS
314
    int hw_threads;
315

316
    if (requested <= 0) {
×
317
        hw_threads = sixel_get_hw_threads();
×
318
        if (hw_threads < 1) {
×
319
            hw_threads = 1;
×
320
        }
321
        normalized = hw_threads;
×
322
    } else {
323
        normalized = requested;
×
324
    }
325

326
    if (normalized < 1) {
×
327
        normalized = 1;
×
328
    }
329
#else
330
    (void)requested;
331
    normalized = 1;
332
#endif
333

334
    return normalized;
×
335
}
336

337
static int
338
sixel_threads_parse_env_value(char const *text, int *value)
×
339
{
340
    long parsed;
341
    char *endptr;
342
    int normalized;
343

344
    if (text == NULL || value == NULL) {
×
345
        return 0;
×
346
    }
347

348
    if (sixel_threads_token_is_auto(text)) {
×
349
        normalized = sixel_threads_normalize(0);
×
350
        *value = normalized;
×
351
        return 1;
×
352
    }
353

354
    errno = 0;
×
355
    parsed = strtol(text, &endptr, 10);
×
356
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
×
357
        return 0;
×
358
    }
359

360
    if (parsed < 1) {
×
361
        normalized = sixel_threads_normalize(1);
×
362
    } else if (parsed > INT_MAX) {
×
363
        normalized = sixel_threads_normalize(INT_MAX);
×
364
    } else {
365
        normalized = sixel_threads_normalize((int)parsed);
×
366
    }
367

368
    *value = normalized;
×
369
    return 1;
×
370
}
371

372
static void
373
sixel_threads_load_env(void)
1,236✔
374
{
375
    char const *text;
376
    int parsed;
377

378
    if (g_thread_config.env_checked) {
1,236✔
379
        return;
1,236✔
380
    }
381

382
    g_thread_config.env_checked = 1;
278✔
383
    g_thread_config.env_valid = 0;
278✔
384

385
    text = getenv("SIXEL_THREADS");
278✔
386
    if (text == NULL || text[0] == '\0') {
278!
387
        return;
278✔
388
    }
389

390
    if (sixel_threads_parse_env_value(text, &parsed)) {
×
391
        g_thread_config.env_threads = parsed;
×
392
        g_thread_config.env_valid = 1;
×
393
    }
394
}
395

396
static int
397
sixel_threads_resolve(void)
1,236✔
398
{
399
    int resolved;
400

401
#if SIXEL_ENABLE_THREADS
402
    if (g_thread_config.override_active) {
1,236!
403
        return g_thread_config.requested_threads;
×
404
    }
405
#endif
406

407
    sixel_threads_load_env();
1,236✔
408

409
#if SIXEL_ENABLE_THREADS
410
    if (g_thread_config.env_valid) {
1,236!
411
        resolved = g_thread_config.env_threads;
×
412
    } else {
413
        resolved = 1;
1,236✔
414
    }
415
#else
416
    resolved = 1;
417
#endif
418

419
    return resolved;
1,236✔
420
}
421

422
/*
423
 * Public setter so CLI/bindings may override the runtime thread preference.
424
 */
425
SIXELAPI void
426
sixel_set_threads(int threads)
×
427
{
428
#if SIXEL_ENABLE_THREADS
429
    g_thread_config.requested_threads = sixel_threads_normalize(threads);
×
430
#else
431
    (void)threads;
432
    g_thread_config.requested_threads = 1;
433
#endif
434
    g_thread_config.override_active = 1;
×
435
}
×
436

437
#if SIXEL_ENABLE_THREADS
438
static void
NEW
439
sixel_parallel_logger_prepare(sixel_parallel_logger_t *logger)
×
440
{
441
    char const *path;
442
    SIXELSTATUS status;
443

NEW
444
    if (logger == NULL) {
×
NEW
445
        return;
×
446
    }
447

NEW
448
    sixel_parallel_logger_init(logger);
×
NEW
449
    path = getenv("SIXEL_PARALLEL_LOG_PATH");
×
NEW
450
    if (path == NULL || path[0] == '\0') {
×
NEW
451
        return;
×
452
    }
453

NEW
454
    status = sixel_parallel_logger_open(logger, path);
×
NEW
455
    if (SIXEL_FAILED(status)) {
×
NEW
456
        sixel_parallel_logger_close(logger);
×
457
    }
458
}
459
#endif
460

461
static void
NEW
462
sixel_parallel_dither_configure(int height,
×
463
                                int pipeline_threads,
464
                                sixel_parallel_dither_config_t *config)
465
{
466
    char const *text;
467
    long parsed;
468
    char *endptr;
469
    int band_height;
470
    int overlap;
471
    int dither_threads;
472
    int encode_threads;
473
    int dither_env_override;
474

NEW
475
    if (config == NULL) {
×
NEW
476
        return;
×
477
    }
478

NEW
479
    config->enabled = 0;
×
NEW
480
    config->band_height = 0;
×
NEW
481
    config->overlap = 0;
×
NEW
482
    config->dither_threads = 0;
×
NEW
483
    config->encode_threads = pipeline_threads;
×
484

NEW
485
    text = getenv("SIXEL_DITHER_PARALLEL_BAND_WIDTH");
×
NEW
486
    if (text == NULL || text[0] == '\0') {
×
NEW
487
        return;
×
488
    }
489

NEW
490
    errno = 0;
×
NEW
491
    parsed = strtol(text, &endptr, 10);
×
NEW
492
    if (endptr == text || errno == ERANGE || parsed <= 0) {
×
NEW
493
        band_height = 72;
×
494
    } else {
NEW
495
        band_height = (int)parsed;
×
496
    }
NEW
497
    if (band_height < 6) {
×
NEW
498
        band_height = 6;
×
499
    }
NEW
500
    if ((band_height % 6) != 0) {
×
NEW
501
        band_height = ((band_height + 5) / 6) * 6;
×
502
    }
503

NEW
504
    text = getenv("SIXEL_DITHER_PARALLEL_BAND_OVERWRAP");
×
NEW
505
    if (text == NULL || text[0] == '\0') {
×
NEW
506
        overlap = 12;
×
507
    } else {
NEW
508
        errno = 0;
×
NEW
509
        parsed = strtol(text, &endptr, 10);
×
NEW
510
        if (endptr == text || errno == ERANGE || parsed < 0) {
×
NEW
511
            overlap = 12;
×
512
        } else {
NEW
513
            overlap = (int)parsed;
×
514
        }
515
    }
NEW
516
    if (overlap < 0) {
×
NEW
517
        overlap = 0;
×
518
    }
NEW
519
    if (overlap > band_height / 2) {
×
NEW
520
        overlap = band_height / 2;
×
521
    }
522

NEW
523
    if (pipeline_threads <= 1 || height <= 0) {
×
NEW
524
        return;
×
525
    }
526

NEW
527
    dither_env_override = 0;
×
NEW
528
    dither_threads = (pipeline_threads * 7 + 9) / 10;
×
NEW
529
    text = getenv("SIXEL_DITHER_PARALLEL_THREADS_MAX");
×
NEW
530
    if (text != NULL && text[0] != '\0') {
×
NEW
531
        errno = 0;
×
NEW
532
        parsed = strtol(text, &endptr, 10);
×
NEW
533
        if (endptr != text && errno != ERANGE && parsed > 0) {
×
NEW
534
            if (parsed > INT_MAX) {
×
NEW
535
                parsed = INT_MAX;
×
536
            }
NEW
537
            dither_threads = (int)parsed;
×
NEW
538
            dither_env_override = 1;
×
539
        }
540
    }
NEW
541
    if (dither_threads < 1) {
×
NEW
542
        dither_threads = 1;
×
543
    }
NEW
544
    if (dither_threads > pipeline_threads) {
×
NEW
545
        dither_threads = pipeline_threads;
×
546
    }
547

NEW
548
    if (!dither_env_override && pipeline_threads >= 4 && dither_threads < 2) {
×
549
        /*
550
         * When the total budget is ample, keep at least two dither workers so
551
         * the banded producer can feed the encoder fast enough to pipeline.
552
         */
NEW
553
        dither_threads = pipeline_threads - 2;
×
554
    }
555

NEW
556
    encode_threads = pipeline_threads - dither_threads;
×
NEW
557
    if (encode_threads < 2 && pipeline_threads > 2) {
×
558
        /*
559
         * Preserve a minimal pair of encoder workers to keep the pipeline
560
         * alive while leaving the rest to dithering. Small budgets fall back
561
         * to the serial encoder path later in the caller.
562
         */
NEW
563
        encode_threads = 2;
×
NEW
564
        dither_threads = pipeline_threads - encode_threads;
×
565
    }
NEW
566
    if (encode_threads < 1) {
×
NEW
567
        encode_threads = 1;
×
NEW
568
        dither_threads = pipeline_threads - encode_threads;
×
569
    }
NEW
570
    if (dither_threads < 1) {
×
NEW
571
        return;
×
572
    }
573

NEW
574
    config->enabled = 1;
×
NEW
575
    config->band_height = band_height;
×
NEW
576
    config->overlap = overlap;
×
NEW
577
    config->dither_threads = dither_threads;
×
NEW
578
    config->encode_threads = encode_threads;
×
579
}
580

581
#if SIXEL_ENABLE_THREADS
582
static int sixel_parallel_band_writer(char *data, int size, void *priv);
583
static int sixel_parallel_worker_main(tp_job_t job,
584
                                      void *userdata,
585
                                      void *workspace);
586
static SIXELSTATUS
587
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
588
                             sixel_index_t *pixels,
589
                             int width,
590
                             int height,
591
                             int ncolors,
592
                             int keycolor,
593
                             unsigned char *palstate,
594
                             sixel_output_t *output,
595
                             sixel_allocator_t *allocator,
596
                             int requested_threads,
597
                             int queue_capacity,
598
                             sixel_parallel_logger_t *logger);
599
static void sixel_parallel_submit_band(sixel_parallel_context_t *ctx,
600
                                       int band_index);
601
static SIXELSTATUS sixel_parallel_context_wait(sixel_parallel_context_t *ctx,
602
                                               int force_abort);
603
static void sixel_parallel_palette_row_ready(void *priv, int row_index);
604
static SIXELSTATUS sixel_encode_emit_palette(int bodyonly,
605
                          int ncolors,
606
                          int keycolor,
607
                          unsigned char const *palette,
608
                          float const *palette_float,
609
                          sixel_output_t *output,
610
                          int encode_probe_active);
611

612
static void
613
sixel_parallel_context_init(sixel_parallel_context_t *ctx)
×
614
{
615
    ctx->pixels = NULL;
×
616
    ctx->width = 0;
×
617
    ctx->height = 0;
×
618
    ctx->ncolors = 0;
×
619
    ctx->keycolor = (-1);
×
620
    ctx->palstate = NULL;
×
621
    ctx->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
×
622
    ctx->allocator = NULL;
×
623
    ctx->output = NULL;
×
624
    ctx->encode_probe_active = 0;
×
625
    ctx->thread_count = 0;
×
626
    ctx->band_count = 0;
×
627
    ctx->bands = NULL;
×
628
    ctx->workers = NULL;
×
629
    ctx->worker_capacity = 0;
×
630
    ctx->worker_registered = 0;
×
631
    ctx->pool = NULL;
×
NEW
632
    ctx->logger = NULL;
×
633
    ctx->mutex_ready = 0;
×
634
    ctx->cond_ready = 0;
×
635
    ctx->writer_started = 0;
×
636
    ctx->next_band_to_flush = 0;
×
637
    ctx->writer_should_stop = 0;
×
638
    ctx->writer_error = SIXEL_OK;
×
639
    ctx->queue_capacity = 0;
×
640
}
×
641

642
static void
643
sixel_parallel_worker_release_nodes(sixel_parallel_worker_state_t *state,
×
644
                                    sixel_allocator_t *allocator)
645
{
646
    sixel_node_t *np;
647

648
    if (state == NULL || state->output == NULL) {
×
649
        return;
×
650
    }
651

652
    while ((np = state->output->node_free) != NULL) {
×
653
        state->output->node_free = np->next;
×
654
        sixel_allocator_free(allocator, np);
×
655
    }
656
    state->output->node_top = NULL;
×
657
}
658

659
static void
660
sixel_parallel_worker_cleanup(sixel_parallel_worker_state_t *state,
×
661
                              sixel_allocator_t *allocator)
662
{
663
    if (state == NULL) {
×
664
        return;
×
665
    }
666
    sixel_parallel_worker_release_nodes(state, allocator);
×
667
    if (state->output != NULL) {
×
668
        sixel_output_unref(state->output);
×
669
        state->output = NULL;
×
670
    }
671
    sixel_encode_work_cleanup(&state->work, allocator);
×
672
    sixel_band_state_reset(&state->band);
×
673
    sixel_encode_metrics_init(&state->metrics);
×
674
    state->initialized = 0;
×
675
    state->index = 0;
×
676
    state->writer_error = SIXEL_OK;
×
677
    state->band_buffer = NULL;
×
678
    state->context = NULL;
×
679
}
680

681
static void
682
sixel_parallel_context_cleanup(sixel_parallel_context_t *ctx)
×
683
{
684
    int i;
685

686
    if (ctx->workers != NULL) {
×
687
        for (i = 0; i < ctx->worker_capacity; i++) {
×
688
            sixel_parallel_worker_cleanup(ctx->workers[i], ctx->allocator);
×
689
        }
690
        free(ctx->workers);
×
691
        ctx->workers = NULL;
×
692
    }
693
    sixel_parallel_writer_stop(ctx, 1);
×
694
    if (ctx->bands != NULL) {
×
695
        for (i = 0; i < ctx->band_count; i++) {
×
696
            free(ctx->bands[i].data);
×
697
        }
698
        free(ctx->bands);
×
699
        ctx->bands = NULL;
×
700
    }
701
    if (ctx->pool != NULL) {
×
702
        threadpool_destroy(ctx->pool);
×
703
        ctx->pool = NULL;
×
704
    }
705
    if (ctx->cond_ready) {
×
706
        sixel_cond_destroy(&ctx->cond_band_ready);
×
707
        ctx->cond_ready = 0;
×
708
    }
709
    if (ctx->mutex_ready) {
×
710
        sixel_mutex_destroy(&ctx->mutex);
×
711
        ctx->mutex_ready = 0;
×
712
    }
713
}
×
714

715
/*
716
 * Abort the pipeline when either a worker or the writer encounters an error.
717
 * The helper normalizes the `writer_error` bookkeeping so later callers see a
718
 * consistent stop request regardless of whether the mutex has been
719
 * initialized.
720
 */
721
static void
722
sixel_parallel_context_abort_locked(sixel_parallel_context_t *ctx,
×
723
                                    SIXELSTATUS status)
724
{
725
    if (ctx == NULL) {
×
726
        return;
×
727
    }
728
    if (!ctx->mutex_ready) {
×
729
        if (ctx->writer_error == SIXEL_OK) {
×
730
            ctx->writer_error = status;
×
731
        }
732
        ctx->writer_should_stop = 1;
×
733
        return;
×
734
    }
735

736
    sixel_mutex_lock(&ctx->mutex);
×
737
    if (ctx->writer_error == SIXEL_OK) {
×
738
        ctx->writer_error = status;
×
739
    }
740
    ctx->writer_should_stop = 1;
×
741
    sixel_cond_broadcast(&ctx->cond_band_ready);
×
742
    sixel_mutex_unlock(&ctx->mutex);
×
743
}
744

745
/*
746
 * Determine whether additional bands should be queued or executed.  The
747
 * producer and workers call this guard to avoid redundant work once the writer
748
 * decides to shut the pipeline down.
749
 */
750
static int
751
sixel_parallel_jobs_allowed(sixel_parallel_context_t *ctx)
×
752
{
753
    int accept;
754

755
    if (ctx == NULL) {
×
756
        return 0;
×
757
    }
758
    if (!ctx->mutex_ready) {
×
759
        if (ctx->writer_should_stop || ctx->writer_error != SIXEL_OK) {
×
760
            return 0;
×
761
        }
762
        return 1;
×
763
    }
764

765
    sixel_mutex_lock(&ctx->mutex);
×
766
    accept = (!ctx->writer_should_stop && ctx->writer_error == SIXEL_OK);
×
767
    sixel_mutex_unlock(&ctx->mutex);
×
768
    return accept;
×
769
}
770

771
static void
772
sixel_parallel_worker_reset(sixel_parallel_worker_state_t *state)
×
773
{
774
    if (state == NULL || state->output == NULL) {
×
775
        return;
×
776
    }
777

778
    sixel_band_state_reset(&state->band);
×
779
    sixel_band_clear_map(&state->work);
×
780
    sixel_encode_metrics_init(&state->metrics);
×
781
    /* Parallel workers avoid touching the global assessment recorder. */
782
    state->metrics.encode_probe_active = 0;
×
783
    state->metrics.emit_cells_ptr = NULL;
×
784
    state->writer_error = SIXEL_OK;
×
785
    state->output->pos = 0;
×
786
    state->output->save_count = 0;
×
787
    state->output->save_pixel = 0;
×
788
    state->output->active_palette = (-1);
×
789
    state->output->node_top = NULL;
×
790
    state->output->node_free = NULL;
×
791
}
792

793
static SIXELSTATUS
794
sixel_parallel_worker_prepare(sixel_parallel_worker_state_t *state,
×
795
                              sixel_parallel_context_t *ctx)
796
{
797
    SIXELSTATUS status;
798

799
    if (state->initialized) {
×
800
        return SIXEL_OK;
×
801
    }
802

803
    sixel_encode_work_init(&state->work);
×
804
    sixel_band_state_reset(&state->band);
×
805
    sixel_encode_metrics_init(&state->metrics);
×
806
    state->metrics.encode_probe_active = 0;
×
807
    state->metrics.emit_cells_ptr = NULL;
×
808
    state->writer_error = SIXEL_OK;
×
809
    state->band_buffer = NULL;
×
810
    state->context = ctx;
×
811

812
    status = sixel_encode_work_allocate(&state->work,
×
813
                                        ctx->width,
814
                                        ctx->ncolors,
815
                                        ctx->allocator);
816
    if (SIXEL_FAILED(status)) {
×
817
        return status;
×
818
    }
819

820
    status = sixel_output_new(&state->output,
×
821
                              sixel_parallel_band_writer,
822
                              state,
823
                              ctx->allocator);
824
    if (SIXEL_FAILED(status)) {
×
825
        sixel_encode_work_cleanup(&state->work, ctx->allocator);
×
826
        return status;
×
827
    }
828

829
    state->output->has_8bit_control = ctx->output->has_8bit_control;
×
830
    state->output->has_sixel_scrolling = ctx->output->has_sixel_scrolling;
×
831
    state->output->has_sdm_glitch = ctx->output->has_sdm_glitch;
×
832
    state->output->has_gri_arg_limit = ctx->output->has_gri_arg_limit;
×
833
    state->output->skip_dcs_envelope = 1;
×
834
    state->output->skip_header = 1;
×
835
    state->output->palette_type = ctx->output->palette_type;
×
836
    state->output->colorspace = ctx->output->colorspace;
×
837
    state->output->source_colorspace = ctx->output->source_colorspace;
×
838
    state->output->pixelformat = ctx->output->pixelformat;
×
839
    state->output->penetrate_multiplexer =
×
840
        ctx->output->penetrate_multiplexer;
×
841
    state->output->encode_policy = ctx->output->encode_policy;
×
842
    state->output->ormode = ctx->output->ormode;
×
843

844
    state->initialized = 1;
×
845
    state->index = (-1);
×
846

847
    if (ctx->mutex_ready) {
×
848
        sixel_mutex_lock(&ctx->mutex);
×
849
    }
850
    if (ctx->worker_registered < ctx->worker_capacity) {
×
851
        state->index = ctx->worker_registered;
×
852
        ctx->workers[state->index] = state;
×
853
        ctx->worker_registered += 1;
×
854
    }
855
    if (ctx->mutex_ready) {
×
856
        sixel_mutex_unlock(&ctx->mutex);
×
857
    }
858

859
    if (state->index < 0) {
×
860
        sixel_parallel_worker_cleanup(state, ctx->allocator);
×
861
        return SIXEL_RUNTIME_ERROR;
×
862
    }
863

864
    return SIXEL_OK;
×
865
}
866

867
static int
868
sixel_parallel_band_writer(char *data, int size, void *priv)
×
869
{
870
    sixel_parallel_worker_state_t *state;
871
    sixel_parallel_band_buffer_t *band;
872
    size_t required;
873
    size_t capacity;
874
    size_t new_capacity;
875
    unsigned char *tmp;
876

877
    state = (sixel_parallel_worker_state_t *)priv;
×
878
    if (state == NULL || data == NULL || size <= 0) {
×
879
        return size;
×
880
    }
881
    band = state->band_buffer;
×
882
    if (band == NULL) {
×
883
        state->writer_error = SIXEL_RUNTIME_ERROR;
×
884
        return size;
×
885
    }
886
    if (state->writer_error != SIXEL_OK) {
×
887
        return size;
×
888
    }
889

890
    required = band->used + (size_t)size;
×
891
    if (required < band->used) {
×
892
        state->writer_error = SIXEL_BAD_INTEGER_OVERFLOW;
×
893
        return size;
×
894
    }
895
    capacity = band->size;
×
896
    if (required > capacity) {
×
897
        if (capacity == 0) {
×
898
            new_capacity = (size_t)SIXEL_OUTPUT_PACKET_SIZE;
×
899
        } else {
900
            new_capacity = capacity;
×
901
        }
902
        while (new_capacity < required) {
×
903
            if (new_capacity > SIZE_MAX / 2) {
×
904
                new_capacity = required;
×
905
                break;
×
906
            }
907
            new_capacity *= 2;
×
908
        }
909
        tmp = (unsigned char *)realloc(band->data, new_capacity);
×
910
        if (tmp == NULL) {
×
911
            state->writer_error = SIXEL_BAD_ALLOCATION;
×
912
            return size;
×
913
        }
914
        band->data = tmp;
×
915
        band->size = new_capacity;
×
916
    }
917

918
    memcpy(band->data + band->used, data, (size_t)size);
×
919
    band->used += (size_t)size;
×
920

921
    return size;
×
922
}
923

924
static SIXELSTATUS
925
sixel_parallel_context_begin(sixel_parallel_context_t *ctx,
×
926
                             sixel_index_t *pixels,
927
                             int width,
928
                             int height,
929
                             int ncolors,
930
                             int keycolor,
931
                             unsigned char *palstate,
932
                             sixel_output_t *output,
933
                             sixel_allocator_t *allocator,
934
                             int requested_threads,
935
                             int queue_capacity,
936
                             sixel_parallel_logger_t *logger)
937
{
938
    SIXELSTATUS status;
939
    int nbands;
940
    int threads;
941
    int i;
942

943
    if (ctx == NULL || pixels == NULL || output == NULL) {
×
944
        return SIXEL_BAD_ARGUMENT;
×
945
    }
946

947
    ctx->pixels = pixels;
×
948
    ctx->width = width;
×
949
    ctx->height = height;
×
950
    ctx->ncolors = ncolors;
×
951
    ctx->keycolor = keycolor;
×
952
    ctx->palstate = palstate;
×
953
    ctx->encode_policy = output->encode_policy;
×
954
    ctx->allocator = allocator;
×
955
    ctx->output = output;
×
956
    ctx->encode_probe_active = 0;
×
NEW
957
    ctx->logger = logger;
×
958

959
    nbands = (height + 5) / 6;
×
960
    if (nbands <= 0) {
×
961
        return SIXEL_OK;
×
962
    }
963
    ctx->band_count = nbands;
×
964

965
    threads = requested_threads;
×
966
    if (threads > nbands) {
×
967
        threads = nbands;
×
968
    }
969
    if (threads < 1) {
×
970
        threads = 1;
×
971
    }
972
    ctx->thread_count = threads;
×
973
    ctx->worker_capacity = threads;
×
974
    sixel_assessment_set_encode_parallelism(threads);
×
975

NEW
976
    if (logger != NULL) {
×
NEW
977
        sixel_parallel_logger_logf(logger,
×
978
                                   "encode",
979
                                   "context_begin",
980
                                   -1,
981
                                   -1,
982
                                   0,
983
                                   height,
984
                                   0,
985
                                   height,
986
                                   "threads=%d queue=%d bands=%d",
987
                                   threads,
988
                                   queue_capacity,
989
                                   nbands);
990
    }
991

UNCOV
992
    ctx->bands = (sixel_parallel_band_buffer_t *)calloc((size_t)nbands,
×
993
                                                        sizeof(*ctx->bands));
994
    if (ctx->bands == NULL) {
×
995
        return SIXEL_BAD_ALLOCATION;
×
996
    }
997

998
    ctx->workers = (sixel_parallel_worker_state_t **)
×
999
        calloc((size_t)threads, sizeof(*ctx->workers));
×
1000
    if (ctx->workers == NULL) {
×
1001
        return SIXEL_BAD_ALLOCATION;
×
1002
    }
1003

1004
    status = sixel_mutex_init(&ctx->mutex);
×
1005
    if (status != SIXEL_OK) {
×
1006
        return status;
×
1007
    }
1008
    ctx->mutex_ready = 1;
×
1009

1010
    status = sixel_cond_init(&ctx->cond_band_ready);
×
1011
    if (status != SIXEL_OK) {
×
1012
        return status;
×
1013
    }
1014
    ctx->cond_ready = 1;
×
1015

1016
    ctx->queue_capacity = queue_capacity;
×
1017
    if (ctx->queue_capacity < 1) {
×
1018
        ctx->queue_capacity = nbands;
×
1019
    }
1020
    if (ctx->queue_capacity > nbands) {
×
1021
        ctx->queue_capacity = nbands;
×
1022
    }
1023

1024
    ctx->pool = threadpool_create(threads,
×
1025
                                  ctx->queue_capacity,
1026
                                  sizeof(sixel_parallel_worker_state_t),
1027
                                  sixel_parallel_worker_main,
1028
                                  ctx);
1029
    if (ctx->pool == NULL) {
×
1030
        return SIXEL_RUNTIME_ERROR;
×
1031
    }
1032

1033
    status = sixel_thread_create(&ctx->writer_thread,
×
1034
                                 sixel_parallel_writer_main,
1035
                                 ctx);
1036
    if (SIXEL_FAILED(status)) {
×
1037
        return status;
×
1038
    }
1039
    ctx->writer_started = 1;
×
1040
    ctx->next_band_to_flush = 0;
×
1041
    ctx->writer_should_stop = 0;
×
1042
    ctx->writer_error = SIXEL_OK;
×
1043

1044
    for (i = 0; i < nbands; ++i) {
×
1045
        ctx->bands[i].used = 0;
×
1046
        ctx->bands[i].status = SIXEL_OK;
×
1047
        ctx->bands[i].ready = 0;
×
1048
        ctx->bands[i].dispatched = 0;
×
1049
    }
1050

1051
    return SIXEL_OK;
×
1052
}
1053

1054
static void
1055
sixel_parallel_submit_band(sixel_parallel_context_t *ctx, int band_index)
×
1056
{
1057
    tp_job_t job;
1058
    int dispatch;
1059

1060
    if (ctx == NULL || ctx->pool == NULL) {
×
1061
        return;
×
1062
    }
1063
    if (band_index < 0 || band_index >= ctx->band_count) {
×
1064
        return;
×
1065
    }
1066

NEW
1067
    dispatch = 0;
×
1068
    /*
1069
     * Multiple producers may notify the same band when PaletteApply runs in
1070
     * parallel.  Guard the dispatched flag so only the first notifier pushes
1071
     * work into the encoder queue.
1072
     */
NEW
1073
    if (ctx->mutex_ready) {
×
NEW
1074
        sixel_mutex_lock(&ctx->mutex);
×
NEW
1075
        if (!ctx->bands[band_index].dispatched
×
NEW
1076
                && !ctx->writer_should_stop
×
NEW
1077
                && ctx->writer_error == SIXEL_OK) {
×
NEW
1078
            ctx->bands[band_index].dispatched = 1;
×
NEW
1079
            dispatch = 1;
×
1080
        }
NEW
1081
        sixel_mutex_unlock(&ctx->mutex);
×
1082
    } else {
NEW
1083
        if (!ctx->bands[band_index].dispatched
×
NEW
1084
                && sixel_parallel_jobs_allowed(ctx)) {
×
NEW
1085
            ctx->bands[band_index].dispatched = 1;
×
NEW
1086
            dispatch = 1;
×
1087
        }
1088
    }
1089

NEW
1090
    if (!dispatch) {
×
UNCOV
1091
        return;
×
1092
    }
1093

1094
    sixel_fence_release();
×
NEW
1095
    if (ctx->logger != NULL) {
×
1096
        int y0;
1097
        int y1;
1098

NEW
1099
        y0 = band_index * 6;
×
NEW
1100
        y1 = y0 + 6;
×
NEW
1101
        if (ctx->height > 0 && y1 > ctx->height) {
×
NEW
1102
            y1 = ctx->height;
×
1103
        }
NEW
1104
        sixel_parallel_logger_logf(ctx->logger,
×
1105
                                   "encode",
1106
                                   "dispatch",
1107
                                   band_index,
1108
                                   y1 - 1,
1109
                                   y0,
1110
                                   y1,
1111
                                   y0,
1112
                                   y1,
1113
                                   "enqueue band");
1114
    }
1115
    job.band_index = band_index;
×
1116
    threadpool_push(ctx->pool, job);
×
1117
}
1118

1119
static SIXELSTATUS
1120
sixel_parallel_context_wait(sixel_parallel_context_t *ctx, int force_abort)
×
1121
{
1122
    int pool_error;
1123

1124
    if (ctx == NULL || ctx->pool == NULL) {
×
1125
        return SIXEL_BAD_ARGUMENT;
×
1126
    }
1127

1128
    threadpool_finish(ctx->pool);
×
1129
    pool_error = threadpool_get_error(ctx->pool);
×
1130
    sixel_parallel_writer_stop(ctx, force_abort || pool_error != SIXEL_OK);
×
1131

1132
    if (pool_error != SIXEL_OK) {
×
1133
        return pool_error;
×
1134
    }
1135
    if (ctx->writer_error != SIXEL_OK) {
×
1136
        return ctx->writer_error;
×
1137
    }
1138

1139
    return SIXEL_OK;
×
1140
}
1141

1142
/*
1143
 * Producer callback invoked after PaletteApply finishes a scanline.  The
1144
 * helper promotes every sixth row (or the final partial band) into the job
1145
 * queue so workers can begin encoding while dithering continues.
1146
 */
1147
static void
1148
sixel_parallel_palette_row_ready(void *priv, int row_index)
×
1149
{
1150
    sixel_parallel_row_notifier_t *notifier;
1151
    sixel_parallel_context_t *ctx;
1152
    sixel_parallel_logger_t *logger;
1153
    int band_height;
1154
    int band_index;
1155
    int y0;
1156
    int y1;
1157

NEW
1158
    notifier = (sixel_parallel_row_notifier_t *)priv;
×
NEW
1159
    if (notifier == NULL) {
×
NEW
1160
        return;
×
1161
    }
NEW
1162
    ctx = notifier->context;
×
NEW
1163
    logger = notifier->logger;
×
1164
    if (ctx == NULL || ctx->band_count <= 0 || ctx->height <= 0) {
×
1165
        return;
×
1166
    }
1167
    if (row_index < 0) {
×
1168
        return;
×
1169
    }
NEW
1170
    band_height = notifier->band_height;
×
NEW
1171
    if (band_height < 1) {
×
NEW
1172
        band_height = 6;
×
1173
    }
NEW
1174
    if ((row_index % band_height) != band_height - 1
×
NEW
1175
            && row_index != ctx->height - 1) {
×
UNCOV
1176
        return;
×
1177
    }
1178

NEW
1179
    band_index = row_index / band_height;
×
1180
    if (band_index >= ctx->band_count) {
×
1181
        band_index = ctx->band_count - 1;
×
1182
    }
1183
    if (band_index < 0) {
×
1184
        return;
×
1185
    }
1186

NEW
1187
    y0 = band_index * band_height;
×
NEW
1188
    y1 = y0 + band_height;
×
NEW
1189
    if (notifier->image_height > 0 && y1 > notifier->image_height) {
×
NEW
1190
        y1 = notifier->image_height;
×
1191
    }
NEW
1192
    if (logger != NULL) {
×
NEW
1193
        sixel_parallel_logger_logf(logger,
×
1194
                                   "encode",
1195
                                   "row_gate",
1196
                                   band_index,
1197
                                   row_index,
1198
                                   y0,
1199
                                   y1,
1200
                                   y0,
1201
                                   y1,
1202
                                   "trigger band enqueue");
1203
    }
1204

UNCOV
1205
    sixel_parallel_submit_band(ctx, band_index);
×
1206
}
1207

1208
static SIXELSTATUS
1209
sixel_parallel_flush_band(sixel_parallel_context_t *ctx, int band_index)
×
1210
{
1211
    sixel_parallel_band_buffer_t *band;
1212
    size_t offset;
1213
    size_t chunk;
1214

1215
    band = &ctx->bands[band_index];
×
NEW
1216
    if (ctx->logger != NULL) {
×
1217
        int y0;
1218
        int y1;
1219

NEW
1220
        y0 = band_index * 6;
×
NEW
1221
        y1 = y0 + 6;
×
NEW
1222
        if (ctx->height > 0 && y1 > ctx->height) {
×
NEW
1223
            y1 = ctx->height;
×
1224
        }
NEW
1225
        sixel_parallel_logger_logf(ctx->logger,
×
1226
                                   "encode",
1227
                                   "writer_flush",
1228
                                   band_index,
1229
                                   y1 - 1,
1230
                                   y0,
1231
                                   y1,
1232
                                   y0,
1233
                                   y1,
1234
                                   "bytes=%zu",
1235
                                   band->used);
1236
    }
1237
    offset = 0;
×
1238
    while (offset < band->used) {
×
1239
        chunk = band->used - offset;
×
1240
        if (chunk > (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos)) {
×
1241
            chunk = (size_t)(SIXEL_OUTPUT_PACKET_SIZE - ctx->output->pos);
×
1242
        }
1243
        memcpy(ctx->output->buffer + ctx->output->pos,
×
1244
               band->data + offset,
×
1245
               chunk);
1246
        sixel_advance(ctx->output, (int)chunk);
×
1247
        offset += chunk;
×
1248
    }
1249
    return SIXEL_OK;
×
1250
}
1251

1252
static int
1253
sixel_parallel_worker_main(tp_job_t job, void *userdata, void *workspace)
×
1254
{
1255
    sixel_parallel_context_t *ctx;
1256
    sixel_parallel_worker_state_t *state;
1257
    sixel_parallel_band_buffer_t *band;
1258
    SIXELSTATUS status;
1259
    int band_index;
1260
    int band_start;
1261
    int band_height;
1262
    int row_index;
1263
    int absolute_row;
1264
    int last_row_index;
1265
    size_t emitted_bytes;
1266

1267
    ctx = (sixel_parallel_context_t *)userdata;
×
1268
    state = (sixel_parallel_worker_state_t *)workspace;
×
1269

1270
    if (ctx == NULL || state == NULL) {
×
1271
        return SIXEL_BAD_ARGUMENT;
×
1272
    }
1273

1274
    band = NULL;
×
1275
    status = SIXEL_OK;
×
1276
    band_index = job.band_index;
×
NEW
1277
    band_start = 0;
×
NEW
1278
    band_height = 0;
×
NEW
1279
    last_row_index = -1;
×
NEW
1280
    emitted_bytes = 0U;
×
1281
    if (band_index < 0 || band_index >= ctx->band_count) {
×
1282
        status = SIXEL_BAD_ARGUMENT;
×
1283
        goto cleanup;
×
1284
    }
1285

1286
    band = &ctx->bands[band_index];
×
1287
    band->used = 0;
×
1288
    band->status = SIXEL_OK;
×
1289
    band->ready = 0;
×
1290

1291
    sixel_fence_acquire();
×
1292

1293
    status = sixel_parallel_worker_prepare(state, ctx);
×
1294
    if (SIXEL_FAILED(status)) {
×
1295
        goto cleanup;
×
1296
    }
1297

1298
    if (!sixel_parallel_jobs_allowed(ctx)) {
×
1299
        if (ctx->mutex_ready) {
×
1300
            sixel_mutex_lock(&ctx->mutex);
×
1301
            if (ctx->writer_error != SIXEL_OK) {
×
1302
                status = ctx->writer_error;
×
1303
            } else {
1304
                status = SIXEL_RUNTIME_ERROR;
×
1305
            }
1306
            sixel_mutex_unlock(&ctx->mutex);
×
1307
        } else if (ctx->writer_error != SIXEL_OK) {
×
1308
            status = ctx->writer_error;
×
1309
        } else {
1310
            status = SIXEL_RUNTIME_ERROR;
×
1311
        }
1312
        goto cleanup;
×
1313
    }
1314

1315
    state->band_buffer = band;
×
1316
    sixel_parallel_worker_reset(state);
×
1317

1318
    band_start = band_index * 6;
×
1319
    band_height = ctx->height - band_start;
×
1320
    if (band_height > 6) {
×
1321
        band_height = 6;
×
1322
    }
1323
    if (band_height <= 0) {
×
1324
        goto cleanup;
×
1325
    }
1326

NEW
1327
    if (ctx->logger != NULL) {
×
NEW
1328
        sixel_parallel_logger_logf(ctx->logger,
×
1329
                                   "encode",
1330
                                   "worker_start",
1331
                                   band_index,
1332
                                   band_start,
1333
                                   band_start,
1334
                                   band_start + band_height,
1335
                                   band_start,
1336
                                   band_start + band_height,
1337
                                   "worker=%d",
1338
                                   state->index);
1339
    }
1340

1341
    for (row_index = 0; row_index < band_height; row_index++) {
×
1342
        absolute_row = band_start + row_index;
×
1343
        status = sixel_band_classify_row(&state->work,
×
1344
                                         &state->band,
1345
                                         ctx->pixels,
1346
                                         ctx->width,
1347
                                         absolute_row,
1348
                                         ctx->ncolors,
1349
                                         ctx->keycolor,
1350
                                         ctx->palstate,
1351
                                         ctx->encode_policy);
1352
        if (SIXEL_FAILED(status)) {
×
1353
            goto cleanup;
×
1354
        }
1355
    }
1356

1357
    status = sixel_band_compose(&state->work,
×
1358
                                &state->band,
1359
                                state->output,
1360
                                ctx->width,
1361
                                ctx->ncolors,
1362
                                ctx->keycolor,
1363
                                ctx->allocator,
1364
                                &state->metrics);
1365
    if (SIXEL_FAILED(status)) {
×
1366
        goto cleanup;
×
1367
    }
1368

1369
    last_row_index = band_start + band_height - 1;
×
1370
    status = sixel_band_emit(&state->work,
×
1371
                             &state->band,
1372
                             state->output,
1373
                             ctx->ncolors,
1374
                             ctx->keycolor,
1375
                             last_row_index,
1376
                             &state->metrics);
1377
    if (SIXEL_FAILED(status)) {
×
1378
        goto cleanup;
×
1379
    }
1380

1381
    status = sixel_put_flash(state->output);
×
1382
    if (SIXEL_FAILED(status)) {
×
1383
        goto cleanup;
×
1384
    }
1385

1386
    if (state->output->pos > 0) {
×
1387
        state->output->fn_write((char *)state->output->buffer,
×
1388
                                state->output->pos,
×
1389
                                state);
1390
        state->output->pos = 0;
×
1391
    }
NEW
1392
    emitted_bytes = band->used;
×
1393

1394
    if (state->writer_error != SIXEL_OK) {
×
1395
        status = state->writer_error;
×
1396
        goto cleanup;
×
1397
    }
1398

1399
    sixel_band_finish(&state->work, &state->band);
×
1400
    status = SIXEL_OK;
×
1401

1402
cleanup:
×
1403
    sixel_parallel_worker_release_nodes(state, ctx->allocator);
×
1404
    if (band != NULL && ctx->mutex_ready && ctx->cond_ready) {
×
1405
        sixel_fence_release();
×
1406
        sixel_mutex_lock(&ctx->mutex);
×
1407
        band->status = status;
×
1408
        band->ready = 1;
×
1409
        sixel_cond_broadcast(&ctx->cond_band_ready);
×
1410
        sixel_mutex_unlock(&ctx->mutex);
×
1411
    }
NEW
1412
    if (ctx->logger != NULL) {
×
NEW
1413
        sixel_parallel_logger_logf(ctx->logger,
×
1414
                                   "encode",
1415
                                   "worker_done",
1416
                                   band_index,
1417
                                   last_row_index,
1418
                                   band_start,
1419
                                   band_start + band_height,
1420
                                   band_start,
1421
                                   band_start + band_height,
1422
                                   "status=%d bytes=%zu",
1423
                                   status,
1424
                                   emitted_bytes);
1425
    }
1426
    if (SIXEL_FAILED(status)) {
×
1427
        return status;
×
1428
    }
1429
    return SIXEL_OK;
×
1430
}
1431

1432
static void
1433
sixel_parallel_writer_stop(sixel_parallel_context_t *ctx, int force_abort)
×
1434
{
1435
    int should_signal;
1436

1437
    if (ctx == NULL || !ctx->writer_started) {
×
1438
        return;
×
1439
    }
1440

1441
    should_signal = ctx->mutex_ready && ctx->cond_ready;
×
1442
    if (should_signal) {
×
1443
        sixel_mutex_lock(&ctx->mutex);
×
1444
        if (force_abort) {
×
1445
            ctx->writer_should_stop = 1;
×
1446
        }
1447
        sixel_cond_broadcast(&ctx->cond_band_ready);
×
1448
        sixel_mutex_unlock(&ctx->mutex);
×
1449
    } else if (force_abort) {
×
1450
        ctx->writer_should_stop = 1;
×
1451
    }
1452

1453
    sixel_thread_join(&ctx->writer_thread);
×
1454
    ctx->writer_started = 0;
×
1455
    ctx->writer_should_stop = 0;
×
NEW
1456
    if (ctx->logger != NULL) {
×
NEW
1457
        sixel_parallel_logger_logf(ctx->logger,
×
1458
                                   "encode",
1459
                                   "writer_stop",
1460
                                   -1,
1461
                                   -1,
1462
                                   0,
1463
                                   ctx->height,
1464
                                   0,
1465
                                   ctx->height,
1466
                                   "force=%d",
1467
                                   force_abort);
1468
    }
1469
}
1470

1471
static int
1472
sixel_parallel_writer_main(void *arg)
×
1473
{
1474
    sixel_parallel_context_t *ctx;
1475
    sixel_parallel_band_buffer_t *band;
1476
    SIXELSTATUS status;
1477
    int band_index;
1478

1479
    ctx = (sixel_parallel_context_t *)arg;
×
1480
    if (ctx == NULL) {
×
1481
        return SIXEL_BAD_ARGUMENT;
×
1482
    }
1483

NEW
1484
    if (ctx->logger != NULL) {
×
NEW
1485
        sixel_parallel_logger_logf(ctx->logger,
×
1486
                                   "encode",
1487
                                   "writer_start",
1488
                                   -1,
1489
                                   -1,
1490
                                   0,
1491
                                   ctx->height,
1492
                                   0,
1493
                                   ctx->height,
1494
                                   "bands=%d",
1495
                                   ctx->band_count);
1496
    }
1497

1498
    for (;;) {
1499
        sixel_mutex_lock(&ctx->mutex);
×
1500
        while (!ctx->writer_should_stop &&
×
1501
               ctx->next_band_to_flush < ctx->band_count) {
×
NEW
1502
    band_index = ctx->next_band_to_flush;
×
NEW
1503
    band = &ctx->bands[band_index];
×
NEW
1504
    if (band->ready) {
×
NEW
1505
        break;
×
1506
    }
UNCOV
1507
            sixel_cond_wait(&ctx->cond_band_ready, &ctx->mutex);
×
1508
        }
1509

1510
        if (ctx->writer_should_stop) {
×
1511
            sixel_mutex_unlock(&ctx->mutex);
×
1512
            break;
×
1513
        }
1514

1515
        if (ctx->next_band_to_flush >= ctx->band_count) {
×
1516
            sixel_mutex_unlock(&ctx->mutex);
×
1517
            break;
×
1518
        }
1519

1520
        band_index = ctx->next_band_to_flush;
×
1521
        band = &ctx->bands[band_index];
×
1522
        if (!band->ready) {
×
1523
            sixel_mutex_unlock(&ctx->mutex);
×
1524
            continue;
×
1525
        }
1526
        band->ready = 0;
×
1527
        ctx->next_band_to_flush += 1;
×
1528
        sixel_mutex_unlock(&ctx->mutex);
×
1529

1530
        sixel_fence_acquire();
×
1531
        status = band->status;
×
NEW
1532
        if (ctx->logger != NULL) {
×
1533
            int y0;
1534
            int y1;
1535

NEW
1536
            y0 = band_index * 6;
×
NEW
1537
            y1 = y0 + 6;
×
NEW
1538
            if (ctx->height > 0 && y1 > ctx->height) {
×
NEW
1539
                y1 = ctx->height;
×
1540
            }
NEW
1541
            sixel_parallel_logger_logf(ctx->logger,
×
1542
                                       "encode",
1543
                                       "writer_dequeue",
1544
                                       band_index,
1545
                                       y1 - 1,
1546
                                       y0,
1547
                                       y1,
1548
                                       y0,
1549
                                       y1,
1550
                                       "bytes=%zu",
1551
                                       band->used);
1552
        }
1553
        if (SIXEL_SUCCEEDED(status)) {
×
1554
            status = sixel_parallel_flush_band(ctx, band_index);
×
1555
        }
1556
        if (SIXEL_FAILED(status)) {
×
1557
            sixel_parallel_context_abort_locked(ctx, status);
×
1558
            break;
×
1559
        }
1560
    }
1561

1562
    return SIXEL_OK;
×
1563
}
1564

1565
static SIXELSTATUS
1566
sixel_encode_body_parallel(sixel_index_t *pixels,
×
1567
                           int width,
1568
                           int height,
1569
                           int ncolors,
1570
                           int keycolor,
1571
                           sixel_output_t *output,
1572
                           unsigned char *palstate,
1573
                           sixel_allocator_t *allocator,
1574
                           int requested_threads)
1575
{
1576
    sixel_parallel_context_t ctx;
1577
    SIXELSTATUS status;
1578
    int nbands;
1579
    int threads;
1580
    int i;
1581
    int queue_depth;
1582
    sixel_parallel_logger_t logger;
1583

1584
    sixel_parallel_context_init(&ctx);
×
NEW
1585
    sixel_parallel_logger_prepare(&logger);
×
1586
    nbands = (height + 5) / 6;
×
1587
    ctx.band_count = nbands;
×
1588
    if (nbands <= 0) {
×
NEW
1589
        sixel_parallel_logger_close(&logger);
×
UNCOV
1590
        return SIXEL_OK;
×
1591
    }
1592

1593
    threads = requested_threads;
×
1594
    if (threads > nbands) {
×
1595
        threads = nbands;
×
1596
    }
1597
    if (threads < 1) {
×
1598
        threads = 1;
×
1599
    }
1600
    sixel_assessment_set_encode_parallelism(threads);
×
1601
    ctx.thread_count = threads;
×
1602
    queue_depth = threads * 3;
×
1603
    if (queue_depth > nbands) {
×
1604
        queue_depth = nbands;
×
1605
    }
1606
    if (queue_depth < 1) {
×
1607
        queue_depth = 1;
×
1608
    }
1609

1610
    status = sixel_parallel_context_begin(&ctx,
×
1611
                                          pixels,
1612
                                          width,
1613
                                          height,
1614
                                          ncolors,
1615
                                          keycolor,
1616
                                          palstate,
1617
                                          output,
1618
                                          allocator,
1619
                                          threads,
1620
                                          queue_depth,
1621
                                          &logger);
1622
    if (SIXEL_FAILED(status)) {
×
1623
        sixel_parallel_context_cleanup(&ctx);
×
NEW
1624
        sixel_parallel_logger_close(&logger);
×
UNCOV
1625
        return status;
×
1626
    }
1627

1628
    for (i = 0; i < nbands; i++) {
×
1629
        sixel_parallel_submit_band(&ctx, i);
×
1630
    }
1631

1632
    status = sixel_parallel_context_wait(&ctx, 0);
×
1633
    if (SIXEL_FAILED(status)) {
×
1634
        sixel_parallel_context_cleanup(&ctx);
×
NEW
1635
        sixel_parallel_logger_close(&logger);
×
UNCOV
1636
        return status;
×
1637
    }
1638

1639
    sixel_parallel_context_cleanup(&ctx);
×
NEW
1640
    sixel_parallel_logger_close(&logger);
×
UNCOV
1641
    return SIXEL_OK;
×
1642
}
1643
#endif
1644

1645
#if SIXEL_ENABLE_THREADS
1646
/*
1647
 * Execute PaletteApply, band encoding, and output emission as a pipeline.
1648
 * The producer owns the dithered index buffer and enqueues bands once every
1649
 * six rows have been produced.  Worker threads encode in parallel while the
1650
 * writer emits completed bands in-order to preserve deterministic output.
1651
 */
1652
static SIXELSTATUS
1653
sixel_encode_body_pipeline(unsigned char *pixels,
×
1654
                           int width,
1655
                           int height,
1656
                           unsigned char const *palette,
1657
                           float const *palette_float,
1658
                           sixel_dither_t *dither,
1659
                           sixel_output_t *output,
1660
                           int encode_threads)
1661
{
1662
    SIXELSTATUS status;
1663
    SIXELSTATUS wait_status;
1664
    sixel_parallel_context_t ctx;
1665
    sixel_index_t *indexes;
1666
    sixel_index_t *result;
1667
    sixel_allocator_t *allocator;
1668
    size_t pixel_count;
1669
    size_t buffer_size;
1670
    int threads;
1671
    int nbands;
1672
    int queue_depth;
1673
    int encode_probe_active;
1674
    int waited;
1675
    int saved_optimize_palette;
1676
    sixel_parallel_logger_t logger;
1677
    sixel_parallel_row_notifier_t notifier;
1678

1679
    if (pixels == NULL
×
1680
            || (palette == NULL && palette_float == NULL)
×
1681
            || dither == NULL
×
1682
            || output == NULL) {
×
1683
        return SIXEL_BAD_ARGUMENT;
×
1684
    }
1685

NEW
1686
    threads = encode_threads;
×
1687
    nbands = (height + 5) / 6;
×
1688
    if (threads <= 1 || nbands <= 1) {
×
1689
        return SIXEL_RUNTIME_ERROR;
×
1690
    }
1691

1692
    pixel_count = (size_t)width * (size_t)height;
×
1693
    if (height != 0 && pixel_count / (size_t)height != (size_t)width) {
×
1694
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1695
    }
1696
    buffer_size = pixel_count * sizeof(*indexes);
×
1697
    allocator = dither->allocator;
×
1698
    indexes = (sixel_index_t *)sixel_allocator_malloc(allocator, buffer_size);
×
1699
    if (indexes == NULL) {
×
1700
        return SIXEL_BAD_ALLOCATION;
×
1701
    }
1702

1703
    sixel_parallel_context_init(&ctx);
×
NEW
1704
    sixel_parallel_logger_prepare(&logger);
×
NEW
1705
    notifier.context = &ctx;
×
NEW
1706
    notifier.logger = &logger;
×
NEW
1707
    notifier.band_height = 6;
×
NEW
1708
    notifier.image_height = height;
×
1709
    waited = 0;
×
1710
    status = SIXEL_OK;
×
1711

1712
    encode_probe_active = sixel_assessment_encode_probe_enabled();
×
1713
    status = sixel_encode_emit_palette(dither->bodyonly,
×
1714
                                       dither->ncolors,
1715
                                       dither->keycolor,
1716
                                       palette,
1717
                                       palette_float,
1718
                                       output,
1719
                                       encode_probe_active);
1720
    if (SIXEL_FAILED(status)) {
×
1721
        goto cleanup;
×
1722
    }
1723

1724
    queue_depth = threads * 3;
×
1725
    if (queue_depth > nbands) {
×
1726
        queue_depth = nbands;
×
1727
    }
1728
    if (queue_depth < 1) {
×
1729
        queue_depth = 1;
×
1730
    }
1731

1732
    dither->pipeline_index_buffer = indexes;
×
1733
    dither->pipeline_index_size = buffer_size;
×
1734
    dither->pipeline_row_callback = sixel_parallel_palette_row_ready;
×
NEW
1735
    dither->pipeline_row_priv = &notifier;
×
NEW
1736
    dither->pipeline_logger = &logger;
×
NEW
1737
    dither->pipeline_image_height = height;
×
1738

NEW
1739
    if (logger.active) {
×
1740
        /*
1741
         * Record the thread split and band geometry before spawning workers.
1742
         * This clarifies why only a subset of hardware threads might appear
1743
         * in the log when the encoder side is clamped to keep the pipeline
1744
         * draining.
1745
         */
NEW
1746
        sixel_parallel_logger_logf(&logger,
×
1747
                                   "pipeline",
1748
                                   "configure",
1749
                                   -1,
1750
                                   -1,
1751
                                   0,
1752
                                   height,
1753
                                   0,
1754
                                   height,
1755
                                   "encode_threads=%d dither_threads=%d "
1756
                                   "band=%d overlap=%d",
1757
                                   threads,
1758
                                   dither->pipeline_dither_threads,
1759
                                   dither->pipeline_band_height,
1760
                                   dither->pipeline_band_overlap);
1761
    }
1762

1763
    status = sixel_parallel_context_begin(&ctx,
×
1764
                                          indexes,
1765
                                          width,
1766
                                          height,
1767
                                          dither->ncolors,
1768
                                          dither->keycolor,
1769
                                          NULL,
1770
                                          output,
1771
                                          allocator,
1772
                                          threads,
1773
                                          queue_depth,
1774
                                          &logger);
1775
    if (SIXEL_FAILED(status)) {
×
1776
        goto cleanup;
×
1777
    }
1778

1779
    saved_optimize_palette = dither->optimize_palette;
×
1780
    if (saved_optimize_palette != 0) {
×
1781
        /*
1782
         * Palette optimization reorders palette entries after dithering.  The
1783
         * pipeline emits the palette up-front, so workers must see the exact
1784
         * order that we already sent to the output stream.  Disable palette
1785
         * reordering while the pipeline is active and restore the caller
1786
         * preference afterwards.
1787
         */
1788
        dither->optimize_palette = 0;
×
1789
    }
1790
    result = sixel_dither_apply_palette(dither, pixels, width, height);
×
1791
    dither->optimize_palette = saved_optimize_palette;
×
1792
    if (result == NULL) {
×
1793
        status = SIXEL_RUNTIME_ERROR;
×
1794
        goto cleanup;
×
1795
    }
1796
    if (result != indexes) {
×
1797
        status = SIXEL_RUNTIME_ERROR;
×
1798
        goto cleanup;
×
1799
    }
1800

1801
    status = sixel_parallel_context_wait(&ctx, 0);
×
1802
    waited = 1;
×
1803
    if (SIXEL_FAILED(status)) {
×
1804
        goto cleanup;
×
1805
    }
1806

1807
cleanup:
×
1808
    dither->pipeline_row_callback = NULL;
×
1809
    dither->pipeline_row_priv = NULL;
×
1810
    dither->pipeline_index_buffer = NULL;
×
1811
    dither->pipeline_index_size = 0;
×
1812
    if (!waited && ctx.pool != NULL) {
×
1813
        wait_status = sixel_parallel_context_wait(&ctx, status != SIXEL_OK);
×
1814
        if (status == SIXEL_OK) {
×
1815
            status = wait_status;
×
1816
        }
1817
    }
1818
    sixel_parallel_context_cleanup(&ctx);
×
NEW
1819
    sixel_parallel_logger_close(&logger);
×
1820
    if (indexes != NULL) {
×
1821
        sixel_allocator_free(allocator, indexes);
×
1822
    }
1823
    return status;
×
1824
}
1825
#else
1826
static SIXELSTATUS
1827
sixel_encode_body_pipeline(unsigned char *pixels,
1828
                           int width,
1829
                           int height,
1830
                           unsigned char const *palette,
1831
                           float const *palette_float,
1832
                           sixel_dither_t *dither,
1833
                           sixel_output_t *output,
1834
                           int encode_threads)
1835
{
1836
    (void)pixels;
1837
    (void)width;
1838
    (void)height;
1839
    (void)palette;
1840
    (void)palette_float;
1841
    (void)dither;
1842
    (void)output;
1843
    (void)encode_threads;
1844
    return SIXEL_RUNTIME_ERROR;
1845
}
1846
#endif
1847

1848
/* implementation */
1849

1850
/* GNU Screen penetration */
1851
static void
1852
sixel_penetrate(
8✔
1853
    sixel_output_t  /* in */    *output,        /* output context */
1854
    int             /* in */    nwrite,         /* output size */
1855
    char const      /* in */    *dcs_start,     /* DCS introducer */
1856
    char const      /* in */    *dcs_end,       /* DCS terminator */
1857
    int const       /* in */    dcs_start_size, /* size of DCS introducer */
1858
    int const       /* in */    dcs_end_size)   /* size of DCS terminator */
1859
{
1860
    int pos;
1861
    int const splitsize = SCREEN_PACKET_SIZE
8✔
1862
                        - dcs_start_size - dcs_end_size;
8✔
1863

1864
    for (pos = 0; pos < nwrite; pos += splitsize) {
400✔
1865
        output->fn_write((char *)dcs_start, dcs_start_size, output->priv);
392✔
1866
        output->fn_write(((char *)output->buffer) + pos,
392✔
1867
                          nwrite - pos < splitsize ? nwrite - pos: splitsize,
392✔
1868
                          output->priv);
1869
        output->fn_write((char *)dcs_end, dcs_end_size, output->priv);
392✔
1870
    }
1871
}
8✔
1872

1873

1874
static void
1875
sixel_advance(sixel_output_t *output, int nwrite)
27,358,069✔
1876
{
1877
    if ((output->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) {
27,358,069✔
1878
        if (output->penetrate_multiplexer) {
2,080✔
1879
            sixel_penetrate(output,
4✔
1880
                            SIXEL_OUTPUT_PACKET_SIZE,
1881
                            DCS_START_7BIT,
1882
                            DCS_END_7BIT,
1883
                            DCS_START_7BIT_SIZE,
1884
                            DCS_END_7BIT_SIZE);
1885
        } else {
1886
            output->fn_write((char *)output->buffer,
2,076✔
1887
                             SIXEL_OUTPUT_PACKET_SIZE, output->priv);
1888
        }
1889
        memcpy(output->buffer,
2,080✔
1890
               output->buffer + SIXEL_OUTPUT_PACKET_SIZE,
2,080✔
1891
               (size_t)(output->pos -= SIXEL_OUTPUT_PACKET_SIZE));
2,080✔
1892
    }
1893
}
27,358,069✔
1894

1895

1896
static void
1897
sixel_putc(unsigned char *buffer, unsigned char value)
5,903,848✔
1898
{
1899
    *buffer = value;
5,903,848✔
1900
}
5,903,848✔
1901

1902

1903
static void
1904
sixel_puts(unsigned char *buffer, char const *value, int size)
225,410✔
1905
{
1906
    memcpy(buffer, (void *)value, (size_t)size);
225,410✔
1907
}
225,410✔
1908

1909
/*
1910
 * Append a literal byte several times while respecting the output packet
1911
 * boundary.  The helper keeps `sixel_advance` responsible for flushing and
1912
 * preserves the repeating logic used by DECGRI sequences.
1913
 */
1914
static void
1915
sixel_output_emit_literal(sixel_output_t *output,
16,475,028✔
1916
                          unsigned char value,
1917
                          int count)
1918
{
1919
    int chunk;
1920

1921
    if (count <= 0) {
16,475,028!
1922
        return;
×
1923
    }
1924

1925
    while (count > 0) {
32,950,237✔
1926
        chunk = SIXEL_OUTPUT_PACKET_SIZE - output->pos;
16,475,209✔
1927
        if (chunk > count) {
16,475,209✔
1928
            chunk = count;
16,474,089✔
1929
        }
1930
        memset(output->buffer + output->pos, value, (size_t)chunk);
16,475,209✔
1931
        sixel_advance(output, chunk);
16,475,209✔
1932
        count -= chunk;
16,475,209✔
1933
    }
1934
}
1935

1936
static double
1937
sixel_encode_span_start(int probe_active)
102,920✔
1938
{
1939
    if (probe_active) {
102,920!
1940
        return sixel_assessment_timer_now();
×
1941
    }
1942
    return 0.0;
102,920✔
1943
}
1944

1945
static void
1946
sixel_encode_span_commit(int probe_active,
91,616✔
1947
                         sixel_assessment_stage_t stage,
1948
                         double started_at)
1949
{
1950
    double duration;
1951

1952
    if (!probe_active) {
91,616!
1953
        return;
91,616✔
1954
    }
1955
    duration = sixel_assessment_timer_now() - started_at;
×
1956
    if (duration < 0.0) {
×
1957
        duration = 0.0;
×
1958
    }
1959
    sixel_assessment_record_encode_span(stage, duration);
×
1960
}
1961

1962

1963
/*
1964
 * Compose helpers accelerate palette sweeps by skipping zero columns in
1965
 * word-sized chunks.  Each helper mirrors the original probe counters so
1966
 * the performance report still reflects how many cells were inspected.
1967
 */
1968

1969
static int
1970
sixel_compose_find_run_start(unsigned char const *row,
3,050,586✔
1971
                             int width,
1972
                             int start,
1973
                             int encode_probe_active,
1974
                             double *compose_scan_probes)
1975
{
1976
    int idx;
1977
    size_t chunk_size;
1978
    unsigned char const *cursor;
1979
    unsigned long block;
1980

1981
    idx = start;
3,050,586✔
1982
    chunk_size = sizeof(unsigned long);
3,050,586✔
1983
    cursor = row + start;
3,050,586✔
1984

1985
    while ((width - idx) >= (int)chunk_size) {
83,645,202✔
1986
        memcpy(&block, cursor, chunk_size);
82,931,106✔
1987
        if (block != 0UL) {
82,931,106✔
1988
            break;
2,336,490✔
1989
        }
1990
        if (encode_probe_active) {
80,594,616!
1991
            *compose_scan_probes += (double)chunk_size;
×
1992
        }
1993
        idx += (int)chunk_size;
80,594,616✔
1994
        cursor += chunk_size;
80,594,616✔
1995
    }
1996

1997
    while (idx < width) {
13,506,894✔
1998
        if (encode_probe_active) {
12,802,084!
1999
            *compose_scan_probes += 1.0;
×
2000
        }
2001
        if (*cursor != 0) {
12,802,084✔
2002
            break;
2,345,776✔
2003
        }
2004
        idx += 1;
10,456,308✔
2005
        cursor += 1;
10,456,308✔
2006
    }
2007

2008
    return idx;
3,050,586✔
2009
}
2010

2011

2012
static int
2013
sixel_compose_measure_gap(unsigned char const *row,
5,405,816✔
2014
                          int width,
2015
                          int start,
2016
                          int encode_probe_active,
2017
                          double *compose_scan_probes,
2018
                          int *reached_end)
2019
{
2020
    int gap;
2021
    size_t chunk_size;
2022
    unsigned char const *cursor;
2023
    unsigned long block;
2024
    int remaining;
2025

2026
    gap = 0;
5,405,816✔
2027
    *reached_end = 0;
5,405,816✔
2028
    if (start >= width) {
5,405,816✔
2029
        *reached_end = 1;
6,612✔
2030
        return gap;
6,612✔
2031
    }
2032

2033
    chunk_size = sizeof(unsigned long);
5,399,204✔
2034
    cursor = row + start;
5,399,204✔
2035
    remaining = width - start;
5,399,204✔
2036

2037
    while (remaining >= (int)chunk_size) {
58,293,102✔
2038
        memcpy(&block, cursor, chunk_size);
57,567,648✔
2039
        if (block != 0UL) {
57,567,648✔
2040
            break;
4,673,750✔
2041
        }
2042
        gap += (int)chunk_size;
52,893,898✔
2043
        if (encode_probe_active) {
52,893,898!
2044
            *compose_scan_probes += (double)chunk_size;
×
2045
        }
2046
        cursor += chunk_size;
52,893,898✔
2047
        remaining -= (int)chunk_size;
52,893,898✔
2048
    }
2049

2050
    while (remaining > 0) {
17,317,988✔
2051
        if (encode_probe_active) {
16,619,790!
2052
            *compose_scan_probes += 1.0;
×
2053
        }
2054
        if (*cursor != 0) {
16,619,790✔
2055
            return gap;
4,701,006✔
2056
        }
2057
        gap += 1;
11,918,784✔
2058
        cursor += 1;
11,918,784✔
2059
        remaining -= 1;
11,918,784✔
2060
    }
2061

2062
    *reached_end = 1;
698,198✔
2063
    return gap;
698,198✔
2064
}
2065

2066

2067
#if HAVE_LDIV
2068
static int
2069
sixel_putnum_impl(char *buffer, long value, int pos)
9,731,098✔
2070
{
2071
    ldiv_t r;
2072

2073
    r = ldiv(value, 10);
9,731,098✔
2074
    if (r.quot > 0) {
9,731,098✔
2075
        pos = sixel_putnum_impl(buffer, r.quot, pos);
5,062,398✔
2076
    }
2077
    *(buffer + pos) = '0' + r.rem;
9,731,098✔
2078
    return pos + 1;
9,731,098✔
2079
}
2080
#endif  /* HAVE_LDIV */
2081

2082

2083
static int
2084
sixel_putnum(char *buffer, int value)
4,668,700✔
2085
{
2086
    int pos;
2087

2088
#if HAVE_LDIV
2089
    pos = sixel_putnum_impl(buffer, value, 0);
4,668,700✔
2090
#else
2091
    pos = sprintf(buffer, "%d", value);
2092
#endif  /* HAVE_LDIV */
2093

2094
    return pos;
4,668,700✔
2095
}
2096

2097

2098
static SIXELSTATUS
2099
sixel_put_flash(sixel_output_t *const output)
17,934,612✔
2100
{
2101
    int nwrite;
2102

2103
    if (output->save_count <= 0) {
17,934,612!
2104
        return SIXEL_OK;
×
2105
    }
2106

2107
    if (output->has_gri_arg_limit) {  /* VT240 Max 255 ? */
17,934,612!
2108
        while (output->save_count > 255) {
×
2109
            /* argument of DECGRI('!') is limitted to 255 in real VT */
2110
            sixel_puts(output->buffer + output->pos, "!255", 4);
×
2111
            sixel_advance(output, 4);
×
2112
            sixel_putc(output->buffer + output->pos, output->save_pixel);
×
2113
            sixel_advance(output, 1);
×
2114
            output->save_count -= 255;
×
2115
        }
2116
    }
2117

2118
    if (output->save_count > 3) {
17,934,612✔
2119
        /* DECGRI Graphics Repeat Introducer ! Pn Ch */
2120
        sixel_putc(output->buffer + output->pos, '!');
1,459,584✔
2121
        sixel_advance(output, 1);
1,459,584✔
2122
        nwrite = sixel_putnum((char *)output->buffer + output->pos, output->save_count);
1,459,584✔
2123
        sixel_advance(output, nwrite);
1,459,584✔
2124
        sixel_putc(output->buffer + output->pos, output->save_pixel);
1,459,584✔
2125
        sixel_advance(output, 1);
1,459,584✔
2126
    } else {
2127
        sixel_output_emit_literal(output,
16,475,028✔
2128
                                  (unsigned char)output->save_pixel,
16,475,028✔
2129
                                  output->save_count);
2130
    }
2131

2132
    output->save_pixel = 0;
17,934,612✔
2133
    output->save_count = 0;
17,934,612✔
2134

2135
    return SIXEL_OK;
17,934,612✔
2136
}
2137

2138

2139
/*
2140
 * Emit a run of identical SIXEL cells while keeping the existing repeat
2141
 * accumulator intact.  The helper extends the current run when possible and
2142
 * falls back to flushing through DECGRI before starting a new symbol.
2143
 */
2144
static SIXELSTATUS
2145
sixel_emit_run(sixel_output_t *output, int symbol, int count)
17,934,612✔
2146
{
2147
    SIXELSTATUS status = SIXEL_FALSE;
17,934,612✔
2148

2149
    if (count <= 0) {
17,934,612!
2150
        return SIXEL_OK;
×
2151
    }
2152

2153
    if (output->save_count > 0) {
17,934,612✔
2154
        if (output->save_pixel == symbol) {
15,588,836!
2155
            output->save_count += count;
×
2156
            return SIXEL_OK;
×
2157
        }
2158

2159
        status = sixel_put_flash(output);
15,588,836✔
2160
        if (SIXEL_FAILED(status)) {
15,588,836!
2161
            return status;
×
2162
        }
2163
    }
2164

2165
    output->save_pixel = symbol;
17,934,612✔
2166
    output->save_count = count;
17,934,612✔
2167

2168
    return SIXEL_OK;
17,934,612✔
2169
}
2170

2171

2172
/*
2173
 * Walk a composed node and coalesce identical columns into runs so the
2174
 * emitter touches the repeat accumulator only once per symbol.
2175
 */
2176
static SIXELSTATUS
2177
sixel_emit_span_from_map(sixel_output_t *output,
2,345,776✔
2178
                         unsigned char const *map,
2179
                         int length)
2180
{
2181
    SIXELSTATUS status = SIXEL_FALSE;
2,345,776✔
2182
    int index;
2183
    int run_length;
2184
    unsigned char value;
2185
    size_t chunk_size;
2186
    unsigned long pattern;
2187
    unsigned long block;
2188
    int chunk_mismatch;
2189
    int remain;
2190
    int byte_index;
2191

2192
    if (length <= 0) {
2,345,776!
2193
        return SIXEL_OK;
×
2194
    }
2195

2196
    for (index = 0; index < length; index += run_length) {
19,032,598✔
2197
        value = map[index];
16,686,822✔
2198
        if (value > '?') {
16,686,822!
2199
            value = 0;
×
2200
        }
2201

2202
        run_length = 1;
16,686,822✔
2203
        chunk_size = sizeof(unsigned long);
16,686,822✔
2204
        chunk_mismatch = 0;
16,686,822✔
2205
        if (chunk_size > 1) {
16,686,822!
2206
            remain = length - (index + run_length);
16,686,822✔
2207
            pattern = (~0UL / 0xffUL) * (unsigned long)value;
16,686,822✔
2208

2209
            while (remain >= (int)chunk_size) {
16,948,876✔
2210
                memcpy(&block,
9,510,770✔
2211
                       map + index + run_length,
9,510,770✔
2212
                       chunk_size);
2213
                block ^= pattern;
9,510,770✔
2214
                if (block != 0UL) {
9,510,770✔
2215
                    for (byte_index = 0;
9,248,716!
2216
                         byte_index < (int)chunk_size;
12,659,860!
2217
                         byte_index++) {
3,411,144✔
2218
                        if ((block & 0xffUL) != 0UL) {
12,659,860✔
2219
                            chunk_mismatch = 1;
9,248,716✔
2220
                            break;
9,248,716✔
2221
                        }
2222
                        block >>= 8;
3,411,144✔
2223
                        run_length += 1;
3,411,144✔
2224
                    }
2225
                    break;
9,248,716✔
2226
                }
2227
                run_length += (int)chunk_size;
262,054✔
2228
                remain -= (int)chunk_size;
262,054✔
2229
            }
2230
        }
2231

2232
        if (!chunk_mismatch) {
16,686,822✔
2233
            while (index + run_length < length) {
9,340,960✔
2234
                unsigned char next;
2235

2236
                next = map[index + run_length];
6,995,184✔
2237
                if (next > '?') {
6,995,184!
2238
                    next = 0;
×
2239
                }
2240
                if (next != value) {
6,995,184✔
2241
                    break;
5,092,330✔
2242
                }
2243
                run_length += 1;
1,902,854✔
2244
            }
2245
        }
2246

2247
        status = sixel_emit_run(output,
16,686,822✔
2248
                                 (int)value + '?',
16,686,822✔
2249
                                 run_length);
2250
        if (SIXEL_FAILED(status)) {
16,686,822!
2251
            return status;
×
2252
        }
2253
    }
2254

2255
    return SIXEL_OK;
2,345,776✔
2256
}
2257

2258

2259
static SIXELSTATUS
2260
sixel_put_pixel(sixel_output_t *const output, int pix, double *emit_cells)
×
2261
{
2262
    if (pix < 0 || pix > '?') {
×
2263
        pix = 0;
×
2264
    }
2265

2266
    if (emit_cells != NULL) {
×
2267
        *emit_cells += 1.0;
×
2268
    }
2269

2270
    return sixel_emit_run(output, pix + '?', 1);
×
2271
}
2272

2273
static SIXELSTATUS
2274
sixel_node_new(sixel_node_t **np, sixel_allocator_t *allocator)
339,326✔
2275
{
2276
    SIXELSTATUS status = SIXEL_FALSE;
339,326✔
2277

2278
    *np = (sixel_node_t *)sixel_allocator_malloc(allocator,
339,326✔
2279
                                                 sizeof(sixel_node_t));
2280
    if (np == NULL) {
339,326!
2281
        sixel_helper_set_additional_message(
×
2282
            "sixel_node_new: sixel_allocator_malloc() failed.");
2283
        status = SIXEL_BAD_ALLOCATION;
×
2284
        goto end;
×
2285
    }
2286

2287
    status = SIXEL_OK;
339,326✔
2288

2289
end:
339,326✔
2290
    return status;
339,326✔
2291
}
2292

2293
static void
2294
sixel_node_del(sixel_output_t *output, sixel_node_t *np)
2,345,776✔
2295
{
2296
    sixel_node_t *tp;
2297

2298
    if ((tp = output->node_top) == np) {
2,345,776✔
2299
        output->node_top = np->next;
154,402✔
2300
    } else {
2301
        while (tp->next != NULL) {
452,244,696!
2302
            if (tp->next == np) {
452,244,696✔
2303
                tp->next = np->next;
2,191,374✔
2304
                break;
2,191,374✔
2305
            }
2306
            tp = tp->next;
450,053,322✔
2307
        }
2308
    }
2309

2310
    np->next = output->node_free;
2,345,776✔
2311
    output->node_free = np;
2,345,776✔
2312
}
2,345,776✔
2313

2314

2315
static SIXELSTATUS
2316
sixel_put_node(
2,345,776✔
2317
    sixel_output_t /* in */     *output,  /* output context */
2318
    int            /* in/out */ *x,       /* header position */
2319
    sixel_node_t   /* in */     *np,      /* node object */
2320
    int            /* in */     ncolors,  /* number of palette colors */
2321
    int            /* in */     keycolor, /* transparent color number */
2322
    double         /* in/out */ *emit_cells) /* emitted cell accumulator */
2323
{
2324
    SIXELSTATUS status = SIXEL_FALSE;
2,345,776✔
2325
    int nwrite;
2326

2327
    if (ncolors != 2 || keycolor == (-1)) {
2,345,776✔
2328
        /* designate palette index */
2329
        if (output->active_palette != np->pal) {
2,345,154✔
2330
            sixel_putc(output->buffer + output->pos, '#');
2,310,720✔
2331
            sixel_advance(output, 1);
2,310,720✔
2332
            nwrite = sixel_putnum((char *)output->buffer + output->pos, np->pal);
2,310,720✔
2333
            sixel_advance(output, nwrite);
2,310,720✔
2334
            output->active_palette = np->pal;
2,310,720✔
2335
        }
2336
    }
2337

2338
    if (*x < np->sx) {
2,345,776✔
2339
        int span;
2340

2341
        span = np->sx - *x;
1,247,790✔
2342
        status = sixel_emit_run(output, '?', span);
1,247,790✔
2343
        if (SIXEL_FAILED(status)) {
1,247,790!
2344
            goto end;
×
2345
        }
2346
        if (emit_cells != NULL) {
1,247,790!
2347
            *emit_cells += (double)span;
×
2348
        }
2349
        *x = np->sx;
1,247,790✔
2350
    }
2351

2352
    if (*x < np->mx) {
2,345,776!
2353
        int span;
2354

2355
        span = np->mx - *x;
2,345,776✔
2356
        status = sixel_emit_span_from_map(output,
2,345,776✔
2357
                                          (unsigned char const *)np->map + *x,
2,345,776✔
2358
                                          span);
2359
        if (SIXEL_FAILED(status)) {
2,345,776!
2360
            goto end;
×
2361
        }
2362
        if (emit_cells != NULL) {
2,345,776!
2363
            *emit_cells += (double)span;
×
2364
        }
2365
        *x = np->mx;
2,345,776✔
2366
    }
2367

2368
    status = sixel_put_flash(output);
2,345,776✔
2369
    if (SIXEL_FAILED(status)) {
2,345,776!
2370
        goto end;
×
2371
    }
2372

2373
end:
2,345,776✔
2374
    return status;
2,345,776✔
2375
}
2376

2377

2378
static SIXELSTATUS
2379
sixel_encode_header(int width, int height, sixel_output_t *output)
326✔
2380
{
2381
    SIXELSTATUS status = SIXEL_FALSE;
326✔
2382
    int nwrite;
2383
    int p[3] = {0, 0, 0};
326✔
2384
    int pcount = 3;
326✔
2385
    int use_raster_attributes = 1;
326✔
2386

2387
    if (output->ormode) {
326!
2388
        p[0] = 7;
×
2389
        p[1] = 5;
×
2390
    }
2391

2392
    output->pos = 0;
326✔
2393

2394
    if (! output->skip_dcs_envelope) {
326!
2395
        if (output->has_8bit_control) {
326✔
2396
            sixel_puts(output->buffer + output->pos,
10✔
2397
                       DCS_START_8BIT,
2398
                       DCS_START_8BIT_SIZE);
2399
            sixel_advance(output, DCS_START_8BIT_SIZE);
10✔
2400
        } else {
2401
            sixel_puts(output->buffer + output->pos,
316✔
2402
                       DCS_START_7BIT,
2403
                       DCS_START_7BIT_SIZE);
2404
            sixel_advance(output, DCS_START_7BIT_SIZE);
316✔
2405
        }
2406
    }
2407

2408
    if (output->skip_header) {
326!
2409
        goto laster_attr;
×
2410
    }
2411

2412
    if (p[2] == 0) {
326!
2413
        pcount--;
326✔
2414
        if (p[1] == 0) {
326!
2415
            pcount--;
326✔
2416
            if (p[0] == 0) {
326!
2417
                pcount--;
326✔
2418
            }
2419
        }
2420
    }
2421

2422
    if (pcount > 0) {
326!
2423
        nwrite = sixel_putnum((char *)output->buffer + output->pos, p[0]);
×
2424
        sixel_advance(output, nwrite);
×
2425
        if (pcount > 1) {
×
2426
            sixel_putc(output->buffer + output->pos, ';');
×
2427
            sixel_advance(output, 1);
×
2428
            nwrite = sixel_putnum((char *)output->buffer + output->pos, p[1]);
×
2429
            sixel_advance(output, nwrite);
×
2430
            if (pcount > 2) {
×
2431
                sixel_putc(output->buffer + output->pos, ';');
×
2432
                sixel_advance(output, 1);
×
2433
                nwrite = sixel_putnum((char *)output->buffer + output->pos, p[2]);
×
2434
                sixel_advance(output, nwrite);
×
2435
            }
2436
        }
2437
    }
2438

2439
    sixel_putc(output->buffer + output->pos, 'q');
326✔
2440
    sixel_advance(output, 1);
326✔
2441

2442
laster_attr:
326✔
2443
    if (use_raster_attributes) {
326!
2444
        sixel_puts(output->buffer + output->pos, "\"1;1;", 5);
326✔
2445
        sixel_advance(output, 5);
326✔
2446
        nwrite = sixel_putnum((char *)output->buffer + output->pos, width);
326✔
2447
        sixel_advance(output, nwrite);
326✔
2448
        sixel_putc(output->buffer + output->pos, ';');
326✔
2449
        sixel_advance(output, 1);
326✔
2450
        nwrite = sixel_putnum((char *)output->buffer + output->pos, height);
326✔
2451
        sixel_advance(output, nwrite);
326✔
2452
    }
2453

2454
    status = SIXEL_OK;
326✔
2455

2456
    return status;
326✔
2457
}
2458

2459

2460
static int
2461
sixel_palette_float_pixelformat_for_colorspace(int colorspace)
604✔
2462
{
2463
    switch (colorspace) {
604!
2464
    case SIXEL_COLORSPACE_LINEAR:
×
2465
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
×
2466
    case SIXEL_COLORSPACE_OKLAB:
×
2467
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
×
2468
    default:
604✔
2469
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
604✔
2470
    }
2471
}
2472

2473
static int
2474
sixel_palette_float32_matches_u8(unsigned char const *palette,
×
2475
                                 float const *palette_float,
2476
                                 size_t count,
2477
                                 int float_pixelformat)
2478
{
2479
    size_t index;
2480
    size_t limit;
2481
    unsigned char expected;
2482
    int channel;
2483

2484
    if (palette == NULL || palette_float == NULL || count == 0U) {
×
2485
        return 1;
×
2486
    }
2487
    if (count > SIZE_MAX / 3U) {
×
2488
        return 0;
×
2489
    }
2490
    limit = count * 3U;
×
2491
    for (index = 0U; index < limit; ++index) {
×
2492
        channel = (int)(index % 3U);
×
2493
        expected = sixel_pixelformat_float_channel_to_byte(
×
2494
            float_pixelformat,
2495
            channel,
2496
            palette_float[index]);
×
2497
        if (palette[index] != expected) {
×
2498
            return 0;
×
2499
        }
2500
    }
2501
    return 1;
×
2502
}
2503

2504
static void
2505
sixel_palette_sync_float32_from_u8(unsigned char const *palette,
×
2506
                                   float *palette_float,
2507
                                   size_t count,
2508
                                   int float_pixelformat)
2509
{
2510
    size_t index;
2511
    size_t limit;
2512
    int channel;
2513

2514
    if (palette == NULL || palette_float == NULL || count == 0U) {
×
2515
        return;
×
2516
    }
2517
    if (count > SIZE_MAX / 3U) {
×
2518
        return;
×
2519
    }
2520
    limit = count * 3U;
×
2521
    for (index = 0U; index < limit; ++index) {
×
2522
        channel = (int)(index % 3U);
×
2523
        palette_float[index] = sixel_pixelformat_byte_to_float(
×
2524
            float_pixelformat,
2525
            channel,
2526
            palette[index]);
×
2527
    }
2528
}
2529

2530
static int
2531
sixel_output_palette_channel_to_pct(unsigned char const *palette,
511,494✔
2532
                                    float const *palette_float,
2533
                                    int n,
2534
                                    int channel)
2535
{
2536
    size_t index;
2537
    float value;
2538
    int percent;
2539

2540
    index = (size_t)n * 3U + (size_t)channel;
511,494✔
2541
    if (palette_float != NULL) {
511,494!
2542
        value = palette_float[index];
×
2543
        if (value < 0.0f) {
×
2544
            value = 0.0f;
×
2545
        } else if (value > 1.0f) {
×
2546
            value = 1.0f;
×
2547
        }
2548
        percent = (int)(value * 100.0f + 0.5f);
×
2549
        if (percent < 0) {
×
2550
            percent = 0;
×
2551
        } else if (percent > 100) {
×
2552
            percent = 100;
×
2553
        }
2554
        return percent;
×
2555
    }
2556

2557
    if (palette != NULL) {
511,494!
2558
        return (palette[index] * 100 + 127) / 255;
511,494✔
2559
    }
2560

2561
    return 0;
×
2562
}
2563

2564
static double
2565
sixel_output_palette_channel_to_float(unsigned char const *palette,
161,814✔
2566
                                      float const *palette_float,
2567
                                      int n,
2568
                                      int channel)
2569
{
2570
    size_t index;
2571
    double value;
2572

2573
    index = (size_t)n * 3U + (size_t)channel;
161,814✔
2574
    value = 0.0;
161,814✔
2575
    if (palette_float != NULL) {
161,814!
2576
        value = palette_float[index];
×
2577
    } else if (palette != NULL) {
161,814!
2578
        value = (double)palette[index] / 255.0;
161,814✔
2579
    }
2580
    if (value < 0.0) {
161,814!
2581
        value = 0.0;
×
2582
    } else if (value > 1.0) {
161,814!
2583
        value = 1.0;
×
2584
    }
2585

2586
    return value;
161,814✔
2587
}
2588

2589
static SIXELSTATUS
2590
output_rgb_palette_definition(
170,498✔
2591
    sixel_output_t /* in */ *output,
2592
    unsigned char const /* in */ *palette,
2593
    float const /* in */ *palette_float,
2594
    int            /* in */ n,
2595
    int            /* in */ keycolor
2596
)
2597
{
2598
    SIXELSTATUS status = SIXEL_FALSE;
170,498✔
2599
    int nwrite;
2600

2601
    if (n != keycolor) {
170,498!
2602
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
2603
        sixel_putc(output->buffer + output->pos, '#');
170,498✔
2604
        sixel_advance(output, 1);
170,498✔
2605
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
170,498✔
2606
        sixel_advance(output, nwrite);
170,498✔
2607
        sixel_puts(output->buffer + output->pos, ";2;", 3);
170,498✔
2608
        sixel_advance(output, 3);
170,498✔
2609
        nwrite = sixel_putnum(
170,498✔
2610
            (char *)output->buffer + output->pos,
170,498✔
2611
            sixel_output_palette_channel_to_pct(palette,
2612
                                                palette_float,
2613
                                                n,
2614
                                                0));
2615
        sixel_advance(output, nwrite);
170,498✔
2616
        sixel_putc(output->buffer + output->pos, ';');
170,498✔
2617
        sixel_advance(output, 1);
170,498✔
2618
        nwrite = sixel_putnum(
170,498✔
2619
            (char *)output->buffer + output->pos,
170,498✔
2620
            sixel_output_palette_channel_to_pct(palette,
2621
                                                palette_float,
2622
                                                n,
2623
                                                1));
2624
        sixel_advance(output, nwrite);
170,498✔
2625
        sixel_putc(output->buffer + output->pos, ';');
170,498✔
2626
        sixel_advance(output, 1);
170,498✔
2627
        nwrite = sixel_putnum(
170,498✔
2628
            (char *)output->buffer + output->pos,
170,498✔
2629
            sixel_output_palette_channel_to_pct(palette,
2630
                                                palette_float,
2631
                                                n,
2632
                                                2));
2633
        sixel_advance(output, nwrite);
170,498✔
2634
    }
2635

2636
    status = SIXEL_OK;
170,498✔
2637

2638
    return status;
170,498✔
2639
}
2640

2641

2642
static SIXELSTATUS
2643
output_hls_palette_definition(
53,938✔
2644
    sixel_output_t /* in */ *output,
2645
    unsigned char const /* in */ *palette,
2646
    float const /* in */ *palette_float,
2647
    int            /* in */ n,
2648
    int            /* in */ keycolor
2649
)
2650
{
2651
    SIXELSTATUS status = SIXEL_FALSE;
53,938✔
2652
    double r;
2653
    double g;
2654
    double b;
2655
    double maxc;
2656
    double minc;
2657
    double lightness;
2658
    double saturation;
2659
    double hue;
2660
    double diff;
2661
    int h;
2662
    int l;
2663
    int s;
2664
    int nwrite;
2665

2666
    if (n != keycolor) {
53,938!
2667
        r = sixel_output_palette_channel_to_float(palette,
53,938✔
2668
                                                  palette_float,
2669
                                                  n,
2670
                                                  0);
2671
        g = sixel_output_palette_channel_to_float(palette,
53,938✔
2672
                                                  palette_float,
2673
                                                  n,
2674
                                                  1);
2675
        b = sixel_output_palette_channel_to_float(palette,
53,938✔
2676
                                                  palette_float,
2677
                                                  n,
2678
                                                  2);
2679
        maxc = r > g ? (r > b ? r : b) : (g > b ? g : b);
53,938✔
2680
        minc = r < g ? (r < b ? r : b) : (g < b ? g : b);
53,938✔
2681
        lightness = (maxc + minc) * 0.5;
53,938✔
2682
        saturation = 0.0;
53,938✔
2683
        hue = 0.0;
53,938✔
2684
        diff = maxc - minc;
53,938✔
2685
        if (diff <= 0.0) {
53,938✔
2686
            h = 0;
8✔
2687
            s = 0;
8✔
2688
        } else {
2689
            if (lightness < 0.5) {
53,930✔
2690
                saturation = diff / (maxc + minc);
43,930✔
2691
            } else {
2692
                saturation = diff / (2.0 - maxc - minc);
10,000✔
2693
            }
2694
            if (maxc == r) {
53,930✔
2695
                hue = (g - b) / diff;
32,802✔
2696
            } else if (maxc == g) {
21,128✔
2697
                hue = 2.0 + (b - r) / diff;
19,564✔
2698
            } else {
2699
                hue = 4.0 + (r - g) / diff;
1,564✔
2700
            }
2701
            hue *= 60.0;
53,930✔
2702
            if (hue < 0.0) {
53,930✔
2703
                hue += 360.0;
206✔
2704
            }
2705
            if (hue >= 360.0) {
53,930!
2706
                hue -= 360.0;
×
2707
            }
2708
            /*
2709
             * The DEC HLS color wheel used by DECGCI considers
2710
             * hue==0 to be blue instead of red.  Rotate the hue by
2711
             * +120 degrees so that RGB primaries continue to match
2712
             * the historical img2sixel output and VT340 behavior.
2713
             */
2714
            hue += 120.0;
53,930✔
2715
            while (hue >= 360.0) {
54,704✔
2716
                hue -= 360.0;
774✔
2717
            }
2718
            while (hue < 0.0) {
53,930!
2719
                hue += 360.0;
×
2720
            }
2721
            s = (int)(saturation * 100.0 + 0.5);
53,930✔
2722
            if (s < 0) {
53,930!
2723
                s = 0;
×
2724
            } else if (s > 100) {
53,930!
2725
                s = 100;
×
2726
            }
2727
            h = (int)(hue + 0.5);
53,930✔
2728
            if (h >= 360) {
53,930!
2729
                h -= 360;
×
2730
            } else if (h < 0) {
53,930!
2731
                h = 0;
×
2732
            }
2733
        }
2734
        l = (int)(lightness * 100.0 + 0.5);
53,938✔
2735
        if (l < 0) {
53,938!
2736
            l = 0;
×
2737
        } else if (l > 100) {
53,938!
2738
            l = 100;
×
2739
        }
2740
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
2741
        sixel_putc(output->buffer + output->pos, '#');
53,938✔
2742
        sixel_advance(output, 1);
53,938✔
2743
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
53,938✔
2744
        sixel_advance(output, nwrite);
53,938✔
2745
        sixel_puts(output->buffer + output->pos, ";1;", 3);
53,938✔
2746
        sixel_advance(output, 3);
53,938✔
2747
        nwrite = sixel_putnum((char *)output->buffer + output->pos, h);
53,938✔
2748
        sixel_advance(output, nwrite);
53,938✔
2749
        sixel_putc(output->buffer + output->pos, ';');
53,938✔
2750
        sixel_advance(output, 1);
53,938✔
2751
        nwrite = sixel_putnum((char *)output->buffer + output->pos, l);
53,938✔
2752
        sixel_advance(output, nwrite);
53,938✔
2753
        sixel_putc(output->buffer + output->pos, ';');
53,938✔
2754
        sixel_advance(output, 1);
53,938✔
2755
        nwrite = sixel_putnum((char *)output->buffer + output->pos, s);
53,938✔
2756
        sixel_advance(output, nwrite);
53,938✔
2757
    }
2758

2759
    status = SIXEL_OK;
53,938✔
2760
    return status;
53,938✔
2761
}
2762

2763

2764
static void
2765
sixel_encode_work_init(sixel_encode_work_t *work)
1,068✔
2766
{
2767
    work->map = NULL;
1,068✔
2768
    work->map_size = 0;
1,068✔
2769
    work->columns = NULL;
1,068✔
2770
    work->columns_size = 0;
1,068✔
2771
    work->active_colors = NULL;
1,068✔
2772
    work->active_colors_size = 0;
1,068✔
2773
    work->active_color_index = NULL;
1,068✔
2774
    work->active_color_index_size = 0;
1,068✔
2775
    work->requested_threads = 1;
1,068✔
2776
}
1,068✔
2777

2778
static SIXELSTATUS
2779
sixel_encode_work_allocate(sixel_encode_work_t *work,
1,068✔
2780
                           int width,
2781
                           int ncolors,
2782
                           sixel_allocator_t *allocator)
2783
{
2784
    SIXELSTATUS status = SIXEL_FALSE;
1,068✔
2785
    int len;
2786
    size_t columns_size;
2787
    size_t active_colors_size;
2788
    size_t active_color_index_size;
2789

2790
    len = ncolors * width;
1,068✔
2791
    work->map = (char *)sixel_allocator_calloc(allocator,
1,068✔
2792
                                               (size_t)len,
2793
                                               sizeof(char));
2794
    if (work->map == NULL && len > 0) {
1,068!
2795
        sixel_helper_set_additional_message(
×
2796
            "sixel_encode_body: sixel_allocator_calloc() failed.");
2797
        status = SIXEL_BAD_ALLOCATION;
×
2798
        goto end;
×
2799
    }
2800
    work->map_size = (size_t)len;
1,068✔
2801

2802
    columns_size = sizeof(sixel_node_t *) * (size_t)width;
1,068✔
2803
    if (width > 0) {
1,068!
2804
        work->columns = (sixel_node_t **)sixel_allocator_malloc(
1,068✔
2805
            allocator,
2806
            columns_size);
2807
        if (work->columns == NULL) {
1,068!
2808
            sixel_helper_set_additional_message(
×
2809
                "sixel_encode_body: sixel_allocator_malloc() failed.");
2810
            status = SIXEL_BAD_ALLOCATION;
×
2811
            goto end;
×
2812
        }
2813
        memset(work->columns, 0, columns_size);
1,068✔
2814
        work->columns_size = columns_size;
1,068✔
2815
    } else {
2816
        work->columns = NULL;
×
2817
        work->columns_size = 0;
×
2818
    }
2819

2820
    active_colors_size = (size_t)ncolors;
1,068✔
2821
    if (active_colors_size > 0) {
1,068!
2822
        work->active_colors =
1,068✔
2823
            (unsigned char *)sixel_allocator_malloc(allocator,
1,068✔
2824
                                                    active_colors_size);
2825
        if (work->active_colors == NULL) {
1,068!
2826
            sixel_helper_set_additional_message(
×
2827
                "sixel_encode_body: sixel_allocator_malloc() failed.");
2828
            status = SIXEL_BAD_ALLOCATION;
×
2829
            goto end;
×
2830
        }
2831
        memset(work->active_colors, 0, active_colors_size);
1,068✔
2832
        work->active_colors_size = active_colors_size;
1,068✔
2833
    } else {
2834
        work->active_colors = NULL;
×
2835
        work->active_colors_size = 0;
×
2836
    }
2837

2838
    active_color_index_size = sizeof(int) * (size_t)ncolors;
1,068✔
2839
    if (active_color_index_size > 0) {
1,068!
2840
        work->active_color_index = (int *)sixel_allocator_malloc(
1,068✔
2841
            allocator,
2842
            active_color_index_size);
2843
        if (work->active_color_index == NULL) {
1,068!
2844
            sixel_helper_set_additional_message(
×
2845
                "sixel_encode_body: sixel_allocator_malloc() failed.");
2846
            status = SIXEL_BAD_ALLOCATION;
×
2847
            goto end;
×
2848
        }
2849
        memset(work->active_color_index, 0, active_color_index_size);
1,068✔
2850
        work->active_color_index_size = active_color_index_size;
1,068✔
2851
    } else {
2852
        work->active_color_index = NULL;
×
2853
        work->active_color_index_size = 0;
×
2854
    }
2855

2856
    status = SIXEL_OK;
1,068✔
2857

2858
end:
1,068✔
2859
    if (SIXEL_FAILED(status)) {
1,068!
2860
        if (work->active_color_index != NULL) {
×
2861
            sixel_allocator_free(allocator, work->active_color_index);
×
2862
            work->active_color_index = NULL;
×
2863
        }
2864
        work->active_color_index_size = 0;
×
2865
        if (work->active_colors != NULL) {
×
2866
            sixel_allocator_free(allocator, work->active_colors);
×
2867
            work->active_colors = NULL;
×
2868
        }
2869
        work->active_colors_size = 0;
×
2870
        if (work->columns != NULL) {
×
2871
            sixel_allocator_free(allocator, work->columns);
×
2872
            work->columns = NULL;
×
2873
        }
2874
        work->columns_size = 0;
×
2875
        if (work->map != NULL) {
×
2876
            sixel_allocator_free(allocator, work->map);
×
2877
            work->map = NULL;
×
2878
        }
2879
        work->map_size = 0;
×
2880
    }
2881

2882
    return status;
1,068✔
2883
}
2884

2885
static void
2886
sixel_encode_work_cleanup(sixel_encode_work_t *work,
1,068✔
2887
                          sixel_allocator_t *allocator)
2888
{
2889
    if (work->active_color_index != NULL) {
1,068!
2890
        sixel_allocator_free(allocator, work->active_color_index);
1,068✔
2891
        work->active_color_index = NULL;
1,068✔
2892
    }
2893
    work->active_color_index_size = 0;
1,068✔
2894
    if (work->active_colors != NULL) {
1,068!
2895
        sixel_allocator_free(allocator, work->active_colors);
1,068✔
2896
        work->active_colors = NULL;
1,068✔
2897
    }
2898
    work->active_colors_size = 0;
1,068✔
2899
    if (work->columns != NULL) {
1,068!
2900
        sixel_allocator_free(allocator, work->columns);
1,068✔
2901
        work->columns = NULL;
1,068✔
2902
    }
2903
    work->columns_size = 0;
1,068✔
2904
    if (work->map != NULL) {
1,068!
2905
        sixel_allocator_free(allocator, work->map);
1,068✔
2906
        work->map = NULL;
1,068✔
2907
    }
2908
    work->map_size = 0;
1,068✔
2909
}
1,068✔
2910

2911
static void
2912
sixel_band_state_reset(sixel_band_state_t *state)
12,372✔
2913
{
2914
    state->row_in_band = 0;
12,372✔
2915
    state->fillable = 0;
12,372✔
2916
    state->active_color_count = 0;
12,372✔
2917
}
12,372✔
2918

2919
static void
2920
sixel_band_finish(sixel_encode_work_t *work, sixel_band_state_t *state)
11,304✔
2921
{
2922
    int color_index;
2923
    int c;
2924

2925
    if (work->active_colors == NULL
11,304!
2926
        || work->active_color_index == NULL) {
11,304!
2927
        state->active_color_count = 0;
×
2928
        return;
×
2929
    }
2930

2931
    for (color_index = 0;
11,304✔
2932
         color_index < state->active_color_count;
737,974✔
2933
         color_index++) {
726,670✔
2934
        c = work->active_color_index[color_index];
726,670✔
2935
        if (c >= 0
726,670!
2936
            && (size_t)c < work->active_colors_size) {
726,670!
2937
            work->active_colors[c] = 0;
726,670✔
2938
        }
2939
    }
2940
    state->active_color_count = 0;
11,304✔
2941
}
2942

2943
static void
2944
sixel_band_clear_map(sixel_encode_work_t *work)
11,304✔
2945
{
2946
    if (work->map != NULL && work->map_size > 0) {
11,304!
2947
        memset(work->map, 0, work->map_size);
11,304✔
2948
    }
2949
}
11,304✔
2950

2951
static void
2952
sixel_encode_metrics_init(sixel_encode_metrics_t *metrics)
1,068✔
2953
{
2954
    metrics->encode_probe_active = 0;
1,068✔
2955
    metrics->compose_span_started_at = 0.0;
1,068✔
2956
    metrics->compose_queue_started_at = 0.0;
1,068✔
2957
    metrics->compose_span_duration = 0.0;
1,068✔
2958
    metrics->compose_scan_duration = 0.0;
1,068✔
2959
    metrics->compose_queue_duration = 0.0;
1,068✔
2960
    metrics->compose_scan_probes = 0.0;
1,068✔
2961
    metrics->compose_queue_nodes = 0.0;
1,068✔
2962
    metrics->emit_cells = 0.0;
1,068✔
2963
    metrics->emit_cells_ptr = NULL;
1,068✔
2964
}
1,068✔
2965

2966
static SIXELSTATUS
2967
sixel_encode_emit_palette(int bodyonly,
1,068✔
2968
                          int ncolors,
2969
                          int keycolor,
2970
                          unsigned char const *palette,
2971
                          float const *palette_float,
2972
                          sixel_output_t *output,
2973
                          int encode_probe_active)
2974
{
2975
    SIXELSTATUS status = SIXEL_FALSE;
1,068✔
2976
    double span_started_at;
2977
    int n;
2978

2979
    if (bodyonly || (ncolors == 2 && keycolor != (-1))) {
1,068!
2980
        return SIXEL_OK;
8✔
2981
    }
2982

2983
    if (palette == NULL && palette_float == NULL) {
1,060!
2984
        sixel_helper_set_additional_message(
×
2985
            "sixel_encode_emit_palette: missing palette data.");
2986
        return SIXEL_BAD_ARGUMENT;
×
2987
    }
2988

2989
    span_started_at = sixel_encode_span_start(encode_probe_active);
1,060✔
2990
    if (output->palette_type == SIXEL_PALETTETYPE_HLS) {
1,060✔
2991
        for (n = 0; n < ncolors; n++) {
54,150✔
2992
            status = output_hls_palette_definition(output,
53,938✔
2993
                                                   palette,
2994
                                                   palette_float,
2995
                                                   n,
2996
                                                   keycolor);
2997
            if (SIXEL_FAILED(status)) {
53,938!
2998
                goto end;
×
2999
            }
3000
        }
3001
    } else {
3002
        for (n = 0; n < ncolors; n++) {
171,346✔
3003
            status = output_rgb_palette_definition(output,
170,498✔
3004
                                                   palette,
3005
                                                   palette_float,
3006
                                                   n,
3007
                                                   keycolor);
3008
            if (SIXEL_FAILED(status)) {
170,498!
3009
                goto end;
×
3010
            }
3011
        }
3012
    }
3013

3014
    sixel_encode_span_commit(encode_probe_active,
1,060✔
3015
                             SIXEL_ASSESSMENT_STAGE_ENCODE_PREPARE,
3016
                             span_started_at);
3017

3018
    status = SIXEL_OK;
1,060✔
3019

3020
end:
1,060✔
3021
    return status;
1,060✔
3022
}
3023

3024
static SIXELSTATUS
3025
sixel_band_classify_row(sixel_encode_work_t *work,
67,182✔
3026
                        sixel_band_state_t *state,
3027
                        sixel_index_t *pixels,
3028
                        int width,
3029
                        int absolute_row,
3030
                        int ncolors,
3031
                        int keycolor,
3032
                        unsigned char *palstate,
3033
                        int encode_policy)
3034
{
3035
    SIXELSTATUS status = SIXEL_FALSE;
67,182✔
3036
    int row_bit;
3037
    int band_start;
3038
    int pix;
3039
    int x;
3040
    int check_integer_overflow;
3041
    char *map;
3042
    unsigned char *active_colors;
3043
    int *active_color_index;
3044

3045
    map = work->map;
67,182✔
3046
    active_colors = work->active_colors;
67,182✔
3047
    active_color_index = work->active_color_index;
67,182✔
3048
    row_bit = state->row_in_band;
67,182✔
3049
    band_start = absolute_row - row_bit;
67,182✔
3050

3051
    if (row_bit == 0) {
67,182✔
3052
        if (encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
11,304✔
3053
            state->fillable = 0;
11,154✔
3054
        } else if (palstate) {
150!
3055
            if (width > 0) {
×
3056
                pix = pixels[band_start * width];
×
3057
                if (pix >= ncolors) {
×
3058
                    state->fillable = 0;
×
3059
                } else {
3060
                    state->fillable = 1;
×
3061
                }
3062
            } else {
3063
                state->fillable = 0;
×
3064
            }
3065
        } else {
3066
            state->fillable = 1;
150✔
3067
        }
3068
        state->active_color_count = 0;
11,304✔
3069
    }
3070

3071
    for (x = 0; x < width; x++) {
41,391,530✔
3072
        if (absolute_row > INT_MAX / width) {
41,324,348!
3073
            sixel_helper_set_additional_message(
×
3074
                "sixel_encode_body: integer overflow detected."
3075
                " (y > INT_MAX)");
3076
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3077
            goto end;
×
3078
        }
3079
        check_integer_overflow = absolute_row * width;
41,324,348✔
3080
        if (check_integer_overflow > INT_MAX - x) {
41,324,348!
3081
            sixel_helper_set_additional_message(
×
3082
                "sixel_encode_body: integer overflow detected."
3083
                " (y * width > INT_MAX - x)");
3084
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3085
            goto end;
×
3086
        }
3087
        pix = pixels[check_integer_overflow + x];
41,324,348✔
3088
        if (pix >= 0 && pix < ncolors && pix != keycolor) {
41,324,348!
3089
            if (pix > INT_MAX / width) {
37,626,600!
3090
                sixel_helper_set_additional_message(
×
3091
                    "sixel_encode_body: integer overflow detected."
3092
                    " (pix > INT_MAX / width)");
3093
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3094
                goto end;
×
3095
            }
3096
            check_integer_overflow = pix * width;
37,626,600✔
3097
            if (check_integer_overflow > INT_MAX - x) {
37,626,600!
3098
                sixel_helper_set_additional_message(
×
3099
                    "sixel_encode_body: integer overflow detected."
3100
                    " (pix * width > INT_MAX - x)");
3101
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3102
                goto end;
×
3103
            }
3104
            map[pix * width + x] |= (1 << row_bit);
37,626,600✔
3105
            if (active_colors != NULL && active_colors[pix] == 0) {
37,626,600!
3106
                active_colors[pix] = 1;
726,670✔
3107
                if (state->active_color_count < ncolors
726,670!
3108
                    && active_color_index != NULL) {
726,670!
3109
                    active_color_index[state->active_color_count] = pix;
726,670✔
3110
                    state->active_color_count += 1;
726,670✔
3111
                }
3112
            }
3113
        } else if (!palstate) {
3,697,748✔
3114
            state->fillable = 0;
878,604✔
3115
        }
3116
    }
3117

3118
    state->row_in_band += 1;
67,182✔
3119
    status = SIXEL_OK;
67,182✔
3120

3121
end:
67,182✔
3122
    return status;
67,182✔
3123
}
3124

3125
static SIXELSTATUS
3126
sixel_band_compose(sixel_encode_work_t *work,
11,304✔
3127
                   sixel_band_state_t *state,
3128
                   sixel_output_t *output,
3129
                   int width,
3130
                   int ncolors,
3131
                   int keycolor,
3132
                   sixel_allocator_t *allocator,
3133
                   sixel_encode_metrics_t *metrics)
3134
{
3135
    SIXELSTATUS status = SIXEL_FALSE;
11,304✔
3136
    int color_index;
3137
    int c;
3138
    unsigned char *row;
3139
    int sx;
3140
    int mx;
3141
    int gap;
3142
    int gap_reached_end;
3143
    double now;
3144
    sixel_node_t *np;
3145
    sixel_node_t *column_head;
3146
    sixel_node_t *column_tail;
3147
    sixel_node_t top;
3148

3149
    (void)ncolors;
3150
    (void)keycolor;
3151
    row = NULL;
11,304✔
3152
    gap_reached_end = 0;
11,304✔
3153
    now = 0.0;
11,304✔
3154
    np = NULL;
11,304✔
3155
    column_head = NULL;
11,304✔
3156
    column_tail = NULL;
11,304✔
3157
    top.next = NULL;
11,304✔
3158

3159
    if (work->columns != NULL) {
11,304!
3160
        memset(work->columns, 0, work->columns_size);
11,304✔
3161
    }
3162
    output->node_top = NULL;
11,304✔
3163

3164
    metrics->compose_span_started_at =
11,304✔
3165
        sixel_encode_span_start(metrics->encode_probe_active);
11,304✔
3166
    metrics->compose_queue_started_at = 0.0;
11,304✔
3167
    metrics->compose_span_duration = 0.0;
11,304✔
3168
    metrics->compose_queue_duration = 0.0;
11,304✔
3169
    metrics->compose_scan_duration = 0.0;
11,304✔
3170
    metrics->compose_scan_probes = 0.0;
11,304✔
3171
    metrics->compose_queue_nodes = 0.0;
11,304✔
3172

3173
    for (color_index = 0;
11,304✔
3174
         color_index < state->active_color_count;
737,974✔
3175
         color_index++) {
726,670✔
3176
        c = work->active_color_index[color_index];
726,670✔
3177
        row = (unsigned char *)(work->map + c * width);
726,670✔
3178
        sx = 0;
726,670✔
3179
        while (sx < width) {
3,072,446✔
3180
            sx = sixel_compose_find_run_start(
3,050,586✔
3181
                row,
3182
                width,
3183
                sx,
3184
                metrics->encode_probe_active,
3185
                &metrics->compose_scan_probes);
3186
            if (sx >= width) {
3,050,586✔
3187
                break;
704,810✔
3188
            }
3189

3190
            if (metrics->encode_probe_active) {
2,345,776!
3191
                now = sixel_assessment_timer_now();
×
3192
                metrics->compose_queue_started_at = now;
×
3193
                metrics->compose_queue_nodes += 1.0;
×
3194
            }
3195

3196
            mx = sx + 1;
2,345,776✔
3197
            while (mx < width) {
19,776,528✔
3198
                if (metrics->encode_probe_active) {
19,754,668!
3199
                    metrics->compose_scan_probes += 1.0;
×
3200
                }
3201
                if (row[mx] != 0) {
19,754,668✔
3202
                    mx += 1;
14,348,852✔
3203
                    continue;
14,348,852✔
3204
                }
3205

3206
                gap = sixel_compose_measure_gap(
5,405,816✔
3207
                    row,
3208
                    width,
3209
                    mx + 1,
3210
                    metrics->encode_probe_active,
3211
                    &metrics->compose_scan_probes,
3212
                    &gap_reached_end);
3213
                if (gap >= 9 || gap_reached_end) {
5,405,816✔
3214
                    break;
3215
                }
3216
                mx += gap + 1;
3,081,900✔
3217
            }
3218

3219
            if ((np = output->node_free) != NULL) {
2,345,776✔
3220
                output->node_free = np->next;
2,006,450✔
3221
            } else {
3222
                status = sixel_node_new(&np, allocator);
339,326✔
3223
                if (SIXEL_FAILED(status)) {
339,326!
3224
                    goto end;
×
3225
                }
3226
            }
3227

3228
            np->pal = c;
2,345,776✔
3229
            np->sx = sx;
2,345,776✔
3230
            np->mx = mx;
2,345,776✔
3231
            np->map = (char *)row;
2,345,776✔
3232
            np->next = NULL;
2,345,776✔
3233

3234
            if (work->columns != NULL) {
2,345,776!
3235
                column_head = work->columns[sx];
2,345,776✔
3236
                if (column_head == NULL
2,345,776✔
3237
                    || column_head->mx <= np->mx) {
792,742✔
3238
                    np->next = column_head;
1,970,688✔
3239
                    work->columns[sx] = np;
1,970,688✔
3240
                } else {
3241
                    column_tail = column_head;
375,088✔
3242
                    while (column_tail->next != NULL
375,088✔
3243
                           && column_tail->next->mx > np->mx) {
455,534✔
3244
                        column_tail = column_tail->next;
80,446✔
3245
                    }
3246
                    np->next = column_tail->next;
375,088✔
3247
                    column_tail->next = np;
375,088✔
3248
                }
3249
            } else {
3250
                top.next = output->node_top;
×
3251
                column_tail = &top;
×
3252

3253
                while (column_tail->next != NULL) {
×
3254
                    if (np->sx < column_tail->next->sx) {
×
3255
                        break;
×
3256
                    } else if (np->sx == column_tail->next->sx
×
3257
                               && np->mx > column_tail->next->mx) {
×
3258
                        break;
×
3259
                    }
3260
                    column_tail = column_tail->next;
×
3261
                }
3262

3263
                np->next = column_tail->next;
×
3264
                column_tail->next = np;
×
3265
                output->node_top = top.next;
×
3266
            }
3267

3268
            if (metrics->encode_probe_active) {
2,345,776!
3269
                now = sixel_assessment_timer_now();
×
3270
                metrics->compose_queue_duration +=
×
3271
                    now - metrics->compose_queue_started_at;
×
3272
            }
3273

3274
            sx = mx;
2,345,776✔
3275
        }
3276
    }
3277

3278
    if (work->columns != NULL) {
11,304!
3279
        if (metrics->encode_probe_active) {
11,304!
3280
            metrics->compose_queue_started_at =
×
3281
                sixel_assessment_timer_now();
×
3282
        }
3283
        top.next = NULL;
11,304✔
3284
        column_tail = &top;
11,304✔
3285
        for (sx = 0; sx < width; sx++) {
6,913,832✔
3286
            column_head = work->columns[sx];
6,902,528✔
3287
            if (column_head == NULL) {
6,902,528✔
3288
                continue;
5,349,494✔
3289
            }
3290
            column_tail->next = column_head;
1,553,034✔
3291
            while (column_tail->next != NULL) {
3,898,810✔
3292
                column_tail = column_tail->next;
2,345,776✔
3293
            }
3294
            work->columns[sx] = NULL;
1,553,034✔
3295
        }
3296
        output->node_top = top.next;
11,304✔
3297
        if (metrics->encode_probe_active) {
11,304!
3298
            now = sixel_assessment_timer_now();
×
3299
            metrics->compose_queue_duration +=
×
3300
                now - metrics->compose_queue_started_at;
×
3301
        }
3302
    }
3303

3304
    if (metrics->encode_probe_active) {
11,304!
3305
        now = sixel_assessment_timer_now();
×
3306
        metrics->compose_span_duration =
×
3307
            now - metrics->compose_span_started_at;
×
3308
        metrics->compose_scan_duration =
×
3309
            metrics->compose_span_duration
×
3310
            - metrics->compose_queue_duration;
×
3311
        if (metrics->compose_scan_duration < 0.0) {
×
3312
            metrics->compose_scan_duration = 0.0;
×
3313
        }
3314
        if (metrics->compose_queue_duration < 0.0) {
×
3315
            metrics->compose_queue_duration = 0.0;
×
3316
        }
3317
        if (metrics->compose_span_duration < 0.0) {
×
3318
            metrics->compose_span_duration = 0.0;
×
3319
        }
3320
        sixel_assessment_record_encode_span(
×
3321
            SIXEL_ASSESSMENT_STAGE_ENCODE_COMPOSE_SCAN,
3322
            metrics->compose_scan_duration);
3323
        sixel_assessment_record_encode_span(
×
3324
            SIXEL_ASSESSMENT_STAGE_ENCODE_COMPOSE_QUEUE,
3325
            metrics->compose_queue_duration);
3326
        sixel_assessment_record_encode_span(
×
3327
            SIXEL_ASSESSMENT_STAGE_ENCODE_COMPOSE,
3328
            metrics->compose_span_duration);
3329
        sixel_assessment_record_encode_work(
×
3330
            SIXEL_ASSESSMENT_STAGE_ENCODE_COMPOSE_SCAN,
3331
            metrics->compose_scan_probes);
3332
        sixel_assessment_record_encode_work(
×
3333
            SIXEL_ASSESSMENT_STAGE_ENCODE_COMPOSE_QUEUE,
3334
            metrics->compose_queue_nodes);
3335
        sixel_assessment_record_encode_work(
×
3336
            SIXEL_ASSESSMENT_STAGE_ENCODE_COMPOSE,
3337
            metrics->compose_queue_nodes);
3338
    }
3339

3340
    status = SIXEL_OK;
11,304✔
3341

3342
end:
11,304✔
3343
    return status;
11,304✔
3344
}
3345

3346
static SIXELSTATUS
3347
sixel_band_emit(sixel_encode_work_t *work,
11,304✔
3348
                sixel_band_state_t *state,
3349
                sixel_output_t *output,
3350
                int ncolors,
3351
                int keycolor,
3352
                int last_row_index,
3353
                sixel_encode_metrics_t *metrics)
3354
{
3355
    SIXELSTATUS status = SIXEL_FALSE;
11,304✔
3356
    double span_started_at;
3357
    sixel_node_t *np;
3358
    sixel_node_t *next;
3359
    int x;
3360

3361
    span_started_at = sixel_encode_span_start(metrics->encode_probe_active);
11,304✔
3362
    if (last_row_index != 5) {
11,304✔
3363
        output->buffer[output->pos] = '-';
10,236✔
3364
        sixel_advance(output, 1);
10,236✔
3365
    }
3366

3367
    for (x = 0; (np = output->node_top) != NULL;) {
96,508✔
3368
        if (x > np->sx) {
85,204✔
3369
            output->buffer[output->pos] = '$';
73,900✔
3370
            sixel_advance(output, 1);
73,900✔
3371
            x = 0;
73,900✔
3372
        }
3373

3374
        if (state->fillable) {
85,204✔
3375
            memset(np->map + np->sx,
150✔
3376
                   (1 << state->row_in_band) - 1,
150✔
3377
                   (size_t)(np->mx - np->sx));
150✔
3378
        }
3379
        status = sixel_put_node(output,
85,204✔
3380
                                &x,
3381
                                np,
3382
                                ncolors,
3383
                                keycolor,
3384
                                metrics->emit_cells_ptr);
3385
        if (SIXEL_FAILED(status)) {
85,204!
3386
            goto end;
×
3387
        }
3388
        next = np->next;
85,204✔
3389
        sixel_node_del(output, np);
85,204✔
3390
        np = next;
85,204✔
3391

3392
        while (np != NULL) {
10,562,036✔
3393
            if (np->sx < x) {
10,476,832✔
3394
                np = np->next;
8,216,260✔
3395
                continue;
8,216,260✔
3396
            }
3397

3398
            if (state->fillable) {
2,260,572✔
3399
                memset(np->map + np->sx,
154✔
3400
                       (1 << state->row_in_band) - 1,
154✔
3401
                       (size_t)(np->mx - np->sx));
154✔
3402
            }
3403
            status = sixel_put_node(output,
2,260,572✔
3404
                                    &x,
3405
                                    np,
3406
                                    ncolors,
3407
                                    keycolor,
3408
                                    metrics->emit_cells_ptr);
3409
            if (SIXEL_FAILED(status)) {
2,260,572!
3410
                goto end;
×
3411
            }
3412
            next = np->next;
2,260,572✔
3413
            sixel_node_del(output, np);
2,260,572✔
3414
            np = next;
2,260,572✔
3415
        }
3416

3417
        state->fillable = 0;
85,204✔
3418
    }
3419

3420
    sixel_encode_span_commit(metrics->encode_probe_active,
11,304✔
3421
                             SIXEL_ASSESSMENT_STAGE_ENCODE_EMIT,
3422
                             span_started_at);
3423

3424
    status = SIXEL_OK;
11,304✔
3425

3426
end:
11,304✔
3427
    (void)work;
3428
    return status;
11,304✔
3429
}
3430

3431

3432
static SIXELSTATUS
3433
sixel_encode_body(
1,068✔
3434
    sixel_index_t       /* in */ *pixels,
3435
    int                 /* in */ width,
3436
    int                 /* in */ height,
3437
    unsigned char       /* in */ *palette,
3438
    float const         /* in */ *palette_float,
3439
    int                 /* in */ ncolors,
3440
    int                 /* in */ keycolor,
3441
    int                 /* in */ bodyonly,
3442
    sixel_output_t      /* in */ *output,
3443
    unsigned char       /* in */ *palstate,
3444
    sixel_allocator_t   /* in */ *allocator)
3445
{
3446
    SIXELSTATUS status = SIXEL_FALSE;
1,068✔
3447
    int encode_probe_active;
3448
    double span_started_at;
3449
    int band_start;
3450
    int band_height;
3451
    int row_index;
3452
    int absolute_row;
3453
    int last_row_index;
3454
    sixel_node_t *np;
3455
    sixel_encode_work_t work;
3456
    sixel_band_state_t band;
3457
    sixel_encode_metrics_t metrics;
3458

3459
    sixel_encode_work_init(&work);
1,068✔
3460
    sixel_band_state_reset(&band);
1,068✔
3461
    sixel_encode_metrics_init(&metrics);
1,068✔
3462

3463
    /* Record the caller/environment preference even before we fan out. */
3464
    work.requested_threads = sixel_threads_resolve();
1,068✔
3465

3466
    if (ncolors < 1) {
1,068!
3467
        status = SIXEL_BAD_ARGUMENT;
×
3468
        goto cleanup;
×
3469
    }
3470
    output->active_palette = (-1);
1,068✔
3471

3472
    encode_probe_active = sixel_assessment_encode_probe_enabled();
1,068✔
3473
    metrics.encode_probe_active = encode_probe_active;
1,068✔
3474
    if (encode_probe_active != 0) {
1,068!
3475
        metrics.emit_cells_ptr = &metrics.emit_cells;
×
3476
    } else {
3477
        metrics.emit_cells_ptr = NULL;
1,068✔
3478
    }
3479

3480
    sixel_assessment_set_encode_parallelism(1);
1,068✔
3481

3482
    status = sixel_encode_emit_palette(bodyonly,
1,068✔
3483
                                       ncolors,
3484
                                       keycolor,
3485
                                       palette,
3486
                                       palette_float,
3487
                                       output,
3488
                                       encode_probe_active);
3489
    if (SIXEL_FAILED(status)) {
1,068!
3490
        goto cleanup;
×
3491
    }
3492

3493
#if SIXEL_ENABLE_THREADS
3494
    {
3495
        int nbands;
3496
        int threads;
3497

3498
        nbands = (height + 5) / 6;
1,068✔
3499
        threads = work.requested_threads;
1,068✔
3500
        if (nbands > 1 && threads > 1) {
1,068!
3501
            status = sixel_encode_body_parallel(pixels,
×
3502
                                                width,
3503
                                                height,
3504
                                                ncolors,
3505
                                                keycolor,
3506
                                                output,
3507
                                                palstate,
3508
                                                allocator,
3509
                                                threads);
3510
            if (SIXEL_FAILED(status)) {
×
3511
                goto cleanup;
×
3512
            }
3513
            goto finalize;
×
3514
        }
3515
    }
3516
#endif
3517

3518
    status = sixel_encode_work_allocate(&work,
1,068✔
3519
                                        width,
3520
                                        ncolors,
3521
                                        allocator);
3522
    if (SIXEL_FAILED(status)) {
1,068!
3523
        goto cleanup;
×
3524
    }
3525

3526
    band_start = 0;
1,068✔
3527
    while (band_start < height) {
12,372✔
3528
        band_height = height - band_start;
11,304✔
3529
        if (band_height > 6) {
11,304✔
3530
            band_height = 6;
10,236✔
3531
        }
3532

3533
        band.row_in_band = 0;
11,304✔
3534
        band.fillable = 0;
11,304✔
3535
        band.active_color_count = 0;
11,304✔
3536

3537
        for (row_index = 0; row_index < band_height; row_index++) {
78,486✔
3538
            absolute_row = band_start + row_index;
67,182✔
3539
            span_started_at =
3540
                sixel_encode_span_start(encode_probe_active);
67,182✔
3541
            status = sixel_band_classify_row(&work,
67,182✔
3542
                                             &band,
3543
                                             pixels,
3544
                                             width,
3545
                                             absolute_row,
3546
                                             ncolors,
3547
                                             keycolor,
3548
                                             palstate,
3549
                                             output->encode_policy);
3550
            if (SIXEL_FAILED(status)) {
67,182!
3551
                goto cleanup;
×
3552
            }
3553
            sixel_encode_span_commit(
67,182✔
3554
                encode_probe_active,
3555
                SIXEL_ASSESSMENT_STAGE_ENCODE_CLASSIFY,
3556
                span_started_at);
3557
        }
3558

3559
        status = sixel_band_compose(&work,
11,304✔
3560
                                    &band,
3561
                                    output,
3562
                                    width,
3563
                                    ncolors,
3564
                                    keycolor,
3565
                                    allocator,
3566
                                    &metrics);
3567
        if (SIXEL_FAILED(status)) {
11,304!
3568
            goto cleanup;
×
3569
        }
3570

3571
        last_row_index = band_start + band_height - 1;
11,304✔
3572
        status = sixel_band_emit(&work,
11,304✔
3573
                                 &band,
3574
                                 output,
3575
                                 ncolors,
3576
                                 keycolor,
3577
                                 last_row_index,
3578
                                 &metrics);
3579
        if (SIXEL_FAILED(status)) {
11,304!
3580
            goto cleanup;
×
3581
        }
3582

3583
        sixel_band_finish(&work, &band);
11,304✔
3584

3585
        span_started_at =
3586
            sixel_encode_span_start(encode_probe_active);
11,304✔
3587
        sixel_band_clear_map(&work);
11,304✔
3588
        sixel_encode_span_commit(
11,304✔
3589
            encode_probe_active,
3590
            SIXEL_ASSESSMENT_STAGE_ENCODE_PREPARE,
3591
            span_started_at);
3592

3593
        band_start += band_height;
11,304✔
3594
        sixel_band_state_reset(&band);
11,304✔
3595
    }
3596

3597
    status = SIXEL_OK;
1,068✔
3598
    goto finalize;
1,068✔
3599

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

3610
    if (encode_probe_active) {
1,068!
3611
        sixel_assessment_record_encode_work(
×
3612
            SIXEL_ASSESSMENT_STAGE_ENCODE_EMIT,
3613
            metrics.emit_cells);
3614
    }
3615

3616
cleanup:
1,068✔
3617
    while ((np = output->node_free) != NULL) {
340,394✔
3618
        output->node_free = np->next;
339,326✔
3619
        sixel_allocator_free(allocator, np);
339,326✔
3620
    }
3621
    output->node_top = NULL;
1,068✔
3622

3623
    sixel_encode_work_cleanup(&work, allocator);
1,068✔
3624

3625
    return status;
1,068✔
3626
}
3627
static SIXELSTATUS
3628
sixel_encode_footer(sixel_output_t *output)
326✔
3629
{
3630
    SIXELSTATUS status = SIXEL_FALSE;
326✔
3631

3632
    if (!output->skip_dcs_envelope && !output->penetrate_multiplexer) {
326!
3633
        if (output->has_8bit_control) {
322✔
3634
            sixel_puts(output->buffer + output->pos,
10✔
3635
                       DCS_END_8BIT, DCS_END_8BIT_SIZE);
3636
            sixel_advance(output, DCS_END_8BIT_SIZE);
10✔
3637
        } else {
3638
            sixel_puts(output->buffer + output->pos,
312✔
3639
                       DCS_END_7BIT, DCS_END_7BIT_SIZE);
3640
            sixel_advance(output, DCS_END_7BIT_SIZE);
312✔
3641
        }
3642
    }
3643

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

3663
    status = SIXEL_OK;
326✔
3664

3665
    return status;
326✔
3666
}
3667

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

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

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

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

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

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

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

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

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

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

3771
    return 0;
×
3772
}
3773

3774

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

3805
    palette_entries = NULL;
302✔
3806
    palette_entries_float32 = NULL;
302✔
3807
    palette_obj = NULL;
302✔
3808
    palette_count = 0U;
302✔
3809
    palette_float_count = 0U;
302✔
3810
    palette_bytes = 0U;
302✔
3811
    palette_float_bytes = 0U;
302✔
3812
    palette_channels = 0U;
302✔
3813
    palette_index = 0U;
302✔
3814
    palette_source_colorspace = SIXEL_COLORSPACE_GAMMA;
302✔
3815
    palette_float_pixelformat =
3816
        sixel_palette_float_pixelformat_for_colorspace(
302✔
3817
            palette_source_colorspace);
3818
    output_float_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
302✔
3819
    status = sixel_dither_get_quantized_palette(dither, &palette_obj);
302✔
3820
    if (SIXEL_FAILED(status)) {
302!
3821
        sixel_helper_set_additional_message(
×
3822
            "sixel_encode_dither: palette acquisition failed.");
3823
        goto end;
×
3824
    }
3825

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

3893
    if (pipeline_active) {
302!
NEW
3894
        sixel_parallel_dither_configure(height,
×
3895
                                        pipeline_threads,
3896
                                        &dither_parallel);
NEW
3897
        if (dither_parallel.enabled) {
×
NEW
3898
            dither->pipeline_parallel_active = 1;
×
NEW
3899
            dither->pipeline_band_height = dither_parallel.band_height;
×
NEW
3900
            dither->pipeline_band_overlap = dither_parallel.overlap;
×
NEW
3901
            dither->pipeline_dither_threads =
×
NEW
3902
                dither_parallel.dither_threads;
×
NEW
3903
            pipeline_threads = dither_parallel.encode_threads;
×
3904
        }
NEW
3905
        if (pipeline_threads <= 1) {
×
3906
            /*
3907
             * Disable the pipeline when the encode side cannot spawn at
3908
             * least two workers.  A single encode thread cannot consume the
3909
             * six-line jobs that PaletteApply produces, so fall back to the
3910
             * serialized encoder path.
3911
             */
NEW
3912
            pipeline_active = 0;
×
NEW
3913
            dither->pipeline_parallel_active = 0;
×
NEW
3914
            if (paletted_pixels == NULL) {
×
NEW
3915
                paletted_pixels = sixel_dither_apply_palette(dither, pixels,
×
3916
                                                             width, height);
NEW
3917
                if (paletted_pixels == NULL) {
×
NEW
3918
                    status = SIXEL_RUNTIME_ERROR;
×
NEW
3919
                    goto end;
×
3920
                }
3921
            }
NEW
3922
            input_pixels = paletted_pixels;
×
3923
        }
3924
    }
3925

3926
    if (output != NULL) {
302!
3927
        palette_source_colorspace = output->source_colorspace;
302✔
3928
        palette_float_pixelformat =
3929
            sixel_palette_float_pixelformat_for_colorspace(
302✔
3930
                palette_source_colorspace);
3931
    }
3932

3933
    status = sixel_palette_copy_entries_8bit(
302✔
3934
        palette_obj,
3935
        &palette_entries,
3936
        &palette_count,
3937
        SIXEL_PIXELFORMAT_RGB888,
3938
        dither->allocator);
3939

3940
    if (SIXEL_SUCCEEDED(status)) {
302!
3941
        status = sixel_palette_copy_entries_float32(
302✔
3942
            palette_obj,
3943
            &palette_entries_float32,
3944
            &palette_float_count,
3945
            SIXEL_PIXELFORMAT_RGBFLOAT32,
3946
            dither->allocator);
3947
    }
3948

3949
    (void)palette_float_count;
3950

3951
    sixel_palette_unref(palette_obj);
302✔
3952
    palette_obj = NULL;
302✔
3953
    if (palette_entries != NULL && palette_entries_float32 != NULL
302!
3954
            && palette_count == palette_float_count
×
3955
            && palette_count > 0U
×
3956
            && !sixel_palette_float32_matches_u8(
×
3957
                    palette_entries,
3958
                    palette_entries_float32,
3959
                    palette_count,
3960
                    palette_float_pixelformat)) {
3961
        sixel_palette_sync_float32_from_u8(palette_entries,
×
3962
                                           palette_entries_float32,
3963
                                           palette_count,
3964
                                           palette_float_pixelformat);
3965
    }
3966
    if (palette_entries != NULL && palette_count > 0U
302!
3967
            && output != NULL
302!
3968
            && output->source_colorspace != output->colorspace) {
302!
3969
        palette_bytes = palette_count * 3U;
×
3970
        if (palette_entries_float32 != NULL
×
3971
                && palette_float_count == palette_count) {
×
3972
            /*
3973
             * Use the higher-precision palette to change color spaces once and
3974
             * then quantize those float channels down to bytes.  The previous
3975
             * implementation converted the 8bit entries before overwriting
3976
             * them from float again, doubling the amount of work and rounding
3977
             * the palette twice.
3978
             */
3979
            palette_float_bytes = palette_bytes * sizeof(float);
×
3980
            status = sixel_helper_convert_colorspace(
×
3981
                (unsigned char *)palette_entries_float32,
3982
                palette_float_bytes,
3983
                palette_float_pixelformat,
3984
                output->source_colorspace,
3985
                output->colorspace);
3986
            if (SIXEL_FAILED(status)) {
×
3987
                sixel_helper_set_additional_message(
×
3988
                    "sixel_encode_dither: float palette colorspace conversion failed.");
3989
                goto end;
×
3990
            }
3991
            output_float_pixelformat =
3992
                sixel_palette_float_pixelformat_for_colorspace(
×
3993
                    output->colorspace);
3994
            palette_channels = palette_count * 3U;
×
3995
            for (palette_index = 0U; palette_index < palette_channels;
×
3996
                    ++palette_index) {
×
3997
                int channel;
3998

3999
                channel = (int)(palette_index % 3U);
×
4000
                palette_entries[palette_index] =
×
4001
                    sixel_pixelformat_float_channel_to_byte(
×
4002
                        output_float_pixelformat,
4003
                        channel,
4004
                        palette_entries_float32[palette_index]);
×
4005
            }
4006
        } else {
4007
            status = sixel_helper_convert_colorspace(palette_entries,
×
4008
                                                     palette_bytes,
4009
                                                     SIXEL_PIXELFORMAT_RGB888,
4010
                                                     output->source_colorspace,
4011
                                                     output->colorspace);
4012
            if (SIXEL_FAILED(status)) {
×
4013
                sixel_helper_set_additional_message(
×
4014
                    "sixel_encode_dither: palette colorspace conversion failed.");
4015
                goto end;
×
4016
            }
4017
        }
4018
    }
4019
    if (SIXEL_FAILED(status) || palette_entries == NULL) {
302!
4020
        sixel_helper_set_additional_message(
×
4021
            "sixel_encode_dither: palette copy failed.");
4022
        goto end;
×
4023
    }
4024

4025
    status = sixel_encode_header(width, height, output);
302✔
4026
    if (SIXEL_FAILED(status)) {
302!
4027
        goto end;
×
4028
    }
4029

4030
    if (output->ormode) {
302!
4031
        status = sixel_encode_body_ormode(input_pixels,
×
4032
                                          width,
4033
                                          height,
4034
                                          palette_entries,
4035
                                          dither->ncolors,
4036
                                          dither->keycolor,
4037
                                          output);
4038
    } else if (pipeline_active) {
302!
4039
        status = sixel_encode_body_pipeline(pixels,
×
4040
                                            width,
4041
                                            height,
4042
                                            palette_entries,
4043
                                            palette_entries_float32,
4044
                                            dither,
4045
                                            output,
4046
                                            pipeline_threads);
4047
    } else {
4048
        status = sixel_encode_body(input_pixels,
302✔
4049
                                   width,
4050
                                   height,
4051
                                   palette_entries,
4052
                                   palette_entries_float32,
4053
                                   dither->ncolors,
4054
                                   dither->keycolor,
4055
                                   dither->bodyonly,
4056
                                   output,
4057
                                   NULL,
4058
                                   dither->allocator);
4059
    }
4060

4061
    if (SIXEL_FAILED(status)) {
302!
4062
        goto end;
×
4063
    }
4064

4065
    status = sixel_encode_footer(output);
302✔
4066
    if (SIXEL_FAILED(status)) {
302!
4067
        goto end;
×
4068
    }
4069

4070
end:
302✔
4071
    if (palette_obj != NULL) {
302!
4072
        sixel_palette_unref(palette_obj);
×
4073
    }
4074
    if (palette_entries != NULL) {
302!
4075
        sixel_allocator_free(dither->allocator, palette_entries);
302✔
4076
    }
4077
    if (palette_entries_float32 != NULL) {
302!
4078
        sixel_allocator_free(dither->allocator, palette_entries_float32);
×
4079
    }
4080
    sixel_allocator_free(dither->allocator, paletted_pixels);
302✔
4081

4082
    return status;
302✔
4083
}
4084

4085
static void
4086
dither_func_none(unsigned char *data, int width)
27,474✔
4087
{
4088
    (void) data;  /* unused */
4089
    (void) width; /* unused */
4090
}
27,474✔
4091

4092

4093
static void
4094
dither_func_fs(unsigned char *data, int width)
2,687,178✔
4095
{
4096
    int r, g, b;
4097
    int error_r = data[0] & 0x7;
2,687,178✔
4098
    int error_g = data[1] & 0x7;
2,687,178✔
4099
    int error_b = data[2] & 0x7;
2,687,178✔
4100

4101
    /* Floyd Steinberg Method
4102
     *          curr    7/16
4103
     *  3/16    5/48    1/16
4104
     */
4105
    r = (data[3 + 0] + (error_r * 5 >> 4));
2,687,178✔
4106
    g = (data[3 + 1] + (error_g * 5 >> 4));
2,687,178✔
4107
    b = (data[3 + 2] + (error_b * 5 >> 4));
2,687,178✔
4108
    data[3 + 0] = r > 0xff ? 0xff: r;
2,687,178✔
4109
    data[3 + 1] = g > 0xff ? 0xff: g;
2,687,178✔
4110
    data[3 + 2] = b > 0xff ? 0xff: b;
2,687,178✔
4111
    r = data[width * 3 - 3 + 0] + (error_r * 3 >> 4);
2,687,178✔
4112
    g = data[width * 3 - 3 + 1] + (error_g * 3 >> 4);
2,687,178✔
4113
    b = data[width * 3 - 3 + 2] + (error_b * 3 >> 4);
2,687,178✔
4114
    data[width * 3 - 3 + 0] = r > 0xff ? 0xff: r;
2,687,178✔
4115
    data[width * 3 - 3 + 1] = g > 0xff ? 0xff: g;
2,687,178✔
4116
    data[width * 3 - 3 + 2] = b > 0xff ? 0xff: b;
2,687,178✔
4117
    r = data[width * 3 + 0] + (error_r * 5 >> 4);
2,687,178✔
4118
    g = data[width * 3 + 1] + (error_g * 5 >> 4);
2,687,178✔
4119
    b = data[width * 3 + 2] + (error_b * 5 >> 4);
2,687,178✔
4120
    data[width * 3 + 0] = r > 0xff ? 0xff: r;
2,687,178✔
4121
    data[width * 3 + 1] = g > 0xff ? 0xff: g;
2,687,178✔
4122
    data[width * 3 + 2] = b > 0xff ? 0xff: b;
2,687,178✔
4123
}
2,687,178✔
4124

4125

4126
static void
4127
dither_func_atkinson(unsigned char *data, int width)
535,808✔
4128
{
4129
    int r, g, b;
4130
    int error_r = data[0] & 0x7;
535,808✔
4131
    int error_g = data[1] & 0x7;
535,808✔
4132
    int error_b = data[2] & 0x7;
535,808✔
4133

4134
    error_r += 4;
535,808✔
4135
    error_g += 4;
535,808✔
4136
    error_b += 4;
535,808✔
4137

4138
    /* Atkinson's Method
4139
     *          curr    1/8    1/8
4140
     *   1/8     1/8    1/8
4141
     *           1/8
4142
     */
4143
    r = data[(width * 0 + 1) * 3 + 0] + (error_r >> 3);
535,808✔
4144
    g = data[(width * 0 + 1) * 3 + 1] + (error_g >> 3);
535,808✔
4145
    b = data[(width * 0 + 1) * 3 + 2] + (error_b >> 3);
535,808✔
4146
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
535,808✔
4147
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
535,808✔
4148
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
535,808✔
4149
    r = data[(width * 0 + 2) * 3 + 0] + (error_r >> 3);
535,808✔
4150
    g = data[(width * 0 + 2) * 3 + 1] + (error_g >> 3);
535,808✔
4151
    b = data[(width * 0 + 2) * 3 + 2] + (error_b >> 3);
535,808✔
4152
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
535,808✔
4153
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
535,808✔
4154
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
535,808✔
4155
    r = data[(width * 1 - 1) * 3 + 0] + (error_r >> 3);
535,808✔
4156
    g = data[(width * 1 - 1) * 3 + 1] + (error_g >> 3);
535,808✔
4157
    b = data[(width * 1 - 1) * 3 + 2] + (error_b >> 3);
535,808✔
4158
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
535,808✔
4159
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
535,808✔
4160
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
535,808✔
4161
    r = data[(width * 1 + 0) * 3 + 0] + (error_r >> 3);
535,808✔
4162
    g = data[(width * 1 + 0) * 3 + 1] + (error_g >> 3);
535,808✔
4163
    b = data[(width * 1 + 0) * 3 + 2] + (error_b >> 3);
535,808✔
4164
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
535,808✔
4165
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
535,808✔
4166
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
535,808✔
4167
    r = (data[(width * 1 + 1) * 3 + 0] + (error_r >> 3));
535,808✔
4168
    g = (data[(width * 1 + 1) * 3 + 1] + (error_g >> 3));
535,808✔
4169
    b = (data[(width * 1 + 1) * 3 + 2] + (error_b >> 3));
535,808✔
4170
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
535,808✔
4171
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
535,808✔
4172
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
535,808✔
4173
    r = (data[(width * 2 + 0) * 3 + 0] + (error_r >> 3));
535,808✔
4174
    g = (data[(width * 2 + 0) * 3 + 1] + (error_g >> 3));
535,808✔
4175
    b = (data[(width * 2 + 0) * 3 + 2] + (error_b >> 3));
535,808✔
4176
    data[(width * 2 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
535,808✔
4177
    data[(width * 2 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
535,808✔
4178
    data[(width * 2 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
535,808✔
4179
}
535,808✔
4180

4181

4182
static void
4183
dither_func_jajuni(unsigned char *data, int width)
680,574✔
4184
{
4185
    int r, g, b;
4186
    int error_r = data[0] & 0x7;
680,574✔
4187
    int error_g = data[1] & 0x7;
680,574✔
4188
    int error_b = data[2] & 0x7;
680,574✔
4189

4190
    error_r += 4;
680,574✔
4191
    error_g += 4;
680,574✔
4192
    error_b += 4;
680,574✔
4193

4194
    /* Jarvis, Judice & Ninke Method
4195
     *                  curr    7/48    5/48
4196
     *  3/48    5/48    7/48    5/48    3/48
4197
     *  1/48    3/48    5/48    3/48    1/48
4198
     */
4199
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 7 / 48);
680,574✔
4200
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 7 / 48);
680,574✔
4201
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 7 / 48);
680,574✔
4202
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
680,574✔
4203
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
680,574✔
4204
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
680,574✔
4205
    r = data[(width * 0 + 2) * 3 + 0] + (error_r * 5 / 48);
680,574✔
4206
    g = data[(width * 0 + 2) * 3 + 1] + (error_g * 5 / 48);
680,574✔
4207
    b = data[(width * 0 + 2) * 3 + 2] + (error_b * 5 / 48);
680,574✔
4208
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
680,574✔
4209
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
680,574✔
4210
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
680,574✔
4211
    r = data[(width * 1 - 2) * 3 + 0] + (error_r * 3 / 48);
680,574✔
4212
    g = data[(width * 1 - 2) * 3 + 1] + (error_g * 3 / 48);
680,574✔
4213
    b = data[(width * 1 - 2) * 3 + 2] + (error_b * 3 / 48);
680,574✔
4214
    data[(width * 1 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
680,574!
4215
    data[(width * 1 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
680,574!
4216
    data[(width * 1 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
680,574!
4217
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 5 / 48);
680,574✔
4218
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 5 / 48);
680,574✔
4219
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 5 / 48);
680,574✔
4220
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
680,574✔
4221
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
680,574✔
4222
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
680,574✔
4223
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 7 / 48);
680,574✔
4224
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 7 / 48);
680,574✔
4225
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 7 / 48);
680,574✔
4226
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
680,574✔
4227
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
680,574✔
4228
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
680,574✔
4229
    r = data[(width * 1 + 1) * 3 + 0] + (error_r * 5 / 48);
680,574✔
4230
    g = data[(width * 1 + 1) * 3 + 1] + (error_g * 5 / 48);
680,574✔
4231
    b = data[(width * 1 + 1) * 3 + 2] + (error_b * 5 / 48);
680,574✔
4232
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
680,574!
4233
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
680,574✔
4234
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
680,574✔
4235
    r = data[(width * 1 + 2) * 3 + 0] + (error_r * 3 / 48);
680,574✔
4236
    g = data[(width * 1 + 2) * 3 + 1] + (error_g * 3 / 48);
680,574✔
4237
    b = data[(width * 1 + 2) * 3 + 2] + (error_b * 3 / 48);
680,574✔
4238
    data[(width * 1 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
680,574!
4239
    data[(width * 1 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
680,574!
4240
    data[(width * 1 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
680,574!
4241
    r = data[(width * 2 - 2) * 3 + 0] + (error_r * 1 / 48);
680,574✔
4242
    g = data[(width * 2 - 2) * 3 + 1] + (error_g * 1 / 48);
680,574✔
4243
    b = data[(width * 2 - 2) * 3 + 2] + (error_b * 1 / 48);
680,574✔
4244
    data[(width * 2 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
680,574!
4245
    data[(width * 2 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
680,574!
4246
    data[(width * 2 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
680,574!
4247
    r = data[(width * 2 - 1) * 3 + 0] + (error_r * 3 / 48);
680,574✔
4248
    g = data[(width * 2 - 1) * 3 + 1] + (error_g * 3 / 48);
680,574✔
4249
    b = data[(width * 2 - 1) * 3 + 2] + (error_b * 3 / 48);
680,574✔
4250
    data[(width * 2 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
680,574!
4251
    data[(width * 2 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
680,574!
4252
    data[(width * 2 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
680,574!
4253
    r = data[(width * 2 + 0) * 3 + 0] + (error_r * 5 / 48);
680,574✔
4254
    g = data[(width * 2 + 0) * 3 + 1] + (error_g * 5 / 48);
680,574✔
4255
    b = data[(width * 2 + 0) * 3 + 2] + (error_b * 5 / 48);
680,574✔
4256
    data[(width * 2 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
680,574!
4257
    data[(width * 2 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
680,574✔
4258
    data[(width * 2 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
680,574✔
4259
    r = data[(width * 2 + 1) * 3 + 0] + (error_r * 3 / 48);
680,574✔
4260
    g = data[(width * 2 + 1) * 3 + 1] + (error_g * 3 / 48);
680,574✔
4261
    b = data[(width * 2 + 1) * 3 + 2] + (error_b * 3 / 48);
680,574✔
4262
    data[(width * 2 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
680,574!
4263
    data[(width * 2 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
680,574!
4264
    data[(width * 2 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
680,574!
4265
    r = data[(width * 2 + 2) * 3 + 0] + (error_r * 1 / 48);
680,574✔
4266
    g = data[(width * 2 + 2) * 3 + 1] + (error_g * 1 / 48);
680,574✔
4267
    b = data[(width * 2 + 2) * 3 + 2] + (error_b * 1 / 48);
680,574✔
4268
    data[(width * 2 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
680,574!
4269
    data[(width * 2 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
680,574!
4270
    data[(width * 2 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
680,574!
4271
}
680,574✔
4272

4273

4274
static void
4275
dither_func_stucki(unsigned char *data, int width)
666,706✔
4276
{
4277
    int r, g, b;
4278
    int error_r = data[0] & 0x7;
666,706✔
4279
    int error_g = data[1] & 0x7;
666,706✔
4280
    int error_b = data[2] & 0x7;
666,706✔
4281

4282
    error_r += 4;
666,706✔
4283
    error_g += 4;
666,706✔
4284
    error_b += 4;
666,706✔
4285

4286
    /* Stucki's Method
4287
     *                  curr    8/48    4/48
4288
     *  2/48    4/48    8/48    4/48    2/48
4289
     *  1/48    2/48    4/48    2/48    1/48
4290
     */
4291
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 8 / 48);
666,706✔
4292
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 8 / 48);
666,706✔
4293
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 8 / 48);
666,706✔
4294
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
666,706✔
4295
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
666,706✔
4296
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
666,706✔
4297
    r = data[(width * 0 + 2) * 3 + 0] + (error_r * 4 / 48);
666,706✔
4298
    g = data[(width * 0 + 2) * 3 + 1] + (error_g * 4 / 48);
666,706✔
4299
    b = data[(width * 0 + 2) * 3 + 2] + (error_b * 4 / 48);
666,706✔
4300
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
666,706!
4301
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
666,706!
4302
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
666,706!
4303
    r = data[(width * 1 - 2) * 3 + 0] + (error_r * 2 / 48);
666,706✔
4304
    g = data[(width * 1 - 2) * 3 + 1] + (error_g * 2 / 48);
666,706✔
4305
    b = data[(width * 1 - 2) * 3 + 2] + (error_b * 2 / 48);
666,706✔
4306
    data[(width * 1 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
666,706!
4307
    data[(width * 1 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
666,706!
4308
    data[(width * 1 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
666,706!
4309
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 4 / 48);
666,706✔
4310
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 4 / 48);
666,706✔
4311
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 4 / 48);
666,706✔
4312
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
666,706!
4313
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
666,706!
4314
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
666,706!
4315
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 8 / 48);
666,706✔
4316
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 8 / 48);
666,706✔
4317
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 8 / 48);
666,706✔
4318
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
666,706!
4319
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
666,706✔
4320
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
666,706✔
4321
    r = data[(width * 1 + 1) * 3 + 0] + (error_r * 4 / 48);
666,706✔
4322
    g = data[(width * 1 + 1) * 3 + 1] + (error_g * 4 / 48);
666,706✔
4323
    b = data[(width * 1 + 1) * 3 + 2] + (error_b * 4 / 48);
666,706✔
4324
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
666,706!
4325
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
666,706!
4326
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
666,706!
4327
    r = data[(width * 1 + 2) * 3 + 0] + (error_r * 2 / 48);
666,706✔
4328
    g = data[(width * 1 + 2) * 3 + 1] + (error_g * 2 / 48);
666,706✔
4329
    b = data[(width * 1 + 2) * 3 + 2] + (error_b * 2 / 48);
666,706✔
4330
    data[(width * 1 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
666,706!
4331
    data[(width * 1 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
666,706!
4332
    data[(width * 1 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
666,706!
4333
    r = data[(width * 2 - 2) * 3 + 0] + (error_r * 1 / 48);
666,706✔
4334
    g = data[(width * 2 - 2) * 3 + 1] + (error_g * 1 / 48);
666,706✔
4335
    b = data[(width * 2 - 2) * 3 + 2] + (error_b * 1 / 48);
666,706✔
4336
    data[(width * 2 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
666,706!
4337
    data[(width * 2 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
666,706!
4338
    data[(width * 2 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
666,706!
4339
    r = data[(width * 2 - 1) * 3 + 0] + (error_r * 2 / 48);
666,706✔
4340
    g = data[(width * 2 - 1) * 3 + 1] + (error_g * 2 / 48);
666,706✔
4341
    b = data[(width * 2 - 1) * 3 + 2] + (error_b * 2 / 48);
666,706✔
4342
    data[(width * 2 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
666,706!
4343
    data[(width * 2 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
666,706!
4344
    data[(width * 2 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
666,706!
4345
    r = data[(width * 2 + 0) * 3 + 0] + (error_r * 4 / 48);
666,706✔
4346
    g = data[(width * 2 + 0) * 3 + 1] + (error_g * 4 / 48);
666,706✔
4347
    b = data[(width * 2 + 0) * 3 + 2] + (error_b * 4 / 48);
666,706✔
4348
    data[(width * 2 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
666,706!
4349
    data[(width * 2 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
666,706!
4350
    data[(width * 2 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
666,706!
4351
    r = data[(width * 2 + 1) * 3 + 0] + (error_r * 2 / 48);
666,706✔
4352
    g = data[(width * 2 + 1) * 3 + 1] + (error_g * 2 / 48);
666,706✔
4353
    b = data[(width * 2 + 1) * 3 + 2] + (error_b * 2 / 48);
666,706✔
4354
    data[(width * 2 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
666,706!
4355
    data[(width * 2 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
666,706!
4356
    data[(width * 2 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
666,706!
4357
    r = data[(width * 2 + 2) * 3 + 0] + (error_r * 1 / 48);
666,706✔
4358
    g = data[(width * 2 + 2) * 3 + 1] + (error_g * 1 / 48);
666,706✔
4359
    b = data[(width * 2 + 2) * 3 + 2] + (error_b * 1 / 48);
666,706✔
4360
    data[(width * 2 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
666,706!
4361
    data[(width * 2 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
666,706!
4362
    data[(width * 2 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
666,706!
4363
}
666,706✔
4364

4365

4366
static void
4367
dither_func_burkes(unsigned char *data, int width)
697,326✔
4368
{
4369
    int r, g, b;
4370
    int error_r = data[0] & 0x7;
697,326✔
4371
    int error_g = data[1] & 0x7;
697,326✔
4372
    int error_b = data[2] & 0x7;
697,326✔
4373

4374
    error_r += 2;
697,326✔
4375
    error_g += 2;
697,326✔
4376
    error_b += 2;
697,326✔
4377

4378
    /* Burkes' Method
4379
     *                  curr    4/16    2/16
4380
     *  1/16    2/16    4/16    2/16    1/16
4381
     */
4382
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 4 / 16);
697,326✔
4383
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 4 / 16);
697,326✔
4384
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 4 / 16);
697,326✔
4385
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
697,326✔
4386
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
697,326✔
4387
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
697,326✔
4388
    r = data[(width * 0 + 2) * 3 + 0] + (error_r * 2 / 16);
697,326✔
4389
    g = data[(width * 0 + 2) * 3 + 1] + (error_g * 2 / 16);
697,326✔
4390
    b = data[(width * 0 + 2) * 3 + 2] + (error_b * 2 / 16);
697,326✔
4391
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
697,326✔
4392
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
697,326✔
4393
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
697,326✔
4394
    r = data[(width * 1 - 2) * 3 + 0] + (error_r * 1 / 16);
697,326✔
4395
    g = data[(width * 1 - 2) * 3 + 1] + (error_g * 1 / 16);
697,326✔
4396
    b = data[(width * 1 - 2) * 3 + 2] + (error_b * 1 / 16);
697,326✔
4397
    data[(width * 1 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
697,326!
4398
    data[(width * 1 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
697,326!
4399
    data[(width * 1 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
697,326!
4400
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 2 / 16);
697,326✔
4401
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 2 / 16);
697,326✔
4402
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 2 / 16);
697,326✔
4403
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
697,326✔
4404
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
697,326✔
4405
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
697,326✔
4406
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 4 / 16);
697,326✔
4407
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 4 / 16);
697,326✔
4408
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 4 / 16);
697,326✔
4409
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
697,326✔
4410
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
697,326✔
4411
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
697,326✔
4412
    r = data[(width * 1 + 1) * 3 + 0] + (error_r * 2 / 16);
697,326✔
4413
    g = data[(width * 1 + 1) * 3 + 1] + (error_g * 2 / 16);
697,326✔
4414
    b = data[(width * 1 + 1) * 3 + 2] + (error_b * 2 / 16);
697,326✔
4415
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
697,326✔
4416
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
697,326✔
4417
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
697,326✔
4418
    r = data[(width * 1 + 2) * 3 + 0] + (error_r * 1 / 16);
697,326✔
4419
    g = data[(width * 1 + 2) * 3 + 1] + (error_g * 1 / 16);
697,326✔
4420
    b = data[(width * 1 + 2) * 3 + 2] + (error_b * 1 / 16);
697,326✔
4421
    data[(width * 1 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
697,326!
4422
    data[(width * 1 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
697,326!
4423
    data[(width * 1 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
697,326!
4424
}
697,326✔
4425

4426

4427
static void
4428
dither_func_sierra1(unsigned char *data, int width)
×
4429
{
4430
    int r, g, b;
4431
    int error_r = data[0] & 0x7;
×
4432
    int error_g = data[1] & 0x7;
×
4433
    int error_b = data[2] & 0x7;
×
4434

4435
    error_r += 2;
×
4436
    error_g += 2;
×
4437
    error_b += 2;
×
4438

4439
    /* Sierra Lite Method
4440
     *          curr    2/4
4441
     *  1/4     1/4
4442
     */
4443
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 2 / 4);
×
4444
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 2 / 4);
×
4445
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 2 / 4);
×
4446
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4447
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4448
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4449
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 1 / 4);
×
4450
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 1 / 4);
×
4451
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 1 / 4);
×
4452
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4453
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4454
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4455
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 1 / 4);
×
4456
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 1 / 4);
×
4457
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 1 / 4);
×
4458
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
×
4459
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
×
4460
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
×
4461
}
×
4462

4463

4464
static void
4465
dither_func_sierra2(unsigned char *data, int width)
×
4466
{
4467
    int r, g, b;
4468
    int error_r = data[0] & 0x7;
×
4469
    int error_g = data[1] & 0x7;
×
4470
    int error_b = data[2] & 0x7;
×
4471

4472
    error_r += 4;
×
4473
    error_g += 4;
×
4474
    error_b += 4;
×
4475

4476
    /* Sierra Two-row Method
4477
     *                  curr    4/32    3/32
4478
     *  1/32    2/32    3/32    2/32    1/32
4479
     *                  2/32    3/32    2/32
4480
     */
4481
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 4 / 32);
×
4482
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 4 / 32);
×
4483
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 4 / 32);
×
4484
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4485
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4486
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4487
    r = data[(width * 0 + 2) * 3 + 0] + (error_r * 3 / 32);
×
4488
    g = data[(width * 0 + 2) * 3 + 1] + (error_g * 3 / 32);
×
4489
    b = data[(width * 0 + 2) * 3 + 2] + (error_b * 3 / 32);
×
4490
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4491
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4492
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4493
    r = data[(width * 1 - 2) * 3 + 0] + (error_r * 1 / 32);
×
4494
    g = data[(width * 1 - 2) * 3 + 1] + (error_g * 1 / 32);
×
4495
    b = data[(width * 1 - 2) * 3 + 2] + (error_b * 1 / 32);
×
4496
    data[(width * 1 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4497
    data[(width * 1 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4498
    data[(width * 1 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4499
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 2 / 32);
×
4500
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 2 / 32);
×
4501
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 2 / 32);
×
4502
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4503
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4504
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4505
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 3 / 32);
×
4506
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 3 / 32);
×
4507
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 3 / 32);
×
4508
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
×
4509
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
×
4510
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
×
4511
    r = data[(width * 1 + 1) * 3 + 0] + (error_r * 2 / 32);
×
4512
    g = data[(width * 1 + 1) * 3 + 1] + (error_g * 2 / 32);
×
4513
    b = data[(width * 1 + 1) * 3 + 2] + (error_b * 2 / 32);
×
4514
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4515
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4516
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4517
    r = data[(width * 1 + 2) * 3 + 0] + (error_r * 1 / 32);
×
4518
    g = data[(width * 1 + 2) * 3 + 1] + (error_g * 1 / 32);
×
4519
    b = data[(width * 1 + 2) * 3 + 2] + (error_b * 1 / 32);
×
4520
    data[(width * 1 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4521
    data[(width * 1 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4522
    data[(width * 1 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4523
    r = data[(width * 2 - 1) * 3 + 0] + (error_r * 2 / 32);
×
4524
    g = data[(width * 2 - 1) * 3 + 1] + (error_g * 2 / 32);
×
4525
    b = data[(width * 2 - 1) * 3 + 2] + (error_b * 2 / 32);
×
4526
    data[(width * 2 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4527
    data[(width * 2 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4528
    data[(width * 2 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4529
    r = data[(width * 2 + 0) * 3 + 0] + (error_r * 3 / 32);
×
4530
    g = data[(width * 2 + 0) * 3 + 1] + (error_g * 3 / 32);
×
4531
    b = data[(width * 2 + 0) * 3 + 2] + (error_b * 3 / 32);
×
4532
    data[(width * 2 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
×
4533
    data[(width * 2 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
×
4534
    data[(width * 2 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
×
4535
    r = data[(width * 2 + 1) * 3 + 0] + (error_r * 2 / 32);
×
4536
    g = data[(width * 2 + 1) * 3 + 1] + (error_g * 2 / 32);
×
4537
    b = data[(width * 2 + 1) * 3 + 2] + (error_b * 2 / 32);
×
4538
    data[(width * 2 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4539
    data[(width * 2 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4540
    data[(width * 2 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4541
    r = data[(width * 2 + 2) * 3 + 0] + (error_r * 1 / 32);
×
4542
    g = data[(width * 2 + 2) * 3 + 1] + (error_g * 1 / 32);
×
4543
    b = data[(width * 2 + 2) * 3 + 2] + (error_b * 1 / 32);
×
4544
    data[(width * 2 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4545
    data[(width * 2 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4546
    data[(width * 2 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4547
}
×
4548

4549

4550
static void
4551
dither_func_sierra3(unsigned char *data, int width)
×
4552
{
4553
    int r, g, b;
4554
    int error_r = data[0] & 0x7;
×
4555
    int error_g = data[1] & 0x7;
×
4556
    int error_b = data[2] & 0x7;
×
4557

4558
    error_r += 4;
×
4559
    error_g += 4;
×
4560
    error_b += 4;
×
4561

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

4629

4630
static void
4631
dither_func_a_dither(unsigned char *data, int width, int x, int y)
15,898✔
4632
{
4633
    int c;
4634
    float value;
4635
    float mask;
4636

4637
    (void) width; /* unused */
4638

4639
    for (c = 0; c < 3; c ++) {
63,592✔
4640
        mask = (((x + c * 17) + y * 236) * 119) & 255;
47,694✔
4641
        mask = ((mask - 128) / 256.0f) ;
47,694✔
4642
        value = data[c] + mask;
47,694✔
4643
        if (value < 0) {
47,694✔
4644
            value = 0;
196✔
4645
        }
4646
        value = value > 255 ? 255 : value;
47,694!
4647
        data[c] = value;
47,694✔
4648
    }
4649
}
15,898✔
4650

4651

4652
static void
4653
dither_func_x_dither(unsigned char *data, int width, int x, int y)
29,048✔
4654
{
4655
    int c;
4656
    float value;
4657
    float mask;
4658

4659
    (void) width;  /* unused */
4660

4661
    for (c = 0; c < 3; c ++) {
116,192✔
4662
        mask = (((x + c * 17) ^ y * 236) * 1234) & 511;
87,144✔
4663
        mask = ((mask - 128) / 512.0f) ;
87,144✔
4664
        value = data[c] + mask;
87,144✔
4665
        if (value < 0) {
87,144✔
4666
            value = 0;
224✔
4667
        }
4668
        value = value > 255 ? 255 : value;
87,144!
4669
        data[c] = value;
87,144✔
4670
    }
4671
}
29,048✔
4672

4673

4674
static void
4675
sixel_apply_15bpp_dither(
5,368,894✔
4676
    unsigned char *pixels,
4677
    int x, int y, int width, int height,
4678
    int method_for_diffuse)
4679
{
4680
    /* apply floyd steinberg dithering */
4681
    switch (method_for_diffuse) {
5,368,894!
4682
    case SIXEL_DIFFUSE_FS:
2,698,370✔
4683
        if (x < width - 1 && y < height - 1) {
2,698,370✔
4684
            dither_func_fs(pixels, width);
2,687,178✔
4685
        }
4686
        break;
2,698,370✔
4687
    case SIXEL_DIFFUSE_ATKINSON:
540,000✔
4688
        if (x < width - 2 && y < height - 2) {
540,000✔
4689
            dither_func_atkinson(pixels, width);
535,808✔
4690
        }
4691
        break;
540,000✔
4692
    case SIXEL_DIFFUSE_JAJUNI:
685,496✔
4693
        if (x < width - 2 && y < height - 2) {
685,496✔
4694
            dither_func_jajuni(pixels, width);
680,574✔
4695
        }
4696
        break;
685,496✔
4697
    case SIXEL_DIFFUSE_STUCKI:
671,526✔
4698
        if (x < width - 2 && y < height - 2) {
671,526✔
4699
            dither_func_stucki(pixels, width);
666,706✔
4700
        }
4701
        break;
671,526✔
4702
    case SIXEL_DIFFUSE_BURKES:
701,082✔
4703
        if (x < width - 2 && y < height - 1) {
701,082✔
4704
            dither_func_burkes(pixels, width);
697,326✔
4705
        }
4706
        break;
701,082✔
4707
    case SIXEL_DIFFUSE_SIERRA1:
×
4708
        if (x < width - 1 && y < height - 1) {
×
4709
            dither_func_sierra1(pixels, width);
×
4710
        }
4711
        break;
×
4712
    case SIXEL_DIFFUSE_SIERRA2:
×
4713
        if (x < width - 2 && y < height - 2) {
×
4714
            dither_func_sierra2(pixels, width);
×
4715
        }
4716
        break;
×
4717
    case SIXEL_DIFFUSE_SIERRA3:
×
4718
        if (x < width - 2 && y < height - 2) {
×
4719
            dither_func_sierra3(pixels, width);
×
4720
        }
4721
        break;
×
4722
    case SIXEL_DIFFUSE_A_DITHER:
15,898✔
4723
        dither_func_a_dither(pixels, width, x, y);
15,898✔
4724
        break;
15,898✔
4725
    case SIXEL_DIFFUSE_X_DITHER:
29,048✔
4726
        dither_func_x_dither(pixels, width, x, y);
29,048✔
4727
        break;
29,048✔
4728
    case SIXEL_DIFFUSE_NONE:
27,474✔
4729
    default:
4730
        dither_func_none(pixels, width);
27,474✔
4731
        break;
27,474✔
4732
    }
4733
}
5,368,894✔
4734

4735

4736
static SIXELSTATUS
4737
sixel_encode_highcolor(
24✔
4738
        unsigned char *pixels, int width, int height,
4739
        sixel_dither_t *dither, sixel_output_t *output
4740
        )
4741
{
4742
    SIXELSTATUS status = SIXEL_FALSE;
24✔
4743
    sixel_index_t *paletted_pixels = NULL;
24✔
4744
    unsigned char *normalized_pixels = NULL;
24✔
4745
    /* Mark sixel line pixels which have been already drawn. */
4746
    unsigned char *marks;
4747
    unsigned char *rgbhit;
4748
    unsigned char *rgb2pal;
4749
    unsigned char palhitcount[SIXEL_PALETTE_MAX];
4750
    unsigned char palstate[SIXEL_PALETTE_MAX];
4751
    int output_count;
4752
    int const maxcolors = 1 << 15;
24✔
4753
    int whole_size = width * height  /* for paletted_pixels */
24✔
4754
                   + maxcolors       /* for rgbhit */
24✔
4755
                   + maxcolors       /* for rgb2pal */
24✔
4756
                   + width * 6;      /* for marks */
24✔
4757
    int x, y;
4758
    unsigned char *dst;
4759
    unsigned char *mptr;
4760
    int dirty;
4761
    int mod_y;
4762
    int nextpal;
4763
    int threshold;
4764
    int pix;
4765
    int orig_height;
4766
    unsigned char *pal;
4767
    unsigned char *palette_entries = NULL;
24✔
4768
    sixel_palette_t *palette_obj = NULL;
24✔
4769
    size_t palette_count = 0U;
24✔
4770

4771
    if (dither->pixelformat != SIXEL_PIXELFORMAT_RGB888) {
24!
4772
        /* normalize pixelfromat */
4773
        normalized_pixels = (unsigned char *)sixel_allocator_malloc(dither->allocator,
×
4774
                                                                    (size_t)(width * height * 3));
×
4775
        if (normalized_pixels == NULL) {
×
4776
            goto error;
×
4777
        }
4778
        status = sixel_helper_normalize_pixelformat(normalized_pixels,
×
4779
                                                    &dither->pixelformat,
4780
                                                    pixels,
4781
                                                    dither->pixelformat,
4782
                                                    width, height);
4783
        if (SIXEL_FAILED(status)) {
×
4784
            goto error;
×
4785
        }
4786
        pixels = normalized_pixels;
×
4787
    }
4788

4789
    palette_entries = NULL;
24✔
4790
    palette_obj = NULL;
24✔
4791
    palette_count = 0U;
24✔
4792
    status = sixel_dither_get_quantized_palette(dither, &palette_obj);
24✔
4793
    if (SIXEL_FAILED(status)) {
24!
4794
        goto error;
×
4795
    }
4796
    status = sixel_palette_copy_entries_8bit(
24✔
4797
        palette_obj,
4798
        &palette_entries,
4799
        &palette_count,
4800
        SIXEL_PIXELFORMAT_RGB888,
4801
        dither->allocator);
4802
    sixel_palette_unref(palette_obj);
24✔
4803
    palette_obj = NULL;
24✔
4804
    if (SIXEL_FAILED(status) || palette_entries == NULL) {
24!
4805
        goto error;
×
4806
    }
4807

4808
    paletted_pixels = (sixel_index_t *)sixel_allocator_malloc(dither->allocator,
24✔
4809
                                                              (size_t)whole_size);
4810
    if (paletted_pixels == NULL) {
24!
4811
        goto error;
×
4812
    }
4813
    rgbhit = paletted_pixels + width * height;
24✔
4814
    memset(rgbhit, 0, (size_t)(maxcolors * 2 + width * 6));
24✔
4815
    rgb2pal = rgbhit + maxcolors;
24✔
4816
    marks = rgb2pal + maxcolors;
24✔
4817
    output_count = 0;
24✔
4818

4819
next:
764✔
4820
    dst = paletted_pixels;
764✔
4821
    nextpal = 0;
764✔
4822
    threshold = 1;
764✔
4823
    dirty = 0;
764✔
4824
    mptr = marks;
764✔
4825
    memset(palstate, 0, sizeof(palstate));
764✔
4826
    y = mod_y = 0;
764✔
4827

4828
    while (1) {
4829
        for (x = 0; x < width; x++, mptr++, dst++, pixels += 3) {
7,650,862✔
4830
            if (*mptr) {
7,638,212✔
4831
                *dst = 255;
2,269,318✔
4832
            } else {
4833
                sixel_apply_15bpp_dither(pixels,
5,368,894✔
4834
                                         x, y, width, height,
4835
                                         dither->method_for_diffuse);
4836
                pix = ((pixels[0] & 0xf8) << 7) |
5,368,894✔
4837
                      ((pixels[1] & 0xf8) << 2) |
5,368,894✔
4838
                      ((pixels[2] >> 3) & 0x1f);
5,368,894✔
4839

4840
                if (!rgbhit[pix]) {
5,368,894✔
4841
                    while (1) {
4842
                        if (nextpal >= 255) {
1,109,155✔
4843
                            if (threshold >= 255) {
536,490✔
4844
                                break;
534,996✔
4845
                            } else {
4846
                                threshold = (threshold == 1) ? 9: 255;
1,494✔
4847
                                nextpal = 0;
1,494✔
4848
                            }
4849
                        } else if (palstate[nextpal] ||
572,665✔
4850
                                 palhitcount[nextpal] > threshold) {
311,762✔
4851
                            nextpal++;
405,147✔
4852
                        } else {
4853
                            break;
4854
                        }
4855
                    }
4856

4857
                    if (nextpal >= 255) {
702,514✔
4858
                        dirty = 1;
534,996✔
4859
                        *dst = 255;
534,996✔
4860
                    } else {
4861
                        pal = palette_entries + (nextpal * 3);
167,518✔
4862

4863
                        rgbhit[pix] = 1;
167,518✔
4864
                        if (output_count > 0) {
167,518✔
4865
                            rgbhit[((pal[0] & 0xf8) << 7) |
163,242✔
4866
                                   ((pal[1] & 0xf8) << 2) |
163,242✔
4867
                                   ((pal[2] >> 3) & 0x1f)] = 0;
163,242✔
4868
                        }
4869
                        *dst = rgb2pal[pix] = nextpal++;
167,518✔
4870
                        *mptr = 1;
167,518✔
4871
                        palstate[*dst] = PALETTE_CHANGE;
167,518✔
4872
                        palhitcount[*dst] = 1;
167,518✔
4873
                        *(pal++) = pixels[0];
167,518✔
4874
                        *(pal++) = pixels[1];
167,518✔
4875
                        *(pal++) = pixels[2];
167,518✔
4876
                    }
4877
                } else {
4878
                    *dst = rgb2pal[pix];
4,666,380✔
4879
                    *mptr = 1;
4,666,380✔
4880
                    if (!palstate[*dst]) {
4,666,380✔
4881
                        palstate[*dst] = PALETTE_HIT;
23,982✔
4882
                    }
4883
                    if (palhitcount[*dst] < 255) {
4,666,380✔
4884
                        palhitcount[*dst]++;
1,385,763✔
4885
                    }
4886
                }
4887
            }
4888
        }
4889

4890
        if (++y >= height) {
12,650✔
4891
            if (dirty) {
24✔
4892
                mod_y = 5;
2✔
4893
            } else {
4894
                goto end;
22✔
4895
            }
4896
        }
4897
        if (dirty && (mod_y == 5 || y >= height)) {
12,628!
4898
            orig_height = height;
742✔
4899

4900
            if (output_count++ == 0) {
742✔
4901
                status = sixel_encode_header(width, height, output);
16✔
4902
                if (SIXEL_FAILED(status)) {
16!
4903
                    goto error;
×
4904
                }
4905
            }
4906
            height = y;
742✔
4907
            status = sixel_encode_body(paletted_pixels,
742✔
4908
                                       width,
4909
                                       height,
4910
                                       palette_entries,
4911
                                       NULL,
4912
                                       255,
4913
                                       255,
4914
                                       dither->bodyonly,
4915
                                       output,
4916
                                       palstate,
4917
                                       dither->allocator);
4918
            if (SIXEL_FAILED(status)) {
742!
4919
                goto error;
×
4920
            }
4921
            if (y >= orig_height) {
742✔
4922
              goto end;
2✔
4923
            }
4924
            pixels -= (6 * width * 3);
740✔
4925
            height = orig_height - height + 6;
740✔
4926
            goto next;
740✔
4927
        }
4928

4929
        if (++mod_y == 6) {
11,886✔
4930
            mptr = (unsigned char *)memset(marks, 0, (size_t)(width * 6));
1,348✔
4931
            mod_y = 0;
1,348✔
4932
        }
4933
    }
4934

4935
    goto next;
4936

4937
end:
24✔
4938
    if (output_count == 0) {
24✔
4939
        status = sixel_encode_header(width, height, output);
8✔
4940
        if (SIXEL_FAILED(status)) {
8!
4941
            goto error;
×
4942
        }
4943
    }
4944
    status = sixel_encode_body(paletted_pixels,
24✔
4945
                               width,
4946
                               height,
4947
                               palette_entries,
4948
                               NULL,
4949
                               255,
4950
                               255,
4951
                               dither->bodyonly,
4952
                               output,
4953
                               palstate,
4954
                               dither->allocator);
4955
    if (SIXEL_FAILED(status)) {
24!
4956
        goto error;
×
4957
    }
4958

4959
    status = sixel_encode_footer(output);
24✔
4960
    if (SIXEL_FAILED(status)) {
24!
4961
        goto error;
×
4962
    }
4963

4964
error:
24✔
4965
    if (palette_entries != NULL) {
24!
4966
        sixel_allocator_free(dither->allocator, palette_entries);
24✔
4967
    }
4968
    sixel_allocator_free(dither->allocator, paletted_pixels);
24✔
4969
    sixel_allocator_free(dither->allocator, normalized_pixels);
24✔
4970

4971
    return status;
24✔
4972
}
4973

4974

4975
SIXELAPI SIXELSTATUS
4976
sixel_encode(
326✔
4977
    unsigned char  /* in */ *pixels,   /* pixel bytes */
4978
    int            /* in */ width,     /* image width */
4979
    int            /* in */ height,    /* image height */
4980
    int const      /* in */ depth,     /* color depth */
4981
    sixel_dither_t /* in */ *dither,   /* dither context */
4982
    sixel_output_t /* in */ *output)   /* output context */
4983
{
4984
    SIXELSTATUS status = SIXEL_FALSE;
326✔
4985

4986
    (void) depth;
4987

4988
    /* TODO: reference counting should be thread-safe */
4989
    sixel_dither_ref(dither);
326✔
4990
    sixel_output_ref(output);
326✔
4991

4992
    if (width < 1) {
326!
4993
        sixel_helper_set_additional_message(
×
4994
            "sixel_encode: bad width parameter."
4995
            " (width < 1)");
4996
        status = SIXEL_BAD_INPUT;
×
4997
        goto end;
×
4998
    }
4999

5000
    if (height < 1) {
326!
5001
        sixel_helper_set_additional_message(
×
5002
            "sixel_encode: bad height parameter."
5003
            " (height < 1)");
5004
        status = SIXEL_BAD_INPUT;
×
5005
        goto end;
×
5006
    }
5007

5008
    if (dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
326✔
5009
        status = sixel_encode_highcolor(pixels, width, height,
24✔
5010
                                        dither, output);
5011
    } else {
5012
        status = sixel_encode_dither(pixels, width, height,
302✔
5013
                                     dither, output);
5014
    }
5015

5016
end:
326✔
5017
    sixel_output_unref(output);
326✔
5018
    sixel_dither_unref(dither);
326✔
5019

5020
    return status;
326✔
5021
}
5022

5023
#if HAVE_TESTS
5024

5025
typedef struct sixel_tosixel_capture {
5026
    char buffer[256];
5027
} sixel_tosixel_capture_t;
5028

5029
static int
5030
sixel_tosixel_capture_write(char *data, int size, void *priv)
×
5031
{
5032
    sixel_tosixel_capture_t *capture;
5033

5034
    (void)data;
5035
    (void)size;
5036
    capture = (sixel_tosixel_capture_t *)priv;
×
5037
    if (capture != NULL && size >= 0) {
×
5038
        capture->buffer[0] = '\0';
×
5039
    }
5040

5041
    return size;
×
5042
}
5043

5044
static int
5045
test_emit_palette_prefers_float32(void)
×
5046
{
5047
    sixel_output_t *output;
5048
    sixel_tosixel_capture_t capture;
5049
    SIXELSTATUS status;
5050
    unsigned char palette_bytes[3] = { 0, 0, 0 };
×
5051
    float palette_float[3] = { 0.123f, 0.456f, 0.789f };
×
5052

5053
    output = NULL;
×
5054
    memset(&capture, 0, sizeof(capture));
×
5055

5056
    status = sixel_output_new(&output,
×
5057
                              sixel_tosixel_capture_write,
5058
                              &capture,
5059
                              NULL);
5060
    if (SIXEL_FAILED(status) || output == NULL) {
×
5061
        goto error;
×
5062
    }
5063

5064
    output->palette_type = SIXEL_PALETTETYPE_RGB;
×
5065
    output->pos = 0;
×
5066
    status = sixel_encode_emit_palette(0,
×
5067
                                       1,
5068
                                       (-1),
5069
                                       palette_bytes,
5070
                                       palette_float,
5071
                                       output,
5072
                                       0);
5073
    if (SIXEL_FAILED(status)) {
×
5074
        goto error;
×
5075
    }
5076

5077
    output->buffer[output->pos] = '\0';
×
5078
    if (strstr((char *)output->buffer, "#0;2;12;46;79") == NULL) {
×
5079
        goto error;
×
5080
    }
5081

5082
    sixel_output_unref(output);
×
5083
    return EXIT_SUCCESS;
×
5084

5085
error:
×
5086
    sixel_output_unref(output);
×
5087
    return EXIT_FAILURE;
×
5088
}
5089

5090
static int
5091
test_emit_palette_hls_from_float32(void)
×
5092
{
5093
    sixel_output_t *output;
5094
    sixel_tosixel_capture_t capture;
5095
    SIXELSTATUS status;
5096
    unsigned char palette_bytes[3] = { 0, 0, 0 };
×
5097
    float palette_float[3] = { 1.0f, 0.0f, 0.0f };
×
5098

5099
    output = NULL;
×
5100
    memset(&capture, 0, sizeof(capture));
×
5101

5102
    status = sixel_output_new(&output,
×
5103
                              sixel_tosixel_capture_write,
5104
                              &capture,
5105
                              NULL);
5106
    if (SIXEL_FAILED(status) || output == NULL) {
×
5107
        goto error;
×
5108
    }
5109

5110
    output->palette_type = SIXEL_PALETTETYPE_HLS;
×
5111
    output->pos = 0;
×
5112
    status = sixel_encode_emit_palette(0,
×
5113
                                       1,
5114
                                       (-1),
5115
                                       palette_bytes,
5116
                                       palette_float,
5117
                                       output,
5118
                                       0);
5119
    if (SIXEL_FAILED(status)) {
×
5120
        goto error;
×
5121
    }
5122

5123
    output->buffer[output->pos] = '\0';
×
5124
    if (strstr((char *)output->buffer, "#0;1;120;50;100") == NULL) {
×
5125
        goto error;
×
5126
    }
5127

5128
    sixel_output_unref(output);
×
5129
    return EXIT_SUCCESS;
×
5130

5131
error:
×
5132
    sixel_output_unref(output);
×
5133
    return EXIT_FAILURE;
×
5134
}
5135

5136
static int
5137
test_emit_palette_hls_from_bytes(void)
×
5138
{
5139
    sixel_output_t *output;
5140
    sixel_tosixel_capture_t capture;
5141
    SIXELSTATUS status;
5142
    unsigned char palette_bytes[3] = { 255, 0, 0 };
×
5143

5144
    output = NULL;
×
5145
    memset(&capture, 0, sizeof(capture));
×
5146

5147
    status = sixel_output_new(&output,
×
5148
                              sixel_tosixel_capture_write,
5149
                              &capture,
5150
                              NULL);
5151
    if (SIXEL_FAILED(status) || output == NULL) {
×
5152
        goto error;
×
5153
    }
5154

5155
    output->palette_type = SIXEL_PALETTETYPE_HLS;
×
5156
    output->pos = 0;
×
5157
    status = sixel_encode_emit_palette(0,
×
5158
                                       1,
5159
                                       (-1),
5160
                                       palette_bytes,
5161
                                       NULL,
5162
                                       output,
5163
                                       0);
5164
    if (SIXEL_FAILED(status)) {
×
5165
        goto error;
×
5166
    }
5167

5168
    output->buffer[output->pos] = '\0';
×
5169
    if (strstr((char *)output->buffer, "#0;1;120;50;100") == NULL) {
×
5170
        goto error;
×
5171
    }
5172

5173
    sixel_output_unref(output);
×
5174
    return EXIT_SUCCESS;
×
5175

5176
error:
×
5177
    sixel_output_unref(output);
×
5178
    return EXIT_FAILURE;
×
5179
}
5180

5181
SIXELAPI int
5182
sixel_tosixel_tests_main(void)
×
5183
{
5184
    int nret;
5185

5186
    nret = test_emit_palette_prefers_float32();
×
5187
    if (nret != EXIT_SUCCESS) {
×
5188
        return nret;
×
5189
    }
5190

5191
    nret = test_emit_palette_hls_from_float32();
×
5192
    if (nret != EXIT_SUCCESS) {
×
5193
        return nret;
×
5194
    }
5195

5196
    nret = test_emit_palette_hls_from_bytes();
×
5197
    if (nret != EXIT_SUCCESS) {
×
5198
        return nret;
×
5199
    }
5200

5201
    return EXIT_SUCCESS;
×
5202
}
5203

5204
#endif  /* HAVE_TESTS */
5205

5206
/* emacs Local Variables:      */
5207
/* emacs mode: c               */
5208
/* emacs tab-width: 4          */
5209
/* emacs indent-tabs-mode: nil */
5210
/* emacs c-basic-offset: 4     */
5211
/* emacs End:                  */
5212
/* vim: set expandtab ts=4 sts=4 sw=4 : */
5213
/* 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