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

saitoha / libsixel / 20234237201

15 Dec 2025 01:34PM UTC coverage: 50.547%. First build
20234237201

push

github

saitoha
pixelformat: validate palette inputs before expansion

11744 of 42242 branches covered (27.8%)

1 of 4 new or added lines in 1 file covered. (25.0%)

20415 of 40388 relevant lines covered (50.55%)

3913667.26 hits per line

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

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

25
#include "config.h"
26

27
/* STDC_HEADERS */
28
#include <stdio.h>
29
#include <stdlib.h>
30

31
#if HAVE_MATH_H
32
# include <math.h>
33
#endif  /* HAVE_MATH_H */
34

35
#if HAVE_MEMORY_H
36
# include <memory.h>
37
#endif  /* HAVE_MEMORY_H */
38

39
#include <sixel.h>
40

41
#include "threading.h"
42
#include "pixelformat.h"
43

44
#define SIXEL_OKLAB_AB_FLOAT_MIN (-0.5f)
45
#define SIXEL_OKLAB_AB_FLOAT_MAX (0.5f)
46
#define SIXEL_CIELAB_AB_FLOAT_MIN (-1.5f)
47
#define SIXEL_CIELAB_AB_FLOAT_MAX (1.5f)
48
#define SIXEL_CIELAB_L_FLOAT_MIN  (0.0f)
49
#define SIXEL_CIELAB_L_FLOAT_MAX  (1.0f)
50
#define SIXEL_DIN99D_L_FLOAT_MIN  (0.0f)
51
#define SIXEL_DIN99D_L_FLOAT_MAX  (1.0f)
52
#define SIXEL_DIN99D_AB_FLOAT_MIN (-1.0f)
53
#define SIXEL_DIN99D_AB_FLOAT_MAX (1.0f)
54
#define SIXEL_YUV_Y_FLOAT_MIN     (0.0f)
55
#define SIXEL_YUV_Y_FLOAT_MAX     (1.0f)
56
#define SIXEL_YUV_U_FLOAT_MIN     (-0.436f)
57
#define SIXEL_YUV_U_FLOAT_MAX     (0.436f)
58
#define SIXEL_YUV_V_FLOAT_MIN     (-0.615f)
59
#define SIXEL_YUV_V_FLOAT_MAX     (0.615f)
60

61
/*
62
 * Normalize a float32 channel stored in the 0.0-1.0 range and convert
63
 * the value to an 8-bit sample. Out-of-range or NaN inputs are clamped
64
 * to sane defaults so downstream conversions always receive valid bytes.
65
 */
66
static unsigned char
67
sixel_pixelformat_float_to_byte(float value)
×
68
{
69
#if HAVE_MATH_H
70
    if (!isfinite(value)) {
×
71
        value = 0.0f;
72
    }
73
#endif  /* HAVE_MATH_H */
74

75
    if (value <= 0.0f) {
×
76
        return 0;
77
    }
78
    if (value >= 1.0f) {
×
79
        return 255;
80
    }
81

82
    return (unsigned char)(value * 255.0f + 0.5f);
×
83
}
84

85
static unsigned char
86
sixel_pixelformat_oklab_L_to_byte(float value)
×
87
{
88
#if HAVE_MATH_H
89
    if (!isfinite(value)) {
×
90
        value = 0.0f;
91
    }
92
#endif  /* HAVE_MATH_H */
93

94
    if (value <= 0.0f) {
×
95
        return 0;
96
    }
97
    if (value >= 1.0f) {
×
98
        return 255;
99
    }
100

101
    return (unsigned char)(value * 255.0f + 0.5f);
×
102
}
103

104
static unsigned char
105
sixel_pixelformat_oklab_ab_to_byte(float value)
×
106
{
107
    float encoded;
×
108

109
#if HAVE_MATH_H
110
    if (!isfinite(value)) {
×
111
        value = 0.0f;
112
    }
113
#endif  /* HAVE_MATH_H */
114

115
    encoded = value + 0.5f;
×
116
    if (encoded <= 0.0f) {
×
117
        return 0;
118
    }
119
    if (encoded >= 1.0f) {
×
120
        return 255;
121
    }
122

123
    return (unsigned char)(encoded * 255.0f + 0.5f);
×
124
}
125

126
static unsigned char
127
sixel_pixelformat_cielab_L_to_byte(float value)
×
128
{
129
#if HAVE_MATH_H
130
    if (!isfinite(value)) {
×
131
        value = 0.0f;
132
    }
133
#endif  /* HAVE_MATH_H */
134

135
    if (value <= 0.0f) {
×
136
        return 0;
137
    }
138
    if (value >= 1.0f) {
×
139
        return 255;
140
    }
141

142
    return (unsigned char)(value * 255.0f + 0.5f);
×
143
}
144

145
static unsigned char
146
sixel_pixelformat_cielab_ab_to_byte(float value)
×
147
{
148
    float encoded;
×
149

150
#if HAVE_MATH_H
151
    if (!isfinite(value)) {
×
152
        value = 0.0f;
×
153
    }
154
#endif  /* HAVE_MATH_H */
155

156
    encoded = (value / (2.0f * SIXEL_CIELAB_AB_FLOAT_MAX)) + 0.5f;
×
157
    if (encoded <= 0.0f) {
×
158
        return 0;
159
    }
160
    if (encoded >= 1.0f) {
×
161
        return 255;
162
    }
163

164
    return (unsigned char)(encoded * 255.0f + 0.5f);
×
165
}
166

167
static unsigned char
168
sixel_pixelformat_din99d_L_to_byte(float value)
×
169
{
170
#if HAVE_MATH_H
171
    if (!isfinite(value)) {
×
172
        value = 0.0f;
173
    }
174
#endif  /* HAVE_MATH_H */
175

176
    if (value <= 0.0f) {
×
177
        return 0;
178
    }
179
    if (value >= 1.0f) {
×
180
        return 255;
181
    }
182

183
    return (unsigned char)(value * 255.0f + 0.5f);
×
184
}
185

186
static unsigned char
187
sixel_pixelformat_din99d_ab_to_byte(float value)
×
188
{
189
    float encoded;
×
190

191
#if HAVE_MATH_H
192
    if (!isfinite(value)) {
×
193
        value = 0.0f;
×
194
    }
195
#endif  /* HAVE_MATH_H */
196

197
    encoded = (value / (2.0f * SIXEL_DIN99D_AB_FLOAT_MAX)) + 0.5f;
×
198
    if (encoded <= 0.0f) {
×
199
        return 0;
200
    }
201
    if (encoded >= 1.0f) {
×
202
        return 255;
203
    }
204

205
    return (unsigned char)(encoded * 255.0f + 0.5f);
×
206
}
207

208
static unsigned char
209
sixel_pixelformat_yuv_chroma_to_byte(float value, float range)
×
210
{
211
    float encoded;
×
212

213
#if HAVE_MATH_H
214
    if (!isfinite(value)) {
×
215
        value = 0.0f;
×
216
    }
217
#endif  /* HAVE_MATH_H */
218

219
    encoded = (value / (2.0f * range)) + 0.5f;
×
220
    if (encoded <= 0.0f) {
×
221
        return 0;
222
    }
223
    if (encoded >= 1.0f) {
×
224
        return 255;
225
    }
226

227
    return (unsigned char)(encoded * 255.0f + 0.5f);
×
228
}
229

230
static float
231
sixel_pixelformat_yuv_chroma_from_byte(unsigned char value, float range)
×
232
{
233
    float encoded;
×
234

235
    encoded = (float)value / 255.0f;
×
236
    return (encoded - 0.5f) * (2.0f * range);
×
237
}
238

239
static float
240
sixel_pixelformat_float_channel_min_internal(int pixelformat,
×
241
                                             int channel)
