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

saitoha / libsixel / 20578371047

29 Dec 2025 05:06PM UTC coverage: 51.955% (-5.4%) from 57.322%
20578371047

push

github

saitoha
Revert "Merge branch 'refactor/pixelformat' into develop"

This reverts commit 4a6153922, reversing
changes made to 6f3ef3068.

14746 of 45077 branches covered (32.71%)

147 of 262 new or added lines in 15 files covered. (56.11%)

1406 existing lines in 46 files now uncovered.

21419 of 41226 relevant lines covered (51.96%)

3895522.67 hits per line

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

36.87
/src/dither-fixed-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 all
14
 * 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 <string.h>
33

34
#include "dither-fixed-float32.h"
35
#include "dither-common-pipeline.h"
36
#include "pixelformat.h"
37
#include "lookup-common.h"
38

39
typedef void (*diffuse_fixed_float_fn)(float *data,
40
                                       int width,
41
                                       int height,
42
                                       int x,
43
                                       int y,
44
                                       int depth,
45
                                       float error,
46
                                       int direction,
47
                                       int pixelformat,
48
                                       int channel_index);
49

50
static void
51
error_diffuse_float(float *data,
×
52
                    int pos,
53
                    int depth,
54
                    float error,
55
                    int numerator,
56
                    int denominator,
57
                    int pixelformat,
58
                    int channel_index)
59
{
60
    float *channel;
×
61
    float delta;
×
62

63
    channel = data + ((size_t)pos * (size_t)depth);
×
64
    delta = error * ((float)numerator / (float)denominator);
×
65
    *channel += delta;
×
66
    *channel = sixel_pixelformat_float_channel_clamp(pixelformat,
×
67
                                                     channel_index,
68
                                                     *channel);
69
}
×
70

71
static void
72
sixel_dither_scanline_params_fixed_float32(int serpentine,
12✔
73
                             int index,
74
                             int limit,
75
                             int *start,
76
                             int *end,
77
                             int *step,
78
                             int *direction)
79
{
UNCOV
80
    if (serpentine && (index & 1)) {
×
81
        *start = limit - 1;
×
82
        *end = -1;
×
83
        *step = -1;
×
84
        *direction = -1;
×
85
    } else {
86
        *start = 0;
87
        *end = limit;
88
        *step = 1;
89
        *direction = 1;
90
    }
91
}
92

93
static void
94
diffuse_none_float(float *data,
108✔
95
                   int width,
96
                   int height,
97
                   int x,
98
                   int y,
99
                   int depth,
100
                   float error,
101
                   int direction,
102
                   int pixelformat,
103
                   int channel_index)
104
{
105
    (void)data;
108✔
106
    (void)width;
108✔
107
    (void)height;
108✔
108
    (void)x;
108✔
109
    (void)y;
108✔
110
    (void)depth;
108✔
111
    (void)error;
108✔
112
    (void)direction;
108✔
113
    (void)pixelformat;
108✔
114
    (void)channel_index;
108✔
115
}
108✔
116

117
static void
118
diffuse_fs_float(float *data,
×
119
                 int width,
120
                 int height,
121
                 int x,
122
                 int y,
123
                 int depth,
124
                 float error,
125
                 int direction,
126
                 int pixelformat,
127
                 int channel_index)
128
{
129
    int pos;
×
130
    int forward;
×
131

132
    pos = y * width + x;
×
133
    forward = direction >= 0;
×
134

135
    if (forward) {
×
136
        if (x < width - 1) {
×
137
            error_diffuse_float(data,
×
138
                                pos + 1,
139
                                depth,
140
                                error,
141
                                7,
142
                                16,
143
                                pixelformat,
144
                                channel_index);
145
        }
146
        if (y < height - 1) {
×
147
            if (x > 0) {
×
148
                error_diffuse_float(data,
×
149
                                    pos + width - 1,
×
150
                                    depth,
151
                                    error,
152
                                    3,
153
                                    16,
154
                                    pixelformat,
155
                                    channel_index);
156
            }
157
            error_diffuse_float(data,
×
158
                                pos + width,
159
                                depth,
160
                                error,
161
                                5,
162
                                16,
163
                                pixelformat,
164
                                channel_index);
165
            if (x < width - 1) {
×
166
                error_diffuse_float(data,
×
167
                                    pos + width + 1,
168
                                    depth,
169
                                    error,
170
                                    1,
171
                                    16,
172
                                    pixelformat,
173
                                    channel_index);
174
            }
175
        }
176
    } else {
177
        if (x > 0) {
×
178
            error_diffuse_float(data,
×
179
                                pos - 1,
180
                                depth,
181
                                error,
182
                                7,
183
                                16,
184
                                pixelformat,
185
                                channel_index);
186
        }
187
        if (y < height - 1) {
×
188
            if (x < width - 1) {
×
189
                error_diffuse_float(data,
×
190
                                    pos + width + 1,
×
191
                                    depth,
192
                                    error,
193
                                    3,
194
                                    16,
195
                                    pixelformat,
196
                                    channel_index);
197
            }
198
            error_diffuse_float(data,
×
199
                                pos + width,
200
                                depth,
201
                                error,
202
                                5,
203
                                16,
204
                                pixelformat,
205
                                channel_index);
206
            if (x > 0) {
×
207
                error_diffuse_float(data,
×
208
                                    pos + width - 1,
209
                                    depth,
210
                                    error,
211
                                    1,
212
                                    16,
213
                                    pixelformat,
214
                                    channel_index);
215
            }
216
        }
217
    }
218
}
×
219

