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

saitoha / libsixel / 21335896708

25 Jan 2026 04:33PM UTC coverage: 76.581% (-2.3%) from 78.904%
21335896708

push

github

saitoha
meson: set build type to plain

20012 of 44638 branches covered (44.83%)

36354 of 47471 relevant lines covered (76.58%)

13461842.27 hits per line

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

95.87
/src/dither-fixed-float32.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2025 libsixel developers. See `AUTHORS`.
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in all
14
 * copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 */
24

25
#if defined(HAVE_CONFIG_H)
26
#include "config.h"
27
#endif
28

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

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

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

50
static void
51
error_diffuse_float(float *data,
206,411,436✔
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;
120,406,671✔
61
    float delta;
120,406,671✔
62

63
    channel = data + ((size_t)pos * (size_t)depth);
206,411,436✔
64
    delta = error * ((float)numerator / (float)denominator);
206,411,436✔
65
    *channel += delta;
206,411,436✔
66
    *channel = sixel_pixelformat_float_channel_clamp(pixelformat,
258,014,295✔
67
                                                     channel_index,
51,602,859✔
68
                                                     *channel);
51,602,859✔
69
}
206,411,436✔
70

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

93
static void
94
diffuse_none_float(float *data,
68,040,972✔
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;
51,030,729✔
106
    (void)width;
51,030,729✔
107
    (void)height;
51,030,729✔
108
    (void)x;
51,030,729✔
109
    (void)y;
51,030,729✔
110
    (void)depth;
51,030,729✔
111
    (void)error;
51,030,729✔
112
    (void)direction;
51,030,729✔
113
    (void)pixelformat;
51,030,729✔
114
    (void)channel_index;
51,030,729✔
115
}
68,040,972✔
116

117
static void
118
diffuse_fs_float(float *data,
49,632,192✔
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;
28,952,112✔
130
    int forward;
28,952,112✔
131

132
    pos = y * width + x;
49,632,192✔
133
    forward = direction >= 0;
49,632,192✔
134

135
    if (forward) {
49,632,192✔
136
        if (x < width - 1) {
49,558,464✔
137
            error_diffuse_float(data,
61,828,110✔
138
                                pos + 1,
12,365,622✔
139
                                depth,
12,365,622✔
140
                                error,
12,365,622✔
141
                                7,
142
                                16,
143
                                pixelformat,
12,365,622✔
144
                                channel_index);
12,365,622✔
145
        }
12,365,622✔
146
        if (y < height - 1) {
49,558,464✔
147
            if (x > 0) {
49,436,640✔
148
                error_diffuse_float(data,
61,676,325✔
149
                                    pos + width - 1,
49,341,060✔
150
                                    depth,
12,335,265✔
151
                                    error,
12,335,265✔
152
                                    3,
153
                                    16,
154
                                    pixelformat,
12,335,265✔
155
                                    channel_index);
12,335,265✔
156
            }
12,335,265✔
157
            error_diffuse_float(data,
61,795,800✔
158
                                pos + width,
12,359,160✔
159
                                depth,
12,359,160✔
160
                                error,
12,359,160✔
161
                                5,
162
                                16,
163
                                pixelformat,
12,359,160✔
164
                                channel_index);
12,359,160✔
165
            if (x < width - 1) {
49,436,640✔
166
                error_diffuse_float(data,
61,676,325✔
167
                                    pos + width + 1,
24,670,530✔
168
                                    depth,
12,335,265✔
169
                                    error,
12,335,265✔
170
                                    1,
171
                                    16,
172
                                    pixelformat,
12,335,265✔
173
                                    channel_index);
12,335,265✔
174
            }
12,335,265✔
175
        }
12,359,160✔
176
    } else {
12,389,616✔
177
        if (x > 0) {
73,728✔
178
            error_diffuse_float(data,
90,720✔
179
                                pos - 1,
18,144✔
180
                                depth,
18,144✔
181
                                error,
18,144✔
182
                                7,
183
                                16,
184
                                pixelformat,
18,144✔
185
                                channel_index);
18,144✔
186
        }
18,144✔
187
        if (y < height - 1) {
73,728✔
188
            if (x < width - 1) {
71,424✔
189
                error_diffuse_float(data,
87,885✔
190
                                    pos + width + 1,
70,308✔
191
                                    depth,
17,577✔
192
                                    error,
17,577✔
193
                                    3,
194
                                    16,
195
                                    pixelformat,
17,577✔
196
                                    channel_index);
17,577✔
197
            }
17,577✔
198
            error_diffuse_float(data,
89,280✔
199
                                pos + width,
17,856✔
200
                                depth,
17,856✔
201
                                error,
17,856✔
202
                                5,
203
                                16,
204
                                pixelformat,
17,856✔
205
                                channel_index);
17,856✔
206
            if (x > 0) {
71,424✔
207
                error_diffuse_float(data,
87,885✔
208
                                    pos + width - 1,
35,154✔
209
                                    depth,
17,577✔
210
                                    error,
17,577✔
211
                                    1,
212
                                    16,
213
                                    pixelformat,
17,577✔
214
                                    channel_index);
17,577✔
215
            }
17,577✔
216
        }
17,856✔
217
    }
218
}
49,632,192✔
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,
147,456✔
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;
86,016✔
238
    int sign;