242
{
243
    (void)channel;
×
244
    if (pixelformat == SIXEL_PIXELFORMAT_OKLABFLOAT32) {
×
245
        if (channel == 0) {
×
246
            return 0.0f;
247
        }
248
        return SIXEL_OKLAB_AB_FLOAT_MIN;
×
249
    }
250
    if (pixelformat == SIXEL_PIXELFORMAT_CIELABFLOAT32) {
×
251
        if (channel == 0) {
×
252
            return SIXEL_CIELAB_L_FLOAT_MIN;
253
        }
254
        return SIXEL_CIELAB_AB_FLOAT_MIN;
×
255
    }
256
    if (pixelformat == SIXEL_PIXELFORMAT_DIN99DFLOAT32) {
×
257
        if (channel == 0) {
×
258
            return SIXEL_DIN99D_L_FLOAT_MIN;
259
        }
260
        return SIXEL_DIN99D_AB_FLOAT_MIN;
×
261
    }
262
    if (pixelformat == SIXEL_PIXELFORMAT_YUVFLOAT32) {
×
263
        if (channel == 0) {
×
264
            return SIXEL_YUV_Y_FLOAT_MIN;
265
        }
266
        if (channel == 1) {
×
267
            return SIXEL_YUV_U_FLOAT_MIN;
268
        }
269
        return SIXEL_YUV_V_FLOAT_MIN;
×
270
    }
271
    return 0.0f;
272
}
273

274
static float
275
sixel_pixelformat_float_channel_max_internal(int pixelformat,
×
276
                                             int channel)
277
{
278
    (void)channel;
×
279
    if (pixelformat == SIXEL_PIXELFORMAT_OKLABFLOAT32) {
×
280
        if (channel == 0) {
×
281
            return 1.0f;
282
        }
283
        return SIXEL_OKLAB_AB_FLOAT_MAX;
×
284
    }
285
    if (pixelformat == SIXEL_PIXELFORMAT_CIELABFLOAT32) {
×
286
        if (channel == 0) {
×
287
            return SIXEL_CIELAB_L_FLOAT_MAX;
288
        }
289
        return SIXEL_CIELAB_AB_FLOAT_MAX;
×
290
    }
291
    if (pixelformat == SIXEL_PIXELFORMAT_DIN99DFLOAT32) {
×
292
        if (channel == 0) {
×
293
            return SIXEL_DIN99D_L_FLOAT_MAX;
294
        }
295
        return SIXEL_DIN99D_AB_FLOAT_MAX;
296
    }
297
    if (pixelformat == SIXEL_PIXELFORMAT_YUVFLOAT32) {
×
298
        if (channel == 0) {
×
299
            return SIXEL_YUV_Y_FLOAT_MAX;
300
        }
301
        if (channel == 1) {
×
302
            return SIXEL_YUV_U_FLOAT_MAX;
303
        }
304
        return SIXEL_YUV_V_FLOAT_MAX;
×
305
    }
306
    return 1.0f;
307
}
308

309
float
310
sixel_pixelformat_float_channel_clamp(int pixelformat,
×
311
                                      int channel,
312
                                      float value)
313
{
314
    float minimum;
×
315
    float maximum;
×
316

317
#if HAVE_MATH_H
318
    if (!isfinite(value)) {
×
319
        value = 0.0f;
×
320
    }
321
#endif  /* HAVE_MATH_H */
322

323
    minimum = sixel_pixelformat_float_channel_min_internal(pixelformat,
×
324
                                                           channel);
325
    maximum = sixel_pixelformat_float_channel_max_internal(pixelformat,
×
326
                                                           channel);
327
    if (value < minimum) {
×
328
        return minimum;
329
    }
330
    if (value > maximum) {
×
331
        return maximum;
332
    }
333

334
    return value;
335
}
336

337
unsigned char
338
sixel_pixelformat_float_channel_to_byte(int pixelformat,
×
339
                                        int channel,
340
                                        float value)
341
{
342
    float clamped;
×
343

344
    clamped = sixel_pixelformat_float_channel_clamp(pixelformat,
×
345
                                                    channel,
346
                                                    value);
347
    if (pixelformat == SIXEL_PIXELFORMAT_OKLABFLOAT32) {
×
348
        if (channel == 0) {
×
349
            return sixel_pixelformat_oklab_L_to_byte(clamped);
×
350
        }
351
        return sixel_pixelformat_oklab_ab_to_byte(clamped);
×
352
    }
353
    if (pixelformat == SIXEL_PIXELFORMAT_CIELABFLOAT32) {
×
354
        if (channel == 0) {
×
355
            return sixel_pixelformat_cielab_L_to_byte(clamped);
×
356
        }
357
        return sixel_pixelformat_cielab_ab_to_byte(clamped);
×
358
    }
359
    if (pixelformat == SIXEL_PIXELFORMAT_DIN99DFLOAT32) {
×
360
        if (channel == 0) {
×
361
            return sixel_pixelformat_din99d_L_to_byte(clamped);
×
362
        }
363
        return sixel_pixelformat_din99d_ab_to_byte(clamped);
×
364
    }
365
    if (pixelformat == SIXEL_PIXELFORMAT_YUVFLOAT32) {
×
366
        if (channel == 0) {
×
367
            return sixel_pixelformat_float_to_byte(clamped);
×
368
        }
369
        if (channel == 1) {
×
370
            return sixel_pixelformat_yuv_chroma_to_byte(
×
371
                clamped,
372
                SIXEL_YUV_U_FLOAT_MAX);
373
        }
374
        return sixel_pixelformat_yuv_chroma_to_byte(clamped,
×
375
                                                    SIXEL_YUV_V_FLOAT_MAX);
376
    }
377

378
    (void)channel;
×
379
    return sixel_pixelformat_float_to_byte(clamped);
×
380
}
381

382
float
383
sixel_pixelformat_byte_to_float(int pixelformat,
×
384
                                int channel,
385
                                unsigned char value)
386
{
387
    float decoded;
×
388

389
    if (pixelformat == SIXEL_PIXELFORMAT_OKLABFLOAT32) {
×
390
        if (channel == 0) {
×
391
            return (float)value / 255.0f;
×
392
        }
393
        decoded = (float)value / 255.0f;
×
394
        return decoded - 0.5f;
×
395
    }
396
    if (pixelformat == SIXEL_PIXELFORMAT_CIELABFLOAT32) {
×
397
        if (channel == 0) {
×
398
            return (float)value / 255.0f;
×
399
        }
400
        decoded = (float)value / 255.0f;
×
401
        decoded = (decoded - 0.5f)
×
402
                 * (2.0f * SIXEL_CIELAB_AB_FLOAT_MAX);
403
        return decoded;
×
404
    }
405
    if (pixelformat == SIXEL_PIXELFORMAT_DIN99DFLOAT32) {
×
406
        if (channel == 0) {
×
407
            return (float)value / 255.0f;
×
408
        }
409
        decoded = (float)value / 255.0f;
×
410
        decoded = (decoded - 0.5f)
×
411
                 * (2.0f * SIXEL_DIN99D_AB_FLOAT_MAX);
412
        return decoded;
×
413
    }
414
    if (pixelformat == SIXEL_PIXELFORMAT_YUVFLOAT32) {
×
415
        if (channel == 0) {
×
416
            return (float)value / 255.0f;
×
417
        }
418
        if (channel == 1) {
×
419
            return sixel_pixelformat_yuv_chroma_from_byte(
×
420
                value,
421
                SIXEL_YUV_U_FLOAT_MAX);
422
        }
423
        return sixel_pixelformat_yuv_chroma_from_byte(value,
×
424
                                                      SIXEL_YUV_V_FLOAT_MAX);
425
    }
426

427
    (void)channel;
×
428
    return (float)value / 255.0f;
×
429
}
430

431
typedef void (*sixel_rgb_reader_t)(unsigned char const *data,
432
                                    unsigned char *r,
433
                                    unsigned char *g,
434
                                    unsigned char *b);
435

436

437
static unsigned int
438
sixel_rgb_read16(unsigned char const *data)
×
439
{
440
    unsigned int pixels;
×
441
#if SWAP_BYTES
442
    unsigned int low;
443
    unsigned int high;
444
#endif
445

446
    pixels = ((unsigned int)data[0] << 8) | (unsigned int)data[1];
×
447

448
#if SWAP_BYTES
449
    low = pixels & 0xff;
450
    high = (pixels >> 8) & 0xff;
451
    pixels = (low << 8) | high;
452
#endif
453

454
    return pixels;
×
455
}
456

457

458
static void
459
sixel_rgb_from_rgb555(unsigned char const *data,
×
460
                      unsigned char *r,
461
                      unsigned char *g,
462
                      unsigned char *b)
463
{
464
    unsigned int pixels;
×
465

466
    pixels = sixel_rgb_read16(data);
×
467

468
    *r = ((pixels >> 10) & 0x1f) << 3;
×
469
    *g = ((pixels >> 5) & 0x1f) << 3;
×
470
    *b = ((pixels >> 0) & 0x1f) << 3;
×
471
}
×
472

473

