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

saitoha / libsixel / 24335207683

13 Apr 2026 07:44AM UTC coverage: 85.61% (+0.006%) from 85.604%
24335207683

push

github

saitoha
fix(loader): split builtin wrapper TU to avoid pcc ICE

102409 of 214238 branches covered (47.8%)

49 of 49 new or added lines in 2 files covered. (100.0%)

5 existing lines in 3 files now uncovered.

124762 of 145733 relevant lines covered (85.61%)

17549407.25 hits per line

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

88.65
/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,
54,338✔
60
                             int index,
61
                             int limit,
62
                             int *start,
63
                             int *end,
64
                             int *step,
65
                             int *direction)
66
{
67
    if (serpentine && (index & 1)) {
40,568!
68
        *start = limit - 1;
1,748✔
69
        *end = -1;
1,748✔
70
        *step = -1;
1,748✔
71
        *direction = -1;
1,748✔
72
    } else {
722✔
73
        *start = 0;
38,342✔
74
        *end = limit;
38,342✔
75
        *step = 1;
38,342✔
76
        *direction = 1;
53,068✔
77
    }
78
}
39,612✔
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
#if !SIXEL_ENABLE_THREADS
87
/*
88
 * The single-thread fallback uses this flag to emulate one-time init without
89
 * pthread_once/InitOnceExecuteOnce.
90
 */
91
static int g_sixel_pos_inited_float32 = 0;
92
#endif
93

94
static void sixel_positional_strength_init_float32(void);
95
static float positional_mask_blue_float32(int x, int y, int c);
96
static void sixel_bluenoise_conf_init_from_env_float32(void);
97

98
static float
99
positional_mask_a_float32(int x, int y, int c)
2,562,417✔
100
{
101
    return (((((x + c * 67) + y * 236) * 119) & 255) / 128.0f
3,640,796✔
102
            - 1.0f) * g_sixel_pos_strength_a_float32;
2,562,417✔
103
}
104

105
static float
106
positional_mask_x_float32(int x, int y, int c)
3,911,698✔
107
{
108
    return (((((x + c * 29) ^ (y * 149)) * 1234) & 511) / 256.0f
5,541,342✔
109
            - 1.0f) * g_sixel_pos_strength_x_float32;
3,911,698✔
110
}
111

112
/*
113
 * Keep per-file suffixes so unity builds do not merge identical static
114
 * helper symbols from other positional dither sources.
115
 */
116
typedef struct {
117
    float strength;
118
    int ox;
119
    int oy;
120
    int per_channel;
121
    int size;
122
} sixel_bluenoise_conf_float32_t;
123

124
static float positional_mask_blue_with_conf_float32(
125
    sixel_bluenoise_conf_float32_t const *conf,
126
    int x,
127
    int y,
128
    int c);
129

130
static sixel_bluenoise_conf_float32_t g_sixel_bn_conf_float32;
131
#if !SIXEL_ENABLE_THREADS
132
/*
133
 * Keep the fallback init flag out of threaded builds so -Wunused-but-set-global
134
 * does not fire when pthread_once/InitOnceExecuteOnce is active.
135
 */
136
static int g_sixel_bn_inited_float32 = 0;
137
#endif
138

139
static int
140
sixel_bn_parse_int_float32(char const *text, int *out_value)
52✔
141
{
142
    char *endptr;
26✔
143
    long value;
26✔
144

145
    if (text == NULL || text[0] == '\0') {
52!
146
        return 0;
147
    }
148

149
    value = strtol(text, &endptr, 10);
52✔
150
    if (endptr == text || *endptr != '\0') {
52!
151
        return 0;
152
    }
153
    if (value > INT_MAX || value < INT_MIN) {
44!
154
        return 0;
155
    }
156

157
    *out_value = (int)value;
52✔
158
    return 1;
52✔
159
}
22✔
160

161
static int
162
sixel_bn_parse_float_float32(char const *text, float *out_value)
104✔
163
{
164
    char *endptr;
52✔
165
    double value;
52✔
166

167
    if (text == NULL || text[0] == '\0') {
104!
168
        return 0;
169
    }
170

171
    value = strtod(text, &endptr);
104✔
172
    if (endptr == text || *endptr != '\0') {
104!
173
        return 0;
38✔
174
    }
175

176
    *out_value = (float)value;
52✔
177
    return 1;
52✔
178
}
44✔
179