220
/*
221
 * Atkinson's kernel spreads the error within a 3x3 neighborhood using
222
 * symmetric 1/8 weights.  The float variant mirrors the integer version
223
 * but keeps the higher precision samples intact.
224
 */
225
static void
226
diffuse_atkinson_float(float *data,
×
227
                       int width,
228
                       int height,
229
                       int x,
230
                       int y,
231
                       int depth,
232
                       float error,
233
                       int direction,
234
                       int pixelformat,
235
                       int channel_index)
236
{
237
    int pos;
×
238
    int sign;
×
239
    int row;
×
240

241
    pos = y * width + x;
×
242
    sign = direction >= 0 ? 1 : -1;
×
243

244
    if (x + sign >= 0 && x + sign < width) {
×
245
        error_diffuse_float(data,
×
246
                            pos + sign,
247
                            depth,
248
                            error,
249
                            1,
250
                            8,
251
                            pixelformat,
252
                            channel_index);
253
    }
254
    if (x + sign * 2 >= 0 && x + sign * 2 < width) {
×
255
        error_diffuse_float(data,
×
256
                            pos + sign * 2,
257
                            depth,
258
                            error,
259
                            1,
260
                            8,
261
                            pixelformat,
262
                            channel_index);
263
    }
264
    if (y < height - 1) {
×
265
        row = pos + width;
×
266
        if (x - sign >= 0 && x - sign < width) {
×
267
            error_diffuse_float(data,
×
268
                                row - sign,
269
                                depth,
270
                                error,
271
                                1,
272
                                8,
273
                                pixelformat,
274
                                channel_index);
275
        }
276
        error_diffuse_float(data,
×
277
                            row,
278
                            depth,
279
                            error,
280
                            1,
281
                            8,
282
                            pixelformat,
283
                            channel_index);
284
        if (x + sign >= 0 && x + sign < width) {
×
285
            error_diffuse_float(data,
×
286
                                row + sign,
287
                                depth,
288
                                error,
289
                                1,
290
                                8,
291
                                pixelformat,
292
                                channel_index);
293
        }
294
    }
295
    if (y < height - 2) {
×
296
        error_diffuse_float(data,
×
297
                            pos + width * 2,
×
298
                            depth,
299
                            error,
300
                            1,
301
                            8,
302
                            pixelformat,
303
                            channel_index);
304
    }
305
}
×
306

307
/*
308
 * Shared helper that applies a row of diffusion weights to neighbors on the
309
 * current or subsequent scanlines.  Each caller provides the offset table and
310
 * numerator/denominator pairs so the classic kernels can be described using a
311
 * compact table instead of open-coded loops.
312
 */
313
static void
314
diffuse_weighted_row(float *data,
×
315
                     int pos,
316
                     int depth,
317
                     float error,
318
                     int direction,
319
                     int pixelformat,
320
                     int channel_index,
321
                     int x,
322
                     int width,
323
                     int row_offset,
324
                     int const *offsets,
325
                     int const *numerators,
326
                     int const *denominators,
327
                     int count)
328
{
329
    int i;
×
330
    int neighbor;
×
331
    int row_base;
×
332
    int sign;
×
333

334
    sign = direction >= 0 ? 1 : -1;
×
335
    row_base = pos + row_offset;
×
336
    for (i = 0; i < count; ++i) {
×
337
        neighbor = x + sign * offsets[i];
×
338
        if (neighbor < 0 || neighbor >= width) {
×
339
            continue;
×
340
        }
341
        error_diffuse_float(data,
×
342
                            row_base + (neighbor - x),
343
                            depth,
344
                            error,
345
                            numerators[i],
×
346
                            denominators[i],
×
347
                            pixelformat,
348
                            channel_index);
349
    }
350
}
×
351