474
static void
475
sixel_rgb_from_rgb565(unsigned char const *data,
×
476
                      unsigned char *r,
477
                      unsigned char *g,
478
                      unsigned char *b)
479
{
480
    unsigned int pixels;
×
481

482
    pixels = sixel_rgb_read16(data);
×
483

484
    *r = ((pixels >> 11) & 0x1f) << 3;
×
485
    *g = ((pixels >> 5) & 0x3f) << 2;
×
486
    *b = ((pixels >> 0) & 0x1f) << 3;
×
487
}
×
488

489

490
static void
491
sixel_rgb_from_bgr555(unsigned char const *data,
×
492
                      unsigned char *r,
493
                      unsigned char *g,
494
                      unsigned char *b)
495
{
496
    unsigned int pixels;
×
497

498
    pixels = sixel_rgb_read16(data);
×
499

500
    *r = ((pixels >> 0) & 0x1f) << 3;
×
501
    *g = ((pixels >> 5) & 0x1f) << 3;
×
502
    *b = ((pixels >> 10) & 0x1f) << 3;
×
503
}
×
504

505

506
static void
507
sixel_rgb_from_bgr565(unsigned char const *data,
×
508
                      unsigned char *r,
509
                      unsigned char *g,
510
                      unsigned char *b)
511
{
512
    unsigned int pixels;
×
513

514
    pixels = sixel_rgb_read16(data);
×
515

516
    *r = ((pixels >> 0) & 0x1f) << 3;
×
517
    *g = ((pixels >> 5) & 0x3f) << 2;
×
518
    *b = ((pixels >> 11) & 0x1f) << 3;
×
519
}
×
520

521

522
static void
523
sixel_rgb_from_ga88(unsigned char const *data,
×
524
                    unsigned char *r,
525
                    unsigned char *g,
526
                    unsigned char *b)
527
{
528
    unsigned int pixels;
×
529

530
    pixels = sixel_rgb_read16(data);
×
531

532
    *r = (pixels >> 8) & 0xff;
×
533
    *g = (pixels >> 8) & 0xff;
×
534
    *b = (pixels >> 8) & 0xff;
×
535
}
×
536

537

538
static void
539
sixel_rgb_from_ag88(unsigned char const *data,
×
540
                    unsigned char *r,
541
                    unsigned char *g,
542
                    unsigned char *b)
543
{
544
    unsigned int pixels;
×
545

546
    pixels = sixel_rgb_read16(data);
×
547

548
    *r = pixels & 0xff;
×
549
    *g = pixels & 0xff;
×
550
    *b = pixels & 0xff;
×
551
}
×
552

553

554
static void
555
sixel_rgb_from_rgb888(unsigned char const *data,
×
556
                      unsigned char *r,
557
                      unsigned char *g,
558
                      unsigned char *b)
559
{
560
    *r = data[0];
×
561
    *g = data[1];
×
562
    *b = data[2];
×
563
}
×
564

565

566
static void
567
sixel_rgb_from_bgr888(unsigned char const *data,
×
568
                      unsigned char *r,
569
                      unsigned char *g,
570
                      unsigned char *b)
571
{
572
    *r = data[2];
×
573
    *g = data[1];
×
574
    *b = data[0];
×
575
}
×
576

577

578
static void
579
sixel_rgb_from_rgba8888(unsigned char const *data,
×
580
                        unsigned char *r,
581
                        unsigned char *g,
582
                        unsigned char *b)
583
{
584
    *r = data[0];
×
585
    *g = data[1];
×
586
    *b = data[2];
×
587
}
×
588

589

590
static void
591
sixel_rgb_from_argb8888(unsigned char const *data,
×
592
                        unsigned char *r,
593
                        unsigned char *g,
594
                        unsigned char *b)
595
{
596
    *r = data[1];
×
597
    *g = data[2];
×
598
    *b = data[3];
×
599
}
×
600

601

602
static void
603
sixel_rgb_from_bgra8888(unsigned char const *data,
×
604
                        unsigned char *r,
605
                        unsigned char *g,
606
                        unsigned char *b)
607
{
608
    *r = data[2];
×
609
    *g = data[1];
×
610
    *b = data[0];
×
611
}
×
612

613

614
static void
615
sixel_rgb_from_abgr8888(unsigned char const *data,
×
616
                        unsigned char *r,
617
                        unsigned char *g,
618
                        unsigned char *b)
619
{
620
    *r = data[3];
×
621
    *g = data[2];
×
622
    *b = data[1];
×
623
}
×
624

625

626
static void
627
sixel_rgb_from_g8(unsigned char const *data,
270,000✔
628
                  unsigned char *r,
629
                  unsigned char *g,
630
                  unsigned char *b)
631
{
632
    *r = data[0];
270,000✔
633
    *g = data[0];
270,000✔
634
    *b = data[0];
270,000✔
635
}
270,000✔
636

637

638
static void
639
sixel_rgb_from_rgbfloat32(unsigned char const *data,
×
640
                          unsigned char *r,
641
                          unsigned char *g,
642
                          unsigned char *b)
643
{
644
    float const *fpixels;
×
645

646
    fpixels = (float const *)(void const *)data;
×
647

648
    *r = sixel_pixelformat_float_to_byte(fpixels[0]);
×
649
    *g = sixel_pixelformat_float_to_byte(fpixels[1]);
×
650
    *b = sixel_pixelformat_float_to_byte(fpixels[2]);
×
651
}
×
652

653

654
static void
655
sixel_rgb_from_oklabfloat32(unsigned char const *data,
×
656
                            unsigned char *r,
657
                            unsigned char *g,
658
                            unsigned char *b)
659
{
660
    float const *fpixels;
×
661

662
    fpixels = (float const *)(void const *)data;
×
663

664
    *r = sixel_pixelformat_oklab_L_to_byte(fpixels[0]);
×
665
    *g = sixel_pixelformat_oklab_ab_to_byte(fpixels[1]);
×
666
    *b = sixel_pixelformat_oklab_ab_to_byte(fpixels[2]);
×
667
}
×
668

669

670
static void
671
sixel_rgb_from_cielabfloat32(unsigned char const *data,
×
672
                             unsigned char *r,
673
                             unsigned char *g,
674
                             unsigned char *b)
675
{
676
    float const *fpixels;
×
677

678
    fpixels = (float const *)(void const *)data;
×
679

680
    *r = sixel_pixelformat_cielab_L_to_byte(fpixels[0]);
×
681
    *g = sixel_pixelformat_cielab_ab_to_byte(fpixels[1]);
×
682
    *b = sixel_pixelformat_cielab_ab_to_byte(fpixels[2]);
×
683
}
×
684

685

686
static void
687
sixel_rgb_from_din99dfloat32(unsigned char const *data,
×
688
                             unsigned char *r,
689
                             unsigned char *g,
690
                             unsigned char *b)
691
{
692
    float const *fpixels;
×
693

694
    fpixels = (float const *)(void const *)data;
×
695

696
    *r = sixel_pixelformat_din99d_L_to_byte(fpixels[0]);
×
697
    *g = sixel_pixelformat_din99d_ab_to_byte(fpixels[1]);
×
698
    *b = sixel_pixelformat_din99d_ab_to_byte(fpixels[2]);
×
699
}
×
700

701

702
static void
703
sixel_rgb_from_yuvfloat32(unsigned char const *data,
×
704
                          unsigned char *r,
705
                          unsigned char *g,
706
                          unsigned char *b)
707
{
708
    float const *fpixels;
×
709

710
    fpixels = (float const *)(void const *)data;
×
711

712
    *r = sixel_pixelformat_float_to_byte(fpixels[0]);
×
713
    *g = sixel_pixelformat_yuv_chroma_to_byte(fpixels[1],
×
714
                                              SIXEL_YUV_U_FLOAT_MAX);
715
    *b = sixel_pixelformat_yuv_chroma_to_byte(fpixels[2],
×
716
                                              SIXEL_YUV_V_FLOAT_MAX);
717
}
×
718

719

720
static void
721
sixel_rgb_from_unknown(unsigned char const *data,
×
722
                       unsigned char *r,
723
                       unsigned char *g,
724
                       unsigned char *b)
725
{
726
    (void)data;
×
727

728
    *r = 0;
×
729
    *g = 0;
×
730
    *b = 0;
×
731
}
×
732

733