86,016✔
239
    int row;
86,016✔
240

241
    pos = y * width + x;
147,456✔
242
    sign = direction >= 0 ? 1 : -1;
147,456!
243

244
    if (x + sign >= 0 && x + sign < width) {
147,456!
245
        error_diffuse_float(data,
181,440✔
246
                            pos + sign,
36,288✔
247
                            depth,
36,288✔
248
                            error,
36,288✔
249
                            1,
250
                            8,
251
                            pixelformat,
36,288✔
252
                            channel_index);
36,288✔
253
    }
36,288✔
254
    if (x + sign * 2 >= 0 && x + sign * 2 < width) {
147,456!
255
        error_diffuse_float(data,
178,560✔
256
                            pos + sign * 2,
71,424✔
257
                            depth,
35,712✔
258
                            error,
35,712✔
259
                            1,
260
                            8,
261
                            pixelformat,
35,712✔
262
                            channel_index);
35,712✔
263
    }
35,712✔
264
    if (y < height - 1) {
147,456✔
265
        row = pos + width;
145,152✔
266
        if (x - sign >= 0 && x - sign < width) {
145,152!
267
            error_diffuse_float(data,
178,605✔
268
                                row - sign,
35,721✔
269
                                depth,
35,721✔
270
                                error,
35,721✔
271
                                1,
272
                                8,
273
                                pixelformat,
35,721✔
274
                                channel_index);
35,721✔
275
        }
35,721✔
276
        error_diffuse_float(data,
181,440✔
277
                            row,
36,288✔
278
                            depth,
36,288✔
279
                            error,
36,288✔
280
                            1,
281
                            8,
282
                            pixelformat,
36,288✔
283
                            channel_index);
36,288✔
284
        if (x + sign >= 0 && x + sign < width) {
145,152!
285
            error_diffuse_float(data,
178,605✔
286
                                row + sign,
35,721✔
287
                                depth,
35,721✔
288
                                error,
35,721✔
289
                                1,
290
                                8,
291
                                pixelformat,
35,721✔
292
                                channel_index);
35,721✔
293
        }
35,721✔
294
    }
36,288✔
295
    if (y < height - 2) {
147,456✔
296
        error_diffuse_float(data,
178,560✔
297
                            pos + width * 2,
142,848✔
298
                            depth,
35,712✔
299
                            error,
35,712✔
300
                            1,
301
                            8,
302
                            pixelformat,
35,712✔
303
                            channel_index);
35,712✔
304
    }
35,712✔
305
}
147,456✔
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,
2,327,040✔
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;
1,357,440✔
330
    int neighbor;
1,357,440✔
331
    int row_base;
1,357,440✔
332
    int sign;
1,357,440✔
333

334
    sign = direction >= 0 ? 1 : -1;
2,327,040!
335
    row_base = pos + row_offset;
2,327,040✔
336
    for (i = 0; i < count; ++i) {
10,153,728✔
337
        neighbor = x + sign * offsets[i];
7,826,688✔
338
        if (neighbor < 0 || neighbor >= width) {
7,826,688✔
339
            continue;
142,884✔
340
        }
341
        error_diffuse_float(data,
9,604,755✔
342
                            row_base + (neighbor - x),
3,841,902✔
343
                            depth,
1,920,951✔
344
                            error,
1,920,951✔
345
                            numerators[i],
7,683,804✔
346
                            denominators[i],
7,683,804✔
347
                            pixelformat,
1,920,951✔
348
                            channel_index);
1,920,951✔
349
    }
