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

saitoha / libsixel / 19765269522

28 Nov 2025 01:30PM UTC coverage: 39.983% (-1.6%) from 41.616%
19765269522

push

github

web-flow
Merge pull request #214 from saitoha/codex/add-logging-to-resize-processing

Limit scale logging and add timeline window controls

9788 of 35562 branches covered (27.52%)

9 of 63 new or added lines in 1 file covered. (14.29%)

281 existing lines in 19 files now uncovered.

12991 of 32491 relevant lines covered (39.98%)

619662.16 hits per line

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

47.36
/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,
×
UNCOV
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

UNCOV
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

UNCOV
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(
8✔
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;
1805
    int const splitsize = SCREEN_PACKET_SIZE
8✔
1806
                        - dcs_start_size - dcs_end_size;
8✔
1807

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

1817

1818
static void
1819
sixel_advance(sixel_output_t *output, int nwrite)
25,306,473✔
1820
{
1821
    if ((output->pos += nwrite) >= SIXEL_OUTPUT_PACKET_SIZE) {
25,306,473✔
1822
        if (output->penetrate_multiplexer) {
1,930✔
1823
            sixel_penetrate(output,
4✔
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,
1,926✔
1831
                             SIXEL_OUTPUT_PACKET_SIZE, output->priv);
1832
        }
1833
        memcpy(output->buffer,
1,930✔
1834
               output->buffer + SIXEL_OUTPUT_PACKET_SIZE,
1,930✔
1835
               (size_t)(output->pos -= SIXEL_OUTPUT_PACKET_SIZE));
1,930✔
1836
    }
1837
}
25,306,473✔
1838

1839

1840
static void
1841
sixel_putc(unsigned char *buffer, unsigned char value)
5,386,220✔
1842
{
1843
    *buffer = value;
5,386,220✔
1844
}
5,386,220✔
1845

1846

1847
static void
1848
sixel_puts(unsigned char *buffer, char const *value, int size)
190,246✔
1849
{
1850
    memcpy(buffer, (void *)value, (size_t)size);
190,246✔
1851
}
190,246✔
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,
15,429,050✔
1860
                          unsigned char value,
1861
                          int count)
1862
{
1863
    int chunk;
1864

1865
    if (count <= 0) {
15,429,050!
1866
        return;
×
1867
    }
1868

1869
    while (count > 0) {
30,858,275✔
1870
        chunk = SIXEL_OUTPUT_PACKET_SIZE - output->pos;
15,429,225✔
1871
        if (chunk > count) {
15,429,225✔
1872
            chunk = count;
15,428,178✔
1873
        }
1874
        memset(output->buffer + output->pos, value, (size_t)chunk);
15,429,225✔
1875
        sixel_advance(output, chunk);
15,429,225✔
1876
        count -= chunk;
15,429,225✔
1877
    }
1878
}
1879

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

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

1896
    if (!probe_active) {
90,368!
1897
        return;
90,368✔
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
}
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,
2,760,364✔
1915
                             int width,
1916
                             int start,
1917
                             int encode_probe_active,
1918
                             double *compose_scan_probes)
1919
{
1920
    int idx;
1921
    size_t chunk_size;
1922
    unsigned char const *cursor;
1923
    unsigned long block;
1924

1925
    idx = start;
2,760,364✔
1926
    chunk_size = sizeof(unsigned long);
2,760,364✔
1927
    cursor = row + start;
2,760,364✔
1928

1929
    while ((width - idx) >= (int)chunk_size) {
77,725,350✔
1930
        memcpy(&block, cursor, chunk_size);
77,115,022✔
1931
        if (block != 0UL) {
77,115,022✔
1932
            break;
2,150,036✔
1933
        }
1934
        if (encode_probe_active) {
74,964,986!
1935
            *compose_scan_probes += (double)chunk_size;
×
1936
        }
1937
        idx += (int)chunk_size;
74,964,986✔
1938
        cursor += chunk_size;
74,964,986✔
1939
    }
1940

1941
    while (idx < width) {
12,219,284✔
1942
        if (encode_probe_active) {
11,615,446!
1943
            *compose_scan_probes += 1.0;
×
1944
        }
1945
        if (*cursor != 0) {
11,615,446✔
1946
            break;
2,156,526✔
1947
        }
1948
        idx += 1;
9,458,920✔
1949
        cursor += 1;
9,458,920✔
1950
    }
1951

1952
    return idx;
2,760,364✔
1953
}
1954

1955

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

1970
    gap = 0;
5,038,170✔
1971
    *reached_end = 0;
5,038,170✔
1972
    if (start >= width) {
5,038,170✔
1973
        *reached_end = 1;
5,114✔
1974
        return gap;
5,114✔
1975
    }
1976

1977
    chunk_size = sizeof(unsigned long);
5,033,056✔
1978
    cursor = row + start;
5,033,056✔
1979
    remaining = width - start;
5,033,056✔
1980

1981
    while (remaining >= (int)chunk_size) {
55,475,634✔
1982
        memcpy(&block, cursor, chunk_size);
54,854,710✔
1983
        if (block != 0UL) {
54,854,710✔
1984
            break;
4,412,132✔
1985
        }
1986
        gap += (int)chunk_size;
50,442,578✔
1987
        if (encode_probe_active) {
50,442,578!
1988
            *compose_scan_probes += (double)chunk_size;
×
1989
        }
1990
        cursor += chunk_size;
50,442,578✔
1991
        remaining -= (int)chunk_size;
50,442,578✔
1992
    }
1993

1994
    while (remaining > 0) {
16,038,930✔
1995
        if (encode_probe_active) {
15,440,206!
1996
            *compose_scan_probes += 1.0;
×
1997
        }
1998
        if (*cursor != 0) {
15,440,206✔
1999
            return gap;
4,434,332✔
2000
        }
2001
        gap += 1;
11,005,874✔
2002
        cursor += 1;
11,005,874✔
2003
        remaining -= 1;
11,005,874✔
2004
    }
2005

2006
    *reached_end = 1;
598,724✔
2007
    return gap;
598,724✔
2008
}
2009

2010

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

2017
    r = ldiv(value, 10);
8,804,403✔
2018
    if (r.quot > 0) {
8,804,403✔
2019
        pos = sixel_putnum_impl(buffer, r.quot, pos);
4,576,525✔
2020
    }
2021
    *(buffer + pos) = '0' + r.rem;