180
static int
181
sixel_bn_parse_phase_float32(char const *text, int *out_ox, int *out_oy)
26✔
182
{
183
    char *endptr;
13✔
184
    char const *comma;
13✔
185
    long ox;
13✔
186
    long oy;
13✔
187

188
    if (text == NULL || text[0] == '\0') {
26!
189
        return 0;
190
    }
191

192
    comma = strchr(text, ',');
26✔
193
    if (comma == NULL) {
26!
194
        return 0;
19✔
195
    }
196

197
    ox = strtol(text, &endptr, 10);
×
198
    if (endptr == text || endptr != comma) {
×
199
        return 0;
200
    }
201

202
    oy = strtol(comma + 1, &endptr, 10);
×
203
    if (endptr == comma + 1 || *endptr != '\0') {
×
204
        return 0;
205
    }
206
    if (ox > INT_MAX || ox < INT_MIN || oy > INT_MAX || oy < INT_MIN) {
×
207
        return 0;
208
    }
209

210
    *out_ox = (int)ox;
×
211
    *out_oy = (int)oy;
×
212
    return 1;
×
213
}
11✔
214

215
static void
216
sixel_positional_strength_init_body_float32(void)
520✔
217
{
218
    char const *text;
260✔
219
    float strength_a;
260✔
220
    float strength_x;
260✔
221
    int parsed;
260✔
222

223
    /*
224
     * Default strengths are per-dither values. Environment overrides use
225
     * the same parser for consistency and fall back to defaults on error.
226
     */
227
    strength_a = 0.150f;
520✔
228
    text = sixel_compat_getenv("SIXEL_DITHER_A_DITHER_STRENGTH");
520✔
229
    if (text != NULL) {
520✔
230
        parsed = sixel_bn_parse_float_float32(text, &strength_a);
26✔
231
        if (parsed == 0) {
26!
232
            strength_a = 0.150f;
26✔
233
        }
11✔
234
    }
11✔
235

236
    strength_x = 0.100f;
501✔
237
    text = sixel_compat_getenv("SIXEL_DITHER_X_DITHER_STRENGTH");
501✔
238
    if (text != NULL) {
501✔
239
        parsed = sixel_bn_parse_float_float32(text, &strength_x);
26✔
240
        if (parsed == 0) {
26!
241
            strength_x = 0.100f;
26✔
242
        }
11✔
243
    }
11✔
244

245
    g_sixel_pos_strength_a_float32 = strength_a;
520✔
246
    g_sixel_pos_strength_x_float32 = strength_x;
520✔
247
#if !SIXEL_ENABLE_THREADS
248
    g_sixel_pos_inited_float32 = 1;
80✔
249
#endif
250
}
520✔
251

252
#if SIXEL_ENABLE_THREADS && defined(SIXEL_POS_FLOAT32_USE_WIN32_ONCE)
253
static BOOL CALLBACK
254
sixel_positional_strength_once_cb_float32(PINIT_ONCE init_once,
140✔
255
                                          PVOID parameter,
256
                                          PVOID *context)
257
{
258
    (void)init_once;
80✔
259
    (void)parameter;
80✔
260
    (void)context;
80✔
261
    sixel_positional_strength_init_body_float32();
140✔
262
    return TRUE;
140✔
263
}
264
#endif
265

266
static void
267
sixel_positional_strength_init_float32(void)
800✔
268
{
269
#if SIXEL_ENABLE_THREADS
270
# if defined(SIXEL_POS_FLOAT32_USE_WIN32_ONCE)
271
    BOOL executed;
120✔
272

273
    executed = InitOnceExecuteOnce(&g_sixel_pos_strength_once_float32,
280✔
274
                                   sixel_positional_strength_once_cb_float32,
275
                                   NULL,
276
                                   NULL);
277
    if (executed == FALSE) {
280✔
278
        sixel_positional_strength_init_body_float32();
279
    }
280
# else
281
    int status;
220✔
282

283
    status = pthread_once(&g_sixel_pos_strength_once_float32,
440✔
284
                          sixel_positional_strength_init_body_float32);
285
    if (status != 0) {
440!
286
        sixel_positional_strength_init_body_float32();
287
    }
288
# endif
289
#else
290
    if (g_sixel_pos_inited_float32 == 0) {
80!
291
        sixel_positional_strength_init_body_float32();
80✔
292
    }
293
#endif
294
}
760✔
295

296
static unsigned int
297
sixel_bn_hash32_float32(unsigned int value)
26✔
298
{
299
    value += 0x9e3779b9U;
26✔
300
    value ^= value >> 16;
26✔
301
    value *= 0x85ebca6bU;
26✔
302
    value ^= value >> 13;
26✔
303
    value *= 0xc2b2ae35U;
26✔
304
    value ^= value >> 16;
26✔
305
    return value;
26✔
306
}
307