1,920,951✔
350
}
2,327,040✔
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,
147,456✔
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 };
73,728✔
370
    static const int row0_num[] = { 7, 5 };
73,728✔
371
    static const int row0_den[] = { 48, 48 };
73,728✔
372
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
73,728✔
373
    static const int row1_num[] = { 3, 5, 7, 5, 3 };
73,728✔
374
    static const int row1_den[] = { 48, 48, 48, 48, 48 };
73,728✔
375
    static const int row2_offsets[] = { -2, -1, 0, 1, 2 };
73,728✔
376
    static const int row2_num[] = { 1, 3, 5, 3, 1 };
73,728✔
377
    static const int row2_den[] = { 48, 48, 48, 48, 48 };
73,728✔
378
    int pos;
86,016✔
379

380
    pos = y * width + x;
147,456✔
381
    diffuse_weighted_row(data,
184,320✔
382
                         pos,
36,864✔
383
                         depth,
36,864✔
384
                         error,
36,864✔
385
                         direction,
36,864✔
386
                         pixelformat,
36,864✔
387
                         channel_index,
36,864✔
388
                         x,
36,864✔
389
                         width,
36,864✔
390
                         0,
391
                         row0_offsets,
392
                         row0_num,
393
                         row0_den,
394
                         2);
395
    if (y < height - 1) {
147,456✔
396
        diffuse_weighted_row(data,
181,440✔
397
                             pos,
36,288✔
398
                             depth,
36,288✔
399
                             error,
36,288✔
400
                             direction,
36,288✔
401
                             pixelformat,
36,288✔
402
                             channel_index,
36,288✔
403
                             x,
36,288✔
404
                             width,
36,288✔
405
                             width,
36,288✔
406
                             row1_offsets,
407
                             row1_num,
408
                             row1_den,
409
                             5);
410
    }
36,288✔
411
    if (y < height - 2) {
147,456✔
412
        diffuse_weighted_row(data,
178,560✔
413
                             pos,
35,712✔
414
                             depth,
35,712✔
415
                             error,
35,712✔
416
                             direction,
35,712✔
417
                             pixelformat,
35,712✔
418
                             channel_index,
35,712✔
419
                             x,
35,712✔
420
                             width,
35,712✔
421
                             width * 2,
35,712✔
422
                             row2_offsets,
423
                             row2_num,
424
                             row2_den,
425
                             5);
426
    }
35,712✔
427
}
147,456✔
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,
147,456✔
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 };
73,728✔
447
    static const int row0_num[] = { 1, 1 };
73,728✔
448
    static const int row0_den[] = { 6, 12 };
73,728✔
449
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
73,728✔
450
    static const int row1_num[] = { 1, 1, 1, 1, 1 };
73,728✔
451
    static const int row1_den[] = { 24, 12, 6, 12, 24 };
73,728✔
452
    static const int row2_offsets[] = { -2, -1, 0, 1, 2 };
73,728✔
453
    static const int row2_num[] = { 1, 1, 1, 1, 1 };
73,728✔
454
    static const int row2_den[] = { 48, 24, 12, 24, 48 };
73,728✔
455
    int pos;
86,016✔
456

457
    pos = y * width + x;
147,456✔
458
    diffuse_weighted_row(data,
184,320✔
459
                         pos,
36,864✔
460
                         depth,
36,864✔
461
                         error,
36,864✔
462
                         direction,
36,864✔
463
                         pixelformat,
36,864✔
464
                         channel_index,
36,864✔
465
                         x,
36,864✔
466
                         width,
36,864✔
467
                         0,
468
                         row0_offsets,
469
                         row0_num,
470
                         row0_den,
471
                         2);
472
    if (y < height - 1) {
147,456✔
473
        diffuse_weighted_row(data,
181,440✔
474
                             pos,
36,288✔
475
                             depth,
36,288✔
476
                             error,
36,288✔
477
                             direction,
36,288✔
478
                             pixelformat,
36,288✔
479
                             channel_index,
36,288✔
480
                             x,
36,288✔
481
                             width,
36,288✔
482
                             width,
36,288✔
483
                             row1_offsets,
484
                             row1_num,
485
                             row1_den,
486
                             5);
487
    }
