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

saitoha / libsixel / 22281294763

22 Feb 2026 04:53PM UTC coverage: 81.971% (-1.7%) from 83.691%
22281294763

push

github

saitoha
tests: enforce SIXEL_TEST_PYTHON contract for python tap runs

27660 of 54661 branches covered (50.6%)

45511 of 55521 relevant lines covered (81.97%)

2582260.61 hits per line

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

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

25
#if defined(HAVE_CONFIG_H)
26
#include "config.h"
27
#endif
28

29
#if HAVE_MATH_H
30
# include <math.h>
31
#endif  /* HAVE_MATH_H */
32
#include <ctype.h>
33
#include <limits.h>
34
#include <stdlib.h>
35
#include <string.h>
36

37
#include "compat_stub.h"
38
#include "dither-positional-float32.h"
39
#include "dither-common-pipeline.h"
40
#include "pixelformat.h"
41
#include "lookup-common.h"
42
#include "bluenoise_64x64.h"
43

44
#if SIXEL_ENABLE_THREADS
45
# if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__MSYS__) && \
46
        !defined(WITH_WINPTHREAD)
47
#  define SIXEL_POS_FLOAT32_USE_WIN32_ONCE 1
48
#  include <windows.h>
49
static INIT_ONCE g_sixel_pos_strength_once_float32 = INIT_ONCE_STATIC_INIT;
50
static INIT_ONCE g_sixel_bn_conf_once_float32 = INIT_ONCE_STATIC_INIT;
51
# else
52
#  include <pthread.h>
53
static pthread_once_t g_sixel_pos_strength_once_float32 = PTHREAD_ONCE_INIT;
54
static pthread_once_t g_sixel_bn_conf_once_float32 = PTHREAD_ONCE_INIT;
55
# endif
56
#endif
57

58
static void
59
sixel_dither_scanline_params_positional_float32(int serpentine,
21,449✔
60
                             int index,
61
                             int limit,
62
                             int *start,
63
                             int *end,
64
                             int *step,
65
                             int *direction)
66
{
67
    if (serpentine && (index & 1)) {
8,636!
68
        *start = limit - 1;
734✔
69
        *end = -1;
734✔
70
        *step = -1;
734✔
71
        *direction = -1;
734✔
72
    } else {
256✔
73
        *start = 0;
7,424✔
74
        *end = limit;
7,424✔
75
        *step = 1;
7,424✔
76
        *direction = 1;
21,193✔
77
    }
78
}
7,680✔
79

80
/*
81
 * Cache SIXEL_DITHER_*_DITHER_STRENGTH for positional arithmetic dithers to
82
 * avoid getenv() in inner loops. Invalid values fall back to defaults.
83
 */
84
static float g_sixel_pos_strength_a_float32 = 0.150f;
85
static float g_sixel_pos_strength_x_float32 = 0.100f;
86
static int g_sixel_pos_inited_float32 = 0;
87

88
static void sixel_positional_strength_init_float32(void);
89
static float positional_mask_blue_float32(int x, int y, int c);
90
static void sixel_bluenoise_conf_init_from_env_float32(void);
91

92
static float
93
positional_mask_a_float32(int x, int y, int c)
1,086,493✔
94
{
95
    return (((((x + c * 67) + y * 236) * 119) & 255) / 128.0f
1,479,709✔
96
            - 1.0f) * g_sixel_pos_strength_a_float32;
1,086,493✔
97
}
98

99
static float
100
positional_mask_x_float32(int x, int y, int c)
1,653,693✔
101
{
102
    return (((((x + c * 29) ^ (y * 149)) * 1234) & 511) / 256.0f
2,243,517✔
103
            - 1.0f) * g_sixel_pos_strength_x_float32;
1,653,693✔
104
}
105

106
/*
107
 * Keep per-file suffixes so unity builds do not merge identical static
108
 * helper symbols from other positional dither sources.
109
 */
110
typedef struct {
111
    float strength;
112
    int ox;
113
    int oy;
114
    int per_channel;
115
    int size;
116
} sixel_bluenoise_conf_float32_t;
117