8,804,403✔
2022
    return pos + 1;
8,804,403✔
2023
}
2024
#endif  /* HAVE_LDIV */
2025

2026

2027
static int
2028
sixel_putnum(char *buffer, int value)
4,227,878✔
2029
{
2030
    int pos;
2031

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

2038
    return pos;
4,227,878✔
2039
}
2040

2041

2042
static SIXELSTATUS
2043
sixel_put_flash(sixel_output_t *const output)
16,776,664✔
2044
{
2045
    int nwrite;
2046

2047
    if (output->save_count <= 0) {
16,776,664!
2048
        return SIXEL_OK;
×
2049
    }
2050

2051
    if (output->has_gri_arg_limit) {  /* VT240 Max 255 ? */
16,776,664!
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) {
16,776,664✔
2063
        /* DECGRI Graphics Repeat Introducer ! Pn Ch */
2064
        sixel_putc(output->buffer + output->pos, '!');
1,347,614✔
2065
        sixel_advance(output, 1);
1,347,614✔
2066
        nwrite = sixel_putnum((char *)output->buffer + output->pos, output->save_count);
1,347,614✔
2067
        sixel_advance(output, nwrite);
1,347,614✔
2068
        sixel_putc(output->buffer + output->pos, output->save_pixel);
1,347,614✔
2069
        sixel_advance(output, 1);
1,347,614✔
2070
    } else {
2071
        sixel_output_emit_literal(output,
15,429,050✔
2072
                                  (unsigned char)output->save_pixel,
15,429,050✔
2073
                                  output->save_count);
2074
    }
2075

2076
    output->save_pixel = 0;
16,776,664✔
2077
    output->save_count = 0;
16,776,664✔
2078

2079
    return SIXEL_OK;
16,776,664✔
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)
16,776,664✔
2090
{
2091
    SIXELSTATUS status = SIXEL_FALSE;
16,776,664✔
2092

2093
    if (count <= 0) {
16,776,664!
2094
        return SIXEL_OK;
×
2095
    }
2096

2097
    if (output->save_count > 0) {
16,776,664✔
2098
        if (output->save_pixel == symbol) {
14,620,138!
2099
            output->save_count += count;
×
2100
            return SIXEL_OK;
×
2101
        }
2102

2103
        status = sixel_put_flash(output);
14,620,138✔
2104
        if (SIXEL_FAILED(status)) {
14,620,138!
2105
            return status;
×
2106
        }
2107
    }
2108

2109
    output->save_pixel = symbol;
16,776,664✔
2110
    output->save_count = count;
16,776,664✔
2111

2112
    return SIXEL_OK;
16,776,664✔
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,
2,156,526✔
2122
                         unsigned char const *map,
2123
                         int length)