36,288✔
488
    if (y < height - 2) {
147,456✔
489
        diffuse_weighted_row(data,
178,560✔
490
                             pos,
35,712✔
491
                             depth,
35,712✔
492
                             error,
35,712✔
493
                             direction,
35,712✔
494
                             pixelformat,
35,712✔
495
                             channel_index,
35,712✔
496
                             x,
35,712✔
497
                             width,
35,712✔
498
                             width * 2,
35,712✔
499
                             row2_offsets,
500
                             row2_num,
501
                             row2_den,
502
                             5);
503
    }
35,712✔
504
}
147,456✔
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,
147,456✔
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 };
73,728✔
523
    static const int row0_num[] = { 1, 1 };
73,728✔
524
    static const int row0_den[] = { 4, 8 };
73,728✔
525
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
73,728✔
526
    static const int row1_num[] = { 1, 1, 1, 1, 1 };
73,728✔
527
    static const int row1_den[] = { 16, 8, 4, 8, 16 };
73,728✔
528
    int pos;
86,016✔
529

530
    pos = y * width + x;
147,456✔
531
    diffuse_weighted_row(data,
184,320✔
532
                         pos,
36,864✔
533
                         depth,
36,864✔
534
                         error,
36,864✔
535
                         direction,
36,864✔
536
                         pixelformat,
36,864✔
537
                         channel_index,
36,864✔
538
                         x,
36,864✔
539
                         width,
36,864✔
540
                         0,
541
                         row0_offsets,
542
                         row0_num,
543
                         row0_den,
544
                         2);
545
    if (y < height - 1) {
147,456✔
546
        diffuse_weighted_row(data,
181,440✔
547
                             pos,
36,288✔
548
                             depth,
36,288✔
549
                             error,
36,288✔
550
                             direction,
36,288✔
551
                             pixelformat,
36,288✔
552
                             channel_index,
36,288✔
553
                             x,
36,288✔
554
                             width,
36,288✔
555
                             width,
36,288✔
556
                             row1_offsets,
557
                             row1_num,
558
                             row1_den,
559
                             5);
560
    }
36,288✔
561
}
147,456✔
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,
147,456✔
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 };
73,728✔
580
    static const int row0_num[] = { 1 };
73,728✔
581
    static const int row0_den[] = { 2 };
73,728✔
582
    static const int row1_offsets[] = { -1, 0 };
73,728✔
583
    static const int row1_num[] = { 1, 1 };
73,728✔
584
    static const int row1_den[] = { 4, 4 };
73,728✔
585
    int pos;
86,016✔
586

587
    pos = y * width + x;
147,456✔
588
    diffuse_weighted_row(data,
184,320✔
589
                         pos,
36,864✔
590
                         depth,
36,864✔
591
                         error,
36,864✔
592
                         direction,
36,864✔
593
                         pixelformat,
36,864✔
594
                         channel_index,
36,864✔
595
                         x,
36,864✔
596
                         width,
36,864✔
597
                         0,
598
                         row0_offsets,
599
                         row0_num,
600
                         row0_den,
601
                         1);
602
    if (y < height - 1) {
147,456✔
603
        diffuse_weighted_row(data,
181,440✔
604
                             pos,
36,288✔
605
                             depth,
36,288✔
606
                             error,
36,288✔
607
                             direction,
36,288✔
608
                             pixelformat,
36,288✔
609
                             channel_index,
36,288✔
610
                             x,
36,288✔
611
                             width,
36,288✔
612
                             width,
36,288✔
613
                             row1_offsets,
614
                             row1_num,
615
                             row1_den,
616
                             2);
617
    }
36,288✔
618
}
147,456✔
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,
147,456✔
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 };
73,728✔
637
    static const int row0_num[] = { 4, 3 };
73,728✔
638
    static const int row0_den[] = { 32, 32 };
73,728✔
639
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
73,728✔
640
    static const int row1_num[] = { 1, 2, 3, 2, 1 };
73,728✔
641
    static const int row1_den[] = { 32, 32, 32, 32, 32 };
73,728✔
642
    static const int row2_offsets[] = { -1, 0, 1 };
73,728✔
643
    static const int row2_num[] = { 2, 3, 2 };
73,728✔
644
    static const int row2_den[] = { 32, 32, 32 };
73,728✔
645
    int pos;
86,016✔
646

647
    pos = y * width + x;