118
static sixel_bluenoise_conf_float32_t g_sixel_bn_conf_float32;
119
static int g_sixel_bn_inited_float32 = 0;
120

121
static int
122
sixel_bn_parse_int_float32(char const *text, int *out_value)
22✔
123
{
124
    char *endptr;
22✔
125
    long value;
22✔
126

127
    if (text == NULL || text[0] == '\0') {
22!
128
        return 0;
129
    }
130

131
    value = strtol(text, &endptr, 10);
22✔
132
    if (endptr == text || *endptr != '\0') {
22!
133
        return 0;
134
    }
135
    if (value > INT_MAX || value < INT_MIN) {
22!
136
        return 0;
137
    }
138

139
    *out_value = (int)value;
22✔
140
    return 1;
22✔
141
}
8✔
142

143
static int
144
sixel_bn_parse_float_float32(char const *text, float *out_value)
22✔
145
{
146
    char *endptr;
22✔
147
    double value;
22✔
148

149
    if (text == NULL || text[0] == '\0') {
22!
150
        return 0;
151
    }
152

153
    value = strtod(text, &endptr);
22✔
154
    if (endptr == text || *endptr != '\0') {
22!
155
        return 0;
8✔
156
    }
157

158
    *out_value = (float)value;
×
159
    return 1;
×
160
}
8✔
161

162
static int
163
sixel_bn_parse_phase_float32(char const *text, int *out_ox, int *out_oy)
11✔
164
{
165
    char *endptr;
11✔
166
    char const *comma;
11✔
167
    long ox;
11✔
168
    long oy;
11✔
169

170
    if (text == NULL || text[0] == '\0') {
11!
171
        return 0;
172
    }
173

174
    comma = strchr(text, ',');
11✔
175
    if (comma == NULL) {
11!
176
        return 0;
4✔
177
    }
178

179
    ox = strtol(text, &endptr, 10);
×
180
    if (endptr == text || endptr != comma) {
×
181
        return 0;
182
    }
183

184
    oy = strtol(comma + 1, &endptr, 10);
×
185
    if (endptr == comma + 1 || *endptr != '\0') {
×
186
        return 0;
187
    }
188
    if (ox > INT_MAX || ox < INT_MIN || oy > INT_MAX || oy < INT_MIN) {
×
189
        return 0;
190
    }
191

192
    *out_ox = (int)ox;
×
193
    *out_oy = (int)oy;
×
194
    return 1;
×
195
}
4✔
196

197
static void
198
sixel_positional_strength_init_body_float32(void)
220✔
199
{
200
    char const *text;
220✔
201
    float strength_a;
220✔
202
    float strength_x;
220✔
203
    int parsed;
220✔
204

205
    /*
206
     * Default strengths are per-dither values. Environment overrides use
207
     * the same parser for consistency and fall back to defaults on error.
208
     */
209
    strength_a = 0.150f;
220✔
210
    text = sixel_compat_getenv("SIXEL_DITHER_A_DITHER_STRENGTH");
220✔
211
    if (text != NULL) {
220✔
212
        parsed = sixel_bn_parse_float_float32(text, &strength_a);
11✔
213
        if (parsed == 0) {
11!
214
            strength_a = 0.150f;
11✔
215
        }
4✔
216
    }
4✔
217

218
    strength_x = 0.100f;
220✔
219
    text = sixel_compat_getenv("SIXEL_DITHER_X_DITHER_STRENGTH");
220✔
220
    if (text != NULL) {
220✔
221
        parsed = sixel_bn_parse_float_float32(text, &strength_x);
11✔
222
        if (parsed == 0) {
11!
223
            strength_x = 0.100f;
11✔
224
        }
4✔
225
    }
4✔
226

227
    g_sixel_pos_strength_a_float32 = strength_a;
220✔
228
    g_sixel_pos_strength_x_float32 = strength_x;
220✔
229
    g_sixel_pos_inited_float32 = 1;
220✔
230
}
220✔
231