308
static int
309
sixel_bn_str_equal_nocase_float32(char const *left, char const *right)
26✔
310
{
311
    unsigned char lc;
13✔
312
    unsigned char rc;
13✔
313

314
    if (left == NULL || right == NULL) {
26!
315
        return 0;
316
    }
317

318
    while (*left != '\0' && *right != '\0') {
104!
319
        lc = (unsigned char)tolower((unsigned char)*left);
78✔
320
        rc = (unsigned char)tolower((unsigned char)*right);
78✔
321
        if (lc != rc) {
78!
322
            return 0;
323
        }
324
        ++left;
78✔
325
        ++right;
78✔
326
    }
327

328
    return (*left == '\0' && *right == '\0');
26!
329
}
11✔
330

331
/*
332
 * Cache bluenoise configuration at first use so we do not hit getenv()
333
 * inside pixel loops. Invalid values fall back to defaults.
334
 */
335
static void
336
sixel_bluenoise_conf_init_from_env_body_float32(void)
312✔
337
{
338
    char const *text;
156✔
339
    float strength;
156✔
340
    int size;
156✔
341
    int ox;
156✔
342
    int oy;
156✔
343
    int seed;
156✔
344
    int phase_set;
156✔
345
    int parsed;
156✔
346
    int per_channel;
156✔
347
    unsigned int hash;
156✔
348

349
    strength = 0.055f;
312✔
350
    text = sixel_compat_getenv("SIXEL_DITHER_BLUENOISE_STRENGTH");
312✔
351
    if (text != NULL) {
312✔
352
        parsed = sixel_bn_parse_float_float32(text, &strength);
52✔
353
        if (parsed == 0) {
52!
354
            strength = 0.055f;
×
355
        }
356
    }
22✔
357

358
    ox = 0;
303✔
359
    oy = 0;
303✔
360
    phase_set = 0;
303✔
361
    text = sixel_compat_getenv("SIXEL_DITHER_BLUENOISE_PHASE");
303✔
362
    if (text != NULL) {
303✔
363
        phase_set = 1;
26✔
364
        parsed = sixel_bn_parse_phase_float32(text, &ox, &oy);
26✔
365
        if (parsed == 0) {
26!
366
            ox = 0;
26✔
367
            oy = 0;
26✔
368
        }
11✔
369
    }
11✔
370
    if (phase_set == 0) {
234✔
371
        text = sixel_compat_getenv("SIXEL_DITHER_BLUENOISE_SEED");
286✔
372
        if (text != NULL) {
286✔
373
            parsed = sixel_bn_parse_int_float32(text, &seed);
26✔
374
            if (parsed != 0) {
26!
375
                hash = sixel_bn_hash32_float32((unsigned int)seed);
26✔
376
                ox = (int)(hash & 63U);
26✔
377
                oy = (int)((hash >> 8) & 63U);
26✔
378
            }
11✔
379
        }
11✔
380
    }
121✔
381

382
    per_channel = 0;
321✔
383
    text = sixel_compat_getenv("SIXEL_DITHER_BLUENOISE_CHANNEL");
321✔
384
    if (text != NULL) {
321✔
385
        if (sixel_bn_str_equal_nocase_float32(text, "rgb") != 0) {
26!
386
            per_channel = 1;
19✔
387
        } else if (sixel_bn_str_equal_nocase_float32(text, "mono") != 0) {
11!
388
            per_channel = 0;
×
389
        }
390
    }
11✔
391

392
    size = SIXEL_BN_W;
301✔
393
    text = sixel_compat_getenv("SIXEL_DITHER_BLUENOISE_SIZE");
301✔
394
    if (text != NULL) {
301✔
395
        parsed = sixel_bn_parse_int_float32(text, &size);
26✔
396
        if (parsed == 0 || size != SIXEL_BN_W) {
26!
397
            size = SIXEL_BN_W;
26✔
398
        }
11✔
399
    }
11✔
400

401
    g_sixel_bn_conf_float32.strength = strength;
312✔
402
    g_sixel_bn_conf_float32.ox = ox;
312✔
403
    g_sixel_bn_conf_float32.oy = oy;
312✔
404
    g_sixel_bn_conf_float32.per_channel = per_channel;
312✔
405
    g_sixel_bn_conf_float32.size = size;
312✔
406
#if !SIXEL_ENABLE_THREADS
407
    g_sixel_bn_inited_float32 = 1;
48✔
408
#endif
409
}
312✔
410

411
#if SIXEL_ENABLE_THREADS && defined(SIXEL_POS_FLOAT32_USE_WIN32_ONCE)
412
static BOOL CALLBACK
413
sixel_bluenoise_conf_once_cb_float32(PINIT_ONCE init_once,
84✔
414
                                     PVOID parameter,
415
                                     PVOID *context)
416
{
417
    (void)init_once;
48✔
418
    (void)parameter;
48✔
419
    (void)context;
48✔
420
    sixel_bluenoise_conf_init_from_env_body_float32();
84✔
421
    return TRUE;
84✔
422
}
423
#endif
424

