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

saitoha / libsixel / 19918707358

04 Dec 2025 05:12AM UTC coverage: 38.402% (-4.0%) from 42.395%
19918707358

push

github

saitoha
tests: fix meson msys dll lookup

9738 of 38220 branches covered (25.48%)

12841 of 33438 relevant lines covered (38.4%)

782420.02 hits per line

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

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

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

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

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

61
#include <sixel.h>
62
#include "output.h"
63
#include "dither.h"
64
#include "pixelformat.h"
65
#include "assessment.h"
66
#include "logger.h"
67
#include "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
typedef struct sixel_parallel_dither_config {
92
    int enabled;
93
    int band_height;
94
    int overlap;
95
    int dither_threads;
96
    int encode_threads;
97
    int dither_env_override;
98
} sixel_parallel_dither_config_t;
99

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

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

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

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

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

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

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

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

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

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

229
static void sixel_encode_work_init(sixel_encode_work_t *work);
230
static SIXELSTATUS sixel_encode_work_allocate(sixel_encode_work_t *work,
231
                                              int width,
232
                                              int ncolors,
233
                                              sixel_allocator_t *allocator);
234
static void sixel_encode_work_cleanup(sixel_encode_work_t *work,
235
                                      sixel_allocator_t *allocator);
236
static void sixel_band_state_reset(sixel_band_state_t *state);
237
static void sixel_band_finish(sixel_encode_work_t *work,
238
                              sixel_band_state_t *state);
239
static void sixel_band_clear_map(sixel_encode_work_t *work);
240
static void sixel_encode_metrics_init(sixel_encode_metrics_t *metrics);
241
static SIXELSTATUS sixel_band_classify_row(sixel_encode_work_t *work,
242
                                           sixel_band_state_t *state,
243
                                           sixel_index_t *pixels,
244
                                           int width,
245
                                           int absolute_row,
246
                                           int ncolors,
247
                                           int keycolor,
248
                                           unsigned char *palstate,
249
                                           int encode_policy);
250
static SIXELSTATUS sixel_band_compose(sixel_encode_work_t *work,
251
                                      sixel_band_state_t *state,
252
                                      sixel_output_t *output,
253
                                      int width,
254
                                      int ncolors,
255
                                      int keycolor,
256
                                      sixel_allocator_t *allocator,
257
                                      sixel_encode_metrics_t *metrics);
258
static SIXELSTATUS sixel_band_emit(sixel_encode_work_t *work,
259
                                   sixel_band_state_t *state,
260
                                   sixel_output_t *output,
261
                                   int ncolors,
262
                                   int keycolor,
263
                                   int last_row_index,
264
                                   sixel_encode_metrics_t *metrics);
265
static SIXELSTATUS sixel_put_flash(sixel_output_t *const output);
266
static void sixel_advance(sixel_output_t *output, int nwrite);
267

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

276
    sixel_logger_init(logger);
302✔
277
    (void)sixel_logger_prepare_env(logger);
302✔
278
}
279
#endif
280

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

705
    return SIXEL_OK;
706
}
707

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

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

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

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

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

754
    return SIXEL_OK;
755
}
756

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

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

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

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

811
    return size;
812
}
813

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

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

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

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

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

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

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

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

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

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

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

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

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

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

949
    return SIXEL_OK;
950
}
951

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

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

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

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

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

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

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

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

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

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

1038
    return SIXEL_OK;
1039
}
1040

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

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

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

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

1105
    sixel_parallel_submit_band(ctx, band_index);
1106
}
×
1107

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

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

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

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

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

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

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

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

1192
    sixel_fence_acquire();
1193

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1468
    return SIXEL_OK;
1469
}
1470

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1792
/* implementation */
1793

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

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

1817

1818
static void
1819
sixel_advance(sixel_output_t *output, int nwrite)
37,959,690✔
1820
{
1821
    if ((output->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) {
37,959,690✔
1822
        if (output->penetrate_multiplexer) {
2,895✔
1823
            sixel_penetrate(output,
6✔
1824
                            SIXEL_OUTPUT_PACKET_SIZE,
1825
                            DCS_START_7BIT,
1826
                            DCS_END_7BIT,
1827
                            DCS_START_7BIT_SIZE,
1828
                            DCS_END_7BIT_SIZE);
1829
        } else {
1830
            output->fn_write((char *)output->buffer,
2,889✔
1831
                             SIXEL_OUTPUT_PACKET_SIZE, output->priv);
1832
        }
1833
        memcpy(output->buffer,
2,895✔
1834
               output->buffer + SIXEL_OUTPUT_PACKET_SIZE,
1835
               (size_t)(output->pos -= SIXEL_OUTPUT_PACKET_SIZE));
2,895✔
1836
    }
1837
}
37,959,690✔
1838

1839

1840
static void
1841
sixel_putc(unsigned char *buffer, unsigned char value)
8,079,330✔
1842
{
1843
    *buffer = value;
8,079,330✔
1844
}
1845

1846

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

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

1865
    if (count <= 0) {
23,143,575!
1866
        return;
1867
    }
1868

1869
    while (count > 0) {
46,287,393✔
1870
        chunk = SIXEL_OUTPUT_PACKET_SIZE - output->pos;
23,143,818✔
1871
        if (chunk > count) {
23,143,818✔
1872
            chunk = count;
1873
        }
1874
        memset(output->buffer + output->pos, value, (size_t)chunk);
23,143,818✔
1875
        sixel_advance(output, chunk);
23,143,818✔
1876
        count -= chunk;
23,143,818✔
1877
    }
1878
}
1!
1879

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

1889
static void
1890
sixel_encode_span_commit(int probe_active,
135,552✔
1891
                         sixel_assessment_stage_t stage,
1892
                         double started_at)
1893
{
1894
    double duration;
135,552✔
1895

1896
    if (!probe_active) {
135,552!
1897
        return;
1898
    }
1899
    duration = sixel_assessment_timer_now() - started_at;
×
1900
    if (duration < 0.0) {
×
1901
        duration = 0.0;
1902
    }
1903
    sixel_assessment_record_encode_span(stage, duration);
×
1904
}
1!
1905

1906

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

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

1925
    idx = start;
4,140,546✔
1926
    chunk_size = sizeof(unsigned long);
4,140,546✔
1927
    cursor = row + start;
4,140,546✔
1928

1929
    while ((width - idx) >= (int)chunk_size) {
116,588,025✔
1930
        memcpy(&block, cursor, chunk_size);
115,672,533✔
1931
        if (block != 0UL) {
115,672,533✔
1932
            break;
1933
        }
1934
        if (encode_probe_active) {
112,447,479!
1935
            *compose_scan_probes += (double)chunk_size;
×
1936
        }
1937
        idx += (int)chunk_size;
112,447,479✔
1938
        cursor += chunk_size;
112,447,479✔
1939
    }
1940

1941
    while (idx < width) {
18,328,926✔
1942
        if (encode_probe_active) {
17,423,169!
1943
            *compose_scan_probes += 1.0;
×
1944
        }
1945
        if (*cursor != 0) {
17,423,169✔
1946
            break;
1947
        }
1948
        idx += 1;
14,188,380✔
1949
        cursor += 1;
14,188,380✔
1950
    }
1951

1952
    return idx;
4,140,546✔
1953
}
1954

1955

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

1970
    gap = 0;
7,557,255✔
1971
    *reached_end = 0;
7,557,255✔
1972
    if (start >= width) {
7,557,255✔
1973
        *reached_end = 1;
7,671✔
1974
        return gap;
7,671✔
1975
    }
1976

1977
    chunk_size = sizeof(unsigned long);
7,549,584✔
1978
    cursor = row + start;
7,549,584✔
1979
    remaining = width - start;
7,549,584✔
1980

1981
    while (remaining >= (int)chunk_size) {
83,213,451✔
1982
        memcpy(&block, cursor, chunk_size);
82,282,065✔
1983
        if (block != 0UL) {
82,282,065✔
1984
            break;
1985
        }
1986
        gap += (int)chunk_size;
75,663,867✔
1987
        if (encode_probe_active) {
75,663,867!
1988
            *compose_scan_probes += (double)chunk_size;
×
1989
        }
1990
        cursor += chunk_size;
75,663,867✔
1991
        remaining -= (int)chunk_size;
75,663,867✔
1992
    }
1993

1994
    while (remaining > 0) {
24,058,395✔
1995
        if (encode_probe_active) {
23,160,309!
1996
            *compose_scan_probes += 1.0;
×
1997
        }
1998
        if (*cursor != 0) {
23,160,309✔
1999
            return gap;
2000
        }
2001
        gap += 1;
16,508,811✔
2002
        cursor += 1;
16,508,811✔
2003
        remaining -= 1;
16,508,811✔
2004
    }
2005

2006
    *reached_end = 1;
898,086✔
2007
    return gap;
898,086✔
2008
}
2009

2010

2011
#if HAVE_LDIV
2012
static int
2013
sixel_putnum_impl(char *buffer, long value, int pos)
13,228,314✔
2014
{
2015
    ldiv_t r;
13,228,314✔
2016

2017
    r = ldiv(value, 10);
13,228,314✔
2018
    if (r.quot > 0) {
13,228,314✔
2019
        pos = sixel_putnum_impl(buffer, r.quot, pos);
6,886,497✔
2020
    }
2021
    *(buffer + pos) = '0' + r.rem;
13,228,314✔
2022
    return pos + 1;
13,228,314✔
2023
}
2024
#endif  /* HAVE_LDIV */
2025

2026

2027
static int
2028
sixel_putnum(char *buffer, int value)
6,341,817✔
2029
{
2030
    int pos;
6,341,817✔
2031

2032
#if HAVE_LDIV
2033
    pos = sixel_putnum_impl(buffer, value, 0);
12,683,634✔
2034
#else
2035
    pos = sprintf(buffer, "%d", value);
2036
#endif  /* HAVE_LDIV */
2037

2038
    return pos;
6,341,817✔
2039
}
2040

2041

2042
static SIXELSTATUS
2043
sixel_put_flash(sixel_output_t *const output)
25,164,996✔
2044
{
2045
    int nwrite;
25,164,996✔
2046

2047
    if (output->save_count <= 0) {
25,164,996!
2048
        return SIXEL_OK;
2049
    }
2050

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

2062
    if (output->save_count > 3) {
25,164,996✔
2063
        /* DECGRI Graphics Repeat Introducer ! Pn Ch */
2064
        sixel_putc(output->buffer + output->pos, '!');
2,021,421✔
2065
        sixel_advance(output, 1);
2,021,421✔
2066
        nwrite = sixel_putnum((char *)output->buffer + output->pos, output->save_count);
2,021,421✔
2067
        sixel_advance(output, nwrite);
2,021,421✔
2068
        sixel_putc(output->buffer + output->pos, output->save_pixel);
2,021,421✔
2069
        sixel_advance(output, 1);
2,021,421✔
2070
    } else {
2071
        sixel_output_emit_literal(output,
23,143,575✔
2072
                                  (unsigned char)output->save_pixel,
23,143,575✔
2073
                                  output->save_count);
2074
    }
2075

2076
    output->save_pixel = 0;
25,164,996✔
2077
    output->save_count = 0;
25,164,996✔
2078

2079
    return SIXEL_OK;
25,164,996✔
2080
}
2081