232
#if SIXEL_ENABLE_THREADS && defined(SIXEL_POS_FLOAT32_USE_WIN32_ONCE)
233
static BOOL CALLBACK
234
sixel_positional_strength_once_cb_float32(PINIT_ONCE init_once,
60✔
235
                                          PVOID parameter,
236
                                          PVOID *context)
237
{
238
    (void)init_once;
60✔
239
    (void)parameter;
60✔
240
    (void)context;
60✔
241
    sixel_positional_strength_init_body_float32();
60✔
242
    return TRUE;
60✔
243
}
244
#endif
245

246
static void
247
sixel_positional_strength_init_float32(void)
320✔
248
{
249
#if SIXEL_ENABLE_THREADS
250
# if defined(SIXEL_POS_FLOAT32_USE_WIN32_ONCE)
251
    BOOL executed;
120✔
252

253
    executed = InitOnceExecuteOnce(&g_sixel_pos_strength_once_float32,
120✔
254
                                   sixel_positional_strength_once_cb_float32,
255
                                   NULL,
256
                                   NULL);
257
    if (executed == FALSE) {
120✔
258
        sixel_positional_strength_init_body_float32();
259
    }
260
# else
261
    int status;
160✔
262

263
    status = pthread_once(&g_sixel_pos_strength_once_float32,
160✔
264
                          sixel_positional_strength_init_body_float32);
265
    if (status != 0) {
160!
266
        sixel_positional_strength_init_body_float32();
267
    }
268
# endif
269
#else
270
    if (g_sixel_pos_inited_float32 == 0) {
40✔
271
        sixel_positional_strength_init_body_float32();
40✔
272
    }
273
#endif
274
}
280✔
275

276
static unsigned int
277
sixel_bn_hash32_float32(unsigned int value)
11✔
278
{
279
    value += 0x9e3779b9U;
11✔
280
    value ^= value >> 16;
11✔
281
    value *= 0x85ebca6bU;
11✔
282
    value ^= value >> 13;
11✔
283
    value *= 0xc2b2ae35U;
11✔
284
    value ^= value >> 16;
11✔
285
    return value;
11✔
286
}
287

288
static int
289
sixel_bn_str_equal_nocase_float32(char const *left, char const *right)
11✔
290
{
291
    unsigned char lc;
11✔
292
    unsigned char rc;
11✔
293

294
    if (left == NULL || right == NULL) {
11!
295
        return 0;
296
    }
297

298
    while (*left != '\0' && *right != '\0') {
44!
299
        lc = (unsigned char)tolower((unsigned char)*left);
33✔
300
        rc = (unsigned char)tolower((unsigned char)*right);
33✔
301
        if (lc != rc) {
33!
302
            return 0;
303
        }
304
        ++left;
33✔
305
        ++right;
33✔
306
    }
307

308
    return (*left == '\0' && *right == '\0');
11!
309
}
4✔
310

311
/*
312
 * Cache bluenoise configuration at first use so we do not hit getenv()
313
 * inside pixel loops. Invalid values fall back to defaults.
314
 */