734
static sixel_rgb_reader_t
735
sixel_select_rgb_reader(int pixelformat)
1✔
736
{
737
    switch (pixelformat) {
1!
738
    case SIXEL_PIXELFORMAT_RGB555:
739
        return sixel_rgb_from_rgb555;
740
    case SIXEL_PIXELFORMAT_RGB565:
741
        return sixel_rgb_from_rgb565;
×
742
    case SIXEL_PIXELFORMAT_RGB888:
743
        return sixel_rgb_from_rgb888;
×
744
    case SIXEL_PIXELFORMAT_RGBA8888:
745
        return sixel_rgb_from_rgba8888;
×
746
    case SIXEL_PIXELFORMAT_ARGB8888:
747
        return sixel_rgb_from_argb8888;
×
748
    case SIXEL_PIXELFORMAT_BGR555:
749
        return sixel_rgb_from_bgr555;
×
750
    case SIXEL_PIXELFORMAT_BGR565:
751
        return sixel_rgb_from_bgr565;
×
752
    case SIXEL_PIXELFORMAT_BGR888:
753
        return sixel_rgb_from_bgr888;
×
754
    case SIXEL_PIXELFORMAT_BGRA8888:
755
        return sixel_rgb_from_bgra8888;
×
756
    case SIXEL_PIXELFORMAT_ABGR8888:
757
        return sixel_rgb_from_abgr8888;
×
758
    case SIXEL_PIXELFORMAT_AG88:
759
        return sixel_rgb_from_ag88;
×
760
    case SIXEL_PIXELFORMAT_GA88:
761
        return sixel_rgb_from_ga88;
×
762
    case SIXEL_PIXELFORMAT_G8:
763
        return sixel_rgb_from_g8;
1✔
764
    case SIXEL_PIXELFORMAT_RGBFLOAT32:
765
    case SIXEL_PIXELFORMAT_LINEARRGBFLOAT32:
766
        return sixel_rgb_from_rgbfloat32;
×
767
    case SIXEL_PIXELFORMAT_OKLABFLOAT32:
768
        return sixel_rgb_from_oklabfloat32;
×
769
    case SIXEL_PIXELFORMAT_CIELABFLOAT32:
770
        return sixel_rgb_from_cielabfloat32;
×
771
    case SIXEL_PIXELFORMAT_DIN99DFLOAT32:
772
        return sixel_rgb_from_din99dfloat32;
×
773
    case SIXEL_PIXELFORMAT_YUVFLOAT32:
774
        return sixel_rgb_from_yuvfloat32;
×
775
    default:
776
        break;
×
777
    }
778

779
    return sixel_rgb_from_unknown;
×
780
}
1✔
781

782

783
SIXELAPI int
784
sixel_helper_compute_depth(int pixelformat)
3,511✔
785
{
786
    int depth = (-1);  /* unknown */
3,511✔
787

788
    switch (pixelformat) {
3,511!
789
    case SIXEL_PIXELFORMAT_ARGB8888:
790
    case SIXEL_PIXELFORMAT_RGBA8888:
791
    case SIXEL_PIXELFORMAT_ABGR8888:
792
    case SIXEL_PIXELFORMAT_BGRA8888:
793
        depth = 4;
×
794
        break;
×
795
    case SIXEL_PIXELFORMAT_RGB888:
1,572✔
796
    case SIXEL_PIXELFORMAT_BGR888:
797
        depth = 3;
2,439✔
798
        break;
2,439✔
799
    case SIXEL_PIXELFORMAT_RGB555:
800
    case SIXEL_PIXELFORMAT_RGB565:
801
    case SIXEL_PIXELFORMAT_BGR555:
802
    case SIXEL_PIXELFORMAT_BGR565:
803
    case SIXEL_PIXELFORMAT_AG88:
804
    case SIXEL_PIXELFORMAT_GA88:
805
        depth = 2;
×
806
        break;
×
807
    case SIXEL_PIXELFORMAT_G1:
231✔
808
    case SIXEL_PIXELFORMAT_G2:
809
    case SIXEL_PIXELFORMAT_G4:
810
    case SIXEL_PIXELFORMAT_G8:
811
    case SIXEL_PIXELFORMAT_PAL1:
812
    case SIXEL_PIXELFORMAT_PAL2:
813
    case SIXEL_PIXELFORMAT_PAL4:
814
    case SIXEL_PIXELFORMAT_PAL8:
815
        depth = 1;
365✔
816
        break;
365✔
817
    case SIXEL_PIXELFORMAT_RGBFLOAT32:
480✔
818
    case SIXEL_PIXELFORMAT_LINEARRGBFLOAT32:
819
    case SIXEL_PIXELFORMAT_OKLABFLOAT32:
820
    case SIXEL_PIXELFORMAT_CIELABFLOAT32:
821
    case SIXEL_PIXELFORMAT_DIN99DFLOAT32:
822
    case SIXEL_PIXELFORMAT_YUVFLOAT32:
823
        depth = (int)(sizeof(float) * 3);
707✔
824
        break;
707✔
825
    default:
826
        break;
827
    }
828

829
    return depth;
4,739✔
830
}
1,228✔
831

832

833
static void
834
expand_rgb(unsigned char *restrict dst,
1✔
835
           unsigned char const *restrict src,
836
           int width, int height,
837
           int pixelformat, int depth)
838
{
839
    int x;
1✔
840
    int y;
1✔
841
    int dst_stride;
1✔
842
    int src_stride;
1✔
843
    sixel_rgb_reader_t reader;
1✔
844
    unsigned char const *src_row;
1✔
845
    unsigned char const *src_pixel;
1✔
846
    unsigned char *dst_row;
1✔
847
    unsigned char *dst_pixel;
1✔
848
    unsigned char r;
1✔
849
    unsigned char g;
1✔
850
    unsigned char b;
1✔
851

852
    /*
853
     * Select the reader once to avoid per-pixel branching. The lookup
854
     * maps each pixelformat to a dedicated decoder so the inner loop
855
     * only performs pointer math and byte stores.
856
     */
857
    reader = sixel_select_rgb_reader(pixelformat);
1✔
858

859
    /*
860
     * Pre-compute strides to avoid repeated multiplications in the
861
     * inner loop. The caller guarantees that the buffers are large
862
     * enough, so we can advance pointers by depth/3 bytes per pixel
863
     * instead of recalculating offsets each time.
864
     */
865
    dst_stride = width * 3;
1✔
866
    src_stride = width * depth;
1✔
867
    src_row = src;
1✔
868
    dst_row = dst;
1✔
869

870
    for (y = 0; y < height; y++) {
451!
871
        src_pixel = src_row;
450✔
872
        dst_pixel = dst_row;
450✔
873
        for (x = 0; x < width; x++) {
270,450!
874
            reader(src_pixel, &r, &g, &b);
270,000✔
875

876
            dst_pixel[0] = r;
270,000✔
877
            dst_pixel[1] = g;
270,000✔
878
            dst_pixel[2] = b;
270,000✔
879

880
            src_pixel += depth;
270,000✔
881
            dst_pixel += 3;
270,000✔
882
        }
270,000✔
883

884
        src_row += src_stride;
450✔
885
        dst_row += dst_stride;
450✔
886
    }
450✔
887
}
1✔
888

889

890
/*
891
 * Lookup tables for expanding packed palette indices. Each entry holds
892
 * the unpacked values for one input byte so the inner loops only copy
893
 * precomputed bytes instead of shifting each pixel.
894
 */
895
static unsigned char palette_table1[256][8];
896
static unsigned char palette_table2[256][4];
897
static unsigned char palette_table4[256][2];
898
static int palette_table_initialized;
899
static sixel_mutex_t palette_table_mutex;
900
static int palette_table_mutex_ready;
901

902