2082

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

2093
    if (count <= 0) {
25,164,996!
2094
        return SIXEL_OK;
2095
    }
2096

2097
    if (output->save_count > 0) {
25,164,996✔
2098
        if (output->save_pixel == symbol) {
21,930,207!
2099
            output->save_count += count;
×
2100
            return SIXEL_OK;
×
2101
        }
2102

2103
        status = sixel_put_flash(output);
21,930,207✔
2104
        if (SIXEL_FAILED(status)) {
21,930,207!
2105
            return status;
2106
        }
2107
    }
2108

2109
    output->save_pixel = symbol;
25,164,996✔
2110
    output->save_count = count;
25,164,996✔
2111

2112
    return SIXEL_OK;
25,164,996✔
2113
}
2114

2115

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

2136
    if (length <= 0) {
3,234,789!
2137
        return SIXEL_OK;
2138
    }
2139

2140
    for (index = 0; index < length; index += run_length) {
26,668,881✔
2141
        value = map[index];
23,434,092✔
2142
        if (value > '?') {
23,434,092!
2143
            value = 0;
×
2144
        }
2145

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

2153
            while (remain >= (int)chunk_size) {
23,887,248✔
2154
                memcpy(&block,
13,529,193✔
2155
                       map + index + run_length,
13,529,193✔
2156
                       chunk_size);
2157
                block ^= pattern;
13,529,193✔
2158
                if (block != 0UL) {
13,529,193✔
2159
                    for (byte_index = 0;
1!
2160
                         byte_index < (int)chunk_size;
17,825,565!
2161
                         byte_index++) {
4,749,528✔
2162
                        if ((block & 0xffUL) != 0UL) {
17,825,565✔
2163
                            chunk_mismatch = 1;
2164
                            break;
2165
                        }
2166
                        block >>= 8;
4,749,528✔
2167
                        run_length += 1;
4,749,528✔
2168
                    }
2169
                    break;
2170
                }
2171
                run_length += (int)chunk_size;
453,156✔
2172
                remain -= (int)chunk_size;
453,156✔
2173
            }
2174
        }
2175

2176
        if (!chunk_mismatch) {
23,434,092✔
2177
            while (index + run_length < length) {
13,006,104✔
2178
                unsigned char next;
9,771,315✔
2179

2180
                next = map[index + run_length];
9,771,315✔
2181
                if (next > '?') {
9,771,315!
2182
                    next = 0;
×
2183
                }
2184
                if (next != value) {
9,771,315✔
2185
                    break;
2186
                }
2187
                run_length += 1;
2,648,049✔
2188
            }
2!
2189
        }
2190

2191
        status = sixel_emit_run(output,
23,434,092✔
2192
                                 (int)value + '?',
2193
                                 run_length);
2194
        if (SIXEL_FAILED(status)) {
23,434,092!
2195
            return status;
2196
        }
2197
    }
2198

2199
    return SIXEL_OK;
2200
}
2201

2202

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

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

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

2217
static SIXELSTATUS
2218
sixel_node_new(sixel_node_t **np, sixel_allocator_t *allocator)
443,295✔
2219
{
2220
    SIXELSTATUS status = SIXEL_FALSE;
443,295✔
2221

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

2231
    status = SIXEL_OK;
2232

2233
end:
443,295✔
2234
    return status;
443,295✔
2235
}
2236

2237
static void
2238
sixel_node_del(sixel_output_t *output, sixel_node_t *np)
3,234,789✔
2239
{
2240
    sixel_node_t *tp;
3,234,789✔
2241

2242
    if ((tp = output->node_top) == np) {
3,234,789✔
2243
        output->node_top = np->next;
199,092✔
2244
    } else {
2245
        while (tp->next != NULL) {
663,894,111!
2246
            if (tp->next == np) {
663,894,111✔
2247
                tp->next = np->next;
3,035,697✔
2248
                break;
3,035,697✔
2249
            }
2250
            tp = tp->next;
2251
        }
2252
    }
2253

2254
    np->next = output->node_free;
3,234,789✔
2255
    output->node_free = np;
3,234,789✔
2256
}
3,234,789✔
2257

2258

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

2271
    if (ncolors != 2 || keycolor == (-1)) {
3,234,789✔
2272
        /* designate palette index */
2273
        if (output->active_palette != np->pal) {
3,233,859✔
2274
            sixel_putc(output->buffer + output->pos, '#');
3,183,786✔
2275
            sixel_advance(output, 1);
3,183,786✔
2276
            nwrite = sixel_putnum((char *)output->buffer + output->pos, np->pal);
3,183,786✔
2277
            sixel_advance(output, nwrite);
3,183,786✔
2278
            output->active_palette = np->pal;
3,183,786✔
2279
        }
2280
    }
2281

2282
    if (*x < np->sx) {
3,234,789✔
2283
        int span;
1,730,904✔
2284

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

2296
    if (*x < np->mx) {
3,234,789!
2297
        int span;
3,234,789✔
2298

2299
        span = np->mx - *x;
3,234,789✔
2300
        status = sixel_emit_span_from_map(output,
6,469,578✔
2301
                                          (unsigned char const *)np->map + *x,
3,234,789✔
2302
                                          span);
2303
        if (SIXEL_FAILED(status)) {
3,234,789!
2304
            goto end;
2305
        }
2306
        if (emit_cells != NULL) {
3,234,789!
2307
            *emit_cells += (double)span;
×
2308
        }
2309
        *x = np->mx;
3,234,789✔
2310
    }
1!
2311

2312
    status = sixel_put_flash(output);
3,234,789✔
2313
    if (SIXEL_FAILED(status)) {
3,234,789!
2314
        goto end;
2315
    }
2316

2317
end:
3,234,789✔
2318
    return status;
3,234,789✔
2319
}
2320

2321

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

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

2336
    output->pos = 0;
489✔
2337

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

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

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

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

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

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

2398
    status = SIXEL_OK;
489✔
2399

2400
    return status;
489✔
2401
}
2402

2403

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

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

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

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

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

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

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

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

2509
    return 0;
2510
}
2511

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

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

2534
    return value;
220,356✔
2535
}
2536

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

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

2584
    status = SIXEL_OK;
210,456✔
2585

2586
    return status;
210,456✔
2587
}
2588

2589

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

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

2707
    status = SIXEL_OK;
73,452✔
2708
    return status;
73,452✔
2709
}
2710

2711

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

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

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

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

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

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

2804
    status = SIXEL_OK;
2805

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

2830
    return status;
1,416✔
2831
}
2832

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

2859
static void
2860
sixel_band_state_reset(sixel_band_state_t *state)
18,183✔
2861
{
2862
    state->row_in_band = 0;
18,183✔
2863
    state->fillable = 0;
18,183✔
2864
    state->active_color_count = 0;
6,061✔
2865
}
2866

2867
static void
2868
sixel_band_finish(sixel_encode_work_t *work, sixel_band_state_t *state)
16,767✔
2869
{
2870
    int color_index;
16,767✔
2871
    int c;
16,767✔
2872

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

2879
    for (color_index = 0;
2✔
2880
         color_index < state->active_color_count;
952,143✔
2881
         color_index++) {
935,376✔
2882
        c = work->active_color_index[color_index];
935,376✔
2883
        if (c >= 0
935,376!
2884
            && (size_t)c < work->active_colors_size) {
935,376!
2885
            work->active_colors[c] = 0;
935,376✔
2886
        }
2887
    }
2888
    state->active_color_count = 0;
16,767✔
2889
}
1!
2890

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

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

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

2927
    if (bodyonly || (ncolors == 2 && keycolor != (-1))) {
1,416!
2928
        return SIXEL_OK;
2929
    }
2930

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

2937
    span_started_at = sixel_encode_span_start(encode_probe_active);
1,404!
2938
    if (output->palette_type == SIXEL_PALETTETYPE_HLS) {
1,404✔
2939
        for (n = 0; n < ncolors; n++) {
73,746✔
2940
            status = output_hls_palette_definition(output,
73,452✔
2941
                                                   palette,
2942
                                                   palette_float,
2943
                                                   n,
2944
                                                   keycolor);
2945
            if (SIXEL_FAILED(status)) {
73,452!
2946
                goto end;
2947
            }
2948
        }
2949
    } else {
2950
        for (n = 0; n < ncolors; n++) {
211,566✔
2951
            status = output_rgb_palette_definition(output,
210,456✔
2952
                                                   palette,
2953
                                                   palette_float,
2954
                                                   n,
2955
                                                   keycolor);
2956
            if (SIXEL_FAILED(status)) {
210,456!
2957
                goto end;
2958
            }
2959
        }
2960
    }
2961

2962
    sixel_encode_span_commit(encode_probe_active,
1,404✔
2963
                             SIXEL_ASSESSMENT_STAGE_ENCODE_PREPARE,
2964
                             span_started_at);
2965

2966
    status = SIXEL_OK;
1,404✔
2967

2968
end:
1,404✔
2969
    return status;
1,404✔
2970
}
2971

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

2993
    map = work->map;
99,651✔
2994
    active_colors = work->active_colors;
99,651✔
2995
    active_color_index = work->active_color_index;
99,651✔
2996
    row_bit = state->row_in_band;
99,651✔
2997
    band_start = absolute_row - row_bit;
99,651✔
2998

2999
    if (row_bit == 0) {
99,651✔
3000
        if (encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
16,767✔
3001
            state->fillable = 0;
16,542✔
3002
        } else if (palstate) {
225!
3003
            if (width > 0) {
×
3004
                pix = pixels[band_start * width];
×
3005
                if (pix >= ncolors) {
×
3006
                    state->fillable = 0;
×
3007
                } else {
3008
                    state->fillable = 1;
×
3009
                }
3010
            } else {
3011
                state->fillable = 0;
×
3012
            }
3013
        } else {
3014
            state->fillable = 1;
225✔
3015
        }
3016
        state->active_color_count = 0;
16,767✔
3017
    }
3018