315
static void
316
sixel_bluenoise_conf_init_from_env_body_float32(void)
110✔
317
{
318
    char const *text;
110✔
319
    float strength;
110✔
320
    int size;
110✔
321
    int ox;
110✔
322
    int oy;
110✔
323
    int seed;
110✔
324
    int phase_set;
110✔
325
    int parsed;
110✔
326
    int per_channel;
110✔
327
    unsigned int hash;
110✔
328

329
    strength = 0.055f;
110✔
330
    text = sixel_compat_getenv("SIXEL_DITHER_BLUENOISE_STRENGTH");
110✔
331
    if (text != NULL) {
110!
332
        parsed = sixel_bn_parse_float_float32(text, &strength);
×
333
        if (parsed == 0) {
×
334
            strength = 0.055f;
×
335
        }
336
    }
337

338
    ox = 0;
110✔
339
    oy = 0;
110✔
340
    phase_set = 0;
110✔
341
    text = sixel_compat_getenv("SIXEL_DITHER_BLUENOISE_PHASE");
110✔
342
    if (text != NULL) {
110✔
343
        phase_set = 1;
11✔
344
        parsed = sixel_bn_parse_phase_float32(text, &ox, &oy);
11✔
345
        if (parsed == 0) {
11!
346
            ox = 0;
11✔
347
            oy = 0;
11✔
348
        }
4✔
349
    }
4✔
350
    if (phase_set == 0) {
47✔
351
        text = sixel_compat_getenv("SIXEL_DITHER_BLUENOISE_SEED");
99✔
352
        if (text != NULL) {
99✔
353
            parsed = sixel_bn_parse_int_float32(text, &seed);
11✔
354
            if (parsed != 0) {
11!
355
                hash = sixel_bn_hash32_float32((unsigned int)seed);
11✔
356
                ox = (int)(hash & 63U);
11✔
357
                oy = (int)((hash >> 8) & 63U);
11✔
358
            }
4✔
359
        }
4✔
360
    }
36✔
361

362
    per_channel = 0;
110✔
363
    text = sixel_compat_getenv("SIXEL_DITHER_BLUENOISE_CHANNEL");
110✔
364
    if (text != NULL) {
110✔
365
        if (sixel_bn_str_equal_nocase_float32(text, "rgb") != 0) {
11!
366
            per_channel = 1;
4✔
367
        } else if (sixel_bn_str_equal_nocase_float32(text, "mono") != 0) {
4!
368
            per_channel = 0;
×
369
        }
370
    }
4✔
371

372
    size = SIXEL_BN_W;
110✔
373
    text = sixel_compat_getenv("SIXEL_DITHER_BLUENOISE_SIZE");
110✔
374
    if (text != NULL) {
110✔
375
        parsed = sixel_bn_parse_int_float32(text, &size);
11✔
376
        if (parsed == 0 || size != SIXEL_BN_W) {
11!
377
            size = SIXEL_BN_W;
11✔
378
        }
4✔
379
    }
4✔
380

381
    g_sixel_bn_conf_float32.strength = strength;
110✔
382
    g_sixel_bn_conf_float32.ox = ox;
110✔
383
    g_sixel_bn_conf_float32.oy = oy;
110✔
384
    g_sixel_bn_conf_float32.per_channel = per_channel;
110✔
385
    g_sixel_bn_conf_float32.size = size;
110✔
386
    g_sixel_bn_inited_float32 = 1;
110✔
387
}
110✔
388

389
#if SIXEL_ENABLE_THREADS && defined(SIXEL_POS_FLOAT32_USE_WIN32_ONCE)
390
static BOOL CALLBACK
391
sixel_bluenoise_conf_once_cb_float32(PINIT_ONCE init_once,
30✔
392
                                     PVOID parameter,
393
                                     PVOID *context)
394
{
395
    (void)init_once;
30✔
396
    (void)parameter;
30✔
397
    (void)context;
30✔
398
    sixel_bluenoise_conf_init_from_env_body_float32();
30✔
399
    return TRUE;
30✔
400
}
401
#endif
402

403
static void
404
sixel_bluenoise_conf_init_from_env_float32(void)
160✔
405
{
406
#if SIXEL_ENABLE_THREADS
407
# if defined(SIXEL_POS_FLOAT32_USE_WIN32_ONCE)
408
    BOOL executed;
60✔
409

410
    executed = InitOnceExecuteOnce(&g_sixel_bn_conf_once_float32,
60✔
411
                                   sixel_bluenoise_conf_once_cb_float32,
412
                                   NULL,
413
                                   NULL);
414
    if (executed == FALSE) {
60✔
415
        sixel_bluenoise_conf_init_from_env_body_float32();
416
    }
417
# else
418
    int status;
80✔
419

420
    status = pthread_once(&g_sixel_bn_conf_once_float32,
80✔
421
                          sixel_bluenoise_conf_init_from_env_body_float32);
422
    if (status != 0) {
80!
423
        sixel_bluenoise_conf_init_from_env_body_float32();
424
    }
425
# endif
426
#else
427
    if (g_sixel_bn_inited_float32 == 0) {
20✔
428
        sixel_bluenoise_conf_init_from_env_body_float32();
20✔
429
    }
430
#endif
431
}
140✔
432