903
static int
904
sixel_init_palette_tables(void)
9✔
905
{
906
    int value;
9✔
907
    int i;
9✔
908
    int init_result;
9✔
909

910
    /*
911
     * Tables are generated once on first use to avoid increasing the
912
     * binary size with large static initializers.
913
     */
914
    if (palette_table_initialized) {
9!
915
        return 1;
916
    }
917

918
    if (palette_table_mutex_ready == 0) {
9!
919
        init_result = sixel_mutex_init(&palette_table_mutex);
9✔
920
        if (init_result == 0) {
9!
921
            palette_table_mutex_ready = 1;
9✔
922
        } else {
9✔
923
            palette_table_mutex_ready = -1;
×
924
        }
925
    }
9✔
926

927
    if (palette_table_mutex_ready < 0) {
9!
928
        /*
929
         * Without a mutex we cannot guarantee a race-free initialization.
930
         * Defer to the shift-based fallback path so multiple threads do not
931
         * write the static tables concurrently.
932
         */
933
        return 0;
934
    }
935

936
    if (palette_table_mutex_ready == 1) {
9!
937
        sixel_mutex_lock(&palette_table_mutex);
9✔
938
        if (palette_table_initialized) {
9!
939
            sixel_mutex_unlock(&palette_table_mutex);
×
940
            return 1;
×
941
        }
942
    }
9✔
943

944
    for (value = 0; value < 256; ++value) {
2,313!
945
        for (i = 0; i < 8; ++i) {
20,736!
946
            palette_table1[value][i] =
18,432✔
947
                (unsigned char)((value >> (7 - i)) & 0x01);
18,432✔
948
        }
18,432✔
949

950
        for (i = 0; i < 4; ++i) {
11,520!
951
            palette_table2[value][i] =
9,216✔
952
                (unsigned char)((value >> (6 - i * 2)) & 0x03);
9,216✔
953
        }
9,216✔
954

955
        for (i = 0; i < 2; ++i) {
6,912!
956
            palette_table4[value][i] =
4,608✔
957
                (unsigned char)((value >> (4 - i * 4)) & 0x0f);
4,608✔
958
        }
4,608✔
959
    }
2,304✔
960

961
    palette_table_initialized = 1;
9✔
962

963
    /*
964
     * Release the mutex after the single initialization pass so later calls
965
     * can reuse the tables without redundant locking.
966
     */
967
    sixel_mutex_unlock(&palette_table_mutex);
9✔
968

969
    return 1;
9✔
970
}
9✔
971

972

973
/*
974
 * Expand packed 1 bpp rows by copying a precomputed 8-pixel block per
975
 * source byte. A tiny tail loop handles the remainder when width is not
976
 * divisible by 8.
977
 */
978
static void
979
sixel_expand_palette_bpp1(unsigned char *restrict dst,
2✔
980
                          unsigned char const *restrict src,
981
                          int width, int height)
982
{
983
    int y;
2✔
984
    int x;
2✔
985
    int remainder;
2✔
986
    int byte_count;
2✔
987
    unsigned char const *table_entry;
2✔
988

989
    byte_count = width / 8;
2✔
990
    remainder = width - byte_count * 8;
2✔
991

992
    if (remainder == 0) {
2!
993
        /*
994
         * Fast path for byte-aligned rows. Removing the per-row
995
         * remainder branch keeps the steady-state inner loop tight.
996
         */
997
        for (y = 0; y < height; ++y) {
66!
998
            for (x = 0; x < byte_count; ++x) {
320!
999
                table_entry = palette_table1[src[0]];
256✔
1000
                memcpy(dst, table_entry, 8);
256✔
1001
                dst += 8;
256✔
1002
                src += 1;
256✔
1003
            }
256✔
1004
        }
64✔
1005
    } else {
2✔
1006
        /*
1007
         * Handle rows with a short tail. The main loop still copies a
1008
         * precomputed 8-pixel block per byte while the tail is expanded
1009
         * via a short memcpy so the steady-state loop remains branch free.
1010
         */
1011
        for (y = 0; y < height; ++y) {
×
1012
            for (x = 0; x < byte_count; ++x) {
×
1013
                table_entry = palette_table1[src[0]];
×
1014
                memcpy(dst, table_entry, 8);
×
1015
                dst += 8;
×
1016
                src += 1;
×
1017
            }
1018

1019
            table_entry = palette_table1[src[0]];
×
1020
            memcpy(dst, table_entry, (size_t)remainder);
×
1021
            dst += remainder;
×
1022
            src += 1;
×
1023
        }
1024
    }
1025
}
2✔
1026

1027

1028
/*
1029
 * Expand packed 2 bpp rows. Each lookup yields four pixels so the inner
1030
 * loop becomes a memcpy per byte, followed by a small tail when the row
1031
 * width leaves a remainder.
1032
 */
1033
static void
1034
sixel_expand_palette_bpp2(unsigned char *restrict dst,
2✔
1035
                          unsigned char const *restrict src,
1036
                          int width, int height)
1037
{
1038
    int y;
2✔
1039
    int x;
2✔
1040
    int remainder;
2✔
1041
    int byte_count;
2✔
1042
    unsigned char const *table_entry;
2✔
1043

1044
    byte_count = width / 4;
2✔
1045
    remainder = width - byte_count * 4;
2✔
1046

1047
    if (remainder == 0) {
2!
1048
        /*
1049
         * Width aligned to 4 pixels: skip the tail branch and keep the
1050
         * inner loop limited to memcpy plus pointer bumps.
1051
         */
1052
        for (y = 0; y < height; ++y) {
66!
1053
            for (x = 0; x < byte_count; ++x) {
576!
1054
                table_entry = palette_table2[src[0]];
512✔
1055
                memcpy(dst, table_entry, 4);
512✔
1056
                dst += 4;
512✔
1057
                src += 1;
512✔
1058
            }
512✔
1059
        }
64✔
1060
    } else {
2✔
1061
        /*
1062
         * Non-multiple-of-four widths still use the table for the bulk
1063
         * of each row and append the remaining pixels via a short memcpy.
1064
         */
1065
        for (y = 0; y < height; ++y) {
×
1066
            for (x = 0; x < byte_count; ++x) {
×
1067
                table_entry = palette_table2[src[0]];
×
1068
                memcpy(dst, table_entry, 4);
×
1069
                dst += 4;
×
1070
                src += 1;
×
1071
            }
1072

1073
            table_entry = palette_table2[src[0]];
×
1074
            memcpy(dst, table_entry, (size_t)remainder);
×
1075
            dst += remainder;
×
1076
            src += 1;
×
1077
        }
1078
    }
1079
}
2✔
1080

1081

1082
/*
1083
 * Expand packed 4 bpp rows using two-pixel lookup entries. Like the
1084
 * other helpers, the remainder loop only executes when the row width is
1085
 * odd.
1086
 */
1087
static void
1088
sixel_expand_palette_bpp4(unsigned char *restrict dst,
5✔
1089
                          unsigned char const *restrict src,
1090
                          int width, int height)
1091
{
1092
    int y;
5✔
1093
    int x;
5✔
1094
    int remainder;
5✔
1095
    int byte_count;
5✔
1096
    unsigned char const *table_entry;
5✔
1097

1098
    byte_count = width / 2;
5✔
1099
    remainder = width - byte_count * 2;
5✔
1100

1101
    if (remainder == 0) {
5!
1102
        /*
1103
         * When width is an even number of pixels the loop becomes a
1104
         * pure memcpy stream with no per-row branching.
1105
         */
1106
        for (y = 0; y < height; ++y) {
109!
1107
            for (x = 0; x < byte_count; ++x) {
13,688!
1108
                table_entry = palette_table4[src[0]];
13,582✔
1109
                memcpy(dst, table_entry, 2);
13,582✔
1110
                dst += 2;
13,582✔
1111
                src += 1;
13,582✔
1112
            }
13,582✔
1113
        }
106✔
1114
    } else {
3✔
1115
        /*
1116
         * Otherwise process the bulk via the lookup table and append the
1117
         * one remaining pixel with a short memcpy.
1118
         */
1119
        for (y = 0; y < height; ++y) {
30!
1120
            for (x = 0; x < byte_count; ++x) {
1,316!
1121
                table_entry = palette_table4[src[0]];
1,288✔
1122
                memcpy(dst, table_entry, 2);
1,288✔
1123
                dst += 2;
1,288✔
1124
                src += 1;
1,288✔
1125
            }
1,288✔
1126

1127
            table_entry = palette_table4[src[0]];
28✔
1128
            memcpy(dst, table_entry, (size_t)remainder);
28✔
1129
            dst += remainder;
28✔
1130
            src += 1;
28✔
1131
        }
28✔
1132
    }
1133
}
5✔
1134

1135

1136
/*
1137
 * Fallback path that mirrors the original shift-and-mask expansion for
1138
 * packed palette formats. This is selected when the lookup tables cannot be
1139
 * initialized, preserving correctness without concurrent writes to the
1140
 * static buffers.
1141
 */
1142
static void
1143
sixel_expand_palette_fallback(unsigned char *restrict dst,
×
1144
                              unsigned char const *restrict src,
1145
                              int width,
1146
                              int height,
1147
                              int bpp)