3019
    for (x = 0; x < width; x++) {
61,192,575✔
3020
        if (absolute_row > INT_MAX / width) {
61,092,924!
3021
            sixel_helper_set_additional_message(
×
3022
                "sixel_encode_body: integer overflow detected."
3023
                " (y > INT_MAX)");
3024
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3025
            goto end;
×
3026
        }
3027
        check_integer_overflow = absolute_row * width;
61,092,924✔
3028
        if (check_integer_overflow > INT_MAX - x) {
61,092,924!
3029
            sixel_helper_set_additional_message(
×
3030
                "sixel_encode_body: integer overflow detected."
3031
                " (y * width > INT_MAX - x)");
3032
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3033
            goto end;
×
3034
        }
3035
        pix = pixels[check_integer_overflow + x];
61,092,924✔
3036
        if (pix >= 0 && pix < ncolors && pix != keycolor) {
61,092,924!
3037
            if (pix > INT_MAX / width) {
56,483,139!
3038
                sixel_helper_set_additional_message(
×
3039
                    "sixel_encode_body: integer overflow detected."
3040
                    " (pix > INT_MAX / width)");
3041
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3042
                goto end;
×
3043
            }
3044
            check_integer_overflow = pix * width;
56,483,139✔
3045
            if (check_integer_overflow > INT_MAX - x) {
56,483,139!
3046
                sixel_helper_set_additional_message(
×
3047
                    "sixel_encode_body: integer overflow detected."
3048
                    " (pix * width > INT_MAX - x)");
3049
                status = SIXEL_BAD_INTEGER_OVERFLOW;
×
3050
                goto end;
×
3051
            }
3052
            map[pix * width + x] |= (1 << row_bit);
56,483,139✔
3053
            if (active_colors != NULL && active_colors[pix] == 0) {
56,483,139!
3054
                active_colors[pix] = 1;
935,376✔
3055
                if (state->active_color_count < ncolors
935,376!
3056
                    && active_color_index != NULL) {
935,376!
3057
                    active_color_index[state->active_color_count] = pix;
935,376✔
3058
                    state->active_color_count += 1;
935,376✔
3059
                }
3060
            }
3061
        } else if (!palstate) {
4,609,785✔
3062
            state->fillable = 0;
1,272,585✔
3063
        }
3064
    }
3065

3066
    state->row_in_band += 1;
99,651✔
3067
    status = SIXEL_OK;
99,651✔
3068

3069
end:
99,651✔
3070
    return status;
99,651✔
3071
}
3072

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

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

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

3112
    metrics->compose_span_started_at =
33,534✔
3113
        sixel_encode_span_start(metrics->encode_probe_active);
16,767!
3114
    metrics->compose_queue_started_at = 0.0;
16,767✔
3115
    metrics->compose_span_duration = 0.0;
16,767✔
3116
    metrics->compose_queue_duration = 0.0;
16,767✔
3117
    metrics->compose_scan_duration = 0.0;
16,767✔
3118
    metrics->compose_scan_probes = 0.0;
16,767✔
3119
    metrics->compose_queue_nodes = 0.0;
16,767✔
3120

3121
    for (color_index = 0;
16,767✔
3122
         color_index < state->active_color_count;
952,143✔
3123
         color_index++) {
935,376✔
3124
        c = work->active_color_index[color_index];
935,376✔
3125
        row = (unsigned char *)(work->map + c * width);
935,376✔
3126
        sx = 0;
935,376✔
3127
        while (sx < width) {
4,170,165✔
3128
            sx = sixel_compose_find_run_start(
4,140,546✔
3129
                row,
3130
                width,
3131
                sx,
3132
                metrics->encode_probe_active,
3133
                &metrics->compose_scan_probes);
3134
            if (sx >= width) {
4,140,546✔
3135
                break;
3136
            }
3137

3138
            if (metrics->encode_probe_active) {
3,234,789!
3139
                now = sixel_assessment_timer_now();
×
3140
                metrics->compose_queue_started_at = now;
×
3141
                metrics->compose_queue_nodes += 1.0;
×
3142
            }
3143

3144
            mx = sx + 1;
3,234,789✔
3145
            while (mx < width) {
28,453,407✔
3146
                if (metrics->encode_probe_active) {
28,423,788!
3147
                    metrics->compose_scan_probes += 1.0;
×
3148
                }
3149
                if (row[mx] != 0) {
28,423,788✔
3150
                    mx += 1;
20,866,533✔
3151
                    continue;
20,866,533✔
3152
                }
3153

3154
                gap = sixel_compose_measure_gap(
7,557,255✔
3155
                    row,
3156
                    width,
3157
                    mx + 1,
3158
                    metrics->encode_probe_active,
3159
                    &metrics->compose_scan_probes,
3160
                    &gap_reached_end);
3161
                if (gap >= 9 || gap_reached_end) {
7,557,255✔
3162
                    break;
3163
                }
3164
                mx += gap + 1;
4,352,085✔
3165
            }
3166

3167
            if ((np = output->node_free) != NULL) {
3,234,789✔
3168
                output->node_free = np->next;
2,791,494✔
3169
            } else {
3170
                status = sixel_node_new(&np, allocator);
443,295✔
3171
                if (SIXEL_FAILED(status)) {
443,295!
3172
                    goto end;
3173
                }
3174
            }
3175

3176
            np->pal = c;
3,234,789✔
3177
            np->sx = sx;
3,234,789✔
3178
            np->mx = mx;
3,234,789✔
3179
            np->map = (char *)row;
3,234,789✔
3180
            np->next = NULL;
3,234,789✔
3181

3182
            if (work->columns != NULL) {
3,234,789!
3183
                column_head = work->columns[sx];
3,234,789✔
3184
                if (column_head == NULL
3,234,789✔
3185
                    || column_head->mx <= np->mx) {
1,081,560✔
3186
                    np->next = column_head;
2,721,066✔
3187
                    work->columns[sx] = np;
2,721,066✔
3188
                } else {
3189
                    column_tail = column_head;
3190
                    while (column_tail->next != NULL
622,572✔
3191
                           && column_tail->next->mx > np->mx) {
622,572✔
3192
                        column_tail = column_tail->next;
3193
                    }
3194
                    np->next = column_tail->next;
513,723✔
3195
                    column_tail->next = np;
513,723✔
3196
                }
3197
            } else {
3198
                top.next = output->node_top;
×
3199
                column_tail = &top;
×
3200

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

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

3216
            if (metrics->encode_probe_active) {
3,234,789!
3217
                now = sixel_assessment_timer_now();
×
3218
                metrics->compose_queue_duration +=
×
3219
                    now - metrics->compose_queue_started_at;
×
3220
            }
3221

3222
            sx = mx;
3223
        }
3224
    }
3225

3226
    if (work->columns != NULL) {
16,767!
3227
        if (metrics->encode_probe_active) {
16,767!
3228
            metrics->compose_queue_started_at =
×
3229
                sixel_assessment_timer_now();
×
3230
        }
3231
        top.next = NULL;
16,767✔
3232
        column_tail = &top;
16,767✔
3233
        for (sx = 0; sx < width; sx++) {
10,219,386✔
3234
            column_head = work->columns[sx];
10,202,619✔
3235
            if (column_head == NULL) {
10,202,619✔
3236
                continue;
8,049,390✔
3237
            }
3238
            column_tail->next = column_head;
2,153,229✔
3239
            while (column_tail->next != NULL) {
5,388,018✔
3240
                column_tail = column_tail->next;
3241
            }
3242
            work->columns[sx] = NULL;
2,153,229✔
3243
        }
3244
        output->node_top = top.next;
16,767✔
3245
        if (metrics->encode_probe_active) {
16,767!
3246
            now = sixel_assessment_timer_now();
×
3247
            metrics->compose_queue_duration +=
×
3248
                now - metrics->compose_queue_started_at;
×
3249
        }
3250
    }
3251

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

3288
    status = SIXEL_OK;
16,767✔
3289

3290
end:
16,767✔
3291
    return status;
16,767✔
3292
}
3293

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

3309
    span_started_at = sixel_encode_span_start(metrics->encode_probe_active);
16,767!
3310
    if (last_row_index != 5) {
16,767✔
3311
        output->buffer[output->pos] = '-';
15,351✔
3312
        sixel_advance(output, 1);
15,351✔
3313
    }
3314

3315
    for (x = 0; (np = output->node_top) != NULL;) {
126,573✔
3316
        if (x > np->sx) {
109,806✔
3317
            output->buffer[output->pos] = '$';
93,042✔
3318
            sixel_advance(output, 1);
93,042✔
3319
            x = 0;
93,042✔
3320
        }
3321

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

3340
        while (np != NULL) {
14,521,692✔
3341
            if (np->sx < x) {
14,411,886✔
3342
                np = np->next;
11,286,903✔
3343
                continue;
11,286,903✔
3344
            }
3345

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

3365
        state->fillable = 0;
109,806✔
3366
    }
3367

3368
    sixel_encode_span_commit(metrics->encode_probe_active,
16,767✔
3369
                             SIXEL_ASSESSMENT_STAGE_ENCODE_EMIT,
3370
                             span_started_at);
3371

3372
    status = SIXEL_OK;
16,767✔
3373

3374
end:
16,767✔
3375
    (void)work;
16,767✔
3376
    return status;
16,767✔
3377
}
3378

3379

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

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

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

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

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

3433
    sixel_assessment_set_encode_parallelism(1);
1,416✔
3434

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

3446
#if SIXEL_ENABLE_THREADS
3447
    {
3448
        int nbands;
944✔
3449
        int threads;
944✔
3450

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

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

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

3494
    band_start = 0;
3495
    while (band_start < height) {
18,183✔
3496
        band_height = height - band_start;
16,767✔
3497
        if (band_height > 6) {
16,767✔
3498
            band_height = 6;
3499
        }
3500

3501
        band.row_in_band = 0;
16,767✔
3502
        band.fillable = 0;
16,767✔
3503
        band.active_color_count = 0;
16,767✔
3504

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

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

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

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

3565
        sixel_band_finish(&work, &band);
16,767✔
3566

3567
        span_started_at =
16,767!
3568
            sixel_encode_span_start(encode_probe_active);
16,767✔
3569
        sixel_band_clear_map(&work);
16,767✔
3570
        sixel_encode_span_commit(
16,767✔
3571
            encode_probe_active,
3572
            SIXEL_ASSESSMENT_STAGE_ENCODE_PREPARE,
3573
            span_started_at);
3574

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

3589
        band_start += band_height;
16,767✔
3590
        sixel_band_state_reset(&band);
16,767✔
3591
        job_index += 1;
16,767✔
3592
    }
3593

3594
    status = SIXEL_OK;
1,416✔
3595
    goto finalize;
1,416✔
3596

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

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

3613
cleanup:
2,832✔
3614
    while ((np = output->node_free) != NULL) {
444,711✔
3615
        output->node_free = np->next;
443,295✔
3616
        sixel_allocator_free(allocator, np);
443,295✔
3617
    }
3618
    output->node_top = NULL;
1,416✔
3619

3620
    sixel_encode_work_cleanup(&work, allocator);
1,416✔
3621

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

3629
    if (!output->skip_dcs_envelope && !output->penetrate_multiplexer) {
489!
3630
        if (output->has_8bit_control) {
483✔
3631
            sixel_puts(output->buffer + output->pos,
15✔
3632
                       DCS_END_8BIT, DCS_END_8BIT_SIZE);
3633
            sixel_advance(output, DCS_END_8BIT_SIZE);
15✔
3634
        } else {
3635
            sixel_puts(output->buffer + output->pos,
468✔
3636
                       DCS_END_7BIT, DCS_END_7BIT_SIZE);
3637
            sixel_advance(output, DCS_END_7BIT_SIZE);
468✔
3638
        }
3639
    }
3640

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

3660
    status = SIXEL_OK;
489✔
3661

3662
    return status;
489✔
3663
}
3664

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

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

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

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

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

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

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

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

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

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