352
/*
353
 * Jarvis, Judice, and Ninke kernel using the canonical 5x3 mask.  Three rows
354
 * of weights are applied with consistent 1/48 denominators to preserve the
355
 * reference diffusion matrix.
356
 */
357
static void
358
diffuse_jajuni_float(float *data,
×
359
                     int width,
360
                     int height,
361
                     int x,
362
                     int y,
363
                     int depth,
364
                     float error,
365
                     int direction,
366
                     int pixelformat,
367
                     int channel_index)
368
{
UNCOV
369
    static const int row0_offsets[] = { 1, 2 };
×
UNCOV
370
    static const int row0_num[] = { 7, 5 };
×
UNCOV
371
    static const int row0_den[] = { 48, 48 };
×
UNCOV
372
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
×
UNCOV
373
    static const int row1_num[] = { 3, 5, 7, 5, 3 };
×
UNCOV
374
    static const int row1_den[] = { 48, 48, 48, 48, 48 };
×
UNCOV
375
    static const int row2_offsets[] = { -2, -1, 0, 1, 2 };
×
UNCOV
376
    static const int row2_num[] = { 1, 3, 5, 3, 1 };
×
UNCOV
377
    static const int row2_den[] = { 48, 48, 48, 48, 48 };
×
378
    int pos;
×
379

380
    pos = y * width + x;
×
381
    diffuse_weighted_row(data,
×
382
                         pos,
383
                         depth,
384
                         error,
385
                         direction,
386
                         pixelformat,
387
                         channel_index,
388
                         x,
389
                         width,
390
                         0,
391
                         row0_offsets,
392
                         row0_num,
393
                         row0_den,
394
                         2);
395
    if (y < height - 1) {
×
396
        diffuse_weighted_row(data,
×
397
                             pos,
398
                             depth,
399
                             error,
400
                             direction,
401
                             pixelformat,
402
                             channel_index,
403
                             x,
404
                             width,
405
                             width,
406
                             row1_offsets,
407
                             row1_num,
408
                             row1_den,
409
                             5);
410
    }
411
    if (y < height - 2) {
×
412
        diffuse_weighted_row(data,
×
413
                             pos,
414
                             depth,
415
                             error,
416
                             direction,
417
                             pixelformat,
418
                             channel_index,
419
                             x,
420
                             width,
421
                             width * 2,
422
                             row2_offsets,
423
                             row2_num,
424
                             row2_den,
425
                             5);
426
    }
427
}
×
428

429
/*
430
 * Stucki's method spreads the error across a 5x3 neighborhood with larger
431
 * emphasis on closer pixels.  The numerators/denominators match the classic
432
 * 8/48, 4/48, and related fractions from the integer backend.
433
 */
434
static void
435
diffuse_stucki_float(float *data,
×
436
                     int width,
437
                     int height,
438
                     int x,
439
                     int y,
440
                     int depth,
441
                     float error,
442
                     int direction,
443
                     int pixelformat,
444
                     int channel_index)
445
{
UNCOV
446
    static const int row0_offsets[] = { 1, 2 };
×
UNCOV
447
    static const int row0_num[] = { 1, 1 };
×
UNCOV
448
    static const int row0_den[] = { 6, 12 };
×
UNCOV
449
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
×
UNCOV
450
    static const int row1_num[] = { 1, 1, 1, 1, 1 };
×
UNCOV
451
    static const int row1_den[] = { 24, 12, 6, 12, 24 };
×
UNCOV
452
    static const int row2_offsets[] = { -2, -1, 0, 1, 2 };
×
UNCOV
453
    static const int row2_num[] = { 1, 1, 1, 1, 1 };
×
UNCOV
454
    static const int row2_den[] = { 48, 24, 12, 24, 48 };
×
455
    int pos;
×
456

457
    pos = y * width + x;
×
458
    diffuse_weighted_row(data,
×
459
                         pos,
460
                         depth,
461
                         error,
462
                         direction,
463
                         pixelformat,
464
                         channel_index,
465
                         x,
466
                         width,
467
                         0,
468
                         row0_offsets,
469
                         row0_num,
470
                         row0_den,
471
                         2);
472
    if (y < height - 1) {
×
473
        diffuse_weighted_row(data,
×
474
                             pos,
475
                             depth,
476
                             error,
477
                             direction,
478
                             pixelformat,
479
                             channel_index,
480
                             x,
481
                             width,
482
                             width,
483
                             row1_offsets,
484
                             row1_num,
485
                             row1_den,
486
                             5);
487
    }
488
    if (y < height - 2) {
×
489
        diffuse_weighted_row(data,
×
490
                             pos,
491
                             depth,
492
                             error,
493
                             direction,
494
                             pixelformat,
495
                             channel_index,
496
                             x,
497
                             width,
498
                             width * 2,
499
                             row2_offsets,
500
                             row2_num,
501
                             row2_den,
502
                             5);
503
    }
504
}
×
505