425
static void
426
sixel_bluenoise_conf_init_from_env_float32(void)
479✔
427
{
428
#if SIXEL_ENABLE_THREADS
429
# if defined(SIXEL_POS_FLOAT32_USE_WIN32_ONCE)
430
    BOOL executed;
71✔
431

432
    executed = InitOnceExecuteOnce(&g_sixel_bn_conf_once_float32,
167✔
433
                                   sixel_bluenoise_conf_once_cb_float32,
434
                                   NULL,
435
                                   NULL);
436
    if (executed == FALSE) {
168✔
437
        sixel_bluenoise_conf_init_from_env_body_float32();
438
    }
439
# else
440
    int status;
132✔
441

442
    status = pthread_once(&g_sixel_bn_conf_once_float32,
264✔
443
                          sixel_bluenoise_conf_init_from_env_body_float32);
444
    if (status != 0) {
264!
445
        sixel_bluenoise_conf_init_from_env_body_float32();
446
    }
447
# endif
448
#else
449
    if (g_sixel_bn_inited_float32 == 0) {
48!
450
        sixel_bluenoise_conf_init_from_env_body_float32();
48✔
451
    }
452
#endif
453
}
456✔
454

455
/*
456
 * Apply per-dither CLI overrides on top of cached environment defaults.
457
 * The phase override has priority over seed, mirroring env semantics.
458
 */
459
static void
460
sixel_bluenoise_conf_apply_dither_overrides_float32(
480✔
461
    sixel_bluenoise_conf_float32_t *conf,
462
    sixel_dither_t const *dither)
463
{
464
    unsigned int hash;
228✔
465

466
    hash = 0U;
480✔
467
    if (conf == NULL || dither == NULL) {
480!
468
        return;
469
    }
470

471
    if (dither->bluenoise_strength_override != 0) {
480✔
472
        conf->strength = dither->bluenoise_strength;
40✔
473
    }
14✔
474
    if (dither->bluenoise_channel_override != 0) {
470!
475
        conf->per_channel = (dither->bluenoise_channel_rgb != 0) ? 1 : 0;
×
476
    }
477
    if (dither->bluenoise_size_override != 0
468!
478
            && dither->bluenoise_size == SIXEL_BN_W) {
156!
479
        conf->size = SIXEL_BN_W;
×
480
    }
481
    if (dither->bluenoise_phase_override != 0) {
480!
482
        conf->ox = dither->bluenoise_phase_x;
×
483
        conf->oy = dither->bluenoise_phase_y;
×
484
    } else if (dither->bluenoise_seed_override != 0) {
480!
485
        hash = sixel_bn_hash32_float32((unsigned int)dither->bluenoise_seed);
×
486
        conf->ox = (int)(hash & 63U);
×
487
        conf->oy = (int)((hash >> 8) & 63U);
×
488
    }
489
}
168!
490

491
static float
492
sixel_bluenoise_tri_with_conf_float32(
3,905,525✔
493
    sixel_bluenoise_conf_float32_t const *conf,
494
    int x,
495
    int y,
496
    int c)
497
{
498
    /* Triangular noise blends two samples from the same tile. */
499
    static int const channel_offset_x[3] = { 17, 34, 51 };
1,061,502✔
500
    static int const channel_offset_y[3] = { 31, 62, 93 };
1,061,502✔
501
    int ox;
1,945,821✔
502
    int oy;
1,945,821✔
503
    int per_channel;
1,945,821✔
504
    int channel_x;
1,945,821✔
505
    int channel_y;
1,945,821✔
506
    int ix0;
1,945,821✔
507
    int iy0;
1,945,821✔
508
    int ix1;
1,945,821✔
509
    int iy1;
1,945,821✔
510
    float u;
1,945,821✔
511
    float v;
1,945,821✔
512

513
    if (conf == NULL) {
3,905,525✔
514
        return 0.0f;
515
    }
516

517
    ox = conf->ox;
3,905,525✔
518
    oy = conf->oy;
3,905,525✔
519
    per_channel = conf->per_channel;
3,905,525✔
520
    channel_x = 0;
3,905,525✔
521
    channel_y = 0;
3,905,525✔
522
    if (per_channel != 0 && c >= 0 && c < 3) {
3,905,525!
523
        channel_x = channel_offset_x[c];
332,568✔
524
        channel_y = channel_offset_y[c];
332,568✔
525
    }
136,651✔
526

527
    ix0 = x + ox + channel_x;
3,905,549✔
528
    iy0 = y + oy + channel_y;
3,905,549✔
529
    ix1 = ix0 + 13;
3,905,549✔
530
    iy1 = iy0 + 29;
3,905,549✔
531
    u = (sixel_bn_mask(ix0, iy0) + 1.0f) * 0.5f;
3,905,549✔
532
    v = (sixel_bn_mask(ix1, iy1) + 1.0f) * 0.5f;
3,905,862✔
533

534
    return (u + v) - 1.0f;
3,906,883✔
535
}
1,625,341✔
536