1148
{
1149
    int x;
×
1150
    int y;
×
1151
    int i;
×
1152
    int bytes_per_row;
×
1153
    int remainder;
×
1154
    int bits_per_byte;
×
1155
    int mask;
×
1156

1157
    bits_per_byte = 8 / bpp;
×
1158
    mask = (1 << bpp) - 1;
×
1159
    bytes_per_row = width * bpp / 8;
×
1160
    remainder = width - bytes_per_row * bits_per_byte;
×
1161

1162
    for (y = 0; y < height; ++y) {
×
1163
        for (x = 0; x < bytes_per_row; ++x) {
×
1164
            for (i = 0; i < bits_per_byte; ++i) {
×
1165
                *dst++ = (unsigned char)((src[0] >>
×
1166
                    (bits_per_byte - 1 - i) * bpp) & mask);
×
1167
            }
1168
            ++src;
×
1169
        }
1170

1171
        if (remainder > 0) {
×
1172
            for (i = 0; i < remainder; ++i) {
×
1173
                *dst++ = (unsigned char)((src[0] >>
×
1174
                    (bits_per_byte * bpp - (i + 1) * bpp)) & mask);
×
1175
            }
1176
            ++src;
×
1177
        }
1178
    }
1179
}
×
1180

1181

1182
static SIXELSTATUS
1183
expand_palette(unsigned char *restrict dst,
9✔
1184
               unsigned char const *restrict src,
1185
               int width, int height, int const pixelformat)
1186
{
1187
    SIXELSTATUS status = SIXEL_FALSE;
9✔
1188
    int bpp;  /* bit per plane */
9✔
1189
    int use_palette_tables;
9✔
1190
    int tables_ready;
9✔
1191
    size_t total_pixels;
9✔
1192

1193
    /*
1194
     * Reject empty dimensions early. An empty row or column would make the
1195
     * byte count calculations negative and does not represent a valid image
1196
     * to expand.
1197
     */
1198
    if (width <= 0 || height <= 0) {
9!
NEW
1199
        sixel_helper_set_additional_message(
×
1200
            "expand_palette: width and height must be positive.");
NEW
1201
        status = SIXEL_BAD_ARGUMENT;
×
NEW
1202
        goto end;
×
1203
    }
1204

1205
    use_palette_tables = 0;
9✔
1206
    tables_ready = 0;
9✔
1207

1208
    switch (pixelformat) {
9!
1209
    case SIXEL_PIXELFORMAT_PAL1:
1210
    case SIXEL_PIXELFORMAT_G1:
1211
        bpp = 1;
2✔
1212
        use_palette_tables = 1;
2✔
1213
        break;
2✔
1214
    case SIXEL_PIXELFORMAT_PAL2:
1215
    case SIXEL_PIXELFORMAT_G2:
1216
        bpp = 2;
2✔
1217
        use_palette_tables = 1;
2✔
1218
        break;
2✔
1219
    case SIXEL_PIXELFORMAT_PAL4:
1220
    case SIXEL_PIXELFORMAT_G4:
1221
        bpp = 4;
5✔
1222
        use_palette_tables = 1;
5✔
1223
        break;
5✔
1224
    case SIXEL_PIXELFORMAT_PAL8:
1225
    case SIXEL_PIXELFORMAT_G8:
1226
        total_pixels = (size_t)width * (size_t)height;
×
1227

1228
        /*
1229
         * Direct copy for already expanded 8 bpp sources. Using memcpy
1230
         * avoids the per-pixel loop overhead when the input is byte
1231
         * aligned and requires no bit unpacking.
1232
         */
1233
        memcpy(dst, src, total_pixels);
×
1234
        status = SIXEL_OK;
×
1235
        goto end;
×
1236
    default:
1237
        status = SIXEL_BAD_ARGUMENT;
×
1238
        sixel_helper_set_additional_message(
×
1239
            "expand_palette: invalid pixelformat.");
1240
        goto end;
×
1241
    }
1242

1243
    if (use_palette_tables) {
9!
1244
        /*
1245
         * Initialize lookup tables only when packed palette input is
1246
         * present. Formats that are already 8 bpp avoid the setup cost.
1247
         */
1248
        tables_ready = sixel_init_palette_tables();
9✔
1249
    }
9✔
1250

1251
#if HAVE_DEBUG
1252
    fprintf(stderr, "expanding PAL%d to PAL8...\n", bpp);
9✔
1253
#endif
1254

1255
    if (tables_ready) {
18!
1256
        /*
1257
         * Use lookup tables to unroll packed indices. Each path copies an
1258
         * entire byte of indices in one memcpy, leaving only a small
1259
         * remainder loop per row for widths that are not byte-aligned.
1260
         */
1261
        switch (bpp) {
9!
1262
        case 1:
1263
            sixel_expand_palette_bpp1(dst, src, width, height);
2✔
1264
            status = SIXEL_OK;
2✔
1265
            break;
2✔
1266
        case 2:
1267
            sixel_expand_palette_bpp2(dst, src, width, height);
2✔
1268
            status = SIXEL_OK;
2✔
1269
            break;
2✔
1270
        case 4:
1271
            sixel_expand_palette_bpp4(dst, src, width, height);
5✔
1272
            status = SIXEL_OK;
5✔
1273
            break;
5✔
1274
        default:
1275
            status = SIXEL_BAD_ARGUMENT;
1276
            break;
1277
        }
1278
    } else {
9✔
1279
        /*
1280
         * Mutex initialization failed or tables are unavailable.
1281
         * Fall back to the original shift-based expansion to avoid
1282
         * concurrent writes to the static lookup buffers.
1283
         */
1284
        sixel_expand_palette_fallback(dst, src, width, height, bpp);
×
1285
        status = SIXEL_OK;
×
1286
    }
1287

1288
end:
1289
    return status;
18✔
1290
}
9✔
1291

1292

1293
SIXELAPI SIXELSTATUS
1294
sixel_helper_normalize_pixelformat(
10✔
1295
    unsigned char       /* out */ *dst,             /* destination buffer */
1296
    int                 /* out */ *dst_pixelformat, /* converted pixelformat */
1297
    unsigned char const /* in */  *src,             /* source pixels */
1298
    int                 /* in */  src_pixelformat,  /* format of source image */
1299
    int                 /* in */  width,            /* width of source image */
1300
    int                 /* in */  height)           /* height of source image */
1301
{
1302
    SIXELSTATUS status = SIXEL_FALSE;
10✔
1303
    int depth;
10✔
1304

1305
    switch (src_pixelformat) {
10!
1306
    case SIXEL_PIXELFORMAT_G8:
1307
        expand_rgb(dst, src, width, height, src_pixelformat, 1);
1✔
1308
        *dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
1✔
1309
        break;
1✔
1310
    case SIXEL_PIXELFORMAT_RGB565:
1311
    case SIXEL_PIXELFORMAT_RGB555:
1312
    case SIXEL_PIXELFORMAT_BGR565:
1313
    case SIXEL_PIXELFORMAT_BGR555:
1314
    case SIXEL_PIXELFORMAT_GA88:
1315
    case SIXEL_PIXELFORMAT_AG88:
1316
        expand_rgb(dst, src, width, height, src_pixelformat, 2);
×
1317
        *dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1318
        break;
×
1319
    case SIXEL_PIXELFORMAT_RGB888:
1320
    case SIXEL_PIXELFORMAT_BGR888:
1321
        expand_rgb(dst, src, width, height, src_pixelformat, 3);
×
1322
        *dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1323
        break;
×
1324
    case SIXEL_PIXELFORMAT_RGBFLOAT32:
1325
    case SIXEL_PIXELFORMAT_LINEARRGBFLOAT32:
1326
    case SIXEL_PIXELFORMAT_OKLABFLOAT32:
1327
    case SIXEL_PIXELFORMAT_CIELABFLOAT32:
1328
    case SIXEL_PIXELFORMAT_DIN99DFLOAT32:
1329
        depth = sixel_helper_compute_depth(src_pixelformat);
×
1330
        if (depth <= 0) {
×
1331
            status = SIXEL_BAD_ARGUMENT;
×
1332
            goto end;
×
1333
        }
1334
        expand_rgb(dst, src, width, height, src_pixelformat, depth);
×
1335
        *dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1336
        break;
×
1337
    case SIXEL_PIXELFORMAT_RGBA8888:
1338
    case SIXEL_PIXELFORMAT_ARGB8888:
1339
    case SIXEL_PIXELFORMAT_BGRA8888:
1340
    case SIXEL_PIXELFORMAT_ABGR8888:
1341
        expand_rgb(dst, src, width, height, src_pixelformat, 4);
×
1342
        *dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1343
        break;
×
1344
    case SIXEL_PIXELFORMAT_PAL1:
1345
    case SIXEL_PIXELFORMAT_PAL2:
1346
    case SIXEL_PIXELFORMAT_PAL4:
1347
        *dst_pixelformat = SIXEL_PIXELFORMAT_PAL8;
9✔
1348
        status = expand_palette(dst, src, width, height, src_pixelformat);
9✔
1349
        if (SIXEL_FAILED(status)) {
9!
1350
            goto end;
×
1351
        }
1352
        break;
9✔
1353
    case SIXEL_PIXELFORMAT_G1:
1354
    case SIXEL_PIXELFORMAT_G2:
1355
    case SIXEL_PIXELFORMAT_G4:
1356
        *dst_pixelformat = SIXEL_PIXELFORMAT_G8;
×
1357
        status = expand_palette(dst, src, width, height, src_pixelformat);
×
1358
        if (SIXEL_FAILED(status)) {
×
1359
            goto end;
×
1360
        }
1361
        break;
1362
    case SIXEL_PIXELFORMAT_PAL8:
1363
        memcpy(dst, src, (size_t)(width * height));
×
1364
        *dst_pixelformat = src_pixelformat;
×
1365
        break;
×
1366
    default:
1367
        status = SIXEL_BAD_ARGUMENT;
×
1368
        goto end;
×
1369
    }
1370

1371
    status = SIXEL_OK;
10✔
1372

1373
end:
1374
    return status;
20✔
1375
}
10✔
1376