433
static float
434
sixel_bluenoise_tri_float32(int x, int y, int c)
1,369,301✔
435
{
436
    /* Triangular noise blends two samples from the same tile. */
437
    static int const channel_offset_x[3] = { 17, 34, 51 };
877,781✔
438
    static int const channel_offset_y[3] = { 31, 62, 93 };
877,781✔
439
    int ox;
1,369,301✔
440
    int oy;
1,369,301✔
441
    int per_channel;
1,369,301✔
442
    int channel_x;
1,369,301✔
443
    int channel_y;
1,369,301✔
444
    int ix0;
1,369,301✔
445
    int iy0;
1,369,301✔
446
    int ix1;
1,369,301✔
447
    int iy1;
1,369,301✔
448
    float u;
1,369,301✔
449
    float v;
1,369,301✔
450

451
    ox = g_sixel_bn_conf_float32.ox;
1,369,301✔
452
    oy = g_sixel_bn_conf_float32.oy;
1,369,301✔
453
    per_channel = g_sixel_bn_conf_float32.per_channel;
1,369,301✔
454
    channel_x = 0;
1,369,301✔
455
    channel_y = 0;
1,369,301✔
456
    if (per_channel != 0 && c >= 0 && c < 3) {
1,369,301!
457
        channel_x = channel_offset_x[c];
139,184✔
458
        channel_y = channel_offset_y[c];
139,184✔
459
    }
49,152✔
460

461
    ix0 = x + ox + channel_x;
1,369,301✔
462
    iy0 = y + oy + channel_y;
1,369,301✔
463
    ix1 = ix0 + 13;
1,369,301✔
464
    iy1 = iy0 + 29;
1,369,301✔
465
    u = (sixel_bn_mask(ix0, iy0) + 1.0f) * 0.5f;
1,369,301✔
466
    v = (sixel_bn_mask(ix1, iy1) + 1.0f) * 0.5f;
1,369,301✔
467

468
    return (u + v) - 1.0f;
1,860,821✔
469
}
491,520✔
470

471
static float
472
positional_mask_blue_float32(int x, int y, int c)
1,369,178✔
473
{
474
    return sixel_bluenoise_tri_float32(x, y, c)
1,860,698✔
475
        * g_sixel_bn_conf_float32.strength;
1,369,178✔
476
}
477

478
SIXELSTATUS
479
sixel_dither_apply_positional_float32(sixel_dither_t *dither,
480✔
480
                                      sixel_dither_context_t *context)