147,456✔
648
    diffuse_weighted_row(data,
184,320✔
649
                         pos,
36,864✔
650
                         depth,
36,864✔
651
                         error,
36,864✔
652
                         direction,
36,864✔
653
                         pixelformat,
36,864✔
654
                         channel_index,
36,864✔
655
                         x,
36,864✔
656
                         width,
36,864✔
657
                         0,
658
                         row0_offsets,
659
                         row0_num,
660
                         row0_den,
661
                         2);
662
    if (y < height - 1) {
147,456✔
663
        diffuse_weighted_row(data,
181,440✔
664
                             pos,
36,288✔
665
                             depth,
36,288✔
666
                             error,
36,288✔
667
                             direction,
36,288✔
668
                             pixelformat,
36,288✔
669
                             channel_index,
36,288✔
670
                             x,
36,288✔
671
                             width,
36,288✔
672
                             width,
36,288✔
673
                             row1_offsets,
674
                             row1_num,
675
                             row1_den,
676
                             5);
677
    }
36,288✔
678
    if (y < height - 2) {
147,456✔
679
        diffuse_weighted_row(data,
178,560✔
680
                             pos,
35,712✔
681
                             depth,
35,712✔
682
                             error,
35,712✔
683
                             direction,
35,712✔
684
                             pixelformat,
35,712✔
685
                             channel_index,
35,712✔
686
                             x,
35,712✔
687
                             width,
35,712✔
688
                             width * 2,
35,712✔
689
                             row2_offsets,
690
                             row2_num,
691
                             row2_den,
692
                             3);
693
    }
35,712✔
694
}
147,456✔
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,
147,456✔
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 };
73,728✔
713
    static const int row0_num[] = { 5, 3 };
73,728✔
714
    static const int row0_den[] = { 32, 32 };
73,728✔
715
    static const int row1_offsets[] = { -2, -1, 0, 1, 2 };
73,728✔
716
    static const int row1_num[] = { 2, 4, 5, 4, 2 };
73,728✔
717
    static const int row1_den[] = { 32, 32, 32, 32, 32 };
73,728✔
718
    static const int row2_offsets[] = { -1, 0, 1 };
73,728✔
719
    static const int row2_num[] = { 2, 3, 2 };
73,728✔
720
    static const int row2_den[] = { 32, 32, 32 };
73,728✔
721
    int pos;
86,016✔
722

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

772

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

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

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

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

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

855
    serpentine = (context->method_for_scan == SIXEL_SCAN_SERPENTINE);
348✔
856
    lookup_wants_float = (context->lookup_source_is_float != 0);
348✔
857
    use_palette_float_lookup = 0;
348✔
858
    if (context->prefer_palette_float_lookup != 0
348!
859
            && palette_float != NULL
87!
860
            && float_depth >= context->depth) {
×
861
        use_palette_float_lookup = 1;
174✔
862
    }
863
    need_float_pixel = lookup_wants_float || use_palette_float_lookup;
522✔
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) {
348!
871
        have_palette_float = 1;
872
    } else {
873
        have_palette_float = 0;
174✔
874
    }
875
    if (new_palette_float != NULL && float_depth >= context->depth) {
348!
876
        have_new_palette_float = 1;
877
    } else {
878
        have_new_palette_float = 0;
348✔
879
    }
880

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

913
    if (context->optimize_palette) {
348!
914
        *context->ncolors = 0;
348✔
915
        memset(context->new_palette, 0x00,
377✔
916
               (size_t)SIXEL_PALETTE_MAX * (size_t)context->depth);
290!
917
        if (new_palette_float != NULL && float_depth > 0) {
348!
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,
348✔
923
               sizeof(unsigned short) * (size_t)SIXEL_PALETTE_MAX);
924
    } else {
87✔
925
        *context->ncolors = context->reqcolor;
×
926
    }
927