1377

1378
/* Normalize RGB888 input without modification. */
1379
#if HAVE_TESTS
1380
static int
1381
pixelformat_test_rgb888_passthrough(void)
×
1382
{
1383
    unsigned char dst[3];
×
1384
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1385
    int src_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1386
    unsigned char src[] = { 0x46, 0xf3, 0xe5 };
×
1387
    int ret = 0;
×
1388

1389
    int nret = EXIT_FAILURE;
×
1390

1391
    ret = sixel_helper_normalize_pixelformat(dst,
×
1392
                                             &dst_pixelformat,
1393
                                             src,
1394
                                             src_pixelformat,
1395
                                             1,
1396
                                             1);
1397
    if (ret != 0) {
×
1398
        goto error;
×
1399
    }
1400
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1401
        goto error;
×
1402
    }
1403
    if ((dst[0] << 16 | dst[1] << 8 | dst[2])
×
1404
            != (src[0] << 16 | src[1] << 8 | src[2])) {
×
1405
        goto error;
×
1406
    }
1407
    return EXIT_SUCCESS;
1408

1409
error:
1410
    perror("pixelformat_test_rgb888_passthrough");
×
1411
    return nret;
×
1412
}
1413

1414

1415
/* Convert RGB555 packed data into RGB888 output. */
1416
static int
1417
pixelformat_test_from_rgb555(void)
×
1418
{
1419
    unsigned char dst[3];
×
1420
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1421
    int src_pixelformat = SIXEL_PIXELFORMAT_RGB555;
×
1422
    unsigned char src[] = { 0x47, 0x9c };
×
1423
    int ret = 0;
×
1424

1425
    int nret = EXIT_FAILURE;
×
1426

1427
    ret = sixel_helper_normalize_pixelformat(dst,
×
1428
                                             &dst_pixelformat,
1429
                                             src,
1430
                                             src_pixelformat,
1431
                                             1,
1432
                                             1);
1433
    if (ret != 0) {
×
1434
        goto error;
×
1435
    }
1436
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1437
        goto error;
×
1438
    }
1439
    if ((dst[0] >> 3 << 10 | dst[1] >> 3 << 5 | dst[2] >> 3)
×
1440
            != (src[0] << 8 | src[1])) {
×
1441
        goto error;
×
1442
    }
1443
    return EXIT_SUCCESS;
1444

1445
error:
1446
    perror("pixelformat_test_from_rgb555");
×
1447
    return nret;
×
1448
}
1449

1450

1451
/* Convert RGB565 packed data into RGB888 output. */
1452
static int
1453
pixelformat_test_from_rgb565(void)
×
1454
{
1455
    unsigned char dst[3];
×
1456
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1457
    int src_pixelformat = SIXEL_PIXELFORMAT_RGB565;
×
1458
    unsigned char src[] = { 0x47, 0x9c };
×
1459
    int ret = 0;
×
1460

1461
    int nret = EXIT_FAILURE;
×
1462

1463
    ret = sixel_helper_normalize_pixelformat(dst,
×
1464
                                             &dst_pixelformat,
1465
                                             src,
1466
                                             src_pixelformat,
1467
                                             1,
1468
                                             1);
1469
    if (ret != 0) {
×
1470
        goto error;
×
1471
    }
1472
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1473
        goto error;
×
1474
    }
1475
    if ((dst[0] >> 3 << 11 | dst[1] >> 2 << 5 | dst[2] >> 3)
×
1476
            != (src[0] << 8 | src[1])) {
×
1477
        goto error;
×
1478
    }
1479
    return EXIT_SUCCESS;
1480

1481
error:
1482
    perror("pixelformat_test_from_rgb565");
×
1483
    return nret;
×
1484
}
1485

1486

1487
/* Swap channels from BGR888 to RGB888. */
1488
static int
1489
pixelformat_test_from_bgr888(void)
×
1490
{
1491
    unsigned char dst[3];
×
1492
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1493
    int src_pixelformat = SIXEL_PIXELFORMAT_BGR888;
×
1494
    unsigned char src[] = { 0x46, 0xf3, 0xe5 };
×
1495
    int ret = 0;
×
1496

1497
    int nret = EXIT_FAILURE;
×
1498

1499
    ret = sixel_helper_normalize_pixelformat(dst,
×
1500
                                             &dst_pixelformat,
1501
                                             src,
1502
                                             src_pixelformat,
1503
                                             1,
1504
                                             1);
1505
    if (ret != 0) {
×
1506
        goto error;
×
1507
    }
1508
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1509
        goto error;
×
1510
    }
1511
    if ((dst[2] << 16 | dst[1] << 8 | dst[0])
×
1512
            != (src[0] << 16 | src[1] << 8 | src[2])) {
×
1513
        goto error;
×
1514
    }
1515
    return EXIT_SUCCESS;
1516

1517
error:
1518
    perror("pixelformat_test_from_bgr888");
×
1519
    return nret;
×
1520
}
1521

1522

1523
/* Convert BGR555 packed data into RGB888 output. */
1524
static int
1525
pixelformat_test_from_bgr555(void)
×
1526
{
1527
    unsigned char dst[3];
×
1528
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1529
    int src_pixelformat = SIXEL_PIXELFORMAT_BGR555;
×
1530
    unsigned char src[] = { 0x23, 0xc8 };
×
1531
    int ret = 0;
×
1532

1533
    int nret = EXIT_FAILURE;
×
1534

1535
    ret = sixel_helper_normalize_pixelformat(dst,
×
1536
                                             &dst_pixelformat,
1537
                                             src,
1538
                                             src_pixelformat,
1539
                                             1,
1540
                                             1);
1541
    if (ret != 0) {
×
1542
        goto error;
×
1543
    }
1544
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1545
        goto error;
×
1546
    }
1547
    if ((dst[2] >> 3 << 10 | dst[1] >> 3 << 5 | dst[0] >> 3)
×
1548
            != (src[0] << 8 | src[1])) {
×
1549
        goto error;
×
1550
    }
1551
    return EXIT_SUCCESS;
1552

1553
error:
1554
    perror("pixelformat_test_from_bgr555");
×
1555
    return nret;
×
1556
}
1557

1558

1559
/* Convert BGR565 packed data into RGB888 output. */
1560
static int
1561
pixelformat_test_from_bgr565(void)
×
1562
{
1563
    unsigned char dst[3];
×
1564
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1565
    int src_pixelformat = SIXEL_PIXELFORMAT_BGR565;
×
1566
    unsigned char src[] = { 0x47, 0x88 };
×
1567
    int ret = 0;
×
1568

1569
    int nret = EXIT_FAILURE;
×
1570

1571
    ret = sixel_helper_normalize_pixelformat(dst,
×
1572
                                             &dst_pixelformat,
1573
                                             src,
1574
                                             src_pixelformat,
1575
                                             1,
1576
                                             1);
1577
    if (ret != 0) {
×
1578
        goto error;
×
1579
    }
1580
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1581
        goto error;
×
1582
    }