506
/*
507
 * Burkes' kernel limits the spread to two rows to reduce directional artifacts
508
 * while keeping the symmetric 1/16-4/16 pattern.
509
 */
510
static void
511
diffuse_burkes_float(float *data,
×
512
                     int width,
513
                     int height,
514
                     int x,
515
                     int y,
516
                     int depth,
517
                     float error,
518
                     int direction,
519
                     int pixelformat,
520
                     int channel_index)
521
{
UNCOV
522
    static const int row0_offsets[] = { 1, 2 };
×
UNCOV
523
    static const int row0_num[] = { 1, 1 };
×
UNCOV
524
    static const int row0_den[] = { 4, 8 };
×
UNCOV
525
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
×
UNCOV
526
    static const int row1_num[] = { 1, 1, 1, 1, 1 };
×
UNCOV
527
    static const int row1_den[] = { 16, 8, 4, 8, 16 };
×
528
    int pos;
×
529

530
    pos = y * width + x;
×
531
    diffuse_weighted_row(data,
×
532
                         pos,
533
                         depth,
534
                         error,
535
                         direction,
536
                         pixelformat,
537
                         channel_index,
538
                         x,
539
                         width,
540
                         0,
541
                         row0_offsets,
542
                         row0_num,
543
                         row0_den,
544
                         2);
545
    if (y < height - 1) {
×
546
        diffuse_weighted_row(data,
×
547
                             pos,
548
                             depth,
549
                             error,
550
                             direction,
551
                             pixelformat,
552
                             channel_index,
553
                             x,
554
                             width,
555
                             width,
556
                             row1_offsets,
557
                             row1_num,
558
                             row1_den,
559
                             5);
560
    }
561
}
×
562

563
/*
564
 * Sierra Lite (Sierra1) uses a compact 2x2 mask to reduce ringing while
565
 * keeping serpentine traversal stable.
566
 */
567
static void
568
diffuse_sierra1_float(float *data,
×
569
                      int width,
570
                      int height,
571
                      int x,
572
                      int y,
573
                      int depth,
574
                      float error,
575
                      int direction,
576
                      int pixelformat,
577
                      int channel_index)
578
{
UNCOV
579
    static const int row0_offsets[] = { 1 };
×
UNCOV
580
    static const int row0_num[] = { 1 };
×
UNCOV
581
    static const int row0_den[] = { 2 };
×
UNCOV
582
    static const int row1_offsets[] = { -1, 0 };
×
UNCOV
583
    static const int row1_num[] = { 1, 1 };
×
UNCOV
584
    static const int row1_den[] = { 4, 4 };
×
585
    int pos;
×
586

587
    pos = y * width + x;
×
588
    diffuse_weighted_row(data,
×
589
                         pos,
590
                         depth,
591
                         error,
592
                         direction,
593
                         pixelformat,
594
                         channel_index,
595
                         x,
596
                         width,
597
                         0,
598
                         row0_offsets,
599
                         row0_num,
600
                         row0_den,
601
                         1);
602
    if (y < height - 1) {
×
603
        diffuse_weighted_row(data,
×
604
                             pos,
605
                             depth,
606
                             error,
607
                             direction,
608
                             pixelformat,
609
                             channel_index,
610
                             x,
611
                             width,
612
                             width,
613
                             row1_offsets,
614
                             row1_num,
615
                             row1_den,
616
                             2);
617
    }
618
}
×
619

620
/*
621
 * Sierra Two-row keeps the full 5x3 footprint but halves the lower row weights
622
 * relative to Sierra-3, matching the 32-denominator formulation.
623
 */
624
static void
625
diffuse_sierra2_float(float *data,
×
626
                      int width,
627
                      int height,
628
                      int x,
629
                      int y,
630
                      int depth,
631
                      float error,
632
                      int direction,
633
                      int pixelformat,
634
                      int channel_index)