3768
    return 0;
×
3769
}
3770

3771

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

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

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

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

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

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

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

3972
    (void)palette_float_count;
453✔
3973

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

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

4049
    status = sixel_encode_header(width, height, output);
453✔
4050
    if (SIXEL_FAILED(status)) {
453!
4051
        goto end;
4052
    }
4053

4054
    if (output->ormode) {
453!
4055
        status = sixel_encode_body_ormode(input_pixels,
×
4056
                                          width,
4057
                                          height,
4058
                                          palette_entries,
4059
                                          dither->ncolors,
4060
                                          dither->keycolor,
4061
                                          output);
4062
    } else if (pipeline_active) {
453!
4063
        status = sixel_encode_body_pipeline(pixels,
4064
                                            width,
4065
                                            height,
4066
                                            palette_entries,
4067
                                            palette_entries_float32,
4068
                                            dither,
4069
                                            output,
4070
                                            pipeline_threads);
4071
    } else {
4072
        status = sixel_encode_body(input_pixels,
755!
4073
                                   width,
4074
                                   height,
4075
                                   palette_entries,
4076
                                   palette_entries_float32,
4077
                                   dither->ncolors,
4078
                                   dither->keycolor,
4079
                                   dither->bodyonly,
4080
                                   output,
4081
                                   NULL,
4082
                                   dither->allocator,
4083
                                   logger != NULL && logger->active ?
1!
4084
                                       logger :
4085
                                       NULL);
4086
    }
4087

4088
    if (SIXEL_FAILED(status)) {
453!
4089
        goto end;
×
4090
    }
4091

4092
    status = sixel_encode_footer(output);
453✔
4093
    if (SIXEL_FAILED(status)) {
453!
4094
        goto end;
4095
    }
4096

4097
end:
453✔
4098
#if SIXEL_ENABLE_THREADS
4099
    if (logger_owned) {
302!
4100
        dither->pipeline_logger = NULL;
4101
        sixel_logger_close(&serial_logger);
4102
    }
4103
#endif
4104
    if (palette_obj != NULL) {
453!
4105
        sixel_palette_unref(palette_obj);
×
4106
    }
4107
    if (palette_entries != NULL) {
453!
4108
        sixel_allocator_free(dither->allocator, palette_entries);
453✔
4109
    }
4110
    if (palette_entries_float32 != NULL) {
453!
4111
        sixel_allocator_free(dither->allocator, palette_entries_float32);
×
4112
    }
4113
    sixel_allocator_free(dither->allocator, paletted_pixels);
453✔
4114

4115
    return status;
453✔
4116
}
4117

4118
static void
4119
dither_func_none(unsigned char *data, int width)
4120
{
4121
    (void) data;  /* unused */
4122
    (void) width; /* unused */
4123
}
4124

4125

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

4134
    /* Floyd Steinberg Method
4135
     *          curr    7/16
4136
     *  3/16    5/48    1/16
4137
     */
4138
    r = (data[3 + 0] + (error_r * 5 >> 4));
3,895,740✔
4139
    g = (data[3 + 1] + (error_g * 5 >> 4));
3,895,740✔
4140
    b = (data[3 + 2] + (error_b * 5 >> 4));
3,895,740✔
4141
    data[3 + 0] = r > 0xff ? 0xff: r;
3,895,740✔
4142
    data[3 + 1] = g > 0xff ? 0xff: g;
3,895,740✔
4143
    data[3 + 2] = b > 0xff ? 0xff: b;
3,895,740✔
4144
    r = data[width * 3 - 3 + 0] + (error_r * 3 >> 4);
3,895,740✔
4145
    g = data[width * 3 - 3 + 1] + (error_g * 3 >> 4);
3,895,740✔
4146
    b = data[width * 3 - 3 + 2] + (error_b * 3 >> 4);
3,895,740✔
4147
    data[width * 3 - 3 + 0] = r > 0xff ? 0xff: r;
3,895,740✔
4148
    data[width * 3 - 3 + 1] = g > 0xff ? 0xff: g;
3,895,740✔
4149
    data[width * 3 - 3 + 2] = b > 0xff ? 0xff: b;
3,895,740✔
4150
    r = data[width * 3 + 0] + (error_r * 5 >> 4);
3,895,740✔
4151
    g = data[width * 3 + 1] + (error_g * 5 >> 4);
3,895,740✔
4152
    b = data[width * 3 + 2] + (error_b * 5 >> 4);
3,895,740✔
4153
    data[width * 3 + 0] = r > 0xff ? 0xff: r;
3,895,740✔
4154
    data[width * 3 + 1] = g > 0xff ? 0xff: g;
3,895,740✔
4155
    data[width * 3 + 2] = b > 0xff ? 0xff: b;
3,895,740✔
4156
}
3,895,740✔
4157

4158

