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

saitoha / libsixel / 19541344273

20 Nov 2025 03:02PM UTC coverage: 40.773% (-0.4%) from 41.21%
19541344273

push

github

saitoha
feat: initial prototyping for parallel dithering

9711 of 33880 branches covered (28.66%)

55 of 483 new or added lines in 10 files covered. (11.39%)

12 existing lines in 4 files now uncovered.

12720 of 31197 relevant lines covered (40.77%)

656879.66 hits per line

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

0.0
/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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

839
    palette_float = NULL;
×
840
    new_palette_float = NULL;
×
841
    float_depth = 0;
×
842

843
    if (dither == NULL || context == NULL) {
×
844
        return SIXEL_BAD_ARGUMENT;
×
845
    }
846
    data = context->pixels_float;
×
847
    if (data == NULL || context->palette == NULL) {
×
848
        return SIXEL_BAD_ARGUMENT;
×
849
    }
850
    if (context->result == NULL || context->new_palette == NULL) {
×
851
        return SIXEL_BAD_ARGUMENT;
×
852
    }
853
    if (context->migration_map == NULL || context->ncolors == NULL) {
×
854
        return SIXEL_BAD_ARGUMENT;
×
855
    }
856
    if (context->lookup == NULL) {
×
857
        return SIXEL_BAD_ARGUMENT;
×
858
    }
859

860
    palette = context->palette;
×
861
    palette_float = context->palette_float;
×
862
    new_palette_float = context->new_palette_float;
×
863
    float_depth = context->float_depth;
×
864
    if (context->depth > max_channels || context->depth != 3) {
×
865
        return SIXEL_BAD_ARGUMENT;
×
866
    }
867
    if (context->reqcolor < 1) {
×
868
        return SIXEL_BAD_ARGUMENT;
×
869
    }
870

871
    serpentine = (context->method_for_scan == SIXEL_SCAN_SERPENTINE);
×
872
    lookup_wants_float = (context->lookup_source_is_float != 0);
×
873
    use_palette_float_lookup = 0;
×
874
    if (context->prefer_palette_float_lookup != 0
×
875
            && palette_float != NULL
×
876
            && float_depth >= context->depth) {
×
877
        use_palette_float_lookup = 1;
×
878
    }
879
    need_float_pixel = lookup_wants_float || use_palette_float_lookup;
×
880

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

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

929
    if (context->optimize_palette) {
×
930
        *context->ncolors = 0;
×
931
        memset(context->new_palette, 0x00,
×
932
               (size_t)SIXEL_PALETTE_MAX * (size_t)context->depth);
×
933
        if (new_palette_float != NULL && float_depth > 0) {
×
934
            memset(new_palette_float, 0x00,
×
935
                   (size_t)SIXEL_PALETTE_MAX
936
                       * (size_t)float_depth * sizeof(float));
×
937
        }
938
        memset(context->migration_map, 0x00,
×
939
               sizeof(unsigned short) * (size_t)SIXEL_PALETTE_MAX);
940
    } else {
941
        *context->ncolors = context->reqcolor;
×
942
    }
943

944
    for (y = 0; y < context->height; ++y) {
×
NEW
945
        absolute_y = context->band_origin + y;
×
NEW
946
        sixel_dither_scanline_params(serpentine, absolute_y,
×
947
                                     context->width,
948
                                     &start, &end, &step, &direction);
949
        for (x = start; x != end; x += step) {
×
950
            pos = y * context->width + x;
×
951
            base = (size_t)pos * (size_t)context->depth;
×
952
            source_pixel = data + base;
×
953

954
            for (n = 0; n < context->depth; ++n) {
×
955
                snapshot[n] = source_pixel[n];
×
956
                if (need_float_pixel) {
×
957
                    lookup_pixel_float[n] = source_pixel[n];
×
958
                }
959
                if (!lookup_wants_float && !use_palette_float_lookup) {
×
960
                    quantized[n]
961
                        = sixel_pixelformat_float_channel_to_byte(
×
962
                              context->pixelformat,
963
                              n,
964
                              source_pixel[n]);
×
965
                }
966
            }
967

968
            if (lookup_wants_float) {
×
969
                lookup_pixel = (unsigned char const *)(void const *)source_pixel;
×
970
                color_index = context->lookup(lookup_pixel,
×
971
                                              context->depth,
972
                                              palette,
973
                                              context->reqcolor,
974
                                              context->indextable,
975
                                              context->complexion);
976
            } else if (use_palette_float_lookup) {
×
977
                color_index = sixel_dither_lookup_palette_float32(
×
978
                    lookup_pixel_float,
979
                    context->depth,
980
                    palette_float,
981
                    context->reqcolor,
982
                    context->complexion);
983
            } else {
984
                lookup_pixel = quantized;
×
985
                color_index = context->lookup(lookup_pixel,
×
986
                                              context->depth,
987
                                              palette,
988
                                              context->reqcolor,
989
                                              context->indextable,
990
                                              context->complexion);
991
            }
992

993
            if (context->optimize_palette) {
×
994
                if (context->migration_map[color_index] == 0) {
×
995
                    output_index = *context->ncolors;
×
996
                    for (n = 0; n < context->depth; ++n) {
×
997
                        context->new_palette[output_index * context->depth + n]
×
998
                            = palette[color_index * context->depth + n];
×
999
                    }
1000
                    if (palette_float != NULL
×
1001
                            && new_palette_float != NULL
×
1002
                            && float_depth > 0) {
×
1003
                        for (float_index = 0;
×
1004
                                float_index < float_depth;
×
1005
                                ++float_index) {
×
1006
                            new_palette_float[output_index * float_depth
×
1007
                                              + float_index]
×
1008
                                = palette_float[color_index * float_depth
×
1009
                                                + float_index];
×
1010
                        }
1011
                    }
1012
                    ++*context->ncolors;
×
1013
                    context->migration_map[color_index] = *context->ncolors;
×
1014
                } else {
1015
                    output_index = context->migration_map[color_index] - 1;
×
1016
                }
NEW
1017
                if (absolute_y >= context->output_start) {
×
NEW
1018
                    context->result[pos] = output_index;
×
1019
                }
1020
            } else {
1021
                output_index = color_index;
×
NEW
1022
                if (absolute_y >= context->output_start) {
×
NEW
1023
                    context->result[pos] = output_index;
×
1024
                }
1025
            }
1026

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

1076
    if (context->optimize_palette) {
×
1077
        memcpy(context->palette,
×
1078
               context->new_palette,
×
1079
               (size_t)(*context->ncolors * context->depth));
×
1080
        if (palette_float != NULL
×
1081
                && new_palette_float != NULL
×
1082
                && float_depth > 0) {
×
1083
            memcpy(palette_float,
×
1084
                   new_palette_float,
1085
                   (size_t)(*context->ncolors * float_depth)
×
1086
                       * sizeof(float));
1087
        }
1088
    }
1089

1090
    status = SIXEL_OK;
×
1091
    SIXEL_DITHER_FLOAT32_HIT();
×
1092
    return status;
×
1093
}
1094

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