635
{
UNCOV
636
    static const int row0_offsets[] = { 1, 2 };
×
UNCOV
637
    static const int row0_num[] = { 4, 3 };
×
UNCOV
638
    static const int row0_den[] = { 32, 32 };
×
UNCOV
639
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
×
UNCOV
640
    static const int row1_num[] = { 1, 2, 3, 2, 1 };
×
UNCOV
641
    static const int row1_den[] = { 32, 32, 32, 32, 32 };
×
UNCOV
642
    static const int row2_offsets[] = { -1, 0, 1 };
×
UNCOV
643
    static const int row2_num[] = { 2, 3, 2 };
×
UNCOV
644
    static const int row2_den[] = { 32, 32, 32 };
×
645
    int pos;
×
646

647
    pos = y * width + x;
×
648
    diffuse_weighted_row(data,
×
649
                         pos,
650
                         depth,
651
                         error,
652
                         direction,
653
                         pixelformat,
654
                         channel_index,
655
                         x,
656
                         width,
657
                         0,
658
                         row0_offsets,
659
                         row0_num,
660
                         row0_den,
661
                         2);
662
    if (y < height - 1) {
×
663
        diffuse_weighted_row(data,
×
664
                             pos,
665
                             depth,
666
                             error,
667
                             direction,
668
                             pixelformat,
669
                             channel_index,
670
                             x,
671
                             width,
672
                             width,
673
                             row1_offsets,
674
                             row1_num,
675
                             row1_den,
676
                             5);
677
    }
678
    if (y < height - 2) {
×
679
        diffuse_weighted_row(data,
×
680
                             pos,
681
                             depth,
682
                             error,
683
                             direction,
684
                             pixelformat,
685
                             channel_index,
686
                             x,
687
                             width,
688
                             width * 2,
689
                             row2_offsets,
690
                             row2_num,
691
                             row2_den,
692
                             3);
693
    }
694
}
×
695

696
/*
697
 * Sierra-3 restores the heavier middle-row contributions (5/32) that
698
 * characterize the original kernel.
699
 */
700
static void
701
diffuse_sierra3_float(float *data,
×
702
                      int width,
703
                      int height,
704
                      int x,
705
                      int y,
706
                      int depth,
707
                      float error,
708
                      int direction,
709
                      int pixelformat,
710
                      int channel_index)
711
{
UNCOV
712
    static const int row0_offsets[] = { 1, 2 };
×
UNCOV
713
    static const int row0_num[] = { 5, 3 };
×
UNCOV
714
    static const int row0_den[] = { 32, 32 };
×
UNCOV
715
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
×
UNCOV
716
    static const int row1_num[] = { 2, 4, 5, 4, 2 };
×
UNCOV
717
    static const int row1_den[] = { 32, 32, 32, 32, 32 };
×
UNCOV
718
    static const int row2_offsets[] = { -1, 0, 1 };
×
UNCOV
719
    static const int row2_num[] = { 2, 3, 2 };
×
UNCOV
720
    static const int row2_den[] = { 32, 32, 32 };
×
721
    int pos;
×
722

723
    pos = y * width + x;
×
724
    diffuse_weighted_row(data,
×
725
                         pos,
726
                         depth,
727
                         error,
728
                         direction,
729
                         pixelformat,
730
                         channel_index,
731
                         x,
732
                         width,
733
                         0,
734
                         row0_offsets,
735
                         row0_num,
736
                         row0_den,
737
                         2);
738
    if (y < height - 1) {
×
739
        diffuse_weighted_row(data,
×
740
                             pos,
741
                             depth,
742
                             error,
743
                             direction,
744
                             pixelformat,
745
                             channel_index,
746
                             x,
747
                             width,
748
                             width,
749
                             row1_offsets,
750
                             row1_num,
751
                             row1_den,
752
                             5);
753
    }
754
    if (y < height - 2) {
×
755
        diffuse_weighted_row(data,
×
756
                             pos,
757
                             depth,
758
                             error,
759
                             direction,
760
                             pixelformat,
761
                             channel_index,
762
                             x,
763
                             width,
764
                             width * 2,
765
                             row2_offsets,
766
                             row2_num,
767
                             row2_den,
768
                             3);
769
    }
770
}
×
771

772
#if HAVE_TESTS
773
static int g_sixel_dither_float32_diffusion_hits = 0;
774

775
void
776
sixel_dither_diffusion_tests_reset_float32_hits(void)
×
777
{
778
    g_sixel_dither_float32_diffusion_hits = 0;
×
779
}
×
780

781
int
782
sixel_dither_diffusion_tests_float32_hits(void)
×
783
{
784
    return g_sixel_dither_float32_diffusion_hits;
×
785
}
786

787
#define SIXEL_DITHER_FLOAT32_HIT()                                      \
788
    do {                                                                \
789
        ++g_sixel_dither_float32_diffusion_hits;                        \
790
    } while (0)
791
#else
792
#define SIXEL_DITHER_FLOAT32_HIT()                                      \
793
    do {                                                                \
794
    } while (0)
795
#endif
796