928
    for (y = 0; y < context->height; ++y) {
76,008✔
929
        absolute_y = context->band_origin + y;
75,660✔
930
        sixel_dither_scanline_params_fixed_float32(serpentine, absolute_y,
94,575✔
931
                                     context->width,
18,915✔
932
                                     &start, &end, &step, &direction);
933
        for (x = start; x != end; x += step) {
39,644,112✔
934
            pos = y * context->width + x;
39,568,452✔
935
            base = (size_t)pos * (size_t)context->depth;
39,568,452✔
936
            source_pixel = data + base;
39,568,452✔
937

938
            for (n = 0; n < context->depth; ++n) {
158,273,808✔
939
                snapshot[n] = source_pixel[n];
118,705,356✔
940
                if (need_float_pixel) {
118,705,356✔
941
                    lookup_pixel_float[n] = source_pixel[n];
118,557,900✔
942
                }
29,639,475✔
943
                if (!lookup_wants_float && !use_palette_float_lookup) {
118,631,628!
944
                    quantized[n]
110,592✔
945
                        = sixel_pixelformat_float_channel_to_byte(
184,320✔
946
                              context->pixelformat,
36,864✔
947
                              n,
36,864✔
948
                              source_pixel[n]);
73,728✔
949
                }
36,864✔
950
            }
29,676,339✔
951

952
            if (lookup_wants_float) {
39,568,452✔
953
                lookup_pixel = (unsigned char const *)(void const *)source_pixel;
39,519,300✔
954
                if (use_fast_lut) {
39,519,300!
955
                    color_index = sixel_lut_map_pixel(fast_lut,
49,399,125✔
956
                                                     lookup_pixel);
9,879,825✔
957
                } else {
9,879,825✔
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) {
9,928,977!
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;
49,152✔
975
                if (use_fast_lut) {
49,152!
976
                    color_index = sixel_lut_map_pixel(fast_lut,
×
977
                                                     lookup_pixel);
978
                } else {
979
                    color_index = context->lookup(lookup_pixel,
61,440✔
980
                                                  context->depth,
12,288✔
981
                                                  palette,
12,288✔
982
                                                  context->reqcolor,
12,288✔
983
                                                  context->indextable,
12,288✔
984
                                                  context->complexion);
12,288✔
985
                }
986
            }
987

988
            if (context->optimize_palette) {
39,568,452!
989
                    if (context->migration_map[color_index] == 0) {
39,568,452✔
990
                        output_index = *context->ncolors;
64,944✔
991
                        for (n = 0; n < context->depth; ++n) {
259,776✔
992
                            context->new_palette[output_index * context->depth + n]
194,832✔
993
                                = palette[color_index * context->depth + n];
243,630✔
994
                    }
48,798✔
995
                    if (palette_float != NULL
64,944!
996
                            && new_palette_float != NULL
48,744!
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;
64,944✔
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]
64,944✔
1013
                        = (unsigned short)(*context->ncolors);
81,210✔
1014
                } else {
16,266✔
1015
                    output_index = context->migration_map[color_index] - 1;
39,503,508✔
1016
                }
1017
                if (absolute_y >= context->output_start) {
39,568,452!
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;
39,568,452✔
1023
                }
9,892,113✔
1024
            } else {
9,892,113✔
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) {
158,273,808✔
1032
                if (context->optimize_palette) {
118,705,356!
1033
                    if (have_new_palette_float) {
118,705,356!
1034
                        palette_value_float =
×
1035
                            new_palette_float[output_index * float_depth
×
1036
                                              + n];
×
1037
                    } else {
1038
                        palette_value =
118,705,356✔
1039
                            context->new_palette[output_index
148,381,695✔
1040
                                                 * context->depth + n];
118,705,356✔
1041
                        palette_value_float
59,352,678✔
1042
                            = sixel_pixelformat_byte_to_float(
118,705,356✔
1043
                                  context->pixelformat,
29,676,339✔
1044
                                  n,
29,676,339✔
1045
                                  (unsigned char)palette_value);
59,352,678✔
1046
                    }
1047
                } else {
29,676,339✔
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;
118,705,356✔
1062
                source_pixel[n] = palette_value_float;
118,705,356✔
1063
                f_diffuse(data + (size_t)n,
148,381,695✔
1064
                          context->width,
29,676,339✔
1065
                          context->height,
29,676,339✔
1066
                          x,
29,676,339✔
1067
                          y,
29,676,339✔
1068
                          context->depth,
29,676,339✔
1069
                          error,
29,676,339✔
1070
                          direction,
29,676,339✔
1071
                          context->pixelformat,
29,676,339✔
1072
                          n);
29,676,339✔
1073
            }
29,676,339✔
1074
        }
9,892,113✔
1075
        if (absolute_y >= context->output_start) {
75,660!
1076
            sixel_dither_pipeline_row_notify(dither, absolute_y);
75,660✔
1077
        }
18,915✔
1078
    }
18,915✔
1079

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

1094
    status = SIXEL_OK;
174✔
1095
    return status;
174✔
1096
}
87✔
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