4159
static void
4160
dither_func_atkinson(unsigned char *data, int width)
803,712✔
4161
{
4162
    int r, g, b;
803,712✔
4163
    int error_r = data[0] & 0x7;
803,712✔
4164
    int error_g = data[1] & 0x7;
803,712✔
4165
    int error_b = data[2] & 0x7;
803,712✔
4166

4167
    error_r += 4;
803,712✔
4168
    error_g += 4;
803,712✔
4169
    error_b += 4;
803,712✔
4170

4171
    /* Atkinson's Method
4172
     *          curr    1/8    1/8
4173
     *   1/8     1/8    1/8
4174
     *           1/8
4175
     */
4176
    r = data[(width * 0 + 1) * 3 + 0] + (error_r >> 3);
803,712✔
4177
    g = data[(width * 0 + 1) * 3 + 1] + (error_g >> 3);
803,712✔
4178
    b = data[(width * 0 + 1) * 3 + 2] + (error_b >> 3);
803,712✔
4179
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
803,712✔
4180
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
803,712✔
4181
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
803,712✔
4182
    r = data[(width * 0 + 2) * 3 + 0] + (error_r >> 3);
803,712✔
4183
    g = data[(width * 0 + 2) * 3 + 1] + (error_g >> 3);
803,712✔
4184
    b = data[(width * 0 + 2) * 3 + 2] + (error_b >> 3);
803,712✔
4185
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
803,712✔
4186
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
803,712✔
4187
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
803,712✔
4188
    r = data[(width * 1 - 1) * 3 + 0] + (error_r >> 3);
803,712✔
4189
    g = data[(width * 1 - 1) * 3 + 1] + (error_g >> 3);
803,712✔
4190
    b = data[(width * 1 - 1) * 3 + 2] + (error_b >> 3);
803,712✔
4191
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
803,712✔
4192
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
803,712✔
4193
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
803,712✔
4194
    r = data[(width * 1 + 0) * 3 + 0] + (error_r >> 3);
803,712✔
4195
    g = data[(width * 1 + 0) * 3 + 1] + (error_g >> 3);
803,712✔
4196
    b = data[(width * 1 + 0) * 3 + 2] + (error_b >> 3);
803,712✔
4197
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
803,712✔
4198
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
803,712✔
4199
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
803,712✔
4200
    r = (data[(width * 1 + 1) * 3 + 0] + (error_r >> 3));
803,712✔
4201
    g = (data[(width * 1 + 1) * 3 + 1] + (error_g >> 3));
803,712✔
4202
    b = (data[(width * 1 + 1) * 3 + 2] + (error_b >> 3));
803,712✔
4203
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
803,712✔
4204
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
803,712✔
4205
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
803,712✔
4206
    r = (data[(width * 2 + 0) * 3 + 0] + (error_r >> 3));
803,712✔
4207
    g = (data[(width * 2 + 0) * 3 + 1] + (error_g >> 3));
803,712✔
4208
    b = (data[(width * 2 + 0) * 3 + 2] + (error_b >> 3));
803,712✔
4209
    data[(width * 2 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
803,712✔
4210
    data[(width * 2 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
803,712✔
4211
    data[(width * 2 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
803,712✔
4212
}
803,712✔
4213

4214

4215
static void
4216
dither_func_jajuni(unsigned char *data, int width)
1,020,861✔
4217
{
4218
    int r, g, b;
1,020,861✔
4219
    int error_r = data[0] & 0x7;
1,020,861✔
4220
    int error_g = data[1] & 0x7;
1,020,861✔
4221
    int error_b = data[2] & 0x7;
1,020,861✔
4222

4223
    error_r += 4;
1,020,861✔
4224
    error_g += 4;
1,020,861✔
4225
    error_b += 4;
1,020,861✔
4226

4227
    /* Jarvis, Judice & Ninke Method
4228
     *                  curr    7/48    5/48
4229
     *  3/48    5/48    7/48    5/48    3/48
4230
     *  1/48    3/48    5/48    3/48    1/48
4231
     */
4232
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 7 / 48);
1,020,861✔
4233
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 7 / 48);
1,020,861✔
4234
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 7 / 48);
1,020,861✔
4235
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,020,861✔
4236
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,020,861✔
4237
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,020,861✔
4238
    r = data[(width * 0 + 2) * 3 + 0] + (error_r * 5 / 48);
1,020,861✔
4239
    g = data[(width * 0 + 2) * 3 + 1] + (error_g * 5 / 48);
1,020,861✔
4240
    b = data[(width * 0 + 2) * 3 + 2] + (error_b * 5 / 48);
1,020,861✔
4241
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,020,861✔
4242
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,020,861✔
4243
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,020,861✔
4244
    r = data[(width * 1 - 2) * 3 + 0] + (error_r * 3 / 48);
1,020,861✔
4245
    g = data[(width * 1 - 2) * 3 + 1] + (error_g * 3 / 48);
1,020,861✔
4246
    b = data[(width * 1 - 2) * 3 + 2] + (error_b * 3 / 48);
1,020,861✔
4247
    data[(width * 1 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,020,861!
4248
    data[(width * 1 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,020,861!
4249
    data[(width * 1 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,020,861!
4250
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 5 / 48);
1,020,861✔
4251
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 5 / 48);
1,020,861✔
4252
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 5 / 48);
1,020,861✔
4253
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,020,861✔
4254
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,020,861✔
4255
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,020,861✔
4256
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 7 / 48);
1,020,861✔
4257
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 7 / 48);
1,020,861✔
4258
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 7 / 48);
1,020,861✔
4259
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
1,020,861✔
4260
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
1,020,861✔
4261
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
1,020,861✔
4262
    r = data[(width * 1 + 1) * 3 + 0] + (error_r * 5 / 48);
1,020,861✔
4263
    g = data[(width * 1 + 1) * 3 + 1] + (error_g * 5 / 48);
1,020,861✔
4264
    b = data[(width * 1 + 1) * 3 + 2] + (error_b * 5 / 48);
1,020,861✔
4265
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,020,861!
4266
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,020,861✔
4267
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,020,861✔
4268
    r = data[(width * 1 + 2) * 3 + 0] + (error_r * 3 / 48);
1,020,861✔
4269
    g = data[(width * 1 + 2) * 3 + 1] + (error_g * 3 / 48);
1,020,861✔
4270
    b = data[(width * 1 + 2) * 3 + 2] + (error_b * 3 / 48);
1,020,861✔
4271
    data[(width * 1 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,020,861!
4272
    data[(width * 1 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,020,861!
4273
    data[(width * 1 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,020,861!
4274
    r = data[(width * 2 - 2) * 3 + 0] + (error_r * 1 / 48);
1,020,861✔
4275
    g = data[(width * 2 - 2) * 3 + 1] + (error_g * 1 / 48);
1,020,861✔
4276
    b = data[(width * 2 - 2) * 3 + 2] + (error_b * 1 / 48);
1,020,861✔
4277
    data[(width * 2 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,020,861!
4278
    data[(width * 2 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,020,861!
4279
    data[(width * 2 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,020,861!
4280
    r = data[(width * 2 - 1) * 3 + 0] + (error_r * 3 / 48);
1,020,861✔
4281
    g = data[(width * 2 - 1) * 3 + 1] + (error_g * 3 / 48);
1,020,861✔
4282
    b = data[(width * 2 - 1) * 3 + 2] + (error_b * 3 / 48);
1,020,861✔
4283
    data[(width * 2 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,020,861!
4284
    data[(width * 2 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,020,861!
4285
    data[(width * 2 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,020,861!
4286
    r = data[(width * 2 + 0) * 3 + 0] + (error_r * 5 / 48);
1,020,861✔
4287
    g = data[(width * 2 + 0) * 3 + 1] + (error_g * 5 / 48);
1,020,861✔
4288
    b = data[(width * 2 + 0) * 3 + 2] + (error_b * 5 / 48);
1,020,861✔
4289
    data[(width * 2 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
1,020,861!
4290
    data[(width * 2 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
1,020,861✔
4291
    data[(width * 2 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
1,020,861✔
4292
    r = data[(width * 2 + 1) * 3 + 0] + (error_r * 3 / 48);
1,020,861✔
4293
    g = data[(width * 2 + 1) * 3 + 1] + (error_g * 3 / 48);
1,020,861✔
4294
    b = data[(width * 2 + 1) * 3 + 2] + (error_b * 3 / 48);
1,020,861✔
4295
    data[(width * 2 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,020,861!
4296
    data[(width * 2 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,020,861!
4297
    data[(width * 2 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,020,861!
4298
    r = data[(width * 2 + 2) * 3 + 0] + (error_r * 1 / 48);
1,020,861✔
4299
    g = data[(width * 2 + 2) * 3 + 1] + (error_g * 1 / 48);
1,020,861✔
4300
    b = data[(width * 2 + 2) * 3 + 2] + (error_b * 1 / 48);
1,020,861✔
4301
    data[(width * 2 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,020,861!
4302
    data[(width * 2 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,020,861!
4303
    data[(width * 2 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,020,861!
4304
}
1,020,861✔
4305

4306

4307
static void
4308
dither_func_stucki(unsigned char *data, int width)
1,000,059✔
4309
{
4310
    int r, g, b;
1,000,059✔
4311
    int error_r = data[0] & 0x7;
1,000,059✔
4312
    int error_g = data[1] & 0x7;
1,000,059✔
4313
    int error_b = data[2] & 0x7;
1,000,059✔
4314

4315
    error_r += 4;
1,000,059✔
4316
    error_g += 4;
1,000,059✔
4317
    error_b += 4;
1,000,059✔
4318

4319
    /* Stucki's Method
4320
     *                  curr    8/48    4/48
4321
     *  2/48    4/48    8/48    4/48    2/48
4322
     *  1/48    2/48    4/48    2/48    1/48
4323
     */
4324
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 8 / 48);
1,000,059✔
4325
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 8 / 48);
1,000,059✔
4326
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 8 / 48);
1,000,059✔
4327
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,000,059✔
4328
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,000,059✔
4329
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,000,059✔
4330
    r = data[(width * 0 + 2) * 3 + 0] + (error_r * 4 / 48);
1,000,059✔
4331
    g = data[(width * 0 + 2) * 3 + 1] + (error_g * 4 / 48);
1,000,059✔
4332
    b = data[(width * 0 + 2) * 3 + 2] + (error_b * 4 / 48);
1,000,059✔
4333
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,000,059!
4334
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,000,059!
4335
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,000,059!
4336
    r = data[(width * 1 - 2) * 3 + 0] + (error_r * 2 / 48);
1,000,059✔
4337
    g = data[(width * 1 - 2) * 3 + 1] + (error_g * 2 / 48);
1,000,059✔
4338
    b = data[(width * 1 - 2) * 3 + 2] + (error_b * 2 / 48);
1,000,059✔
4339
    data[(width * 1 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,000,059!
4340
    data[(width * 1 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,000,059!
4341
    data[(width * 1 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,000,059!
4342
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 4 / 48);
1,000,059✔
4343
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 4 / 48);
1,000,059✔
4344
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 4 / 48);
1,000,059✔
4345
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,000,059!
4346
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,000,059!
4347
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,000,059!
4348
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 8 / 48);
1,000,059✔
4349
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 8 / 48);
1,000,059✔
4350
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 8 / 48);
1,000,059✔
4351
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
1,000,059!
4352
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
1,000,059✔
4353
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
1,000,059✔
4354
    r = data[(width * 1 + 1) * 3 + 0] + (error_r * 4 / 48);
1,000,059✔
4355
    g = data[(width * 1 + 1) * 3 + 1] + (error_g * 4 / 48);
1,000,059✔
4356
    b = data[(width * 1 + 1) * 3 + 2] + (error_b * 4 / 48);
1,000,059✔
4357
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,000,059!
4358
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,000,059!
4359
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,000,059!
4360
    r = data[(width * 1 + 2) * 3 + 0] + (error_r * 2 / 48);
1,000,059✔
4361
    g = data[(width * 1 + 2) * 3 + 1] + (error_g * 2 / 48);
1,000,059✔
4362
    b = data[(width * 1 + 2) * 3 + 2] + (error_b * 2 / 48);
1,000,059✔
4363
    data[(width * 1 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,000,059!
4364
    data[(width * 1 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,000,059!
4365
    data[(width * 1 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,000,059!
4366
    r = data[(width * 2 - 2) * 3 + 0] + (error_r * 1 / 48);
1,000,059✔
4367
    g = data[(width * 2 - 2) * 3 + 1] + (error_g * 1 / 48);
1,000,059✔
4368
    b = data[(width * 2 - 2) * 3 + 2] + (error_b * 1 / 48);
1,000,059✔
4369
    data[(width * 2 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,000,059!
4370
    data[(width * 2 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,000,059!
4371
    data[(width * 2 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,000,059!
4372
    r = data[(width * 2 - 1) * 3 + 0] + (error_r * 2 / 48);
1,000,059✔
4373
    g = data[(width * 2 - 1) * 3 + 1] + (error_g * 2 / 48);
1,000,059✔
4374
    b = data[(width * 2 - 1) * 3 + 2] + (error_b * 2 / 48);
1,000,059✔
4375
    data[(width * 2 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,000,059!
4376
    data[(width * 2 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,000,059!
4377
    data[(width * 2 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,000,059!
4378
    r = data[(width * 2 + 0) * 3 + 0] + (error_r * 4 / 48);
1,000,059✔
4379
    g = data[(width * 2 + 0) * 3 + 1] + (error_g * 4 / 48);
1,000,059✔
4380
    b = data[(width * 2 + 0) * 3 + 2] + (error_b * 4 / 48);
1,000,059✔
4381
    data[(width * 2 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
1,000,059!
4382
    data[(width * 2 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
1,000,059!
4383
    data[(width * 2 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
1,000,059!
4384
    r = data[(width * 2 + 1) * 3 + 0] + (error_r * 2 / 48);
1,000,059✔
4385
    g = data[(width * 2 + 1) * 3 + 1] + (error_g * 2 / 48);
1,000,059✔
4386
    b = data[(width * 2 + 1) * 3 + 2] + (error_b * 2 / 48);
1,000,059✔
4387
    data[(width * 2 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,000,059!
4388
    data[(width * 2 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,000,059!
4389
    data[(width * 2 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,000,059!
4390
    r = data[(width * 2 + 2) * 3 + 0] + (error_r * 1 / 48);
1,000,059✔
4391
    g = data[(width * 2 + 2) * 3 + 1] + (error_g * 1 / 48);
1,000,059✔
4392
    b = data[(width * 2 + 2) * 3 + 2] + (error_b * 1 / 48);
1,000,059✔
4393
    data[(width * 2 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,000,059!
4394
    data[(width * 2 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,000,059!
4395
    data[(width * 2 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,000,059!
4396
}
1,000,059✔
4397

4398

4399
static void
4400
dither_func_burkes(unsigned char *data, int width)
1,045,989✔
4401
{
4402
    int r, g, b;
1,045,989✔
4403
    int error_r = data[0] & 0x7;
1,045,989✔
4404
    int error_g = data[1] & 0x7;
1,045,989✔
4405
    int error_b = data[2] & 0x7;
1,045,989✔
4406

4407
    error_r += 2;
1,045,989✔
4408
    error_g += 2;
1,045,989✔
4409
    error_b += 2;
1,045,989✔
4410

4411
    /* Burkes' Method
4412
     *                  curr    4/16    2/16
4413
     *  1/16    2/16    4/16    2/16    1/16
4414
     */
4415
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 4 / 16);
1,045,989✔
4416
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 4 / 16);
1,045,989✔
4417
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 4 / 16);
1,045,989✔
4418
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,045,989✔
4419
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,045,989✔
4420
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,045,989✔
4421
    r = data[(width * 0 + 2) * 3 + 0] + (error_r * 2 / 16);
1,045,989✔
4422
    g = data[(width * 0 + 2) * 3 + 1] + (error_g * 2 / 16);
1,045,989✔
4423
    b = data[(width * 0 + 2) * 3 + 2] + (error_b * 2 / 16);
1,045,989✔
4424
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,045,989✔
4425
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,045,989✔
4426
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,045,989✔
4427
    r = data[(width * 1 - 2) * 3 + 0] + (error_r * 1 / 16);
1,045,989✔
4428
    g = data[(width * 1 - 2) * 3 + 1] + (error_g * 1 / 16);
1,045,989✔
4429
    b = data[(width * 1 - 2) * 3 + 2] + (error_b * 1 / 16);
1,045,989✔
4430
    data[(width * 1 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,045,989!
4431
    data[(width * 1 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,045,989!
4432
    data[(width * 1 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,045,989!
4433
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 2 / 16);
1,045,989✔
4434
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 2 / 16);
1,045,989✔
4435
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 2 / 16);
1,045,989✔
4436
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,045,989✔
4437
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,045,989✔
4438
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,045,989✔
4439
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 4 / 16);
1,045,989✔
4440
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 4 / 16);
1,045,989✔
4441
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 4 / 16);
1,045,989✔
4442
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
1,045,989✔
4443
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
1,045,989✔
4444
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
1,045,989✔
4445
    r = data[(width * 1 + 1) * 3 + 0] + (error_r * 2 / 16);
1,045,989✔
4446
    g = data[(width * 1 + 1) * 3 + 1] + (error_g * 2 / 16);
1,045,989✔
4447
    b = data[(width * 1 + 1) * 3 + 2] + (error_b * 2 / 16);
1,045,989✔
4448
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
1,045,989✔
4449
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
1,045,989✔
4450
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
1,045,989✔
4451
    r = data[(width * 1 + 2) * 3 + 0] + (error_r * 1 / 16);
1,045,989✔
4452
    g = data[(width * 1 + 2) * 3 + 1] + (error_g * 1 / 16);
1,045,989✔
4453
    b = data[(width * 1 + 2) * 3 + 2] + (error_b * 1 / 16);
1,045,989✔
4454
    data[(width * 1 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
1,045,989!
4455
    data[(width * 1 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
1,045,989!
4456
    data[(width * 1 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
1,045,989!
4457
}
1,045,989✔
4458

4459

4460
static void
4461
dither_func_sierra1(unsigned char *data, int width)
×
4462
{
4463
    int r, g, b;
×
4464
    int error_r = data[0] & 0x7;
×
4465
    int error_g = data[1] & 0x7;
×
4466
    int error_b = data[2] & 0x7;
×
4467

4468
    error_r += 2;
×
4469
    error_g += 2;
×
4470
    error_b += 2;
×
4471

4472
    /* Sierra Lite Method
4473
     *          curr    2/4
4474
     *  1/4     1/4
4475
     */
4476
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 2 / 4);
×
4477
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 2 / 4);
×
4478
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 2 / 4);
×
4479
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4480
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4481
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4482
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 1 / 4);
×
4483
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 1 / 4);
×
4484
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 1 / 4);
×
4485
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4486
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4487
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4488
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 1 / 4);
×
4489
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 1 / 4);
×
4490
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 1 / 4);
×
4491
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
×
4492
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
×
4493
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
×
4494
}
×
4495

4496

4497
static void
4498
dither_func_sierra2(unsigned char *data, int width)
×
4499
{
4500
    int r, g, b;
×
4501
    int error_r = data[0] & 0x7;
×
4502
    int error_g = data[1] & 0x7;
×
4503
    int error_b = data[2] & 0x7;
×
4504

4505
    error_r += 4;
×
4506
    error_g += 4;
×
4507
    error_b += 4;
×
4508

4509
    /* Sierra Two-row Method
4510
     *                  curr    4/32    3/32
4511
     *  1/32    2/32    3/32    2/32    1/32
4512
     *                  2/32    3/32    2/32
4513
     */
4514
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 4 / 32);
×
4515
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 4 / 32);
×
4516
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 4 / 32);
×
4517
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4518
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4519
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4520
    r = data[(width * 0 + 2) * 3 + 0] + (error_r * 3 / 32);
×
4521
    g = data[(width * 0 + 2) * 3 + 1] + (error_g * 3 / 32);
×
4522
    b = data[(width * 0 + 2) * 3 + 2] + (error_b * 3 / 32);
×
4523
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4524
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4525
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4526
    r = data[(width * 1 - 2) * 3 + 0] + (error_r * 1 / 32);
×
4527
    g = data[(width * 1 - 2) * 3 + 1] + (error_g * 1 / 32);
×
4528
    b = data[(width * 1 - 2) * 3 + 2] + (error_b * 1 / 32);
×
4529
    data[(width * 1 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4530
    data[(width * 1 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4531
    data[(width * 1 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4532
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 2 / 32);
×
4533
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 2 / 32);
×
4534
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 2 / 32);
×
4535
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4536
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4537
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4538
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 3 / 32);
×
4539
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 3 / 32);
×
4540
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 3 / 32);
×
4541
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
×
4542
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
×
4543
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
×
4544
    r = data[(width * 1 + 1) * 3 + 0] + (error_r * 2 / 32);
×
4545
    g = data[(width * 1 + 1) * 3 + 1] + (error_g * 2 / 32);
×
4546
    b = data[(width * 1 + 1) * 3 + 2] + (error_b * 2 / 32);
×
4547
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4548
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4549
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4550
    r = data[(width * 1 + 2) * 3 + 0] + (error_r * 1 / 32);
×
4551
    g = data[(width * 1 + 2) * 3 + 1] + (error_g * 1 / 32);
×
4552
    b = data[(width * 1 + 2) * 3 + 2] + (error_b * 1 / 32);
×
4553
    data[(width * 1 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4554
    data[(width * 1 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4555
    data[(width * 1 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4556
    r = data[(width * 2 - 1) * 3 + 0] + (error_r * 2 / 32);
×
4557
    g = data[(width * 2 - 1) * 3 + 1] + (error_g * 2 / 32);
×
4558
    b = data[(width * 2 - 1) * 3 + 2] + (error_b * 2 / 32);
×
4559
    data[(width * 2 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4560
    data[(width * 2 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4561
    data[(width * 2 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4562
    r = data[(width * 2 + 0) * 3 + 0] + (error_r * 3 / 32);
×
4563
    g = data[(width * 2 + 0) * 3 + 1] + (error_g * 3 / 32);
×
4564
    b = data[(width * 2 + 0) * 3 + 2] + (error_b * 3 / 32);
×
4565
    data[(width * 2 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
×
4566
    data[(width * 2 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
×
4567
    data[(width * 2 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
×
4568
    r = data[(width * 2 + 1) * 3 + 0] + (error_r * 2 / 32);
×
4569
    g = data[(width * 2 + 1) * 3 + 1] + (error_g * 2 / 32);
×
4570
    b = data[(width * 2 + 1) * 3 + 2] + (error_b * 2 / 32);
×
4571
    data[(width * 2 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4572
    data[(width * 2 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4573
    data[(width * 2 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4574
    r = data[(width * 2 + 2) * 3 + 0] + (error_r * 1 / 32);
×
4575
    g = data[(width * 2 + 2) * 3 + 1] + (error_g * 1 / 32);
×
4576
    b = data[(width * 2 + 2) * 3 + 2] + (error_b * 1 / 32);
×
4577
    data[(width * 2 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4578
    data[(width * 2 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4579
    data[(width * 2 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4580
}
×
4581

4582

4583
static void
4584
dither_func_sierra3(unsigned char *data, int width)
×
4585
{
4586
    int r, g, b;
×
4587
    int error_r = data[0] & 0x7;
×
4588
    int error_g = data[1] & 0x7;
×
4589
    int error_b = data[2] & 0x7;
×
4590

4591
    error_r += 4;
×
4592
    error_g += 4;
×
4593
    error_b += 4;
×
4594

4595
    /* Sierra-3 Method
4596
     *                  curr    5/32    3/32
4597
     *  2/32    4/32    5/32    4/32    2/32
4598
     *                  2/32    3/32    2/32
4599
     */
4600
    r = data[(width * 0 + 1) * 3 + 0] + (error_r * 5 / 32);
×
4601
    g = data[(width * 0 + 1) * 3 + 1] + (error_g * 5 / 32);
×
4602
    b = data[(width * 0 + 1) * 3 + 2] + (error_b * 5 / 32);
×
4603
    data[(width * 0 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4604
    data[(width * 0 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4605
    data[(width * 0 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4606
    r = data[(width * 0 + 2) * 3 + 0] + (error_r * 3 / 32);
×
4607
    g = data[(width * 0 + 2) * 3 + 1] + (error_g * 3 / 32);
×
4608
    b = data[(width * 0 + 2) * 3 + 2] + (error_b * 3 / 32);
×
4609
    data[(width * 0 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4610
    data[(width * 0 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4611
    data[(width * 0 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4612
    r = data[(width * 1 - 2) * 3 + 0] + (error_r * 2 / 32);
×
4613
    g = data[(width * 1 - 2) * 3 + 1] + (error_g * 2 / 32);
×
4614
    b = data[(width * 1 - 2) * 3 + 2] + (error_b * 2 / 32);
×
4615
    data[(width * 1 - 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4616
    data[(width * 1 - 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4617
    data[(width * 1 - 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4618
    r = data[(width * 1 - 1) * 3 + 0] + (error_r * 4 / 32);
×
4619
    g = data[(width * 1 - 1) * 3 + 1] + (error_g * 4 / 32);
×
4620
    b = data[(width * 1 - 1) * 3 + 2] + (error_b * 4 / 32);
×
4621
    data[(width * 1 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4622
    data[(width * 1 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4623
    data[(width * 1 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4624
    r = data[(width * 1 + 0) * 3 + 0] + (error_r * 5 / 32);
×
4625
    g = data[(width * 1 + 0) * 3 + 1] + (error_g * 5 / 32);
×
4626
    b = data[(width * 1 + 0) * 3 + 2] + (error_b * 5 / 32);
×
4627
    data[(width * 1 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
×
4628
    data[(width * 1 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
×
4629
    data[(width * 1 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
×
4630
    r = data[(width * 1 + 1) * 3 + 0] + (error_r * 4 / 32);
×
4631
    g = data[(width * 1 + 1) * 3 + 1] + (error_g * 4 / 32);
×
4632
    b = data[(width * 1 + 1) * 3 + 2] + (error_b * 4 / 32);
×
4633
    data[(width * 1 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4634
    data[(width * 1 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4635
    data[(width * 1 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4636
    r = data[(width * 1 + 2) * 3 + 0] + (error_r * 2 / 32);
×
4637
    g = data[(width * 1 + 2) * 3 + 1] + (error_g * 2 / 32);
×
4638
    b = data[(width * 1 + 2) * 3 + 2] + (error_b * 2 / 32);
×
4639
    data[(width * 1 + 2) * 3 + 0] = r > 0xff ? 0xff: r;
×
4640
    data[(width * 1 + 2) * 3 + 1] = g > 0xff ? 0xff: g;
×
4641
    data[(width * 1 + 2) * 3 + 2] = b > 0xff ? 0xff: b;
×
4642
    r = data[(width * 2 - 1) * 3 + 0] + (error_r * 2 / 32);
×
4643
    g = data[(width * 2 - 1) * 3 + 1] + (error_g * 2 / 32);
×
4644
    b = data[(width * 2 - 1) * 3 + 2] + (error_b * 2 / 32);
×
4645
    data[(width * 2 - 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4646
    data[(width * 2 - 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4647
    data[(width * 2 - 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4648
    r = data[(width * 2 + 0) * 3 + 0] + (error_r * 3 / 32);
×
4649
    g = data[(width * 2 + 0) * 3 + 1] + (error_g * 3 / 32);
×
4650
    b = data[(width * 2 + 0) * 3 + 2] + (error_b * 3 / 32);
×
4651
    data[(width * 2 + 0) * 3 + 0] = r > 0xff ? 0xff: r;
×
4652
    data[(width * 2 + 0) * 3 + 1] = g > 0xff ? 0xff: g;
×
4653
    data[(width * 2 + 0) * 3 + 2] = b > 0xff ? 0xff: b;
×
4654
    r = data[(width * 2 + 1) * 3 + 0] + (error_r * 2 / 32);
×
4655
    g = data[(width * 2 + 1) * 3 + 1] + (error_g * 2 / 32);
×
4656
    b = data[(width * 2 + 1) * 3 + 2] + (error_b * 2 / 32);
×
4657
    data[(width * 2 + 1) * 3 + 0] = r > 0xff ? 0xff: r;
×
4658
    data[(width * 2 + 1) * 3 + 1] = g > 0xff ? 0xff: g;
×
4659
    data[(width * 2 + 1) * 3 + 2] = b > 0xff ? 0xff: b;
×
4660
}
×
4661

4662

4663
static void
4664
dither_func_a_dither(unsigned char *data, int width, int x, int y)
22,500✔
4665
{
4666
    int c;
22,500✔
4667
    float value;
22,500✔
4668
    float mask;
22,500✔
4669

4670
    (void) width; /* unused */
22,500✔
4671

4672
    for (c = 0; c < 3; c ++) {
90,000✔
4673
        mask = (((x + c * 17) + y * 236) * 119) & 255;
67,500✔
4674
        mask = ((mask - 128) / 256.0f) ;
67,500✔
4675
        value = data[c] + mask;
67,500✔
4676
        if (value < 0) {
67,500!
4677
            value = 0;
4678
        }
4679
        value = value > 255 ? 255 : value;
67,500!
4680
        data[c] = value;
67,500✔
4681
    }
4682
}
22,500✔
4683

4684

4685
static void
4686
dither_func_x_dither(unsigned char *data, int width, int x, int y)
39,900✔
4687
{
4688
    int c;
39,900✔
4689
    float value;
39,900✔
4690
    float mask;
39,900✔
4691

4692
    (void) width;  /* unused */
39,900✔
4693

4694
    for (c = 0; c < 3; c ++) {
159,600✔
4695
        mask = (((x + c * 17) ^ y * 236) * 1234) & 511;
119,700✔
4696
        mask = ((mask - 128) / 512.0f) ;
119,700✔
4697
        value = data[c] + mask;
119,700✔
4698
        if (value < 0) {
119,700✔
4699
            value = 0;
4700
        }
4701
        value = value > 255 ? 255 : value;
119,502!
4702
        data[c] = value;
119,700✔
4703
    }
4704
}
39,900✔
4705

4706

4707
static void
4708
sixel_apply_15bpp_dither(
7,910,844✔
4709
    unsigned char *pixels,
4710
    int x, int y, int width, int height,
4711
    int method_for_diffuse)
4712
{
4713
    /* apply floyd steinberg dithering */
4714
    switch (method_for_diffuse) {
7,910,844!
4715
    case SIXEL_DIFFUSE_FS:
3,912,288✔
4716
        if (x < width - 1 && y < height - 1) {
3,912,288✔
4717
            dither_func_fs(pixels, width);
3,895,740✔
4718
        }
4719
        break;
4720
    case SIXEL_DIFFUSE_ATKINSON:
810,000✔
4721
        if (x < width - 2 && y < height - 2) {
810,000✔
4722
            dither_func_atkinson(pixels, width);
803,712✔
4723
        }
4724
        break;
4725
    case SIXEL_DIFFUSE_JAJUNI:
1,028,244✔
4726
        if (x < width - 2 && y < height - 2) {
1,028,244✔
4727
            dither_func_jajuni(pixels, width);
1,020,861✔
4728
        }
4729
        break;
4730
    case SIXEL_DIFFUSE_STUCKI:
1,007,289✔
4731
        if (x < width - 2 && y < height - 2) {
1,007,289✔
4732
            dither_func_stucki(pixels, width);
1,000,059✔
4733
        }
4734
        break;
4735
    case SIXEL_DIFFUSE_BURKES:
1,051,623✔
4736
        if (x < width - 2 && y < height - 1) {
1,051,623✔
4737
            dither_func_burkes(pixels, width);
1,045,989✔
4738
        }
4739
        break;
4740
    case SIXEL_DIFFUSE_SIERRA1:
×
4741
        if (x < width - 1 && y < height - 1) {
×
4742
            dither_func_sierra1(pixels, width);
×
4743
        }
4744
        break;
4745
    case SIXEL_DIFFUSE_SIERRA2:
×
4746
        if (x < width - 2 && y < height - 2) {
×
4747
            dither_func_sierra2(pixels, width);
×
4748
        }
4749
        break;
4750
    case SIXEL_DIFFUSE_SIERRA3:
×
4751
        if (x < width - 2 && y < height - 2) {
×
4752
            dither_func_sierra3(pixels, width);
×
4753
        }
4754
        break;
4755
    case SIXEL_DIFFUSE_A_DITHER:
22,500✔
4756
        dither_func_a_dither(pixels, width, x, y);
22,500✔
4757
        break;
22,500✔
4758
    case SIXEL_DIFFUSE_X_DITHER:
39,900✔
4759
        dither_func_x_dither(pixels, width, x, y);
39,900✔
4760
        break;
39,900✔
4761
    case SIXEL_DIFFUSE_NONE:
4762
    default:
4763
        dither_func_none(pixels, width);
4764
        break;
4765
    }
4766
}
7,910,844✔
4767

4768

4769
static SIXELSTATUS
4770
sixel_encode_highcolor(
36✔
4771
        unsigned char *pixels, int width, int height,
4772
        sixel_dither_t *dither, sixel_output_t *output
4773
        )
4774
{
4775
    SIXELSTATUS status = SIXEL_FALSE;
36✔
4776
    sixel_index_t *paletted_pixels = NULL;
36✔
4777
    unsigned char *normalized_pixels = NULL;
36✔
4778
    /* Mark sixel line pixels which have been already drawn. */
4779
    unsigned char *marks;
36✔
4780
    unsigned char *rgbhit;
36✔
4781
    unsigned char *rgb2pal;
36✔
4782
    unsigned char palhitcount[SIXEL_PALETTE_MAX];
36✔
4783
    unsigned char palstate[SIXEL_PALETTE_MAX];
36✔
4784
    int output_count;
36✔
4785
    int const maxcolors = 1 << 15;
36✔
4786
    int whole_size = width * height  /* for paletted_pixels */
36✔
4787
                   + maxcolors       /* for rgbhit */
4788
                   + maxcolors       /* for rgb2pal */
36✔
4789
                   + width * 6;      /* for marks */
36✔
4790
    int x, y;
36✔
4791
    unsigned char *dst;
36✔
4792
    unsigned char *mptr;
36✔
4793
    int dirty;
36✔
4794
    int mod_y;
36✔
4795
    int nextpal;
36✔
4796
    int threshold;
36✔
4797
    int pix;
36✔
4798
    int orig_height;
36✔
4799
    unsigned char *pal;
36✔
4800
    unsigned char *palette_entries = NULL;
36✔
4801
    sixel_palette_t *palette_obj = NULL;
36✔
4802
    size_t palette_count = 0U;
36✔
4803

4804
    if (dither->pixelformat != SIXEL_PIXELFORMAT_RGB888) {
36!
4805
        /* normalize pixelfromat */
4806
        normalized_pixels = (unsigned char *)sixel_allocator_malloc(dither->allocator,
×
4807
                                                                    (size_t)(width * height * 3));
×
4808
        if (normalized_pixels == NULL) {
×
4809
            goto error;
×
4810
        }
4811
        status = sixel_helper_normalize_pixelformat(normalized_pixels,
×
4812
                                                    &dither->pixelformat,
4813
                                                    pixels,
4814
                                                    dither->pixelformat,
4815
                                                    width, height);
4816
        if (SIXEL_FAILED(status)) {
×
4817
            goto error;
×
4818
        }
4819
        pixels = normalized_pixels;
4820
    }
4821

4822
    palette_entries = NULL;
36✔
4823
    palette_obj = NULL;
36✔
4824
    palette_count = 0U;
36✔
4825
    status = sixel_dither_get_quantized_palette(dither, &palette_obj);
36✔
4826
    if (SIXEL_FAILED(status)) {
36!
4827
        goto error;
×
4828
    }
4829
    status = sixel_palette_copy_entries_8bit(
36✔
4830
        palette_obj,
4831
        &palette_entries,
4832
        &palette_count,
4833
        SIXEL_PIXELFORMAT_RGB888,
4834
        dither->allocator);
4835
    sixel_palette_unref(palette_obj);
36✔
4836
    palette_obj = NULL;
36✔
4837
    if (SIXEL_FAILED(status) || palette_entries == NULL) {
36!
4838
        goto error;
×
4839
    }
4840

4841
    paletted_pixels = (sixel_index_t *)sixel_allocator_malloc(dither->allocator,
36✔
4842
                                                              (size_t)whole_size);
4843
    if (paletted_pixels == NULL) {
36!
4844
        goto error;
×
4845
    }
4846
    rgbhit = paletted_pixels + width * height;
36✔
4847
    memset(rgbhit, 0, (size_t)(maxcolors * 2 + width * 6));
36✔
4848
    rgb2pal = rgbhit + maxcolors;
36✔
4849
    marks = rgb2pal + maxcolors;
36✔
4850
    output_count = 0;
36✔
4851

4852
next:
963✔
4853
    dst = paletted_pixels;
963✔
4854
    nextpal = 0;
963✔
4855
    threshold = 1;
963✔
4856
    dirty = 0;
963✔
4857
    mptr = marks;
963✔
4858
    memset(palstate, 0, sizeof(palstate));
963✔
4859
    y = mod_y = 0;
963✔
4860

4861
    while (1) {
17,877✔
4862
        for (x = 0; x < width; x++, mptr++, dst++, pixels += 3) {
10,608,477✔
4863
            if (*mptr) {
10,590,600✔
4864
                *dst = 255;
2,679,756✔
4865
            } else {
4866
                sixel_apply_15bpp_dither(pixels,
7,910,844✔
4867
                                         x, y, width, height,
4868
                                         dither->method_for_diffuse);
4869
                pix = ((pixels[0] & 0xf8) << 7) |
7,910,844✔
4870
                      ((pixels[1] & 0xf8) << 2) |
7,910,844✔
4871
                      ((pixels[2] >> 3) & 0x1f);
7,910,844✔
4872

4873
                if (!rgbhit[pix]) {
7,910,844✔
4874
                    while (1) {
1,372,993✔
4875
                        if (nextpal >= 255) {
1,372,993✔
4876
                            if (threshold >= 255) {
659,308✔
4877
                                break;
4878
                            } else {
4879
                                threshold = (threshold == 1) ? 9: 255;
1,864✔
4880
                                nextpal = 0;
4881
                            }
4882
                        } else if (palstate[nextpal] ||
713,685✔
4883
                                 palhitcount[nextpal] > threshold) {
391,620✔
4884
                            nextpal++;
505,846✔
4885
                        } else {
4886
                            break;
4887
                        }
4888
                    }
4889

4890
                    if (nextpal >= 255) {
865,283✔
4891
                        dirty = 1;
657,444✔
4892
                        *dst = 255;
657,444✔
4893
                    } else {
4894
                        pal = palette_entries + (nextpal * 3);
207,839✔
4895

4896
                        rgbhit[pix] = 1;
207,839✔
4897
                        if (output_count > 0) {
207,839✔
4898
                            rgbhit[((pal[0] & 0xf8) << 7) |
204,467✔
4899
                                   ((pal[1] & 0xf8) << 2) |
204,467✔
4900
                                   ((pal[2] >> 3) & 0x1f)] = 0;
204,467✔
4901
                        }
4902
                        *dst = rgb2pal[pix] = nextpal++;
207,839✔
4903
                        *mptr = 1;
207,839✔
4904
                        palstate[*dst] = PALETTE_CHANGE;
207,839✔
4905
                        palhitcount[*dst] = 1;
207,839✔
4906
                        *(pal++) = pixels[0];
207,839✔
4907
                        *(pal++) = pixels[1];
207,839✔
4908
                        *(pal++) = pixels[2];
207,839✔
4909
                    }
4910
                } else {
4911
                    *dst = rgb2pal[pix];
7,045,561✔
4912
                    *mptr = 1;
7,045,561✔
4913
                    if (!palstate[*dst]) {
7,045,561✔
4914
                        palstate[*dst] = PALETTE_HIT;
30,319✔
4915
                    }
4916
                    if (palhitcount[*dst] < 255) {
7,045,561✔
4917
                        palhitcount[*dst]++;
1,684,022✔
4918
                    }
4919
                }
4920
            }
4921
        }
4922

4923
        if (++y >= height) {
17,877✔
4924
            if (dirty) {
36!
4925
                mod_y = 5;
4926
            } else {
4927
                goto end;
36✔
4928
            }
4929
        }
4930
        if (dirty && (mod_y == 5 || y >= height)) {
17,841!
4931
            orig_height = height;
927✔
4932

4933
            if (output_count++ == 0) {
927✔
4934
                status = sixel_encode_header(width, height, output);
12✔
4935
                if (SIXEL_FAILED(status)) {
12!
4936
                    goto error;
4937
                }
4938
            }
4939
            height = y;
927✔
4940
            status = sixel_encode_body(paletted_pixels,
927✔
4941
                                       width,
4942
                                       height,
4943
                                       palette_entries,
4944
                                       NULL,
4945
                                       255,
4946
                                       255,
4947
                                       dither->bodyonly,
4948
                                       output,
4949
                                       palstate,
4950
                                       dither->allocator,
4951
                                       NULL);
4952
            if (SIXEL_FAILED(status)) {
927!
4953
                goto error;
×
4954
            }
4955
            if (y >= orig_height) {
927!
4956
              goto end;
×
4957
            }
4958
            pixels -= (6 * width * 3);
927✔
4959
            height = orig_height - height + 6;
927✔
4960
            goto next;
927✔
4961
        }
4962

4963
        if (++mod_y == 6) {
16,914✔
4964
            mptr = (unsigned char *)memset(marks, 0, (size_t)(width * 6));
2,022✔
4965
            mod_y = 0;
2,022✔
4966
        }
4967
    }
4968

4969
    goto next;
4970

4971
end:
36✔
4972
    if (output_count == 0) {
36✔
4973
        status = sixel_encode_header(width, height, output);
24✔
4974
        if (SIXEL_FAILED(status)) {
24!
4975
            goto error;
4976
        }
4977
    }
4978
    status = sixel_encode_body(paletted_pixels,
36✔
4979
                               width,
4980
                               height,
4981
                               palette_entries,
4982
                               NULL,
4983
                               255,
4984
                               255,
4985
                               dither->bodyonly,
4986
                               output,
4987
                               palstate,
4988
                               dither->allocator,
4989
                               NULL);
4990
    if (SIXEL_FAILED(status)) {
36!
4991
        goto error;
×
4992
    }
4993

4994
    status = sixel_encode_footer(output);
36✔
4995
    if (SIXEL_FAILED(status)) {
36!
4996
        goto error;
4997
    }
4998

4999
error:
36✔
5000
    if (palette_entries != NULL) {
36!
5001
        sixel_allocator_free(dither->allocator, palette_entries);
36✔
5002
    }
5003
    sixel_allocator_free(dither->allocator, paletted_pixels);
36✔
5004
    sixel_allocator_free(dither->allocator, normalized_pixels);
36✔
5005

5006
    return status;
36✔
5007
}
5008

5009

5010
SIXELAPI SIXELSTATUS
5011
sixel_encode(
489✔
5012
    unsigned char  /* in */ *pixels,   /* pixel bytes */
5013
    int            /* in */ width,     /* image width */
5014
    int            /* in */ height,    /* image height */
5015
    int const      /* in */ depth,     /* color depth */
5016
    sixel_dither_t /* in */ *dither,   /* dither context */
5017
    sixel_output_t /* in */ *output)   /* output context */
5018
{
5019
    SIXELSTATUS status = SIXEL_FALSE;
489✔
5020

5021
    (void) depth;
489✔
5022

5023
    /* TODO: reference counting should be thread-safe */
5024
    sixel_dither_ref(dither);
489✔
5025
    sixel_output_ref(output);
489✔
5026

5027
    if (width < 1) {
489!
5028
        sixel_helper_set_additional_message(
×
5029
            "sixel_encode: bad width parameter."
5030
            " (width < 1)");
5031
        status = SIXEL_BAD_INPUT;
×
5032
        goto end;
×
5033
    }
5034

5035
    if (height < 1) {
489!
5036
        sixel_helper_set_additional_message(
×
5037
            "sixel_encode: bad height parameter."
5038
            " (height < 1)");
5039
        status = SIXEL_BAD_INPUT;
×
5040
        goto end;
×
5041
    }
5042

5043
    if (dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
489✔
5044
        status = sixel_encode_highcolor(pixels, width, height,
36✔
5045
                                        dither, output);
5046
    } else {
5047
        status = sixel_encode_dither(pixels, width, height,
453✔
5048
                                     dither, output);
5049
    }
5050

5051
end:
489✔
5052
    sixel_output_unref(output);
489✔
5053
    sixel_dither_unref(dither);
489✔
5054

5055
    return status;
489✔
5056
}
5057

5058
#if HAVE_TESTS
5059

5060
typedef struct sixel_tosixel_capture {
5061
    char buffer[256];
5062
} sixel_tosixel_capture_t;
5063

5064
static int
5065
sixel_tosixel_capture_write(char *data, int size, void *priv)
×
5066
{
5067
    sixel_tosixel_capture_t *capture;
×
5068

5069
    (void)data;
×
5070
    (void)size;
×
5071
    capture = (sixel_tosixel_capture_t *)priv;
×
5072
    if (capture != NULL && size >= 0) {
×
5073
        capture->buffer[0] = '\0';
×
5074
    }
5075

5076
    return size;
×
5077
}
5078

5079
static int
5080
test_emit_palette_prefers_float32(void)
×
5081
{
5082
    sixel_output_t *output;
×
5083
    sixel_tosixel_capture_t capture;
×
5084
    SIXELSTATUS status;
×
5085
    unsigned char palette_bytes[3] = { 0, 0, 0 };
×
5086
    float palette_float[3] = { 0.123f, 0.456f, 0.789f };
×
5087

5088
    output = NULL;
×
5089
    memset(&capture, 0, sizeof(capture));
×
5090

5091
    status = sixel_output_new(&output,
×
5092
                              sixel_tosixel_capture_write,
5093
                              &capture,
5094
                              NULL);
5095
    if (SIXEL_FAILED(status) || output == NULL) {
×
5096
        goto error;
×
5097
    }
5098

5099
    output->palette_type = SIXEL_PALETTETYPE_RGB;
×
5100
    output->pos = 0;
×
5101
    status = sixel_encode_emit_palette(0,
×
5102
                                       1,
5103
                                       (-1),
5104
                                       palette_bytes,
5105
                                       palette_float,
5106
                                       output,
5107
                                       0);
5108
    if (SIXEL_FAILED(status)) {
×
5109
        goto error;
×
5110
    }
5111

5112
    output->buffer[output->pos] = '\0';
×
5113
    if (strstr((char *)output->buffer, "#0;2;12;46;79") == NULL) {
×
5114
        goto error;
×
5115
    }
5116

5117
    sixel_output_unref(output);
×
5118
    return EXIT_SUCCESS;
×
5119

5120
error:
×
5121
    sixel_output_unref(output);
×
5122
    return EXIT_FAILURE;
×
5123
}
5124

5125
static int
5126
test_emit_palette_hls_from_float32(void)
×
5127
{
5128
    sixel_output_t *output;
×
5129
    sixel_tosixel_capture_t capture;
×
5130
    SIXELSTATUS status;
×
5131
    unsigned char palette_bytes[3] = { 0, 0, 0 };
×
5132
    float palette_float[3] = { 1.0f, 0.0f, 0.0f };
×
5133

5134
    output = NULL;
×
5135
    memset(&capture, 0, sizeof(capture));
×
5136

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

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

5158
    output->buffer[output->pos] = '\0';
×
5159
    if (strstr((char *)output->buffer, "#0;1;120;50;100") == NULL) {
×
5160
        goto error;
×
5161
    }
5162

5163
    sixel_output_unref(output);
×
5164
    return EXIT_SUCCESS;
×
5165

5166
error:
×
5167
    sixel_output_unref(output);
×
5168
    return EXIT_FAILURE;
×
5169
}
5170

5171
static int
5172
test_emit_palette_hls_from_bytes(void)
×
5173
{
5174
    sixel_output_t *output;
×
5175
    sixel_tosixel_capture_t capture;
×
5176
    SIXELSTATUS status;
×
5177
    unsigned char palette_bytes[3] = { 255, 0, 0 };
×
5178

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

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

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

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

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

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

5216
SIXELAPI int
5217
sixel_tosixel_tests_main(void)
×
5218
{
5219
    int nret;
×
5220

5221
    nret = test_emit_palette_prefers_float32();
×
5222
    if (nret != EXIT_SUCCESS) {
×
5223
        return nret;
5224
    }
5225

5226
    nret = test_emit_palette_hls_from_float32();
×
5227
    if (nret != EXIT_SUCCESS) {
×
5228
        return nret;
5229
    }
5230

5231
    nret = test_emit_palette_hls_from_bytes();
×
5232
    if (nret != EXIT_SUCCESS) {
×
5233
        return nret;
5234
    }
5235

5236
    return EXIT_SUCCESS;
5237
}
5238

5239
#endif  /* HAVE_TESTS */
5240

5241
/* emacs Local Variables:      */
5242
/* emacs mode: c               */
5243
/* emacs tab-width: 4          */
5244
/* emacs indent-tabs-mode: nil */
5245
/* emacs c-basic-offset: 4     */
5246
/* emacs End:                  */
5247
/* vim: set expandtab ts=4 sts=4 sw=4 : */
5248
/* 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