481
{
482
    int serpentine;
480✔
483
    int y;
480✔
484
    int absolute_y;
480✔
485
    float (*f_mask)(int x, int y, int c);
480✔
486
    float jitter_scale;
480✔
487
    float *palette_float;
480✔
488
    float *new_palette_float;
480✔
489
    int float_depth;
480✔
490
    int float_index;
480✔
491
    unsigned char *quantized;
480✔
492
    float lookup_pixel_float[SIXEL_MAX_CHANNELS];
480✔
493
    unsigned char const *lookup_pixel;
480✔
494
    sixel_lut_t *fast_lut;
480✔
495
    int use_fast_lut;
480✔
496
    int lookup_wants_float;
480✔
497
    int use_palette_float_lookup;
480✔
498
    int need_float_pixel;
480✔
499

500
    palette_float = NULL;
480✔
501
    new_palette_float = NULL;
480✔
502
    float_depth = 0;
480✔
503
    quantized = NULL;
480✔
504
    lookup_wants_float = 0;
480✔
505

506
    if (dither == NULL || context == NULL) {
480!
507
        return SIXEL_BAD_ARGUMENT;
508
    }
509
    if (context->pixels_float == NULL || context->scratch == NULL) {
480!
510
        return SIXEL_BAD_ARGUMENT;
511
    }
512
    if (context->palette == NULL || context->result == NULL) {
480!
513
        return SIXEL_BAD_ARGUMENT;
514
    }
515

516
    switch (context->method_for_diffuse) {
480!
517
    case SIXEL_DIFFUSE_A_DITHER:
80✔
518
        sixel_positional_strength_init_float32();
128!
519
        f_mask = positional_mask_a_float32;
112✔
520
        break;
112✔
521
    case SIXEL_DIFFUSE_X_DITHER:
120✔
522
        sixel_positional_strength_init_float32();
192!
523
        f_mask = positional_mask_x_float32;
168✔
524
        break;
168✔
525
    case SIXEL_DIFFUSE_BLUENOISE_DITHER:
100✔
526
        sixel_bluenoise_conf_init_from_env_float32();
160!
527
        f_mask = positional_mask_blue_float32;
140✔
528
        break;
140✔
529
    default:
530
        sixel_positional_strength_init_float32();
×
531
        f_mask = positional_mask_x_float32;
532
        break;
533
    }
534

535
    serpentine = (context->method_for_scan == SIXEL_SCAN_SERPENTINE);
480✔
536
    jitter_scale = 32.0f / 255.0f;
480✔
537
    palette_float = context->palette_float;
480✔
538
    new_palette_float = context->new_palette_float;
480✔
539
    float_depth = context->float_depth;
480✔
540
    quantized = context->scratch;
480✔
541
    fast_lut = context->lut;
480✔
542
    use_fast_lut = (fast_lut != NULL);
480✔
543
    lookup_wants_float = (context->lookup_source_is_float != 0);
480✔
544
    use_palette_float_lookup = 0;
480✔
545
    if (context->prefer_palette_float_lookup != 0
480!
546
            && palette_float != NULL
120!
547
            && float_depth >= context->depth) {
×
548
        use_palette_float_lookup = 1;
360✔
549
    }
550
    need_float_pixel = lookup_wants_float || use_palette_float_lookup;
480✔
551

552
    if (context->optimize_palette) {
480!
553
        int x;
58✔
554

555
        *context->ncolors = 0;
58✔
556
        memset(context->new_palette, 0x00,
58✔
557
               (size_t)SIXEL_PALETTE_MAX * (size_t)context->depth);
58!
558
        if (new_palette_float != NULL && float_depth > 0) {
58!
559
            memset(new_palette_float, 0x00,
×
560
                   (size_t)SIXEL_PALETTE_MAX
561
                       * (size_t)float_depth * sizeof(float));
562
        }
563
        memset(context->migration_map, 0x00,
58✔
564
               sizeof(unsigned short) * (size_t)SIXEL_PALETTE_MAX);
565
        for (y = 0; y < context->height; ++y) {
3,770!
566
            absolute_y = context->band_origin + y;
3,712✔
567
            int start;
3,712✔
568
            int end;
3,712✔
569
            int step;
3,712✔
570
            int direction;
3,712✔
571

572
            sixel_dither_scanline_params_positional_float32(serpentine, absolute_y,
3,712!
573
                                         context->width,
574
                                         &start, &end, &step, &direction);
575
            (void)direction;
3,712✔
576
            for (x = start; x != end; x += step) {
241,280!
577
                int pos;
237,568✔
578
                int d;
237,568✔
579
                int color_index;
237,568✔
580

581
                pos = y * context->width + x;
237,568✔
582
                for (d = 0; d < context->depth; ++d) {
950,272!
583
                    float val;
712,704✔
584

585
                    val = context->pixels_float[pos * context->depth + d]
1,425,408✔
586
                        + f_mask(x, y, d) * jitter_scale;
712,704✔
587
                    val = sixel_pixelformat_float_channel_clamp(
712,704✔
588
                        context->pixelformat,
589
                        d,
590
                        val);
591
                    if (need_float_pixel) {
712,704!
592
                        lookup_pixel_float[d] = val;
688,128✔
593
                    }
594
                    if (!lookup_wants_float && !use_palette_float_lookup) {
688,128!
595
                        quantized[d]
24,576✔
596
                            = sixel_pixelformat_float_channel_to_byte(
24,576✔
597
                                  context->pixelformat,
598
                                  d,
599
                                  val);
600
                    }
601
                }
602
                if (lookup_wants_float) {
237,568!
603
                    lookup_pixel = (unsigned char const *)(void const *)
229,376✔
604
                        lookup_pixel_float;
605
                    if (use_fast_lut) {
229,376!
606
                        color_index = sixel_lut_map_pixel(fast_lut,
229,376✔
607
                                                         lookup_pixel);
608
                    } else {
609
                        color_index = context->lookup(lookup_pixel,
×
610
                                                      context->depth,
611
                                                      context->palette,
×
612
                                                      context->reqcolor,
613
                                                      context->indextable,
614
                                                      context->complexion);
615
                    }
616
                } else if (use_palette_float_lookup) {
8,192!
617
                    color_index = sixel_dither_lookup_palette_float32(
×
618
                        lookup_pixel_float,
619
                        context->depth,
620
                        palette_float,
621
                        context->reqcolor,
622
                        context->complexion);
623
                } else {
624
                    lookup_pixel = quantized;
8,192✔
625
                    if (use_fast_lut) {
8,192!
626
                        color_index = sixel_lut_map_pixel(fast_lut,
×
627
                                                         lookup_pixel);
628
                    } else {
629
                        color_index = context->lookup(lookup_pixel,
8,192✔
630
                                                      context->depth,
631
                                                      context->palette,
8,192✔
632
                                                      context->reqcolor,
633
                                                      context->indextable,
634
                                                      context->complexion);
635
                    }
636
                }
637
                if (context->migration_map[color_index] == 0) {
237,568!
638
                    if (absolute_y >= context->output_start) {
9,491!
639
                        /*
640
                         * Palette indices never exceed SIXEL_PALETTE_MAX, so
641
                         * the cast to sixel_index_t (unsigned char) is safe.
642
                         */
643
                        context->result[pos]
9,491✔
644
                            = (sixel_index_t)(*context->ncolors);
9,491✔
645
                    }
646
                    for (d = 0; d < context->depth; ++d) {
37,964!
647
                        context->new_palette[*context->ncolors
28,473✔
648
                                             * context->depth + d]
28,473✔
649
                            = context->palette[color_index
28,473✔
650
                                               * context->depth + d];
28,473✔
651
                    }
652
                    if (palette_float != NULL
9,491!
653
                            && new_palette_float != NULL
9,491!
654
                            && float_depth > 0) {
×
655
                        for (float_index = 0;
×
656
                                float_index < float_depth;
×
657
                                ++float_index) {
×
658
                            new_palette_float[*context->ncolors
×
659
                                               * float_depth
×
660
                                               + float_index]
×
661
                                = palette_float[color_index * float_depth
×
662
                                                + float_index];
×
663
                        }
664
                    }
665
                    ++*context->ncolors;
9,491✔
666
                    /*
667
                     * Migration map entries are limited to the palette size
668
                     * (<= 256), so storing them as unsigned short is safe.
669
                     */
670
                    context->migration_map[color_index]
9,491✔
671
                        = (unsigned short)(*context->ncolors);
9,491✔
672
                } else {
673
                    if (absolute_y >= context->output_start) {
228,077!
674
                        context->result[pos]
228,077✔
675
                            = (sixel_index_t)(context->migration_map[
228,077✔
676
                                  color_index] - 1);
677
                    }
678
                }
679
            }
680
            if (absolute_y >= context->output_start) {
3,712!
681
                sixel_dither_pipeline_row_notify(dither, absolute_y);
3,712✔
682
            }
683
        }
684
        memcpy(context->palette, context->new_palette,
58✔
685
               (size_t)(*context->ncolors * context->depth));
58!
686
        if (palette_float != NULL
58!
687
                && new_palette_float != NULL
58!
688
                && float_depth > 0) {
×
689
            memcpy(palette_float,
×
690
                   new_palette_float,
691
                   (size_t)(*context->ncolors * float_depth)
692
                       * sizeof(float));
693
        }
694
    } else {
695
        int x;
120✔
696

697
        for (y = 0; y < context->height; ++y) {
18,159✔
698
            absolute_y = context->band_origin + y;
17,737✔
699
            int start;
17,737✔
700
            int end;
17,737✔
701
            int step;
17,737✔
702
            int direction;
17,737✔
703

704
            sixel_dither_scanline_params_positional_float32(serpentine, absolute_y,
25,417!
705
                                         context->width,
7,680✔
706
                                         &start, &end, &step, &direction);
707
            (void)direction;
17,737✔
708
            for (x = start; x != end; x += step) {
1,151,657✔
709
                int pos;
1,133,921✔
710
                int d;
1,133,921✔
711

712
                pos = y * context->width + x;
1,133,921✔
713
                for (d = 0; d < context->depth; ++d) {
4,529,517✔
714
                    float val;
3,395,715✔
715

716
                    val = context->pixels_float[pos * context->depth + d]
6,789,004✔
717
                        + f_mask(x, y, d) * jitter_scale;
3,395,715✔
718
                    val = sixel_pixelformat_float_channel_clamp(
3,393,289✔
719
                        context->pixelformat,
1,474,560✔
720
                        d,
1,474,560✔
721
                        val);
1,474,560✔
722
                    if (need_float_pixel) {
3,395,596!
723
                        lookup_pixel_float[d] = val;
3,279,244✔
724
                    }
1,425,408✔
725
                    if (!lookup_wants_float && !use_palette_float_lookup) {
3,328,396!
726
                        quantized[d]
116,352✔
727
                            = sixel_pixelformat_float_channel_to_byte(
165,504✔
728
                                  context->pixelformat,
49,152✔
729
                                  d,
49,152✔
730
                                  val);
49,152✔
731
                    }
49,152✔
732
                }
1,474,560✔
733
                if (absolute_y >= context->output_start) {
1,133,802!
734
                    /*
735
                     * Palette indices never exceed SIXEL_PALETTE_MAX, so
736
                     * narrowing to sixel_index_t (unsigned char) is safe.
737
                     */
738
                    if (lookup_wants_float) {
1,112,882!
739
                        lookup_pixel = (unsigned char const *)(void const *)
1,076,018✔
740
                            lookup_pixel_float;
475,136✔
741
                        context->result[pos] = (sixel_index_t)
1,076,136✔
742
                            context->lookup(
1,551,154✔
743
                                lookup_pixel,
475,136✔
744
                                context->depth,
475,136✔
745
                                context->palette,
1,076,018✔
746
                                context->reqcolor,
475,136✔
747
                                context->indextable,
475,136✔
748
                                context->complexion);
475,136✔
749
                    } else if (use_palette_float_lookup) {
512,000!
750
                        context->result[pos] = (sixel_index_t)
×
751
                            sixel_dither_lookup_palette_float32(
×
752
                                lookup_pixel_float,
753
                                context->depth,
754
                                palette_float,
755
                                context->reqcolor,
756
                                context->complexion);
757
                    } else {
758
                        lookup_pixel = quantized;
36,864✔
759
                        context->result[pos] = (sixel_index_t)
36,864✔
760
                            context->lookup(
53,248✔
761
                                lookup_pixel,
16,384✔
762
                                context->depth,
16,384✔
763
                                context->palette,
36,864✔
764
                                context->reqcolor,
16,384✔
765
                                context->indextable,
16,384✔
766
                                context->complexion);
16,384✔
767
                    }
768
                }
491,520✔
769
            }
491,520✔
770
            if (absolute_y >= context->output_start) {
17,736!
771
                sixel_dither_pipeline_row_notify(dither, absolute_y);
17,407✔
772
            }
7,680✔
773
        }
7,680✔
774
        *context->ncolors = context->reqcolor;
422✔
775
    }
120✔
776

777
    return SIXEL_OK;
120✔
778
}
120✔
779

780
/* emacs Local Variables:      */
781
/* emacs mode: c               */
782
/* emacs tab-width: 4          */
783
/* emacs indent-tabs-mode: nil */
784
/* emacs c-basic-offset: 4     */
785
/* emacs End:                  */
786
/* vim: set expandtab ts=4 sts=4 sw=4 : */
787
/* 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