2124
{
2125
    SIXELSTATUS status = SIXEL_FALSE;
2,156,526✔
2126
    int index;
2127
    int run_length;
2128
    unsigned char value;
2129
    size_t chunk_size;
2130
    unsigned long pattern;
2131
    unsigned long block;
2132
    int chunk_mismatch;
2133
    int remain;
2134
    int byte_index;
2135

2136
    if (length <= 0) {
2,156,526!
2137
        return SIXEL_OK;
×
2138
    }
2139

2140
    for (index = 0; index < length; index += run_length) {
17,779,254✔
2141
        value = map[index];
15,622,728✔
2142
        if (value > '?') {
15,622,728!
2143
            value = 0;
×
2144
        }
2145

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

2153
            while (remain >= (int)chunk_size) {
15,924,832✔
2154
                memcpy(&block,
9,019,462✔
2155
                       map + index + run_length,
9,019,462✔
2156
                       chunk_size);
2157
                block ^= pattern;
9,019,462✔
2158
                if (block != 0UL) {
9,019,462✔
2159
                    for (byte_index = 0;
8,717,358!
2160
                         byte_index < (int)chunk_size;
11,883,710!
2161
                         byte_index++) {
3,166,352✔
2162
                        if ((block & 0xffUL) != 0UL) {
11,883,710✔
2163
                            chunk_mismatch = 1;
8,717,358✔
2164
                            break;
8,717,358✔
2165
                        }
2166
                        block >>= 8;
3,166,352✔
2167
                        run_length += 1;
3,166,352✔
2168
                    }
2169
                    break;
8,717,358✔
2170
                }
2171
                run_length += (int)chunk_size;
302,104✔
2172
                remain -= (int)chunk_size;
302,104✔
2173
            }
2174
        }
2175

2176
        if (!chunk_mismatch) {
15,622,728✔
2177
            while (index + run_length < length) {
8,670,736✔
2178
                unsigned char next;
2179

2180
                next = map[index + run_length];
6,514,210✔
2181
                if (next > '?') {
6,514,210!
2182
                    next = 0;
×
2183
                }
2184
                if (next != value) {
6,514,210✔
2185
                    break;
4,748,844✔
2186
                }
2187
                run_length += 1;
1,765,366✔
2188
            }
2189
        }
2190

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

2199
    return SIXEL_OK;
2,156,526✔
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)
295,530✔
2219
{
2220
    SIXELSTATUS status = SIXEL_FALSE;
295,530✔
2221

2222
    *np = (sixel_node_t *)sixel_allocator_malloc(allocator,
295,530✔
2223
                                                 sizeof(sixel_node_t));
2224
    if (np == NULL) {
295,530!
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;
295,530✔
2232

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

2237
static void
2238
sixel_node_del(sixel_output_t *output, sixel_node_t *np)
2,156,526✔
2239
{
2240
    sixel_node_t *tp;
2241

2242
    if ((tp = output->node_top) == np) {
2,156,526✔
2243
        output->node_top = np->next;
132,728✔
2244
    } else {
2245
        while (tp->next != NULL) {
442,596,074!
2246
            if (tp->next == np) {
442,596,074✔
2247
                tp->next = np->next;
2,023,798✔
2248
                break;
2,023,798✔
2249
            }
2250
            tp = tp->next;
440,572,276✔
2251
        }
2252
    }
2253

2254
    np->next = output->node_free;
2,156,526✔
2255
    output->node_free = np;
2,156,526✔
2256
}
2,156,526✔
2257

2258

2259
static SIXELSTATUS
2260
sixel_put_node(
2,156,526✔
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;
2,156,526✔
2269
    int nwrite;
2270

2271
    if (ncolors != 2 || keycolor == (-1)) {
2,156,526✔
2272
        /* designate palette index */
2273
        if (output->active_palette != np->pal) {
2,155,906✔
2274
            sixel_putc(output->buffer + output->pos, '#');
2,122,524✔
2275
            sixel_advance(output, 1);
2,122,524✔
2276
            nwrite = sixel_putnum((char *)output->buffer + output->pos, np->pal);
2,122,524✔
2277
            sixel_advance(output, nwrite);
2,122,524✔
2278
            output->active_palette = np->pal;
2,122,524✔
2279
        }
2280
    }
2281

2282
    if (*x < np->sx) {
2,156,526✔
2283
        int span;
2284

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

2296
    if (*x < np->mx) {
2,156,526!
2297
        int span;
2298

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

2312
    status = sixel_put_flash(output);
2,156,526✔
2313
    if (SIXEL_FAILED(status)) {
2,156,526!
2314
        goto end;
×
2315
    }
2316

2317
end:
2,156,526✔
2318
    return status;
2,156,526✔
2319
}
2320

2321

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

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

2336
    output->pos = 0;
326✔
2337

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

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

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

2366
    if (pcount > 0) {
326!
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');
326✔
2384
    sixel_advance(output, 1);
326✔
2385

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

2398
    status = SIXEL_OK;
326✔
2399

2400
    return status;
326✔
2401
}
2402

2403

2404
static int
2405
sixel_palette_float_pixelformat_for_colorspace(int colorspace)
604✔
2406
{
2407
    switch (colorspace) {
604!
UNCOV
2408
    case SIXEL_COLORSPACE_LINEAR:
×
2409
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
×
UNCOV
2410
    case SIXEL_COLORSPACE_OKLAB:
×
2411
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
×
UNCOV
2412
    case SIXEL_COLORSPACE_CIELAB:
×
2413
        return SIXEL_PIXELFORMAT_CIELABFLOAT32;
×
UNCOV
2414
    case SIXEL_COLORSPACE_DIN99D:
×
2415
        return SIXEL_PIXELFORMAT_DIN99DFLOAT32;
×
2416
    default:
604✔
2417
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
604✔
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,
420,912✔
2480
                                    float const *palette_float,
2481
                                    int n,
2482
                                    int channel)
2483
{
2484
    size_t index;
2485
    float value;
2486
    int percent;
2487

2488
    index = (size_t)n * 3U + (size_t)channel;
420,912✔
2489
    if (palette_float != NULL) {
420,912!
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) {
420,912!
2506
        return (palette[index] * 100 + 127) / 255;
420,912✔
2507
    }
2508

2509
    return 0;
×
2510
}
2511

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

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

2534
    return value;
146,904✔
2535
}
2536

2537
static SIXELSTATUS
2538
output_rgb_palette_definition(
140,304✔
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;
140,304✔
2547
    int nwrite;
2548

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

2584
    status = SIXEL_OK;
140,304✔
2585

2586
    return status;
140,304✔
2587
}
2588

2589

2590
static SIXELSTATUS
2591
output_hls_palette_definition(
48,968✔
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;
48,968✔
2600
    double r;
2601
    double g;
2602
    double b;
2603
    double maxc;
2604
    double minc;
2605
    double lightness;
2606
    double saturation;
2607
    double hue;
2608
    double diff;
2609
    int h;
2610
    int l;
2611
    int s;
2612
    int nwrite;
2613

2614
    if (n != keycolor) {
48,968!
2615
        r = sixel_output_palette_channel_to_float(palette,
48,968✔
2616
                                                  palette_float,
2617
                                                  n,
2618
                                                  0);
2619
        g = sixel_output_palette_channel_to_float(palette,
48,968✔
2620
                                                  palette_float,
2621
                                                  n,
2622
                                                  1);
2623
        b = sixel_output_palette_channel_to_float(palette,
48,968✔
2624
                                                  palette_float,
2625
                                                  n,
2626
                                                  2);
2627
        maxc = r > g ? (r > b ? r : b) : (g > b ? g : b);
48,968✔
2628
        minc = r < g ? (r < b ? r : b) : (g < b ? g : b);
48,968✔
2629
        lightness = (maxc + minc) * 0.5;
48,968✔
2630
        saturation = 0.0;
48,968✔
2631
        hue = 0.0;
48,968✔
2632
        diff = maxc - minc;
48,968✔
2633
        if (diff <= 0.0) {
48,968✔
2634
            h = 0;
22✔
2635
            s = 0;
22✔
2636
        } else {
2637
            if (lightness < 0.5) {
48,946✔
2638
                saturation = diff / (maxc + minc);
39,873✔
2639
            } else {
2640
                saturation = diff / (2.0 - maxc - minc);
9,073✔
2641
            }
2642
            if (maxc == r) {
48,946✔
2643
                hue = (g - b) / diff;
29,584✔
2644
            } else if (maxc == g) {
19,362✔
2645
                hue = 2.0 + (b - r) / diff;
17,871✔
2646
            } else {
2647
                hue = 4.0 + (r - g) / diff;
1,491✔
2648
            }
2649
            hue *= 60.0;
48,946✔
2650
            if (hue < 0.0) {
48,946✔
2651
                hue += 360.0;
198✔
2652
            }
2653
            if (hue >= 360.0) {
48,946!
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;
48,946✔
2663
            while (hue >= 360.0) {
49,704✔
2664
                hue -= 360.0;
758✔
2665
            }
2666
            while (hue < 0.0) {
48,946!
2667
                hue += 360.0;
×
2668
            }
2669
            s = (int)(saturation * 100.0 + 0.5);
48,946✔
2670
            if (s < 0) {
48,946!
2671
                s = 0;
×
2672
            } else if (s > 100) {
48,946!
2673
                s = 100;
×
2674
            }
2675
            h = (int)(hue + 0.5);
48,946✔
2676
            if (h >= 360) {
48,946!
2677
                h -= 360;
×
2678
            } else if (h < 0) {
48,946!
2679
                h = 0;
×
2680
            }
2681
        }
2682
        l = (int)(lightness * 100.0 + 0.5);
48,968✔
2683
        if (l < 0) {
48,968!
2684
            l = 0;
×
2685
        } else if (l > 100) {
48,968!
2686
            l = 100;
×
2687
        }
2688
        /* DECGCI Graphics Color Introducer  # Pc ; Pu; Px; Py; Pz */
2689
        sixel_putc(output->buffer + output->pos, '#');
48,968✔
2690
        sixel_advance(output, 1);
48,968✔
2691
        nwrite = sixel_putnum((char *)output->buffer + output->pos, n);
48,968✔
2692
        sixel_advance(output, nwrite);
48,968✔
2693
        sixel_puts(output->buffer + output->pos, ";1;", 3);
48,968✔
2694
        sixel_advance(output, 3);
48,968✔
2695
        nwrite = sixel_putnum((char *)output->buffer + output->pos, h);
48,968✔
2696
        sixel_advance(output, nwrite);
48,968✔
2697
        sixel_putc(output->buffer + output->pos, ';');
48,968✔
2698
        sixel_advance(output, 1);
48,968✔
2699
        nwrite = sixel_putnum((char *)output->buffer + output->pos, l);
48,968✔
2700
        sixel_advance(output, nwrite);
48,968✔
2701
        sixel_putc(output->buffer + output->pos, ';');
48,968✔
2702
        sixel_advance(output, 1);
48,968✔
2703
        nwrite = sixel_putnum((char *)output->buffer + output->pos, s);
48,968✔
2704
        sixel_advance(output, nwrite);
48,968✔
2705
    }
2706

2707
    status = SIXEL_OK;
48,968✔
2708
    return status;
48,968✔
2709
}
2710

2711

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

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

2738
    len = ncolors * width;
944✔
2739
    work->map = (char *)sixel_allocator_calloc(allocator,
944✔
2740
                                               (size_t)len,
2741
                                               sizeof(char));
2742
    if (work->map == NULL && len > 0) {
944!
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;
944✔
2749

2750
    columns_size = sizeof(sixel_node_t *) * (size_t)width;
944✔
2751
    if (width > 0) {
944!
2752
        work->columns = (sixel_node_t **)sixel_allocator_malloc(
944✔
2753
            allocator,
2754
            columns_size);
2755
        if (work->columns == NULL) {
944!
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);
944✔
2762
        work->columns_size = columns_size;
944✔
2763
    } else {
2764
        work->columns = NULL;
×
2765
        work->columns_size = 0;
×
2766
    }
2767

2768
    active_colors_size = (size_t)ncolors;
944✔
2769
    if (active_colors_size > 0) {
944!
2770
        work->active_colors =
944✔
2771
            (unsigned char *)sixel_allocator_malloc(allocator,
944✔
2772
                                                    active_colors_size);
2773
        if (work->active_colors == NULL) {
944!
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);
944✔
2780
        work->active_colors_size = active_colors_size;
944✔
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;
944✔
2787
    if (active_color_index_size > 0) {
944!
2788
        work->active_color_index = (int *)sixel_allocator_malloc(
944✔
2789
            allocator,
2790
            active_color_index_size);
2791
        if (work->active_color_index == NULL) {
944!
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);
944✔
2798
        work->active_color_index_size = active_color_index_size;
944✔
2799
    } else {
2800
        work->active_color_index = NULL;
×
2801
        work->active_color_index_size = 0;
×
2802
    }
2803

2804
    status = SIXEL_OK;
944✔
2805

2806
end:
944✔
2807
    if (SIXEL_FAILED(status)) {
944!
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;
944✔
2831
}
2832

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

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

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

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

2879
    for (color_index = 0;
11,178✔
2880
         color_index < state->active_color_count;
634,762✔
2881
         color_index++) {
623,584✔
2882
        c = work->active_color_index[color_index];
623,584✔
2883
        if (c >= 0
623,584!
2884
            && (size_t)c < work->active_colors_size) {
623,584!
2885
            work->active_colors[c] = 0;
623,584✔
2886
        }
2887
    }
2888
    state->active_color_count = 0;
11,178✔
2889
}
2890

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

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

2914
static SIXELSTATUS
2915
sixel_encode_emit_palette(int bodyonly,
944✔
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;
944✔
2924
    double span_started_at;
2925
    int n;
2926

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

2931
    if (palette == NULL && palette_float == NULL) {
936!
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);
936✔
2938
    if (output->palette_type == SIXEL_PALETTETYPE_HLS) {
936✔
2939
        for (n = 0; n < ncolors; n++) {
49,164✔
2940
            status = output_hls_palette_definition(output,
48,968✔
2941
                                                   palette,
2942
                                                   palette_float,
2943
                                                   n,
2944
                                                   keycolor);
2945
            if (SIXEL_FAILED(status)) {
48,968!
2946
                goto end;
×
2947
            }
2948
        }
2949
    } else {
2950
        for (n = 0; n < ncolors; n++) {
141,044✔
2951
            status = output_rgb_palette_definition(output,
140,304✔
2952
                                                   palette,
2953
                                                   palette_float,
2954
                                                   n,
2955
                                                   keycolor);
2956
            if (SIXEL_FAILED(status)) {
140,304!
2957
                goto end;
×
2958
            }
2959
        }
2960
    }
2961

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

2966
    status = SIXEL_OK;
936✔
2967

2968
end:
936✔
2969
    return status;
936✔
2970
}
2971

2972
static SIXELSTATUS
2973
sixel_band_classify_row(sixel_encode_work_t *work,
66,434✔
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;
66,434✔
2984
    int row_bit;
2985
    int band_start;
2986
    int pix;
2987
    int x;
2988
    int check_integer_overflow;
2989
    char *map;
2990
    unsigned char *active_colors;
2991
    int *active_color_index;
2992

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

2999
    if (row_bit == 0) {
66,434✔
3000
        if (encode_policy != SIXEL_ENCODEPOLICY_SIZE) {
11,178✔
3001
            state->fillable = 0;
11,028✔
3002
        } else if (palstate) {
150!
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;
150✔
3015
        }
3016
        state->active_color_count = 0;
11,178✔
3017
    }
3018

3019
    for (x = 0; x < width; x++) {
40,795,050✔
3020
        if (absolute_row > INT_MAX / width) {
40,728,616!
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;
40,728,616✔
3028
        if (check_integer_overflow > INT_MAX - x) {
40,728,616!
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];
40,728,616✔
3036
        if (pix >= 0 && pix < ncolors && pix != keycolor) {
40,728,616!
3037
            if (pix > INT_MAX / width) {
37,655,426!
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;
37,655,426✔
3045
            if (check_integer_overflow > INT_MAX - x) {
37,655,426!
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);
37,655,426✔
3053
            if (active_colors != NULL && active_colors[pix] == 0) {
37,655,426!
3054
                active_colors[pix] = 1;
623,584✔
3055
                if (state->active_color_count < ncolors
623,584!
3056
                    && active_color_index != NULL) {
623,584!
3057
                    active_color_index[state->active_color_count] = pix;
623,584✔
3058
                    state->active_color_count += 1;
623,584✔
3059
                }
3060
            }
3061
        } else if (!palstate) {
3,073,190✔
3062
            state->fillable = 0;
848,390✔
3063
        }
3064
    }
3065

3066
    state->row_in_band += 1;
66,434✔
3067
    status = SIXEL_OK;
66,434✔
3068

3069
end:
66,434✔
3070
    return status;
66,434✔
3071
}
3072

3073
static SIXELSTATUS
3074
sixel_band_compose(sixel_encode_work_t *work,
11,178✔
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;
11,178✔
3084
    int color_index;
3085
    int c;
3086
    unsigned char *row;
3087
    int sx;
3088
    int mx;
3089
    int gap;
3090
    int gap_reached_end;
3091
    double now;
3092
    sixel_node_t *np;
3093
    sixel_node_t *column_head;
3094
    sixel_node_t *column_tail;
3095
    sixel_node_t top;
3096

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

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

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

3121
    for (color_index = 0;
11,178✔
3122
         color_index < state->active_color_count;
634,762✔
3123
         color_index++) {
623,584✔
3124
        c = work->active_color_index[color_index];
623,584✔
3125
        row = (unsigned char *)(work->map + c * width);
623,584✔
3126
        sx = 0;
623,584✔
3127
        while (sx < width) {
2,780,110✔
3128
            sx = sixel_compose_find_run_start(
2,760,364✔
3129
                row,
3130
                width,
3131
                sx,
3132
                metrics->encode_probe_active,
3133
                &metrics->compose_scan_probes);
3134
            if (sx >= width) {
2,760,364✔
3135
                break;
603,838✔
3136
            }
3137

3138
            if (metrics->encode_probe_active) {
2,156,526!
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;
2,156,526✔
3145
            while (mx < width) {
18,968,938✔
3146
                if (metrics->encode_probe_active) {
18,949,192!
3147
                    metrics->compose_scan_probes += 1.0;
×
3148
                }
3149
                if (row[mx] != 0) {
18,949,192✔
3150
                    mx += 1;
13,911,022✔
3151
                    continue;
13,911,022✔
3152
                }
3153

3154
                gap = sixel_compose_measure_gap(
5,038,170✔
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) {
5,038,170✔
3162
                    break;
3163
                }
3164
                mx += gap + 1;
2,901,390✔
3165
            }
3166

3167
            if ((np = output->node_free) != NULL) {
2,156,526✔
3168
                output->node_free = np->next;
1,860,996✔
3169
            } else {
3170
                status = sixel_node_new(&np, allocator);
295,530✔
3171
                if (SIXEL_FAILED(status)) {
295,530!
3172
                    goto end;
×
3173
                }
3174
            }
3175

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

3182
            if (work->columns != NULL) {
2,156,526!
3183
                column_head = work->columns[sx];
2,156,526✔
3184
                if (column_head == NULL
2,156,526✔
3185
                    || column_head->mx <= np->mx) {
721,040✔
3186
                    np->next = column_head;
1,814,044✔
3187
                    work->columns[sx] = np;
1,814,044✔
3188
                } else {
3189
                    column_tail = column_head;
342,482✔
3190
                    while (column_tail->next != NULL
342,482✔
3191
                           && column_tail->next->mx > np->mx) {
415,048✔
3192
                        column_tail = column_tail->next;
72,566✔
3193
                    }
3194
                    np->next = column_tail->next;
342,482✔
3195
                    column_tail->next = np;
342,482✔
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) {
2,156,526!
3217
                now = sixel_assessment_timer_now();
×
3218
                metrics->compose_queue_duration +=
×
3219
                    now - metrics->compose_queue_started_at;
×
3220
            }
3221

3222
            sx = mx;
2,156,526✔
3223
        }
3224
    }
3225

3226
    if (work->columns != NULL) {
11,178!
3227
        if (metrics->encode_probe_active) {
11,178!
3228
            metrics->compose_queue_started_at =
×
3229
                sixel_assessment_timer_now();
×
3230
        }
3231
        top.next = NULL;
11,178✔
3232
        column_tail = &top;
11,178✔
3233
        for (sx = 0; sx < width; sx++) {
6,812,924✔
3234
            column_head = work->columns[sx];
6,801,746✔
3235
            if (column_head == NULL) {
6,801,746✔
3236
                continue;
5,366,260✔
3237
            }
3238
            column_tail->next = column_head;
1,435,486✔
3239
            while (column_tail->next != NULL) {
3,592,012✔
3240
                column_tail = column_tail->next;
2,156,526✔
3241
            }
3242
            work->columns[sx] = NULL;
1,435,486✔
3243
        }
3244
        output->node_top = top.next;
11,178✔
3245
        if (metrics->encode_probe_active) {
11,178!
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) {
11,178!
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;
11,178✔
3289

3290
end:
11,178✔
3291
    return status;
11,178✔
3292
}
3293

3294
static SIXELSTATUS
3295
sixel_band_emit(sixel_encode_work_t *work,
11,178✔
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;
11,178✔
3304
    double span_started_at;
3305
    sixel_node_t *np;
3306
    sixel_node_t *next;
3307
    int x;
3308

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

3315
    for (x = 0; (np = output->node_top) != NULL;) {
84,382✔
3316
        if (x > np->sx) {
73,204✔
3317
            output->buffer[output->pos] = '$';
62,028✔
3318
            sixel_advance(output, 1);
62,028✔
3319
            x = 0;
62,028✔
3320
        }
3321

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

3340
        while (np != NULL) {
9,681,128✔
3341
            if (np->sx < x) {
9,607,924✔
3342
                np = np->next;
7,524,602✔
3343
                continue;
7,524,602✔
3344
            }
3345

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

3365
        state->fillable = 0;
73,204✔
3366
    }
3367

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

3372
    status = SIXEL_OK;
11,178✔
3373

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

3379

3380
static SIXELSTATUS
3381
sixel_encode_body(
944✔
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;
944✔
3396
    int encode_probe_active;
3397
    double span_started_at;
3398
    int band_start;
3399
    int band_height;
3400
    int row_index;
3401
    int absolute_row;
3402
    int last_row_index;
3403
    sixel_node_t *np;
3404
    sixel_encode_work_t work;
3405
    sixel_band_state_t band;
3406
    sixel_encode_metrics_t metrics;
3407
    int logging_active;
3408
    int job_index;
3409

3410
    sixel_encode_work_init(&work);
944✔
3411
    sixel_band_state_reset(&band);
944✔
3412
    sixel_encode_metrics_init(&metrics);
944✔
3413

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

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

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

3433
    sixel_assessment_set_encode_parallelism(1);
944✔
3434

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

3446
#if SIXEL_ENABLE_THREADS
3447
    {
3448
        int nbands;
3449
        int threads;
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
    }
3469
#endif
3470

3471
    if (logging_active) {
944!
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,
944✔
3487
                                        width,
3488
                                        ncolors,
3489
                                        allocator);
3490
    if (SIXEL_FAILED(status)) {
944!
3491
        goto cleanup;
×
3492
    }
3493

3494
    band_start = 0;
944✔
3495
    while (band_start < height) {
12,122✔
3496
        band_height = height - band_start;
11,178✔
3497
        if (band_height > 6) {
11,178✔
3498
            band_height = 6;
10,234✔
3499
        }
3500

3501
        band.row_in_band = 0;
11,178✔
3502
        band.fillable = 0;
11,178✔
3503
        band.active_color_count = 0;
11,178✔
3504

3505
        if (logging_active) {
11,178!
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++) {
77,612✔
3520
            absolute_row = band_start + row_index;
66,434✔
3521
            span_started_at =
3522
                sixel_encode_span_start(encode_probe_active);
66,434✔
3523
            status = sixel_band_classify_row(&work,
66,434✔
3524
                                             &band,
3525
                                             pixels,
3526
                                             width,
3527
                                             absolute_row,
3528
                                             ncolors,
3529
                                             keycolor,
3530
                                             palstate,
3531
                                             output->encode_policy);
3532
            if (SIXEL_FAILED(status)) {
66,434!
3533
                goto cleanup;
×
3534
            }
3535
            sixel_encode_span_commit(
66,434✔
3536
                encode_probe_active,
3537
                SIXEL_ASSESSMENT_STAGE_ENCODE_CLASSIFY,
3538
                span_started_at);
3539
        }
3540

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

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

3565
        sixel_band_finish(&work, &band);
11,178✔
3566

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

3575
        if (logging_active) {
11,178!
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;
11,178✔
3590
        sixel_band_state_reset(&band);
11,178✔
3591
        job_index += 1;
11,178✔
3592
    }
3593

3594
    status = SIXEL_OK;
944✔
3595
    goto finalize;
944✔
3596

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

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

3613
cleanup:
944✔
3614
    while ((np = output->node_free) != NULL) {
296,474✔
3615
        output->node_free = np->next;
295,530✔
3616
        sixel_allocator_free(allocator, np);
295,530✔
3617
    }
3618
    output->node_top = NULL;
944✔
3619

3620
    sixel_encode_work_cleanup(&work, allocator);
944✔
3621

3622
    return status;
944✔
3623
}
3624
static SIXELSTATUS
3625
sixel_encode_footer(sixel_output_t *output)
326✔
3626
{
3627
    SIXELSTATUS status = SIXEL_FALSE;
326✔
3628

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

3641
    if (output->pos > 0) {
326!
3642
        if (output->penetrate_multiplexer) {
326✔
3643
            sixel_penetrate(output,
4✔
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("\\"),
4✔
3650
                             (DCS_START_7BIT_SIZE + 1 +
3651
                              DCS_END_7BIT_SIZE) * 2,
3652
                             output->priv);
3653
        } else {
3654
            output->fn_write((char *)output->buffer,
322✔
3655
                             output->pos,
3656
                             output->priv);
3657
        }
3658
    }
3659

3660
    status = SIXEL_OK;
326✔
3661

3662
    return status;
326✔
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) {
×
UNCOV
3740
                case 1:
×
3741
                    pix |= (((buf_p[width * 4] >> plane) << 4) & 0x10);
×
3742
                    /* Fall through */
UNCOV
3743
                case 2:
×
3744
                    pix |= (((buf_p[width * 3] >> plane) << 3) & 0x8);
×
3745
                    /* Fall through */
UNCOV
3746
                case 3:
×
3747
                    pix |= (((buf_p[width * 2] >> plane) << 2) & 0x4);
×
3748
                    /* Fall through */
UNCOV
3749
                case 4:
×
3750
                    pix |= (((buf_p[width] >> plane) << 1) & 0x2);
×
3751
                    /* Fall through */
UNCOV
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(
302✔
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;
302✔
3781
    sixel_index_t *paletted_pixels = NULL;
302✔
3782
    sixel_index_t *input_pixels;
3783
    size_t bufsize;
3784
    unsigned char *palette_entries = NULL;
302✔
3785
    float *palette_entries_float32 = NULL;
302✔
3786
    sixel_palette_t *palette_obj = NULL;
302✔
3787
    size_t palette_count = 0U;
302✔
3788
    size_t palette_float_count = 0U;
302✔
3789
    size_t palette_bytes = 0U;
302✔
3790
    size_t palette_float_bytes = 0U;
302✔
3791
    size_t palette_channels = 0U;
302✔
3792
    size_t palette_index = 0U;
302✔
3793
    int palette_source_colorspace;
3794
    int palette_float_pixelformat;
3795
    int output_float_pixelformat;
3796
    int pipeline_active;
3797
    int pipeline_threads;
3798
    int pipeline_nbands;
3799
    sixel_parallel_dither_config_t dither_parallel;
3800
    char const *band_env_text;
3801
#if SIXEL_ENABLE_THREADS
3802
    sixel_logger_t serial_logger;
3803
    int logger_owned = 0;
302✔
3804
#endif  /* SIXEL_ENABLE_THREADS */
3805
    sixel_logger_t *logger = NULL;
302✔
3806

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

3819
    pipeline_active = 0;
302✔
3820
#if SIXEL_ENABLE_THREADS
3821
    sixel_logger_init(&serial_logger);
302✔
3822
#endif
3823
    dither_parallel.enabled = 0;
302✔
3824
    dither_parallel.band_height = 0;
302✔
3825
    dither_parallel.overlap = 0;
302✔
3826
    dither_parallel.dither_threads = 0;
302✔
3827
    dither_parallel.encode_threads = 0;
302✔
3828
    switch (dither->pixelformat) {
302!
UNCOV
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:
134✔
3854
    case SIXEL_PIXELFORMAT_G8:
3855
    case SIXEL_PIXELFORMAT_GA88:
3856
    case SIXEL_PIXELFORMAT_AG88:
3857
        input_pixels = pixels;
134✔
3858
        break;
134✔
3859
    default:
168✔
3860
        /* apply palette */
3861
        pipeline_threads = sixel_threads_resolve();
168✔
3862
        band_env_text = getenv("SIXEL_DITHER_PARALLEL_BAND_WIDTH");
168✔
3863
        if (pipeline_threads <= 1 && band_env_text != NULL
168!
UNCOV
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;
168✔
3874
        if (pipeline_threads > 1 && pipeline_nbands > 1) {
168!
3875
            pipeline_active = 1;
×
3876
            input_pixels = NULL;
×
3877
        } else {
3878
            paletted_pixels = sixel_dither_apply_palette(dither, pixels,
168✔
3879
                                                         width, height);
3880
            if (paletted_pixels == NULL) {
168!
3881
                status = SIXEL_RUNTIME_ERROR;
×
3882
                goto end;
×
3883
            }
3884
            input_pixels = paletted_pixels;
168✔
3885
        }
3886
        break;
168✔
3887
    }
3888

3889
    if (pipeline_active) {
302!
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) {
302!
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) {
302!
3950
        palette_source_colorspace = output->source_colorspace;
302✔
3951
        palette_float_pixelformat =
3952
            sixel_palette_float_pixelformat_for_colorspace(
302✔
3953
                palette_source_colorspace);
3954
    }
3955

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

3963
    if (SIXEL_SUCCEEDED(status)) {
302!
3964
        status = sixel_palette_copy_entries_float32(
302✔
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;
3973

3974
    sixel_palette_unref(palette_obj);
302✔
3975
    palette_obj = NULL;
302✔
3976
    if (palette_entries != NULL && palette_entries_float32 != NULL
302!
UNCOV
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
302!
3990
            && output != NULL
302!
3991
            && output->source_colorspace != output->colorspace) {
302!
3992
        palette_bytes = palette_count * 3U;
66✔
3993
        if (palette_entries_float32 != NULL
66!
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,
66✔
4031
                                                     palette_bytes,
4032
                                                     SIXEL_PIXELFORMAT_RGB888,
4033
                                                     output->source_colorspace,
4034
                                                     output->colorspace);
4035
            if (SIXEL_FAILED(status)) {
66!
4036
                sixel_helper_set_additional_message(
×
4037
                    "sixel_encode_dither: palette colorspace conversion failed.");
4038
                goto end;
×
4039
            }
4040
        }
4041
    }
4042
    if (SIXEL_FAILED(status) || palette_entries == NULL) {
302!
4043
        sixel_helper_set_additional_message(
×
4044
            "sixel_encode_dither: palette copy failed.");
4045
        goto end;
×
4046
    }
4047

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

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

4087
    if (SIXEL_FAILED(status)) {
302!
4088
        goto end;
×
4089
    }
4090

4091
    status = sixel_encode_footer(output);
302✔
4092
    if (SIXEL_FAILED(status)) {
302!
4093
        goto end;
×
4094
    }
4095

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

4114
    return status;
302✔
4115
}
4116

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

4124

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

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

4157

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

4166
    error_r += 4;
535,808✔
4167
    error_g += 4;
535,808✔
4168
    error_b += 4;
535,808✔
4169

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

4213

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

4222
    error_r += 4;
680,574✔
4223
    error_g += 4;
680,574✔
4224
    error_b += 4;
680,574✔
4225

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

4305

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

4314
    error_r += 4;
666,706✔
4315
    error_g += 4;
666,706✔
4316
    error_b += 4;
666,706✔
4317

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

4397

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

4406
    error_r += 2;
697,326✔
4407
    error_g += 2;
697,326✔
4408
    error_b += 2;
697,326✔
4409

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

4458

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

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

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

4495

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

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

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

4581

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

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

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

4661

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

4669
    (void) width; /* unused */
4670

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

4683

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

4691
    (void) width;  /* unused */
4692

4693
    for (c = 0; c < 3; c ++) {
106,400✔
4694
        mask = (((x + c * 17) ^ y * 236) * 1234) & 511;
79,800✔
4695
        mask = ((mask - 128) / 512.0f) ;
79,800✔
4696
        value = data[c] + mask;
79,800✔
4697
        if (value < 0) {
79,800✔
4698
            value = 0;
132✔
4699
        }
4700
        value = value > 255 ? 255 : value;
79,800!
4701
        data[c] = value;
79,800✔
4702
    }
4703
}
26,600✔
4704

4705

4706
static void
4707
sixel_apply_15bpp_dither(
5,273,896✔
4708
    unsigned char *pixels,
4709
    int x, int y, int width, int height,
4710
    int method_for_diffuse)
4711
{
4712
    /* apply floyd steinberg dithering */
4713
    switch (method_for_diffuse) {
5,273,896!
4714
    case SIXEL_DIFFUSE_FS:
2,608,192✔
4715
        if (x < width - 1 && y < height - 1) {
2,608,192✔
4716
            dither_func_fs(pixels, width);
2,597,160✔
4717
        }
4718
        break;
2,608,192✔
4719
    case SIXEL_DIFFUSE_ATKINSON:
540,000✔
4720
        if (x < width - 2 && y < height - 2) {
540,000✔
4721
            dither_func_atkinson(pixels, width);
535,808✔
4722
        }
4723
        break;
540,000✔
4724
    case SIXEL_DIFFUSE_JAJUNI:
685,496✔
4725
        if (x < width - 2 && y < height - 2) {
685,496✔
4726
            dither_func_jajuni(pixels, width);
680,574✔
4727
        }
4728
        break;
685,496✔
4729
    case SIXEL_DIFFUSE_STUCKI:
671,526✔
4730
        if (x < width - 2 && y < height - 2) {
671,526✔
4731
            dither_func_stucki(pixels, width);
666,706✔
4732
        }
4733
        break;
671,526✔
4734
    case SIXEL_DIFFUSE_BURKES:
701,082✔
4735
        if (x < width - 2 && y < height - 1) {
701,082✔
4736
            dither_func_burkes(pixels, width);
697,326✔
4737
        }
4738
        break;
701,082✔
UNCOV
4739
    case SIXEL_DIFFUSE_SIERRA1:
×
4740
        if (x < width - 1 && y < height - 1) {
×
4741
            dither_func_sierra1(pixels, width);
×
4742
        }
4743
        break;
×
UNCOV
4744
    case SIXEL_DIFFUSE_SIERRA2:
×
4745
        if (x < width - 2 && y < height - 2) {
×
4746
            dither_func_sierra2(pixels, width);
×
4747
        }
4748
        break;
×
UNCOV
4749
    case SIXEL_DIFFUSE_SIERRA3:
×
4750
        if (x < width - 2 && y < height - 2) {
×
4751
            dither_func_sierra3(pixels, width);
×
4752
        }
4753
        break;
×
4754
    case SIXEL_DIFFUSE_A_DITHER:
15,000✔
4755
        dither_func_a_dither(pixels, width, x, y);
15,000✔
4756
        break;
15,000✔
4757
    case SIXEL_DIFFUSE_X_DITHER:
26,600✔
4758
        dither_func_x_dither(pixels, width, x, y);
26,600✔
4759
        break;
26,600✔
4760
    case SIXEL_DIFFUSE_NONE:
26,000✔
4761
    default:
4762
        dither_func_none(pixels, width);
26,000✔
4763
        break;
26,000✔
4764
    }
4765
}
5,273,896✔
4766

4767

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

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

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

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

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

4860
    while (1) {
4861
        for (x = 0; x < width; x++, mptr++, dst++, pixels += 3) {
7,072,318✔
4862
            if (*mptr) {
7,060,400✔
4863
                *dst = 255;
1,786,504✔
4864
            } else {
4865
                sixel_apply_15bpp_dither(pixels,
5,273,896✔
4866
                                         x, y, width, height,
4867
                                         dither->method_for_diffuse);
4868
                pix = ((pixels[0] & 0xf8) << 7) |
5,273,896✔
4869
                      ((pixels[1] & 0xf8) << 2) |
5,273,896✔
4870
                      ((pixels[2] >> 3) & 0x1f);
5,273,896✔
4871

4872
                if (!rgbhit[pix]) {
5,273,896✔
4873
                    while (1) {
4874
                        if (nextpal >= 255) {
915,040✔
4875
                            if (threshold >= 255) {
439,538✔
4876
                                break;
438,296✔
4877
                            } else {
4878
                                threshold = (threshold == 1) ? 9: 255;
1,242✔
4879
                                nextpal = 0;
1,242✔
4880
                            }
4881
                        } else if (palstate[nextpal] ||
475,502✔
4882
                                 palhitcount[nextpal] > threshold) {
260,150✔
4883
                            nextpal++;
336,952✔
4884
                        } else {
4885
                            break;
4886
                        }
4887
                    }
4888

4889
                    if (nextpal >= 255) {
576,846✔
4890
                        dirty = 1;
438,296✔
4891
                        *dst = 255;
438,296✔
4892
                    } else {
4893
                        pal = palette_entries + (nextpal * 3);
138,550✔
4894

4895
                        rgbhit[pix] = 1;
138,550✔
4896
                        if (output_count > 0) {
138,550✔
4897
                            rgbhit[((pal[0] & 0xf8) << 7) |
136,302✔
4898
                                   ((pal[1] & 0xf8) << 2) |
136,302✔
4899
                                   ((pal[2] >> 3) & 0x1f)] = 0;
136,302✔
4900
                        }
4901
                        *dst = rgb2pal[pix] = nextpal++;
138,550✔
4902
                        *mptr = 1;
138,550✔
4903
                        palstate[*dst] = PALETTE_CHANGE;
138,550✔
4904
                        palhitcount[*dst] = 1;
138,550✔
4905
                        *(pal++) = pixels[0];
138,550✔
4906
                        *(pal++) = pixels[1];
138,550✔
4907
                        *(pal++) = pixels[2];
138,550✔
4908
                    }
4909
                } else {
4910
                    *dst = rgb2pal[pix];
4,697,050✔
4911
                    *mptr = 1;
4,697,050✔
4912
                    if (!palstate[*dst]) {
4,697,050✔
4913
                        palstate[*dst] = PALETTE_HIT;
20,222✔
4914
                    }
4915
                    if (palhitcount[*dst] < 255) {
4,697,050✔
4916
                        palhitcount[*dst]++;
1,122,784✔
4917
                    }
4918
                }
4919
            }
4920
        }
4921

4922
        if (++y >= height) {
11,918✔
4923
            if (dirty) {
24!
UNCOV
4924
                mod_y = 5;
×
4925
            } else {
4926
                goto end;
24✔
4927
            }
4928
        }
4929
        if (dirty && (mod_y == 5 || y >= height)) {
11,894!
4930
            orig_height = height;
618✔
4931

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

4962
        if (++mod_y == 6) {
11,276✔
4963
            mptr = (unsigned char *)memset(marks, 0, (size_t)(width * 6));
1,348✔
4964
            mod_y = 0;
1,348✔
4965
        }
4966
    }
4967

4968
    goto next;
4969

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

4993
    status = sixel_encode_footer(output);
24✔
4994
    if (SIXEL_FAILED(status)) {
24!
4995
        goto error;
×
4996
    }
4997

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

5005
    return status;
24✔
5006
}
5007

5008

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

5020
    (void) depth;
5021

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

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

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

5042
    if (dither->quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
326✔
5043
        status = sixel_encode_highcolor(pixels, width, height,
24✔
5044
                                        dither, output);
5045
    } else {
5046
        status = sixel_encode_dither(pixels, width, height,
302✔
5047
                                     dither, output);
5048
    }
5049

5050
end:
326✔
5051
    sixel_output_unref(output);
326✔
5052
    sixel_dither_unref(dither);
326✔
5053

5054
    return status;
326✔
5055
}
5056

5057
#if HAVE_TESTS
5058

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

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

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

5075
    return size;
×
5076
}
5077

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5235
    return EXIT_SUCCESS;
×
5236
}
5237

5238
#endif  /* HAVE_TESTS */
5239

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