1583
    if ((dst[2] >> 3 << 11 | dst[1] >> 2 << 5 | dst[0] >> 3)
×
1584
            != (src[0] << 8 | src[1])) {
×
1585
        goto error;
×
1586
    }
1587
    return EXIT_SUCCESS;
1588

1589
error:
1590
    perror("pixelformat_test_from_bgr565");
×
1591
    return nret;
×
1592
}
1593

1594

1595
/* Convert AG88 data by discarding alpha and keeping gray. */
1596
static int
1597
pixelformat_test_from_ag88(void)
×
1598
{
1599
    unsigned char dst[3];
×
1600
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1601
    int src_pixelformat = SIXEL_PIXELFORMAT_AG88;
×
1602
    unsigned char src[] = { 0x47, 0x88 };
×
1603
    int ret = 0;
×
1604

1605
    int nret = EXIT_FAILURE;
×
1606

1607
    ret = sixel_helper_normalize_pixelformat(dst,
×
1608
                                             &dst_pixelformat,
1609
                                             src,
1610
                                             src_pixelformat,
1611
                                             1,
1612
                                             1);
1613
    if (ret != 0) {
×
1614
        goto error;
×
1615
    }
1616
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1617
        goto error;
×
1618
    }
1619
    if (dst[0] != src[1]) {
×
1620
        goto error;
×
1621
    }
1622
    return EXIT_SUCCESS;
1623

1624
error:
1625
    perror("pixelformat_test_from_ag88");
×
1626
    return nret;
×
1627
}
1628

1629

1630
/* Convert GA88 data by duplicating gray channel into RGB. */
1631
static int
1632
pixelformat_test_from_ga88(void)
×
1633
{
1634
    unsigned char dst[3];
×
1635
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1636
    int src_pixelformat = SIXEL_PIXELFORMAT_GA88;
×
1637
    unsigned char src[] = { 0x47, 0x88 };
×
1638
    int ret = 0;
×
1639

1640
    int nret = EXIT_FAILURE;
×
1641

1642
    ret = sixel_helper_normalize_pixelformat(dst,
×
1643
                                             &dst_pixelformat,
1644
                                             src,
1645
                                             src_pixelformat,
1646
                                             1,
1647
                                             1);
1648
    if (ret != 0) {
×
1649
        goto error;
×
1650
    }
1651
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1652
        goto error;
×
1653
    }
1654
    if (dst[0] != src[0]) {
×
1655
        goto error;
×
1656
    }
1657
    return EXIT_SUCCESS;
1658

1659
error:
1660
    perror("pixelformat_test_from_ga88");
×
1661
    return nret;
×
1662
}
1663

1664

1665
/* Normalize RGBA8888 by dropping alpha. */
1666
static int
1667
pixelformat_test_from_rgba8888(void)
×
1668
{
1669
    unsigned char dst[3];
×
1670
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1671
    int src_pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
×
1672
    unsigned char src[] = { 0x46, 0xf3, 0xe5, 0xf0 };
×
1673
    int ret = 0;
×
1674

1675
    int nret = EXIT_FAILURE;
×
1676

1677
    ret = sixel_helper_normalize_pixelformat(dst,
×
1678
                                             &dst_pixelformat,
1679
                                             src,
1680
                                             src_pixelformat,
1681
                                             1,
1682
                                             1);
1683
    if (ret != 0) {
×
1684
        goto error;
×
1685
    }
1686
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1687
        goto error;
×
1688
    }
1689
    if (dst[0] != src[0]) {
×
1690
        goto error;
×
1691
    }
1692
    if (dst[1] != src[1]) {
×
1693
        goto error;
×
1694
    }
1695
    if (dst[2] != src[2]) {
×
1696
        goto error;
×
1697
    }
1698
    return EXIT_SUCCESS;
1699

1700
error:
1701
    perror("pixelformat_test_from_rgba8888");
×
1702
    return nret;
×
1703
}
1704

1705

1706
/* Normalize ARGB8888 while skipping the leading alpha byte. */
1707
static int
1708
pixelformat_test_from_argb8888(void)
×
1709
{
1710
    unsigned char dst[3];
×
1711
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1712
    int src_pixelformat = SIXEL_PIXELFORMAT_ARGB8888;
×
1713
    unsigned char src[] = { 0x46, 0xf3, 0xe5, 0xf0 };
×
1714
    int ret = 0;
×
1715

1716
    int nret = EXIT_FAILURE;
×
1717

1718
    ret = sixel_helper_normalize_pixelformat(dst,
×
1719
                                             &dst_pixelformat,
1720
                                             src,
1721
                                             src_pixelformat,
1722
                                             1,
1723
                                             1);
1724
    if (ret != 0) {
×
1725
        goto error;
×
1726
    }
1727
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1728
        goto error;
×
1729
    }
1730
    if (dst[0] != src[1]) {
×
1731
        goto error;
×
1732
    }
1733
    if (dst[1] != src[2]) {
×
1734
        goto error;
×
1735
    }
1736
    if (dst[2] != src[3]) {
×
1737
        goto error;
×
1738
    }
1739
    return EXIT_SUCCESS;
1740

1741
error:
1742
    perror("pixelformat_test_from_argb8888");
×
1743
    return nret;
×
1744
}
1745

1746

1747
/* Convert floating point RGB data to normalized 8-bit output. */
1748
static int
1749
pixelformat_test_from_rgbfloat32(void)
×
1750
{
1751
    unsigned char dst[3];
×
1752
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1753
    int src_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
×
1754
    float srcf[] = { 0.0f, 0.5f, 1.0f };
×
1755
    unsigned char const *src = (unsigned char const *)srcf;
×
1756
    int ret = 0;
×
1757
    int depth;
×
1758

1759
    int nret = EXIT_FAILURE;
×
1760

1761
    ret = sixel_helper_normalize_pixelformat(dst,
×
1762
                                             &dst_pixelformat,
1763
                                             src,
1764
                                             src_pixelformat,
1765
                                             1,
1766
                                             1);
1767
    if (ret != 0) {
×
1768
        goto error;
×
1769
    }
1770
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1771
        goto error;
×
1772
    }
1773
    if (dst[0] != 0 || dst[1] != 128 || dst[2] != 255) {
×
1774
        goto error;
×
1775
    }
1776
    depth = sixel_helper_compute_depth(src_pixelformat);
×
1777
    if (depth != (int)(sizeof(float) * 3)) {
×
1778
        goto error;
×
1779
    }
1780
    return EXIT_SUCCESS;
1781

1782
error:
1783
    perror("pixelformat_test_from_rgbfloat32");
×
1784
    return nret;
×
1785
}
1786

1787

1788
SIXELAPI int
1789
sixel_pixelformat_tests_main(void)
×
1790
{
1791
    int nret = EXIT_FAILURE;
×
1792
    size_t i;
×
1793
    typedef int (* testcase)(void);
1794

1795
    static testcase const testcases[] = {
1796
        pixelformat_test_rgb888_passthrough,
1797
        pixelformat_test_from_rgb555,
1798
        pixelformat_test_from_rgb565,
1799
        pixelformat_test_from_bgr888,
1800
        pixelformat_test_from_bgr555,
1801
        pixelformat_test_from_bgr565,
1802
        pixelformat_test_from_ag88,
1803
        pixelformat_test_from_ga88,
1804
        pixelformat_test_from_rgba8888,
1805
        pixelformat_test_from_argb8888,
1806
        pixelformat_test_from_rgbfloat32,
1807
    };
1808

1809
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
1810
        nret = testcases[i]();
×
1811
        if (nret != EXIT_SUCCESS) {
×
1812
            goto error;
×
1813
        }
1814
    }
1815

1816
    nret = EXIT_SUCCESS;
1817

1818
error:
1819
    return nret;
×
1820
}
1821
#endif  /* HAVE_TESTS */
1822

1823
/* emacs Local Variables:      */
1824
/* emacs mode: c               */
1825
/* emacs tab-width: 4          */
1826
/* emacs indent-tabs-mode: nil */
1827
/* emacs c-basic-offset: 4     */
1828
/* emacs End:                  */
1829
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1830
/* 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