537
static float
538
sixel_bluenoise_tri_float32(int x, int y, int c)
539
{
540
    return sixel_bluenoise_tri_with_conf_float32(&g_sixel_bn_conf_float32,
×
541
                                                 x,
542
                                                 y,
543
                                                 c);
544
}
545

546
static float
547
positional_mask_blue_float32(int x, int y, int c)
548
{
549
    return sixel_bluenoise_tri_float32(x, y, c)
×
550
        * g_sixel_bn_conf_float32.strength;
×
551
}
552

553
static float
554
positional_mask_blue_with_conf_float32(
3,905,766✔
555
    sixel_bluenoise_conf_float32_t const *conf,
556
    int x,
557
    int y,
558
    int c)
559
{
560
    if (conf == NULL) {
3,905,766!
561
        return 0.0f;
562
    }
563

564
    return sixel_bluenoise_tri_with_conf_float32(conf, x, y, c)
5,531,351✔
565
        * conf->strength;
3,906,528✔
566
}
1,625,585✔
567

568
SIXELSTATUS
569
sixel_dither_apply_positional_float32(sixel_dither_t *dither,
1,279✔
570
                                      sixel_dither_context_t *context)
571
{
572
    int serpentine;
608✔
573
    int y;
608✔
574
    int absolute_y;
608✔
575
    float (*f_mask)(int x, int y, int c);
608✔
576
    float jitter_scale;
608✔
577
    float *palette_float;
608✔
578
    float *new_palette_float;
608✔
579
    int float_depth;
608✔
580
    int float_index;
608✔
581
    unsigned char *quantized;
608✔
582
    float lookup_pixel_float[SIXEL_MAX_CHANNELS];
608✔
583
    unsigned char const *lookup_pixel;
608✔
584
    sixel_lut_t *fast_lut;
608✔
585
    int use_fast_lut;
608✔
586
    int lookup_wants_float;
608✔
587
    int use_palette_float_lookup;
608✔
588
    int need_float_pixel;
608✔
589
    unsigned char const *transparent_mask;
608✔
590
    size_t transparent_mask_size;
608✔
591
    int transparent_keycolor;
608✔
592
    int use_transparent_fence;
608✔
593
    int is_transparent;
608✔
594
    size_t absolute_index;
608✔
595
    sixel_bluenoise_conf_float32_t bluenoise_conf;
608✔
596
    int use_bluenoise_conf;
608✔
597
    float noise;
608✔
598

599
    palette_float = NULL;
1,279✔
600
    new_palette_float = NULL;
1,279✔
601
    float_depth = 0;
1,279✔
602
    quantized = NULL;
1,279✔
603
    lookup_wants_float = 0;
1,279✔
604
    bluenoise_conf.strength = 0.055f;
1,279✔
605
    bluenoise_conf.ox = 0;
1,279✔
606
    bluenoise_conf.oy = 0;
1,279✔
607
    bluenoise_conf.per_channel = 0;
1,279✔
608
    bluenoise_conf.size = SIXEL_BN_W;
1,279✔
609
    use_bluenoise_conf = 0;
1,279✔
610
    noise = 0.0f;
1,279✔
611

612
    if (dither == NULL || context == NULL) {
1,279!
613
        return SIXEL_BAD_ARGUMENT;
614
    }
615
    if (context->pixels_float == NULL || context->scratch == NULL) {
1,280!
616
        return SIXEL_BAD_ARGUMENT;
617
    }
618
    if (context->palette == NULL || context->result == NULL) {
1,280!
619
        return SIXEL_BAD_ARGUMENT;
620
    }
621

622
    switch (context->method_for_diffuse) {
1,280!
623
    case SIXEL_DIFFUSE_A_DITHER:
192!
624
        sixel_positional_strength_init_float32();
320!
625
        f_mask = positional_mask_a_float32;
304✔
626
        break;
304✔
627
    case SIXEL_DIFFUSE_X_DITHER:
288!
628
        sixel_positional_strength_init_float32();
480!
629
        f_mask = positional_mask_x_float32;
456✔
630
        break;
456✔
631
    case SIXEL_DIFFUSE_BLUENOISE_DITHER:
288!
632
        sixel_bluenoise_conf_init_from_env_float32();
480!
633
        bluenoise_conf = g_sixel_bn_conf_float32;
480✔
634
        sixel_bluenoise_conf_apply_dither_overrides_float32(&bluenoise_conf,
480✔
635
                                                            dither);
168✔
636
        use_bluenoise_conf = 1;
480✔
637
        f_mask = positional_mask_blue_float32;
480✔
638
        break;
480✔
639
    default:
×
UNCOV
640
        sixel_positional_strength_init_float32();
×
641
        f_mask = positional_mask_x_float32;
642
        break;
643
    }
644

645
    serpentine = (context->method_for_scan == SIXEL_SCAN_SERPENTINE);
1,280✔
646
    jitter_scale = 32.0f / 255.0f;
1,280✔
647
    palette_float = context->palette_float;
1,280✔
648
    new_palette_float = context->new_palette_float;
1,280✔
649
    float_depth = context->float_depth;
1,280✔
650
    quantized = context->scratch;
1,280✔
651
    fast_lut = context->lut;
1,280✔
652
    use_fast_lut = (fast_lut != NULL);
1,280✔
653
    transparent_mask = context->transparent_mask;
1,280✔
654
    transparent_mask_size = context->transparent_mask_size;
1,280✔
655
    transparent_keycolor = context->transparent_keycolor;
1,280✔
656
    use_transparent_fence = 0;
1,280✔
657
    if (transparent_mask != NULL
1,280!
658
            && transparent_keycolor >= 0
832!
659
            && transparent_keycolor < SIXEL_PALETTE_MAX) {
×
660
        use_transparent_fence = 1;
384✔
661
    }
662
    lookup_wants_float = (context->lookup_source_is_float != 0);
1,248✔
663
    use_palette_float_lookup = 0;
1,248✔
664
    if (context->prefer_palette_float_lookup != 0
1,248!
665
            && palette_float != NULL
416!
666
            && float_depth >= context->depth) {
×
667
        use_palette_float_lookup = 1;
384✔
668
    }
669
    need_float_pixel = lookup_wants_float || use_palette_float_lookup;
1,249!
670

671
    if (context->optimize_palette) {
1,280!
672
        int x;
118✔
673

674
        *context->ncolors = 0;
186✔
675
        memset(context->new_palette, 0x00,
192✔
676
               (size_t)SIXEL_PALETTE_MAX * (size_t)context->depth);
130!
677
        if (new_palette_float != NULL && float_depth > 0) {
186!
678
            memset(new_palette_float, 0x00,
×
679
                   (size_t)SIXEL_PALETTE_MAX
680
                       * (size_t)float_depth * sizeof(float));
681
        }
682
        memset(context->migration_map, 0x00,
186✔
683
               sizeof(unsigned short) * (size_t)SIXEL_PALETTE_MAX);
684
        for (y = 0; y < context->height; ++y) {
8,122!
685
            absolute_y = context->band_origin + y;
7,936✔
686
            int start;
3,968✔
687
            int end;
3,968✔
688
            int step;
3,968✔
689
            int direction;
3,968✔
690

691
            sixel_dither_scanline_params_positional_float32(serpentine, absolute_y,
7,936!
692
                                         context->width,
693
                                         &start, &end, &step, &direction);
694
            (void)direction;
3,968✔
695
            for (x = start; x != end; x += step) {
515,840!
696
                int pos;
253,952✔
697
                int d;
253,952✔
698
                int color_index;
253,952✔
699

700
                pos = y * context->width + x;
507,904✔
701
                is_transparent = 0;
507,904✔
702
                if (use_transparent_fence && absolute_y >= 0) {
507,904!
703
                    absolute_index = (size_t)absolute_y
×
704
                        * (size_t)context->width
×
705
                        + (size_t)x;
×
706
                    if (absolute_index < transparent_mask_size
×
707
                            && transparent_mask[absolute_index] != 0U) {
×
708
                        is_transparent = 1;
×
709
                    }
710
                }
711
                if (is_transparent) {
253,952!
712
                    if (absolute_y >= context->output_start) {
×
713
                        context->result[pos]
×
714
                            = (sixel_index_t)transparent_keycolor;
×
715
                    }
716
                    continue;
×
717
                }
718
                for (d = 0; d < context->depth; ++d) {
2,031,616!
719
                    float val;
761,856✔
720

721
                    if (use_bluenoise_conf != 0) {
1,523,712!
722
                        noise = positional_mask_blue_with_conf_float32(
589,824✔
723
                            &bluenoise_conf,
724
                            x,
725
                            y,
726
                            d);
727
                    } else {
728
                        noise = f_mask(x, y, d);
933,888✔
729
                    }
730
                    val = context->pixels_float[pos * context->depth + d]
1,523,712✔
731
                        + noise * jitter_scale;
1,523,712✔
732
                    val = sixel_pixelformat_float_channel_clamp(
1,523,712✔
733
                        context->pixelformat,
734
                        d,
735
                        val);
736
                    if (need_float_pixel) {
1,523,712!
737
                        lookup_pixel_float[d] = val;
1,474,560✔
738
                    }
739
                    if (!lookup_wants_float && !use_palette_float_lookup) {
1,499,136!
740
                        quantized[d]
49,152✔
741
                            = sixel_pixelformat_float_channel_to_byte(
49,152✔
742
                                  context->pixelformat,
743
                                  d,
744
                                  val);
745
                    }
746
                }
747
                if (lookup_wants_float) {
507,904!
748
                    lookup_pixel = (unsigned char const *)(void const *)
491,520✔
749
                        lookup_pixel_float;
750
                    if (use_fast_lut) {
491,520!
751
                        color_index = sixel_lut_map_pixel(fast_lut,
491,520✔
752
                                                         lookup_pixel);
753
                    } else {
754
                        color_index = context->lookup(lookup_pixel,
×
755
                                                      context->depth,
756
                                                      context->palette,
×
757
                                                      context->reqcolor,
758
                                                      context->indextable,
759
                                                      context->complexion);
760
                    }
761
                } else if (use_palette_float_lookup) {
16,384!
762
                    color_index = sixel_dither_lookup_palette_float32(
×
763
                        lookup_pixel_float,
764
                        context->depth,
765
                        palette_float,
766
                        context->reqcolor,
767
                        context->complexion);
768
                } else {
769
                    lookup_pixel = quantized;
16,384✔
770
                    if (use_fast_lut) {
16,384!
771
                        color_index = sixel_lut_map_pixel(fast_lut,
×
772
                                                         lookup_pixel);
773
                    } else {
774
                        color_index = context->lookup(lookup_pixel,
16,384✔
775
                                                      context->depth,
776
                                                      context->palette,
16,384✔
777
                                                      context->reqcolor,
778
                                                      context->indextable,
779
                                                      context->complexion);
780
                    }
781
                }
782
                if (context->migration_map[color_index] == 0) {
507,904!
783
                    if (absolute_y >= context->output_start) {
19,136!
784
                        /*
785
                         * Palette indices never exceed SIXEL_PALETTE_MAX, so
786
                         * the cast to sixel_index_t (unsigned char) is safe.
787
                         */
788
                        context->result[pos]
19,136✔
789
                            = (sixel_index_t)(*context->ncolors);
19,136✔
790
                    }
791
                    for (d = 0; d < context->depth; ++d) {
76,544!
792
                        context->new_palette[*context->ncolors
57,408✔
793
                                             * context->depth + d]
57,408✔
794
                            = context->palette[color_index
57,408✔
795
                                               * context->depth + d];
57,408✔
796
                    }
797
                    if (palette_float != NULL
19,136!
798
                            && new_palette_float != NULL
9,568!
799
                            && float_depth > 0) {
×
800
                        for (float_index = 0;
×
801
                                float_index < float_depth;
×
802
                                ++float_index) {
×
803
                            new_palette_float[*context->ncolors
×
804
                                               * float_depth
×
805
                                               + float_index]
×
806
                                = palette_float[color_index * float_depth
×
807
                                                + float_index];
×
808
                        }
809
                    }
810
                    ++*context->ncolors;
19,136✔
811
                    /*
812
                     * Migration map entries are limited to the palette size
813
                     * (<= 256), so storing them as unsigned short is safe.
814
                     */
815
                    context->migration_map[color_index]
19,136✔
816
                        = (unsigned short)(*context->ncolors);
19,136✔
817
                } else {
818
                    if (absolute_y >= context->output_start) {
488,768!
819
                        context->result[pos]
488,768✔
820
                            = (sixel_index_t)(context->migration_map[
488,768✔
821
                                  color_index] - 1);
822
                    }
823
                }
824
            }
×
825
            if (absolute_y >= context->output_start) {
7,936!
826
                sixel_dither_pipeline_row_notify(dither, absolute_y);
7,936✔
827
            }
828
        }
829
        memcpy(context->palette, context->new_palette,
192✔
830
               (size_t)(*context->ncolors * context->depth));
130!
831
        if (palette_float != NULL
186!
832
                && new_palette_float != NULL
124!
833
                && float_depth > 0) {
×
834
            memcpy(palette_float,
×
835
                   new_palette_float,
836
                   (size_t)(*context->ncolors * float_depth)
837
                       * sizeof(float));
838
        }
839
    } else {
62✔
840
        int x;
168✔
841

842
        for (y = 0; y < context->height; ++y) {
47,496!
843
            absolute_y = context->band_origin + y;
46,340✔
844
            int start;
23,067✔
845
            int end;
23,067✔
846
            int step;
23,067✔
847
            int direction;
23,067✔
848

849
            sixel_dither_scanline_params_positional_float32(serpentine, absolute_y,
69,039!
850
                                         context->width,
22,699✔
851
                                         &start, &end, &step, &direction);
852
            (void)direction;
33,457✔
853
            for (x = start; x != end; x += step) {
3,004,736!
854
                int pos;
1,471,624✔
855
                int d;
1,471,624✔
856

857
                pos = y * context->width + x;
2,959,123✔
858
                is_transparent = 0;
2,959,123✔
859
                if (use_transparent_fence && absolute_y >= 0) {
2,959,123!
860
                    absolute_index = (size_t)absolute_y
×
861
                        * (size_t)context->width
×
862
                        + (size_t)x;
×
863
                    if (absolute_index < transparent_mask_size
×
864
                            && transparent_mask[absolute_index] != 0U) {
×
865
                        is_transparent = 1;
×
866
                    }
867
                }
868
                if (is_transparent) {
2,272,580!
869
                    if (absolute_y >= context->output_start) {
×
870
                        context->result[pos]
×
871
                            = (sixel_index_t)transparent_keycolor;
×
872
                    }
873
                    continue;
×
874
                }
875
                for (d = 0; d < context->depth; ++d) {
11,815,381!
876
                    float val;
4,405,755✔
877

878
                    if (use_bluenoise_conf != 0) {
8,856,826!
879
                        noise = positional_mask_blue_with_conf_float32(
3,316,475✔
880
                            &bluenoise_conf,
881
                            x,
1,625,946✔
882
                            y,
1,625,946✔
883
                            d);
1,625,946✔
884
                    } else {
1,625,946✔
885
                        noise = f_mask(x, y, d);
5,540,351✔
886
                    }
887
                    val = context->pixels_float[pos * context->depth + d]
13,189,416✔
888
                        + noise * jitter_scale;
8,855,325✔
889
                    val = sixel_pixelformat_float_channel_clamp(
8,855,325✔
890
                        context->pixelformat,
4,334,091✔
891
                        d,
4,334,091✔
892
                        val);
4,334,091✔
893
                    if (need_float_pixel) {
8,856,286!
894
                        lookup_pixel_float[d] = val;
8,573,748✔
895
                    }
4,198,433✔
896
                    if (!lookup_wants_float && !use_palette_float_lookup) {
9,146,068!
897
                        quantized[d]
282,834✔
898
                            = sixel_pixelformat_float_channel_to_byte(
418,897✔
899
                                  context->pixelformat,
136,063✔
900
                                  d,
136,063✔
901
                                  val);
136,063✔
902
                    }
136,063✔
903
                }
4,334,097✔
904
                if (absolute_y >= context->output_start) {
2,958,555!
905
                    /*
906
                     * Palette indices never exceed SIXEL_PALETTE_MAX, so
907
                     * narrowing to sixel_index_t (unsigned char) is safe.
908
                     */
909
                    if (lookup_wants_float) {
2,889,531!
910
                        lookup_pixel = (unsigned char const *)(void const *)
2,800,321✔
911
                            lookup_pixel_float;
1,389,105✔
912
                        context->result[pos] = (sixel_index_t)
2,800,091✔
913
                            context->lookup(
4,189,426✔
914
                                lookup_pixel,
1,389,105✔
915
                                context->depth,
1,389,105✔
916
                                context->palette,
2,800,321✔
917
                                context->reqcolor,
1,389,105✔
918
                                context->indextable,
1,389,105✔
919
                                context->complexion);
1,389,105✔
920
                    } else if (use_palette_float_lookup) {
1,478,315!
921
                        context->result[pos] = (sixel_index_t)
×
922
                            sixel_dither_lookup_palette_float32(
×
923
                                lookup_pixel_float,
924
                                context->depth,
925
                                palette_float,
926
                                context->reqcolor,
927
                                context->complexion);
928
                    } else {
929
                        lookup_pixel = quantized;
89,210✔
930
                        context->result[pos] = (sixel_index_t)
89,225✔
931
                            context->lookup(
133,406✔
932
                                lookup_pixel,
44,196✔
933
                                context->depth,
44,196✔
934
                                context->palette,
89,210✔
935
                                context->reqcolor,
44,196✔
936
                                context->indextable,
44,196✔
937
                                context->complexion);
44,196✔
938
                    }
939
                }
1,433,301✔
940
            }
1,448,475✔
941
            if (absolute_y >= context->output_start) {
45,613!
942
                sixel_dither_pipeline_row_notify(dither, absolute_y);
45,311✔
943
            }
22,528✔
944
        }
22,761✔
945
        *context->ncolors = context->reqcolor;
1,156✔
946
    }
224✔
947

948
    return SIXEL_OK;
958✔
949
}
510✔
950

951
/* emacs Local Variables:      */
952
/* emacs mode: c               */
953
/* emacs tab-width: 4          */
954
/* emacs indent-tabs-mode: nil */
955
/* emacs c-basic-offset: 4     */
956
/* emacs End:                  */
957
/* vim: set expandtab ts=4 sts=4 sw=4 : */
958
/* 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