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

saitoha / libsixel / 20466639304

23 Dec 2025 04:53PM UTC coverage: 51.46% (-6.3%) from 57.773%
20466639304

push

github

saitoha
build: fix windows find path in images meson build

14511 of 44933 branches covered (32.29%)

21089 of 40981 relevant lines covered (51.46%)

3915123.44 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
#include "config.h"
26

27
#if HAVE_MATH_H
28
# include <math.h>
29
#endif  /* HAVE_MATH_H */
30
#include <string.h>
31

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

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

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

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

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

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

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

130
    pos = y * width + x;
×
131
    forward = direction >= 0;
×
132

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

770
#if HAVE_TESTS
771
static int g_sixel_dither_float32_diffusion_hits = 0;
772

773
void
774
sixel_dither_diffusion_tests_reset_float32_hits(void)
×
775
{
776
    g_sixel_dither_float32_diffusion_hits = 0;
×
777
}
×
778

779
int
780
sixel_dither_diffusion_tests_float32_hits(void)
×
781
{
782
    return g_sixel_dither_float32_diffusion_hits;
×
783
}
784

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

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

842
    palette_float = NULL;
4✔
843
    new_palette_float = NULL;
4✔
844
    float_depth = 0;
4✔
845

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

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

874
    fast_lut = context->lut;
4✔
875
    use_fast_lut = (fast_lut != NULL);
4✔
876

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

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

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

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

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

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

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

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

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

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

1115
    status = SIXEL_OK;
4✔
1116
    SIXEL_DITHER_FLOAT32_HIT();
4✔
1117
    return status;
4✔
1118
}
1119

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