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

saitoha / libsixel / 20807202489

08 Jan 2026 06:00AM UTC coverage: 55.056% (-1.6%) from 56.69%
20807202489

push

github

saitoha
ci: refine

20832 of 37838 relevant lines covered (55.06%)

1153472.81 hits per line

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

36.76
/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,
9✔
73
                             int index,
74
                             int limit,
75
                             int *start,
76
                             int *end,
77
                             int *step,
78
                             int *direction)
79
{
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,
81✔
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;
81✔
106
    (void)width;
81✔
107
    (void)height;
81✔
108
    (void)x;
81✔
109
    (void)y;
81✔
110
    (void)depth;
81✔
111
    (void)error;
81✔
112
    (void)direction;
81✔
113
    (void)pixelformat;
81✔
114
    (void)channel_index;
81✔
115
}
81✔
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
{
369
    static const int row0_offsets[] = { 1, 2 };
×
370
    static const int row0_num[] = { 7, 5 };
×
371
    static const int row0_den[] = { 48, 48 };
×
372
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
×
373
    static const int row1_num[] = { 3, 5, 7, 5, 3 };
×
374
    static const int row1_den[] = { 48, 48, 48, 48, 48 };
×
375
    static const int row2_offsets[] = { -2, -1, 0, 1, 2 };
×
376
    static const int row2_num[] = { 1, 3, 5, 3, 1 };
×
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
{
446
    static const int row0_offsets[] = { 1, 2 };
×
447
    static const int row0_num[] = { 1, 1 };
×
448
    static const int row0_den[] = { 6, 12 };
×
449
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
×
450
    static const int row1_num[] = { 1, 1, 1, 1, 1 };
×
451
    static const int row1_den[] = { 24, 12, 6, 12, 24 };
×
452
    static const int row2_offsets[] = { -2, -1, 0, 1, 2 };
×
453
    static const int row2_num[] = { 1, 1, 1, 1, 1 };
×
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
{
522
    static const int row0_offsets[] = { 1, 2 };
×
523
    static const int row0_num[] = { 1, 1 };
×
524
    static const int row0_den[] = { 4, 8 };
×
525
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
×
526
    static const int row1_num[] = { 1, 1, 1, 1, 1 };
×
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
{
579
    static const int row0_offsets[] = { 1 };
×
580
    static const int row0_num[] = { 1 };
×
581
    static const int row0_den[] = { 2 };
×
582
    static const int row1_offsets[] = { -1, 0 };
×
583
    static const int row1_num[] = { 1, 1 };
×
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
{
636
    static const int row0_offsets[] = { 1, 2 };
×
637
    static const int row0_num[] = { 4, 3 };
×
638
    static const int row0_den[] = { 32, 32 };
×
639
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
×
640
    static const int row1_num[] = { 1, 2, 3, 2, 1 };
×
641
    static const int row1_den[] = { 32, 32, 32, 32, 32 };
×
642
    static const int row2_offsets[] = { -1, 0, 1 };
×
643
    static const int row2_num[] = { 2, 3, 2 };
×
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
{
712
    static const int row0_offsets[] = { 1, 2 };
×
713
    static const int row0_num[] = { 5, 3 };
×
714
    static const int row0_den[] = { 32, 32 };
×
715
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
×
716
    static const int row1_num[] = { 2, 4, 5, 4, 2 };
×
717
    static const int row1_den[] = { 32, 32, 32, 32, 32 };
×
718
    static const int row2_offsets[] = { -1, 0, 1 };
×
719
    static const int row2_num[] = { 2, 3, 2 };
×
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

773
SIXELSTATUS
774
sixel_dither_apply_fixed_float32(sixel_dither_t *dither,
3✔
775
                                 sixel_dither_context_t *context)
776
{
3✔
777
#if _MSC_VER
778
    enum { max_channels = 4 };
779
#else
780
    const int max_channels = 4;
3✔
781
#endif
782
    SIXELSTATUS status;
3✔
783
    float *palette_float;
3✔
784
    float *new_palette_float;
3✔
785
    int float_depth;
3✔
786
    int serpentine;
3✔
787
    int y;
3✔
788
    int absolute_y;
3✔
789
    int start;
3✔
790
    int end;
3✔
791
    int step;
3✔
792
    int direction;
3✔
793
    int x;
3✔
794
    int pos;
3✔
795
    size_t base;
3✔
796
    float *source_pixel;
3✔
797
    unsigned char quantized[max_channels];
3✔
798
    float snapshot[max_channels];
3✔
799
    float lookup_pixel_float[max_channels];
3✔
800
    int color_index;
3✔
801
    int output_index;
3✔
802
    int palette_value;
3✔
803
    float palette_value_float;
3✔
804
    float error;
3✔
805
    int n;
3✔
806
    float *data;
3✔
807
    unsigned char *palette;
3✔
808
    int float_index;
3✔
809
    int lookup_wants_float;
3✔
810
    int use_palette_float_lookup;
3✔
811
    int need_float_pixel;
3✔
812
    unsigned char const *lookup_pixel;
3✔
813
    sixel_lut_t *fast_lut;
3✔
814
    int use_fast_lut;
3✔
815
    int have_palette_float;
3✔
816
    int have_new_palette_float;
3✔
817
    diffuse_fixed_float_fn f_diffuse;
3✔
818
    int method_for_diffuse;
3✔
819

820
    palette_float = NULL;
3✔
821
    new_palette_float = NULL;
3✔
822
    float_depth = 0;
3✔
823

824
    if (dither == NULL || context == NULL) {
3✔
825
        return SIXEL_BAD_ARGUMENT;
826
    }
827
    data = context->pixels_float;
3✔
828
    if (data == NULL || context->palette == NULL) {
3✔
829
        return SIXEL_BAD_ARGUMENT;
830
    }
831
    if (context->result == NULL || context->new_palette == NULL) {
3✔
832
        return SIXEL_BAD_ARGUMENT;
833
    }
834
    if (context->migration_map == NULL || context->ncolors == NULL) {
3✔
835
        return SIXEL_BAD_ARGUMENT;
836
    }
837
    if (context->lookup == NULL) {
3✔
838
        return SIXEL_BAD_ARGUMENT;
839
    }
840

841
    palette = context->palette;
3✔
842
    palette_float = context->palette_float;
3✔
843
    new_palette_float = context->new_palette_float;
3✔
844
    float_depth = context->float_depth;
3✔
845
    if (context->depth > max_channels || context->depth != 3) {
3✔
846
        return SIXEL_BAD_ARGUMENT;
847
    }
848
    if (context->reqcolor < 1) {
3✔
849
        return SIXEL_BAD_ARGUMENT;
850
    }
851

852
    fast_lut = context->lut;
3✔
853
    use_fast_lut = (fast_lut != NULL);
3✔
854

855
    serpentine = (context->method_for_scan == SIXEL_SCAN_SERPENTINE);
3✔
856
    lookup_wants_float = (context->lookup_source_is_float != 0);
3✔
857
    use_palette_float_lookup = 0;
3✔
858
    if (context->prefer_palette_float_lookup != 0
3✔
859
            && palette_float != NULL
×
860
            && float_depth >= context->depth) {
×
861
        use_palette_float_lookup = 1;
3✔
862
    }
863
    need_float_pixel = lookup_wants_float || use_palette_float_lookup;
6✔
864

865
    /*
866
     * Remember whether each palette buffer exposes float32 components so
867
     * later loops can preserve precision instead of converting back to
868
     * bytes before computing the diffusion error.
869
     */
870
    if (palette_float != NULL && float_depth >= context->depth) {
3✔
871
        have_palette_float = 1;
872
    } else {
873
        have_palette_float = 0;
874
    }
875
    if (new_palette_float != NULL && float_depth >= context->depth) {
3✔
876
        have_new_palette_float = 1;
877
    } else {
878
        have_new_palette_float = 0;
3✔
879
    }
880

881
    method_for_diffuse = context->method_for_diffuse;
3✔
882
    switch (method_for_diffuse) {
3✔
883
    case SIXEL_DIFFUSE_NONE:
884
        f_diffuse = diffuse_none_float;
885
        break;
886
    case SIXEL_DIFFUSE_ATKINSON:
×
887
        f_diffuse = diffuse_atkinson_float;
×
888
        break;
×
889
    case SIXEL_DIFFUSE_JAJUNI:
×
890
        f_diffuse = diffuse_jajuni_float;
×
891
        break;
×
892
    case SIXEL_DIFFUSE_STUCKI:
×
893
        f_diffuse = diffuse_stucki_float;
×
894
        break;
×
895
    case SIXEL_DIFFUSE_BURKES:
×
896
        f_diffuse = diffuse_burkes_float;
×
897
        break;
×
898
    case SIXEL_DIFFUSE_SIERRA1:
×
899
        f_diffuse = diffuse_sierra1_float;
×
900
        break;
×
901
    case SIXEL_DIFFUSE_SIERRA2:
×
902
        f_diffuse = diffuse_sierra2_float;
×
903
        break;
×
904
    case SIXEL_DIFFUSE_SIERRA3:
×
905
        f_diffuse = diffuse_sierra3_float;
×
906
        break;
×
907
    case SIXEL_DIFFUSE_FS:
×
908
    default:
909
        f_diffuse = diffuse_fs_float;
×
910
        break;
×
911
    }
912

913
    if (context->optimize_palette) {
3✔
914
        *context->ncolors = 0;
3✔
915
        memset(context->new_palette, 0x00,
3✔
916
               (size_t)SIXEL_PALETTE_MAX * (size_t)context->depth);
3✔
917
        if (new_palette_float != NULL && float_depth > 0) {
3✔
918
            memset(new_palette_float, 0x00,
×
919
                   (size_t)SIXEL_PALETTE_MAX
920
                       * (size_t)float_depth * sizeof(float));
×
921
        }
922
        memset(context->migration_map, 0x00,
3✔
923
               sizeof(unsigned short) * (size_t)SIXEL_PALETTE_MAX);
924
    } else {
925
        *context->ncolors = context->reqcolor;
×
926
    }
927

928
    for (y = 0; y < context->height; ++y) {
12✔
929
        absolute_y = context->band_origin + y;
9✔
930
        sixel_dither_scanline_params_fixed_float32(serpentine, absolute_y,
9✔
931
                                     context->width,
932
                                     &start, &end, &step, &direction);
933
        for (x = start; x != end; x += step) {
36✔
934
            pos = y * context->width + x;
27✔
935
            base = (size_t)pos * (size_t)context->depth;
27✔
936
            source_pixel = data + base;
27✔
937

938
            for (n = 0; n < context->depth; ++n) {
108✔
939
                snapshot[n] = source_pixel[n];
81✔
940
                if (need_float_pixel) {
81✔
941
                    lookup_pixel_float[n] = source_pixel[n];
81✔
942
                }
943
                if (!lookup_wants_float && !use_palette_float_lookup) {
81✔
944
                    quantized[n]
×
945
                        = sixel_pixelformat_float_channel_to_byte(
×
946
                              context->pixelformat,
947
                              n,
948
                              source_pixel[n]);
949
                }
950
            }
951

952
            if (lookup_wants_float) {
27✔
953
                lookup_pixel = (unsigned char const *)(void const *)source_pixel;
27✔
954
                if (use_fast_lut) {
27✔
955
                    color_index = sixel_lut_map_pixel(fast_lut,
27✔
956
                                                     lookup_pixel);
957
                } else {
958
                    color_index = context->lookup(lookup_pixel,
×
959
                                                  context->depth,
960
                                                  palette,
961
                                                  context->reqcolor,
962
                                                  context->indextable,
963
                                                  context->complexion);
964
                }
965
            } else if (use_palette_float_lookup) {
×
966
                color_index = sixel_dither_lookup_palette_float32(
×
967
                    lookup_pixel_float,
968
                    context->depth,
969
                    palette_float,
970
                    context->reqcolor,
971
                    context->complexion,
972
                    0);
973
            } else {
974
                lookup_pixel = quantized;
×
975
                if (use_fast_lut) {
×
976
                    color_index = sixel_lut_map_pixel(fast_lut,
×
977
                                                     lookup_pixel);
978
                } else {
979
                    color_index = context->lookup(lookup_pixel,
×
980
                                                  context->depth,
981
                                                  palette,
982
                                                  context->reqcolor,
983
                                                  context->indextable,
984
                                                  context->complexion);
985
                }
986
            }
987

988
            if (context->optimize_palette) {
27✔
989
                    if (context->migration_map[color_index] == 0) {
27✔
990
                        output_index = *context->ncolors;
9✔
991
                        for (n = 0; n < context->depth; ++n) {
36✔
992
                            context->new_palette[output_index * context->depth + n]
27✔
993
                                = palette[color_index * context->depth + n];
27✔
994
                    }
995
                    if (palette_float != NULL
9✔
996
                            && new_palette_float != NULL
9✔
997
                            && float_depth > 0) {
×
998
                        for (float_index = 0;
999
                                float_index < float_depth;
×
1000
                                ++float_index) {
×
1001
                            new_palette_float[output_index * float_depth
×
1002
                                              + float_index]
×
1003
                                = palette_float[color_index * float_depth
×
1004
                                                + float_index];
×
1005
                        }
1006
                    }
1007
                    ++*context->ncolors;
9✔
1008
                    /*
1009
                     * The palette count never exceeds SIXEL_PALETTE_MAX (256),
1010
                     * so storing it in an unsigned short is safe.
1011
                     */
1012
                    context->migration_map[color_index]
9✔
1013
                        = (unsigned short)(*context->ncolors);
9✔
1014
                } else {
1015
                    output_index = context->migration_map[color_index] - 1;
18✔
1016
                }
1017
                if (absolute_y >= context->output_start) {
27✔
1018
                    /*
1019
                     * Palette indices are bounded by SIXEL_PALETTE_MAX, which
1020
                     * fits in sixel_index_t (unsigned char).
1021
                     */
1022
                    context->result[pos] = (sixel_index_t)output_index;
27✔
1023
                }
1024
            } else {
1025
                output_index = color_index;
×
1026
                if (absolute_y >= context->output_start) {
×
1027
                    context->result[pos] = (sixel_index_t)output_index;
×
1028
                }
1029
            }
1030

1031
            for (n = 0; n < context->depth; ++n) {
108✔
1032
                if (context->optimize_palette) {
81✔
1033
                    if (have_new_palette_float) {
81✔
1034
                        palette_value_float =
×
1035
                            new_palette_float[output_index * float_depth
×
1036
                                              + n];
×
1037
                    } else {
1038
                        palette_value =
81✔
1039
                            context->new_palette[output_index
81✔
1040
                                                 * context->depth + n];
81✔
1041
                        palette_value_float
81✔
1042
                            = sixel_pixelformat_byte_to_float(
81✔
1043
                                  context->pixelformat,
1044
                                  n,
1045
                                  (unsigned char)palette_value);
1046
                    }
1047
                } else {
1048
                    if (have_palette_float) {
×
1049
                        palette_value_float =
×
1050
                            palette_float[color_index * float_depth + n];
×
1051
                    } else {
1052
                        palette_value =
×
1053
                            palette[color_index * context->depth + n];
×
1054
                        palette_value_float
×
1055
                            = sixel_pixelformat_byte_to_float(
×
1056
                                  context->pixelformat,
1057
                                  n,
1058
                                  (unsigned char)palette_value);
1059
                    }
1060
                }
1061
                error = snapshot[n] - palette_value_float;
81✔
1062
                source_pixel[n] = palette_value_float;
81✔
1063
                f_diffuse(data + (size_t)n,
81✔
1064
                          context->width,
1065
                          context->height,
1066
                          x,
1067
                          y,
1068
                          context->depth,
1069
                          error,
1070
                          direction,
1071
                          context->pixelformat,
1072
                          n);
1073
            }
1074
        }
1075
        if (absolute_y >= context->output_start) {
9✔
1076
            sixel_dither_pipeline_row_notify(dither, absolute_y);
9✔
1077
        }
1078
    }
1079

1080
    if (context->optimize_palette) {
3✔
1081
        memcpy(context->palette,
3✔
1082
               context->new_palette,
3✔
1083
               (size_t)(*context->ncolors * context->depth));
3✔
1084
        if (palette_float != NULL
3✔
1085
                && new_palette_float != NULL
3✔
1086
                && float_depth > 0) {
×
1087
            memcpy(palette_float,
×
1088
                   new_palette_float,
1089
                   (size_t)(*context->ncolors * float_depth)
×
1090
                       * sizeof(float));
×
1091
        }
1092
    }
1093

1094
    status = SIXEL_OK;
1095
    return status;
1096
}
1097

1098
/* emacs Local Variables:      */
1099
/* emacs mode: c               */
1100
/* emacs tab-width: 4          */
1101
/* emacs indent-tabs-mode: nil */
1102
/* emacs c-basic-offset: 4     */
1103
/* emacs End:                  */
1104
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1105
/* 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