797
SIXELSTATUS
798
sixel_dither_apply_fixed_float32(sixel_dither_t *dither,
4✔
799
                                 sixel_dither_context_t *context)
800
{
4✔
801
#if _MSC_VER
802
    enum { max_channels = 4 };
803
#else
804
    const int max_channels = 4;
4✔
805
#endif
806
    SIXELSTATUS status;
4✔
807
    float *palette_float;
4✔
808
    float *new_palette_float;
4✔
809
    int float_depth;
4✔
810
    int serpentine;
4✔
811
    int y;
4✔
812
    int absolute_y;
4✔
813
    int start;
4✔
814
    int end;
4✔
815
    int step;
4✔
816
    int direction;
4✔
817
    int x;
4✔
818
    int pos;
4✔
819
    size_t base;
4✔
820
    float *source_pixel;
4✔
821
    unsigned char quantized[max_channels];
4✔
822
    float snapshot[max_channels];
4✔
823
    float lookup_pixel_float[max_channels];
4✔
824
    int color_index;
4✔
825
    int output_index;
4✔
826
    int palette_value;
4✔
827
    float palette_value_float;
4✔
828
    float error;
4✔
829
    int n;
4✔
830
    float *data;
4✔
831
    unsigned char *palette;
4✔
832
    int float_index;
4✔
833
    int lookup_wants_float;
4✔
834
    int use_palette_float_lookup;
4✔
835
    int need_float_pixel;
4✔
836
    unsigned char const *lookup_pixel;
4✔
837
    sixel_lut_t *fast_lut;
4✔
838
    int use_fast_lut;
4✔
839
    int have_palette_float;
4✔
840
    int have_new_palette_float;
4✔
841
    diffuse_fixed_float_fn f_diffuse;
4✔
842
    int method_for_diffuse;
4✔
843

844
    palette_float = NULL;
4✔
845
    new_palette_float = NULL;
4✔
846
    float_depth = 0;
4✔
847

848
    if (dither == NULL || context == NULL) {
4!
849
        return SIXEL_BAD_ARGUMENT;
850
    }
851
    data = context->pixels_float;
4✔
852
    if (data == NULL || context->palette == NULL) {
4!
853
        return SIXEL_BAD_ARGUMENT;
854
    }
855
    if (context->result == NULL || context->new_palette == NULL) {
4!
856
        return SIXEL_BAD_ARGUMENT;
857
    }
858
    if (context->migration_map == NULL || context->ncolors == NULL) {
4!
859
        return SIXEL_BAD_ARGUMENT;
860
    }
861
    if (context->lookup == NULL) {
4!
862
        return SIXEL_BAD_ARGUMENT;
863
    }
864

865
    palette = context->palette;
4✔
866
    palette_float = context->palette_float;
4✔
867
    new_palette_float = context->new_palette_float;
4✔
868
    float_depth = context->float_depth;
4✔
869
    if (context->depth > max_channels || context->depth != 3) {
4!
870
        return SIXEL_BAD_ARGUMENT;
871
    }
872
    if (context->reqcolor < 1) {
4!
873
        return SIXEL_BAD_ARGUMENT;
874
    }
875

876
    fast_lut = context->lut;
4✔
877
    use_fast_lut = (fast_lut != NULL);
4✔
878

879
    serpentine = (context->method_for_scan == SIXEL_SCAN_SERPENTINE);
4✔
880
    lookup_wants_float = (context->lookup_source_is_float != 0);
4✔
881
    use_palette_float_lookup = 0;
4✔
882
    if (context->prefer_palette_float_lookup != 0
4!
UNCOV
883
            && palette_float != NULL
×
884
            && float_depth >= context->depth) {
×
885
        use_palette_float_lookup = 1;
4✔
886
    }
887
    need_float_pixel = lookup_wants_float || use_palette_float_lookup;
8!
888

889
    /*
890
     * Remember whether each palette buffer exposes float32 components so
891
     * later loops can preserve precision instead of converting back to
892
     * bytes before computing the diffusion error.
893
     */
894
    if (palette_float != NULL && float_depth >= context->depth) {
4!
895
        have_palette_float = 1;
896
    } else {
897
        have_palette_float = 0;
898
    }
899
    if (new_palette_float != NULL && float_depth >= context->depth) {
4!
900
        have_new_palette_float = 1;
901
    } else {
902
        have_new_palette_float = 0;
4✔
903
    }
904

905
    method_for_diffuse = context->method_for_diffuse;
4✔
906
    switch (method_for_diffuse) {
4!
907
    case SIXEL_DIFFUSE_NONE:
908
        f_diffuse = diffuse_none_float;
909
        break;
UNCOV
910
    case SIXEL_DIFFUSE_ATKINSON:
×
911
        f_diffuse = diffuse_atkinson_float;
×
912
        break;
×
UNCOV
913
    case SIXEL_DIFFUSE_JAJUNI:
×
914
        f_diffuse = diffuse_jajuni_float;
×
915
        break;
×
UNCOV
916
    case SIXEL_DIFFUSE_STUCKI:
×
917
        f_diffuse = diffuse_stucki_float;
×
918
        break;
×
UNCOV
919
    case SIXEL_DIFFUSE_BURKES:
×
920
        f_diffuse = diffuse_burkes_float;
×
921
        break;
×
UNCOV
922
    case SIXEL_DIFFUSE_SIERRA1:
×
923
        f_diffuse = diffuse_sierra1_float;
×
924
        break;
×
UNCOV
925
    case SIXEL_DIFFUSE_SIERRA2:
×
926
        f_diffuse = diffuse_sierra2_float;
×
927
        break;
×
UNCOV
928
    case SIXEL_DIFFUSE_SIERRA3:
×
929
        f_diffuse = diffuse_sierra3_float;
×
930
        break;
×
931
    case SIXEL_DIFFUSE_FS:
×
932
    default:
933
        f_diffuse = diffuse_fs_float;
×
934
        break;
×
935
    }
936

937
    if (context->optimize_palette) {
4!
938
        *context->ncolors = 0;
4✔
939
        memset(context->new_palette, 0x00,
4✔
940
               (size_t)SIXEL_PALETTE_MAX * (size_t)context->depth);
4!
941
        if (new_palette_float != NULL && float_depth > 0) {
4!
942
            memset(new_palette_float, 0x00,
×
943
                   (size_t)SIXEL_PALETTE_MAX
UNCOV
944
                       * (size_t)float_depth * sizeof(float));
×
945
        }
946
        memset(context->migration_map, 0x00,
4✔
947
               sizeof(unsigned short) * (size_t)SIXEL_PALETTE_MAX);
948
    } else {
949
        *context->ncolors = context->reqcolor;
×
950
    }
951

952
    for (y = 0; y < context->height; ++y) {
16✔
953
        absolute_y = context->band_origin + y;
12✔
954
        sixel_dither_scanline_params_fixed_float32(serpentine, absolute_y,
12!
955
                                     context->width,
956
                                     &start, &end, &step, &direction);
957
        for (x = start; x != end; x += step) {
48✔
958
            pos = y * context->width + x;
36✔
959
            base = (size_t)pos * (size_t)context->depth;
36✔
960
            source_pixel = data + base;
36✔
961

962
            for (n = 0; n < context->depth; ++n) {
144✔
963
                snapshot[n] = source_pixel[n];
108✔
964
                if (need_float_pixel) {
108!
965
                    lookup_pixel_float[n] = source_pixel[n];
108✔
966
                }
967
                if (!lookup_wants_float && !use_palette_float_lookup) {
108!
968
                    quantized[n]
×
969
                        = sixel_pixelformat_float_channel_to_byte(
×
970
                              context->pixelformat,
971
                              n,
972
                              source_pixel[n]);
973
                }
974
            }
975

976
            if (lookup_wants_float) {
36!
977
                lookup_pixel = (unsigned char const *)(void const *)source_pixel;
36✔
978
                if (use_fast_lut) {
36!
979
                    color_index = sixel_lut_map_pixel(fast_lut,
36✔
980
                                                     lookup_pixel);
981
                } else {
982
                    color_index = context->lookup(lookup_pixel,
×
983
                                                  context->depth,
984
                                                  palette,
985
                                                  context->reqcolor,
986
                                                  context->indextable,
987
                                                  context->complexion);
988
                }
UNCOV
989
            } else if (use_palette_float_lookup) {
×
990
                color_index = sixel_dither_lookup_palette_float32(
×
991
                    lookup_pixel_float,
992
                    context->depth,
993
                    palette_float,
994
                    context->reqcolor,
995
                    context->complexion);
996
            } else {
997
                lookup_pixel = quantized;
×
998
                if (use_fast_lut) {
×
999
                    color_index = sixel_lut_map_pixel(fast_lut,
×
1000
                                                     lookup_pixel);
1001
                } else {
1002
                    color_index = context->lookup(lookup_pixel,
×
1003
                                                  context->depth,
1004
                                                  palette,
1005
                                                  context->reqcolor,
1006
                                                  context->indextable,
1007
                                                  context->complexion);
1008
                }
1009
            }
1010

1011
            if (context->optimize_palette) {
36!
1012
                    if (context->migration_map[color_index] == 0) {
36✔
1013
                        output_index = *context->ncolors;
12✔
1014
                        for (n = 0; n < context->depth; ++n) {
48✔
1015
                            context->new_palette[output_index * context->depth + n]
36✔
1016
                                = palette[color_index * context->depth + n];
36✔
1017
                    }
1018
                    if (palette_float != NULL
12!
1019
                            && new_palette_float != NULL
12!
1020
                            && float_depth > 0) {
×
1021
                        for (float_index = 0;
×
1022
                                float_index < float_depth;
×
1023
                                ++float_index) {
×
1024
                            new_palette_float[output_index * float_depth
×
1025
                                              + float_index]
×
1026
                                = palette_float[color_index * float_depth
×
1027
                                                + float_index];
×
1028
                        }
1029
                    }
1030
                    ++*context->ncolors;
12✔
1031
                    /*
1032
                     * The palette count never exceeds SIXEL_PALETTE_MAX (256),
1033
                     * so storing it in an unsigned short is safe.
1034
                     */
1035
                    context->migration_map[color_index]
12✔
1036
                        = (unsigned short)(*context->ncolors);
12✔
1037
                } else {
1038
                    output_index = context->migration_map[color_index] - 1;
24✔
1039
                }
1040
                if (absolute_y >= context->output_start) {
36!
1041
                    /*
1042
                     * Palette indices are bounded by SIXEL_PALETTE_MAX, which
1043
                     * fits in sixel_index_t (unsigned char).
1044
                     */
1045
                    context->result[pos] = (sixel_index_t)output_index;
36✔
1046
                }
1047
            } else {
1048
                output_index = color_index;
×
1049
                if (absolute_y >= context->output_start) {
×
1050
                    context->result[pos] = (sixel_index_t)output_index;
×
1051
                }
1052
            }
1053

1054
            for (n = 0; n < context->depth; ++n) {
144✔
1055
                if (context->optimize_palette) {
108!
1056
                    if (have_new_palette_float) {
108!
1057
                        palette_value_float =
×
1058
                            new_palette_float[output_index * float_depth
×
1059
                                              + n];
×
1060
                    } else {
1061
                        palette_value =
108✔
1062
                            context->new_palette[output_index
108✔
1063
                                                 * context->depth + n];
108✔
1064
                        palette_value_float
108✔
1065
                            = sixel_pixelformat_byte_to_float(
108✔
1066
                                  context->pixelformat,
1067
                                  n,
1068
                                  (unsigned char)palette_value);
1069
                    }
1070
                } else {
1071
                    if (have_palette_float) {
×
1072
                        palette_value_float =
×
1073
                            palette_float[color_index * float_depth + n];
×
1074
                    } else {
1075
                        palette_value =
×
1076
                            palette[color_index * context->depth + n];
×
UNCOV
1077
                        palette_value_float
×
1078
                            = sixel_pixelformat_byte_to_float(
×
1079
                                  context->pixelformat,
1080
                                  n,
1081
                                  (unsigned char)palette_value);
1082
                    }
1083
                }
1084
                error = snapshot[n] - palette_value_float;
108✔
1085
                source_pixel[n] = palette_value_float;
108✔
1086
                f_diffuse(data + (size_t)n,
108✔
1087
                          context->width,
1088
                          context->height,
1089
                          x,
1090
                          y,
1091
                          context->depth,
1092
                          error,
1093
                          direction,
1094
                          context->pixelformat,
1095
                          n);
1096
            }
1097
        }
1098
        if (absolute_y >= context->output_start) {
12!
1099
            sixel_dither_pipeline_row_notify(dither, absolute_y);
12✔
1100
        }
1101
    }
1102

1103
    if (context->optimize_palette) {
4!
1104
        memcpy(context->palette,
4✔
1105
               context->new_palette,
4✔
1106
               (size_t)(*context->ncolors * context->depth));
4!
1107
        if (palette_float != NULL
4!
1108
                && new_palette_float != NULL
4!
1109
                && float_depth > 0) {
×
1110
            memcpy(palette_float,
×
1111
                   new_palette_float,
UNCOV
1112
                   (size_t)(*context->ncolors * float_depth)
×
1113
                       * sizeof(float));
1114
        }
1115
    }
1116

1117
    status = SIXEL_OK;
4✔
1118
    SIXEL_DITHER_FLOAT32_HIT();
4✔
1119
    return status;
4✔
1120
}
1121

1122
/* emacs Local Variables:      */
1123
/* emacs mode: c               */
1124
/* emacs tab-width: 4          */
1125
/* emacs indent-tabs-mode: nil */
1126
/* emacs c-basic-offset: 4     */
1127
/* emacs End:                  */
1128
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1129
/* 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