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

saitoha / libsixel / 20609368106

31 Dec 2025 12:57AM UTC coverage: 52.011% (-6.3%) from 58.281%
20609368106

push

github

saitoha
tests: split converter option tap suites

14741 of 45141 branches covered (32.66%)

21394 of 41134 relevant lines covered (52.01%)

3932390.77 hits per line

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

14.52
/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
#if defined(HAVE_CONFIG_H)
26
#include "config.h"
27
#endif
28

29
/* STDC_HEADERS */
30
#include <stdio.h>
31
#include <stdlib.h>
32

33
#if HAVE_MATH_H
34
# include <math.h>
35
#endif  /* HAVE_MATH_H */
36

37
#if HAVE_MEMORY_H
38
# include <memory.h>
39
#endif  /* HAVE_MEMORY_H */
40

41
#include <sixel.h>
42

43
#include "compat_stub.h"
44
#include "threading.h"
45
#include "pixelformat.h"
46

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

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

78
    if (value <= 0.0f) {
2,096,398✔
79
        return 0;
80
    }
81
    if (value >= 1.0f) {
176,806✔
82
        return 255;
83
    }
84

85
    return (unsigned char)(value * 255.0f + 0.5f);
176,512✔
86
}
87

88
static unsigned char
89
sixel_pixelformat_oklab_L_to_byte(float value)
108✔
90
{
91
#if HAVE_MATH_H
92
    if (!isfinite(value)) {
108!
93
        value = 0.0f;
94
    }
95
#endif  /* HAVE_MATH_H */
96

97
    if (value <= 0.0f) {
108!
98
        return 0;
99
    }
100
    if (value >= 1.0f) {
108!
101
        return 255;
102
    }
103

104
    return (unsigned char)(value * 255.0f + 0.5f);
108✔
105
}
106

107
static unsigned char
108
sixel_pixelformat_oklab_ab_to_byte(float value)
216✔
109
{
110
    float encoded;
216✔
111

112
#if HAVE_MATH_H
113
    if (!isfinite(value)) {
216!
114
        value = 0.0f;
115
    }
116
#endif  /* HAVE_MATH_H */
117

118
    encoded = value + 0.5f;
432✔
119
    if (encoded <= 0.0f) {
216!
120
        return 0;
121
    }
122
    if (encoded >= 1.0f) {
216!
123
        return 255;
124
    }
125

126
    return (unsigned char)(encoded * 255.0f + 0.5f);
216✔
127
}
128

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

138
    if (value <= 0.0f) {
×
139
        return 0;
140
    }
141
    if (value >= 1.0f) {
×
142
        return 255;
143
    }
144

145
    return (unsigned char)(value * 255.0f + 0.5f);
×
146
}
147

148
static unsigned char
149
sixel_pixelformat_cielab_ab_to_byte(float value)
×
150
{
151
    float encoded;
×
152

153
#if HAVE_MATH_H
154
    if (!isfinite(value)) {
×
155
        value = 0.0f;
×
156
    }
157
#endif  /* HAVE_MATH_H */
158

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

167
    return (unsigned char)(encoded * 255.0f + 0.5f);
×
168
}
169

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

179
    if (value <= 0.0f) {
×
180
        return 0;
181
    }
182
    if (value >= 1.0f) {
×
183
        return 255;
184
    }
185

186
    return (unsigned char)(value * 255.0f + 0.5f);
×
187
}
188

189
static unsigned char
190
sixel_pixelformat_din99d_ab_to_byte(float value)
×
191
{
192
    float encoded;
×
193

194
#if HAVE_MATH_H
195
    if (!isfinite(value)) {
×
196
        value = 0.0f;
×
197
    }
198
#endif  /* HAVE_MATH_H */
199

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

208
    return (unsigned char)(encoded * 255.0f + 0.5f);
×
209
}
210

211
static unsigned char
212
sixel_pixelformat_yuv_chroma_to_byte(float value, float range)
×
213
{
214
    float encoded;
×
215

216
#if HAVE_MATH_H
217
    if (!isfinite(value)) {
×
218
        value = 0.0f;
×
219
    }
220
#endif  /* HAVE_MATH_H */
221

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

230
    return (unsigned char)(encoded * 255.0f + 0.5f);
×
231
}
232

233
static float
234
sixel_pixelformat_yuv_chroma_from_byte(unsigned char value, float range)
×
235
{
236
    float encoded;
×
237

238
    encoded = (float)value / 255.0f;
×
239
    return (encoded - 0.5f) * (2.0f * range);
×
240
}
241

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

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

312
float
313
sixel_pixelformat_float_channel_clamp(int pixelformat,
324✔
314
                                      int channel,
315
                                      float value)
316
{
317
    float minimum;
324✔
318
    float maximum;
324✔
319

320
#if HAVE_MATH_H
321
    if (!isfinite(value)) {
324!
322
        value = 0.0f;
×
323
    }
324
#endif  /* HAVE_MATH_H */
325

326
    minimum = sixel_pixelformat_float_channel_min_internal(pixelformat,
324✔
327
                                                           channel);
328
    maximum = sixel_pixelformat_float_channel_max_internal(pixelformat,
324✔
329
                                                           channel);
330
    if (value < minimum) {
324!
331
        return minimum;
332
    }
333
    if (value > maximum) {
324!
334
        return maximum;
335
    }
336

337
    return value;
338
}
339

340
unsigned char
341
sixel_pixelformat_float_channel_to_byte(int pixelformat,
324✔
342
                                        int channel,
343
                                        float value)
344
{
345
    float clamped;
324✔
346

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

381
    (void)channel;
×
382
    return sixel_pixelformat_float_to_byte(clamped);
×
383
}
384

385
float
386
sixel_pixelformat_byte_to_float(int pixelformat,
51,502,092✔
387
                                int channel,
388
                                unsigned char value)
389
{
390
    float decoded;
51,502,092✔
391

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

430
    (void)channel;
51,501,552✔
431
    return (float)value / 255.0f;
51,501,552✔
432
}
433

434
typedef void (*sixel_rgb_reader_t)(unsigned char const *data,
435
                                    unsigned char *r,
436
                                    unsigned char *g,
437
                                    unsigned char *b);
438

439

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

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

451
#if SWAP_BYTES
452
    low = pixels & 0xff;
453
    high = (pixels >> 8) & 0xff;
454
    pixels = (low << 8) | high;
455
#endif
456

457
    return pixels;
×
458
}
459

460

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

469
    pixels = sixel_rgb_read16(data);
×
470

471
    *r = ((pixels >> 10) & 0x1f) << 3;
×
472
    *g = ((pixels >> 5) & 0x1f) << 3;
×
473
    *b = ((pixels >> 0) & 0x1f) << 3;
×
474
}
×
475

476

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

485
    pixels = sixel_rgb_read16(data);
×
486

487
    *r = ((pixels >> 11) & 0x1f) << 3;
×
488
    *g = ((pixels >> 5) & 0x3f) << 2;
×
489
    *b = ((pixels >> 0) & 0x1f) << 3;
×
490
}
×
491

492

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

501
    pixels = sixel_rgb_read16(data);
×
502

503
    *r = ((pixels >> 0) & 0x1f) << 3;
×
504
    *g = ((pixels >> 5) & 0x1f) << 3;
×
505
    *b = ((pixels >> 10) & 0x1f) << 3;
×
506
}
×
507

508

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

517
    pixels = sixel_rgb_read16(data);
×
518

519
    *r = ((pixels >> 0) & 0x1f) << 3;
×
520
    *g = ((pixels >> 5) & 0x3f) << 2;
×
521
    *b = ((pixels >> 11) & 0x1f) << 3;
×
522
}
×
523

524

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

533
    pixels = sixel_rgb_read16(data);
×
534

535
    *r = (pixels >> 8) & 0xff;
×
536
    *g = (pixels >> 8) & 0xff;
×
537
    *b = (pixels >> 8) & 0xff;
×
538
}
×
539

540

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

549
    pixels = sixel_rgb_read16(data);
×
550

551
    *r = pixels & 0xff;
×
552
    *g = pixels & 0xff;
×
553
    *b = pixels & 0xff;
×
554
}
×
555

556

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

568

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

580

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

592

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

604

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

616

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

628

629
static void
630
sixel_rgb_from_g8(unsigned char const *data,
×
631
                  unsigned char *r,
632
                  unsigned char *g,
633
                  unsigned char *b)
634
{
635
    *r = data[0];
×
636
    *g = data[0];
×
637
    *b = data[0];
×
638
}
×
639

640

641
static void
642
sixel_rgb_from_rgbfloat32(unsigned char const *data,
698,848✔
643
                          unsigned char *r,
644
                          unsigned char *g,
645
                          unsigned char *b)
646
{
647
    float const *fpixels;
698,848✔
648

649
    fpixels = (float const *)(void const *)data;
698,848✔
650

651
    *r = sixel_pixelformat_float_to_byte(fpixels[0]);
698,848✔
652
    *g = sixel_pixelformat_float_to_byte(fpixels[1]);
698,848✔
653
    *b = sixel_pixelformat_float_to_byte(fpixels[2]);
698,848✔
654
}
698,848✔
655

656

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

665
    fpixels = (float const *)(void const *)data;
×
666

667
    *r = sixel_pixelformat_oklab_L_to_byte(fpixels[0]);
×
668
    *g = sixel_pixelformat_oklab_ab_to_byte(fpixels[1]);
×
669
    *b = sixel_pixelformat_oklab_ab_to_byte(fpixels[2]);
×
670
}
×
671

672

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

681
    fpixels = (float const *)(void const *)data;
×
682

683
    *r = sixel_pixelformat_cielab_L_to_byte(fpixels[0]);
×
684
    *g = sixel_pixelformat_cielab_ab_to_byte(fpixels[1]);
×
685
    *b = sixel_pixelformat_cielab_ab_to_byte(fpixels[2]);
×
686
}
×
687

688

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

697
    fpixels = (float const *)(void const *)data;
×
698

699
    *r = sixel_pixelformat_din99d_L_to_byte(fpixels[0]);
×
700
    *g = sixel_pixelformat_din99d_ab_to_byte(fpixels[1]);
×
701
    *b = sixel_pixelformat_din99d_ab_to_byte(fpixels[2]);
×
702
}
×
703

704

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

713
    fpixels = (float const *)(void const *)data;
×
714

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

722

723
static void
724
sixel_rgb_from_unknown(unsigned char const *data,
×
725
                       unsigned char *r,
726
                       unsigned char *g,
727
                       unsigned char *b)
728
{
729
    (void)data;
×
730

731
    *r = 0;
×
732
    *g = 0;
×
733
    *b = 0;
×
734
}
×
735

736

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

782
    return sixel_rgb_from_unknown;
×
783
}
784

785

786
SIXELAPI int
787
sixel_helper_compute_depth(int pixelformat)
4,425✔
788
{
789
    int depth = (-1);  /* unknown */
4,425✔
790

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

832
    return depth;
4,425✔
833
}
834

835

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

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

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

873
    for (y = 0; y < height; y++) {
4,311✔
874
        src_pixel = src_row;
875
        dst_pixel = dst_row;
876
        for (x = 0; x < width; x++) {
703,105✔
877
            reader(src_pixel, &r, &g, &b);
698,848✔
878

879
            dst_pixel[0] = r;
698,848✔
880
            dst_pixel[1] = g;
698,848✔
881
            dst_pixel[2] = b;
698,848✔
882

883
            src_pixel += depth;
698,848✔
884
            dst_pixel += 3;
698,848✔
885
        }
886

887
        src_row += src_stride;
4,257✔
888
        dst_row += dst_stride;
4,257✔
889
    }
890
}
54✔
891

892

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

905

906
static int
907
sixel_init_palette_tables(void)
×
908
{
909
    char const *disable_tables;
×
910
    int value;
×
911
    int i;
×
912
    int init_result;
×
913

914
    /*
915
     * Allow tests to force the shift-based path by disabling table
916
     * initialization via SIXEL_PALETTE_DISABLE_TABLES. This exercises
917
     * the fallback without introducing additional code paths in
918
     * production builds.
919
     */
920
    disable_tables = sixel_compat_getenv(
×
921
            "SIXEL_PALETTE_DISABLE_TABLES");
922
    if (disable_tables != NULL && disable_tables[0] != '\0' &&
×
923
            disable_tables[0] != '0') {
924
        return 0;
925
    }
926

927
    /*
928
     * Tables are generated once on first use to avoid increasing the
929
     * binary size with large static initializers.
930
     */
931
    if (palette_table_initialized) {
×
932
        return 1;
933
    }
934

935
    if (palette_table_mutex_ready == 0) {
×
936
        init_result = sixel_mutex_init(&palette_table_mutex);
×
937
        if (init_result == 0) {
×
938
            palette_table_mutex_ready = 1;
×
939
        } else {
940
            palette_table_mutex_ready = -1;
×
941
        }
942
    }
943

944
    if (palette_table_mutex_ready < 0) {
×
945
        /*
946
         * Without a mutex we cannot guarantee a race-free initialization.
947
         * Defer to the shift-based fallback path so multiple threads do not
948
         * write the static tables concurrently.
949
         */
950
        return 0;
951
    }
952

953
    if (palette_table_mutex_ready == 1) {
×
954
        sixel_mutex_lock(&palette_table_mutex);
×
955
        if (palette_table_initialized) {
×
956
            sixel_mutex_unlock(&palette_table_mutex);
×
957
            return 1;
×
958
        }
959
    }
960

961
    for (value = 0; value < 256; ++value) {
×
962
        for (i = 0; i < 8; ++i) {
×
963
            palette_table1[value][i] =
×
964
                (unsigned char)((value >> (7 - i)) & 0x01);
×
965
        }
966

967
        for (i = 0; i < 4; ++i) {
×
968
            palette_table2[value][i] =
×
969
                (unsigned char)((value >> (6 - i * 2)) & 0x03);
×
970
        }
971

972
        for (i = 0; i < 2; ++i) {
×
973
            palette_table4[value][i] =
×
974
                (unsigned char)((value >> (4 - i * 4)) & 0x0f);
×
975
        }
976
    }
977

978
    palette_table_initialized = 1;
×
979

980
    /*
981
     * Release the mutex after the single initialization pass so later calls
982
     * can reuse the tables without redundant locking.
983
     */
984
    sixel_mutex_unlock(&palette_table_mutex);
×
985

986
    return 1;
×
987
}
988

989

990
/*
991
 * Expand packed 1 bpp rows by copying a precomputed 8-pixel block per
992
 * source byte. A tiny tail loop handles the remainder when width is not
993
 * divisible by 8.
994
 */
995
static void
996
sixel_expand_palette_bpp1(unsigned char *restrict dst,
×
997
                          unsigned char const *restrict src,
998
                          int width, int height)
999
{
1000
    int y;
×
1001
    int x;
×
1002
    int remainder;
×
1003
    int byte_count;
×
1004
    unsigned char const *table_entry;
×
1005

1006
    byte_count = width / 8;
×
1007
    remainder = width - byte_count * 8;
×
1008

1009
    if (remainder == 0) {
×
1010
        /*
1011
         * Fast path for byte-aligned rows. Removing the per-row
1012
         * remainder branch keeps the steady-state inner loop tight.
1013
         */
1014
        for (y = 0; y < height; ++y) {
×
1015
            for (x = 0; x < byte_count; ++x) {
×
1016
                table_entry = palette_table1[src[0]];
×
1017
                memcpy(dst, table_entry, 8);
×
1018
                dst += 8;
×
1019
                src += 1;
×
1020
            }
1021
        }
1022
    } else {
1023
        /*
1024
         * Handle rows with a short tail. The main loop still copies a
1025
         * precomputed 8-pixel block per byte while the tail is expanded
1026
         * via a short memcpy so the steady-state loop remains branch free.
1027
         */
1028
        for (y = 0; y < height; ++y) {
×
1029
            for (x = 0; x < byte_count; ++x) {
×
1030
                table_entry = palette_table1[src[0]];
×
1031
                memcpy(dst, table_entry, 8);
×
1032
                dst += 8;
×
1033
                src += 1;
×
1034
            }
1035

1036
            table_entry = palette_table1[src[0]];
×
1037
            memcpy(dst, table_entry, (size_t)remainder);
×
1038
            dst += remainder;
×
1039
            src += 1;
×
1040
        }
1041
    }
1042
}
×
1043

1044

1045
/*
1046
 * Expand packed 2 bpp rows. Each lookup yields four pixels so the inner
1047
 * loop becomes a memcpy per byte, followed by a small tail when the row
1048
 * width leaves a remainder.
1049
 */
1050
static void
1051
sixel_expand_palette_bpp2(unsigned char *restrict dst,
×
1052
                          unsigned char const *restrict src,
1053
                          int width, int height)
1054
{
1055
    int y;
×
1056
    int x;
×
1057
    int remainder;
×
1058
    int byte_count;
×
1059
    unsigned char const *table_entry;
×
1060

1061
    byte_count = width / 4;
×
1062
    remainder = width - byte_count * 4;
×
1063

1064
    if (remainder == 0) {
×
1065
        /*
1066
         * Width aligned to 4 pixels: skip the tail branch and keep the
1067
         * inner loop limited to memcpy plus pointer bumps.
1068
         */
1069
        for (y = 0; y < height; ++y) {
×
1070
            for (x = 0; x < byte_count; ++x) {
×
1071
                table_entry = palette_table2[src[0]];
×
1072
                memcpy(dst, table_entry, 4);
×
1073
                dst += 4;
×
1074
                src += 1;
×
1075
            }
1076
        }
1077
    } else {
1078
        /*
1079
         * Non-multiple-of-four widths still use the table for the bulk
1080
         * of each row and append the remaining pixels via a short memcpy.
1081
         */
1082
        for (y = 0; y < height; ++y) {
×
1083
            for (x = 0; x < byte_count; ++x) {
×
1084
                table_entry = palette_table2[src[0]];
×
1085
                memcpy(dst, table_entry, 4);
×
1086
                dst += 4;
×
1087
                src += 1;
×
1088
            }
1089

1090
            table_entry = palette_table2[src[0]];
×
1091
            memcpy(dst, table_entry, (size_t)remainder);
×
1092
            dst += remainder;
×
1093
            src += 1;
×
1094
        }
1095
    }
1096
}
×
1097

1098

1099
/*
1100
 * Expand packed 4 bpp rows using two-pixel lookup entries. Like the
1101
 * other helpers, the remainder loop only executes when the row width is
1102
 * odd.
1103
 */
1104
static void
1105
sixel_expand_palette_bpp4(unsigned char *restrict dst,
×
1106
                          unsigned char const *restrict src,
1107
                          int width, int height)
1108
{
1109
    int y;
×
1110
    int x;
×
1111
    int remainder;
×
1112
    int byte_count;
×
1113
    unsigned char const *table_entry;
×
1114

1115
    byte_count = width / 2;
×
1116
    remainder = width - byte_count * 2;
×
1117

1118
    if (remainder == 0) {
×
1119
        /*
1120
         * When width is an even number of pixels the loop becomes a
1121
         * pure memcpy stream with no per-row branching.
1122
         */
1123
        for (y = 0; y < height; ++y) {
×
1124
            for (x = 0; x < byte_count; ++x) {
×
1125
                table_entry = palette_table4[src[0]];
×
1126
                memcpy(dst, table_entry, 2);
×
1127
                dst += 2;
×
1128
                src += 1;
×
1129
            }
1130
        }
1131
    } else {
1132
        /*
1133
         * Otherwise process the bulk via the lookup table and append the
1134
         * one remaining pixel with a short memcpy.
1135
         */
1136
        for (y = 0; y < height; ++y) {
×
1137
            for (x = 0; x < byte_count; ++x) {
×
1138
                table_entry = palette_table4[src[0]];
×
1139
                memcpy(dst, table_entry, 2);
×
1140
                dst += 2;
×
1141
                src += 1;
×
1142
            }
1143

1144
            table_entry = palette_table4[src[0]];
×
1145
            memcpy(dst, table_entry, (size_t)remainder);
×
1146
            dst += remainder;
×
1147
            src += 1;
×
1148
        }
1149
    }
1150
}
×
1151

1152

1153
/*
1154
 * Fallback path that mirrors the original shift-and-mask expansion for
1155
 * packed palette formats. This is selected when the lookup tables cannot be
1156
 * initialized, preserving correctness without concurrent writes to the
1157
 * static buffers.
1158
 */
1159
static void
1160
sixel_expand_palette_fallback(unsigned char *restrict dst,
×
1161
                              unsigned char const *restrict src,
1162
                              int width,
1163
                              int height,
1164
                              int bpp)
1165
{
1166
    int x;
×
1167
    int y;
×
1168
    int i;
×
1169
    int bytes_per_row;
×
1170
    int remainder;
×
1171
    int bits_per_byte;
×
1172
    int mask;
×
1173

1174
    bits_per_byte = 8 / bpp;
×
1175
    mask = (1 << bpp) - 1;
×
1176
    bytes_per_row = width * bpp / 8;
×
1177
    remainder = width - bytes_per_row * bits_per_byte;
×
1178

1179
    for (y = 0; y < height; ++y) {
×
1180
        for (x = 0; x < bytes_per_row; ++x) {
×
1181
            for (i = 0; i < bits_per_byte; ++i) {
×
1182
                *dst++ = (unsigned char)((src[0] >>
×
1183
                    (bits_per_byte - 1 - i) * bpp) & mask);
×
1184
            }
1185
            ++src;
×
1186
        }
1187

1188
        if (remainder > 0) {
×
1189
            for (i = 0; i < remainder; ++i) {
×
1190
                *dst++ = (unsigned char)((src[0] >>
×
1191
                    (bits_per_byte * bpp - (i + 1) * bpp)) & mask);
×
1192
            }
1193
            ++src;
×
1194
        }
1195
    }
1196
}
×
1197

1198

1199
static SIXELSTATUS
1200
expand_palette(unsigned char *restrict dst,
×
1201
               unsigned char const *restrict src,
1202
               int width, int height, int const pixelformat)
1203
{
1204
    SIXELSTATUS status = SIXEL_FALSE;
×
1205
    int bpp;  /* bit per plane */
×
1206
    int use_palette_tables;
×
1207
    int tables_ready;
×
1208
    size_t total_pixels;
×
1209

1210
    /*
1211
     * Reject empty dimensions early. An empty row or column would make the
1212
     * byte count calculations negative and does not represent a valid image
1213
     * to expand.
1214
     */
1215
    if (width <= 0 || height <= 0) {
×
1216
        sixel_helper_set_additional_message(
×
1217
            "expand_palette: width and height must be positive.");
1218
        status = SIXEL_BAD_ARGUMENT;
×
1219
        goto end;
×
1220
    }
1221

1222
    use_palette_tables = 0;
×
1223
    tables_ready = 0;
×
1224

1225
    switch (pixelformat) {
×
1226
    case SIXEL_PIXELFORMAT_PAL1:
1227
    case SIXEL_PIXELFORMAT_G1:
1228
        bpp = 1;
1229
        use_palette_tables = 1;
1230
        break;
1231
    case SIXEL_PIXELFORMAT_PAL2:
×
1232
    case SIXEL_PIXELFORMAT_G2:
1233
        bpp = 2;
×
1234
        use_palette_tables = 1;
×
1235
        break;
×
1236
    case SIXEL_PIXELFORMAT_PAL4:
×
1237
    case SIXEL_PIXELFORMAT_G4:
1238
        bpp = 4;
×
1239
        use_palette_tables = 1;
×
1240
        break;
×
1241
    case SIXEL_PIXELFORMAT_PAL8:
×
1242
    case SIXEL_PIXELFORMAT_G8:
1243
        total_pixels = (size_t)width * (size_t)height;
×
1244

1245
        /*
1246
         * Direct copy for already expanded 8 bpp sources. Using memcpy
1247
         * avoids the per-pixel loop overhead when the input is byte
1248
         * aligned and requires no bit unpacking.
1249
         */
1250
        memcpy(dst, src, total_pixels);
×
1251
        status = SIXEL_OK;
×
1252
        goto end;
×
1253
    default:
×
1254
        status = SIXEL_BAD_ARGUMENT;
×
1255
        sixel_helper_set_additional_message(
×
1256
            "expand_palette: invalid pixelformat.");
1257
        goto end;
×
1258
    }
1259

1260
    if (use_palette_tables) {
×
1261
        /*
1262
         * Initialize lookup tables only when packed palette input is
1263
         * present. Formats that are already 8 bpp avoid the setup cost.
1264
         */
1265
        tables_ready = sixel_init_palette_tables();
×
1266
    }
1267

1268
#if HAVE_DEBUG
1269
    fprintf(stderr, "expanding PAL%d to PAL8...\n", bpp);
×
1270
#endif
1271

1272
    if (tables_ready) {
×
1273
        /*
1274
         * Use lookup tables to unroll packed indices. Each path copies an
1275
         * entire byte of indices in one memcpy, leaving only a small
1276
         * remainder loop per row for widths that are not byte-aligned.
1277
         */
1278
        switch (bpp) {
×
1279
        case 1:
×
1280
            sixel_expand_palette_bpp1(dst, src, width, height);
×
1281
            status = SIXEL_OK;
×
1282
            break;
×
1283
        case 2:
×
1284
            sixel_expand_palette_bpp2(dst, src, width, height);
×
1285
            status = SIXEL_OK;
×
1286
            break;
×
1287
        case 4:
×
1288
            sixel_expand_palette_bpp4(dst, src, width, height);
×
1289
            status = SIXEL_OK;
×
1290
            break;
×
1291
        default:
1292
            status = SIXEL_BAD_ARGUMENT;
1293
            break;
1294
        }
1295
    } else {
1296
        /*
1297
         * Mutex initialization failed or tables are unavailable.
1298
         * Fall back to the original shift-based expansion to avoid
1299
         * concurrent writes to the static lookup buffers.
1300
         */
1301
        sixel_expand_palette_fallback(dst, src, width, height, bpp);
×
1302
        status = SIXEL_OK;
×
1303
    }
1304

1305
end:
×
1306
    return status;
×
1307
}
1308

1309

1310
SIXELAPI SIXELSTATUS
1311
sixel_helper_normalize_pixelformat(
54✔
1312
    unsigned char       /* out */ *dst,             /* destination buffer */
1313
    int                 /* out */ *dst_pixelformat, /* converted pixelformat */
1314
    unsigned char const /* in */  *src,             /* source pixels */
1315
    int                 /* in */  src_pixelformat,  /* format of source image */
1316
    int                 /* in */  width,            /* width of source image */
1317
    int                 /* in */  height)           /* height of source image */
1318
{
1319
    SIXELSTATUS status = SIXEL_FALSE;
54✔
1320
    int depth;
54✔
1321

1322
    switch (src_pixelformat) {
54!
1323
    case SIXEL_PIXELFORMAT_G8:
×
1324
        expand_rgb(dst, src, width, height, src_pixelformat, 1);
×
1325
        *dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1326
        break;
×
1327
    case SIXEL_PIXELFORMAT_RGB565:
×
1328
    case SIXEL_PIXELFORMAT_RGB555:
1329
    case SIXEL_PIXELFORMAT_BGR565:
1330
    case SIXEL_PIXELFORMAT_BGR555:
1331
    case SIXEL_PIXELFORMAT_GA88:
1332
    case SIXEL_PIXELFORMAT_AG88:
1333
        expand_rgb(dst, src, width, height, src_pixelformat, 2);
×
1334
        *dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1335
        break;
×
1336
    case SIXEL_PIXELFORMAT_RGB888:
×
1337
    case SIXEL_PIXELFORMAT_BGR888:
1338
        expand_rgb(dst, src, width, height, src_pixelformat, 3);
×
1339
        *dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1340
        break;
×
1341
    case SIXEL_PIXELFORMAT_RGBFLOAT32:
54✔
1342
    case SIXEL_PIXELFORMAT_LINEARRGBFLOAT32:
1343
    case SIXEL_PIXELFORMAT_OKLABFLOAT32:
1344
    case SIXEL_PIXELFORMAT_CIELABFLOAT32:
1345
    case SIXEL_PIXELFORMAT_DIN99DFLOAT32:
1346
        depth = sixel_helper_compute_depth(src_pixelformat);
54✔
1347
        if (depth <= 0) {
54!
1348
            status = SIXEL_BAD_ARGUMENT;
×
1349
            goto end;
×
1350
        }
1351
        expand_rgb(dst, src, width, height, src_pixelformat, depth);
54✔
1352
        *dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
54✔
1353
        break;
54✔
1354
    case SIXEL_PIXELFORMAT_RGBA8888:
×
1355
    case SIXEL_PIXELFORMAT_ARGB8888:
1356
    case SIXEL_PIXELFORMAT_BGRA8888:
1357
    case SIXEL_PIXELFORMAT_ABGR8888:
1358
        expand_rgb(dst, src, width, height, src_pixelformat, 4);
×
1359
        *dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1360
        break;
×
1361
    case SIXEL_PIXELFORMAT_PAL1:
×
1362
    case SIXEL_PIXELFORMAT_PAL2:
1363
    case SIXEL_PIXELFORMAT_PAL4:
1364
        *dst_pixelformat = SIXEL_PIXELFORMAT_PAL8;
×
1365
        status = expand_palette(dst, src, width, height, src_pixelformat);
×
1366
        if (SIXEL_FAILED(status)) {
×
1367
            goto end;
×
1368
        }
1369
        break;
1370
    case SIXEL_PIXELFORMAT_G1:
×
1371
    case SIXEL_PIXELFORMAT_G2:
1372
    case SIXEL_PIXELFORMAT_G4:
1373
        *dst_pixelformat = SIXEL_PIXELFORMAT_G8;
×
1374
        status = expand_palette(dst, src, width, height, src_pixelformat);
×
1375
        if (SIXEL_FAILED(status)) {
×
1376
            goto end;
×
1377
        }
1378
        break;
1379
    case SIXEL_PIXELFORMAT_PAL8:
×
1380
        memcpy(dst, src, (size_t)(width * height));
×
1381
        *dst_pixelformat = src_pixelformat;
×
1382
        break;
×
1383
    default:
×
1384
        status = SIXEL_BAD_ARGUMENT;
×
1385
        goto end;
×
1386
    }
1387

1388
    status = SIXEL_OK;
1389

1390
end:
54✔
1391
    return status;
54✔
1392
}
1393

1394

1395
/* Normalize RGB888 input without modification. */
1396
#if HAVE_TESTS
1397
static int
1398
pixelformat_test_rgb888_passthrough(void)
×
1399
{
1400
    unsigned char dst[3];
×
1401
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1402
    int src_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1403
    unsigned char src[] = { 0x46, 0xf3, 0xe5 };
×
1404
    int ret = 0;
×
1405

1406
    int nret = EXIT_FAILURE;
×
1407

1408
    ret = sixel_helper_normalize_pixelformat(dst,
×
1409
                                             &dst_pixelformat,
1410
                                             src,
1411
                                             src_pixelformat,
1412
                                             1,
1413
                                             1);
1414
    if (ret != 0) {
×
1415
        goto error;
×
1416
    }
1417
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1418
        goto error;
×
1419
    }
1420
    if ((dst[0] << 16 | dst[1] << 8 | dst[2])
×
1421
            != (src[0] << 16 | src[1] << 8 | src[2])) {
×
1422
        goto error;
×
1423
    }
1424
    return EXIT_SUCCESS;
1425

1426
error:
×
1427
    perror("pixelformat_test_rgb888_passthrough");
×
1428
    return nret;
×
1429
}
1430

1431

1432
/* Convert RGB555 packed data into RGB888 output. */
1433
static int
1434
pixelformat_test_from_rgb555(void)
×
1435
{
1436
    unsigned char dst[3];
×
1437
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1438
    int src_pixelformat = SIXEL_PIXELFORMAT_RGB555;
×
1439
    unsigned char src[] = { 0x47, 0x9c };
×
1440
    int ret = 0;
×
1441

1442
    int nret = EXIT_FAILURE;
×
1443

1444
    ret = sixel_helper_normalize_pixelformat(dst,
×
1445
                                             &dst_pixelformat,
1446
                                             src,
1447
                                             src_pixelformat,
1448
                                             1,
1449
                                             1);
1450
    if (ret != 0) {
×
1451
        goto error;
×
1452
    }
1453
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1454
        goto error;
×
1455
    }
1456
    if ((dst[0] >> 3 << 10 | dst[1] >> 3 << 5 | dst[2] >> 3)
×
1457
            != (src[0] << 8 | src[1])) {
×
1458
        goto error;
×
1459
    }
1460
    return EXIT_SUCCESS;
1461

1462
error:
×
1463
    perror("pixelformat_test_from_rgb555");
×
1464
    return nret;
×
1465
}
1466

1467

1468
/* Convert RGB565 packed data into RGB888 output. */
1469
static int
1470
pixelformat_test_from_rgb565(void)
×
1471
{
1472
    unsigned char dst[3];
×
1473
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1474
    int src_pixelformat = SIXEL_PIXELFORMAT_RGB565;
×
1475
    unsigned char src[] = { 0x47, 0x9c };
×
1476
    int ret = 0;
×
1477

1478
    int nret = EXIT_FAILURE;
×
1479

1480
    ret = sixel_helper_normalize_pixelformat(dst,
×
1481
                                             &dst_pixelformat,
1482
                                             src,
1483
                                             src_pixelformat,
1484
                                             1,
1485
                                             1);
1486
    if (ret != 0) {
×
1487
        goto error;
×
1488
    }
1489
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1490
        goto error;
×
1491
    }
1492
    if ((dst[0] >> 3 << 11 | dst[1] >> 2 << 5 | dst[2] >> 3)
×
1493
            != (src[0] << 8 | src[1])) {
×
1494
        goto error;
×
1495
    }
1496
    return EXIT_SUCCESS;
1497

1498
error:
×
1499
    perror("pixelformat_test_from_rgb565");
×
1500
    return nret;
×
1501
}
1502

1503

1504
/* Swap channels from BGR888 to RGB888. */
1505
static int
1506
pixelformat_test_from_bgr888(void)
×
1507
{
1508
    unsigned char dst[3];
×
1509
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1510
    int src_pixelformat = SIXEL_PIXELFORMAT_BGR888;
×
1511
    unsigned char src[] = { 0x46, 0xf3, 0xe5 };
×
1512
    int ret = 0;
×
1513

1514
    int nret = EXIT_FAILURE;
×
1515

1516
    ret = sixel_helper_normalize_pixelformat(dst,
×
1517
                                             &dst_pixelformat,
1518
                                             src,
1519
                                             src_pixelformat,
1520
                                             1,
1521
                                             1);
1522
    if (ret != 0) {
×
1523
        goto error;
×
1524
    }
1525
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1526
        goto error;
×
1527
    }
1528
    if ((dst[2] << 16 | dst[1] << 8 | dst[0])
×
1529
            != (src[0] << 16 | src[1] << 8 | src[2])) {
×
1530
        goto error;
×
1531
    }
1532
    return EXIT_SUCCESS;
1533

1534
error:
×
1535
    perror("pixelformat_test_from_bgr888");
×
1536
    return nret;
×
1537
}
1538

1539

1540
/* Convert BGR555 packed data into RGB888 output. */
1541
static int
1542
pixelformat_test_from_bgr555(void)
×
1543
{
1544
    unsigned char dst[3];
×
1545
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1546
    int src_pixelformat = SIXEL_PIXELFORMAT_BGR555;
×
1547
    unsigned char src[] = { 0x23, 0xc8 };
×
1548
    int ret = 0;
×
1549

1550
    int nret = EXIT_FAILURE;
×
1551

1552
    ret = sixel_helper_normalize_pixelformat(dst,
×
1553
                                             &dst_pixelformat,
1554
                                             src,
1555
                                             src_pixelformat,
1556
                                             1,
1557
                                             1);
1558
    if (ret != 0) {
×
1559
        goto error;
×
1560
    }
1561
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1562
        goto error;
×
1563
    }
1564
    if ((dst[2] >> 3 << 10 | dst[1] >> 3 << 5 | dst[0] >> 3)
×
1565
            != (src[0] << 8 | src[1])) {
×
1566
        goto error;
×
1567
    }
1568
    return EXIT_SUCCESS;
1569

1570
error:
×
1571
    perror("pixelformat_test_from_bgr555");
×
1572
    return nret;
×
1573
}
1574

1575

1576
/* Convert BGR565 packed data into RGB888 output. */
1577
static int
1578
pixelformat_test_from_bgr565(void)
×
1579
{
1580
    unsigned char dst[3];
×
1581
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1582
    int src_pixelformat = SIXEL_PIXELFORMAT_BGR565;
×
1583
    unsigned char src[] = { 0x47, 0x88 };
×
1584
    int ret = 0;
×
1585

1586
    int nret = EXIT_FAILURE;
×
1587

1588
    ret = sixel_helper_normalize_pixelformat(dst,
×
1589
                                             &dst_pixelformat,
1590
                                             src,
1591
                                             src_pixelformat,
1592
                                             1,
1593
                                             1);
1594
    if (ret != 0) {
×
1595
        goto error;
×
1596
    }
1597
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1598
        goto error;
×
1599
    }
1600
    if ((dst[2] >> 3 << 11 | dst[1] >> 2 << 5 | dst[0] >> 3)
×
1601
            != (src[0] << 8 | src[1])) {
×
1602
        goto error;
×
1603
    }
1604
    return EXIT_SUCCESS;
1605

1606
error:
×
1607
    perror("pixelformat_test_from_bgr565");
×
1608
    return nret;
×
1609
}
1610

1611

1612
/* Convert AG88 data by discarding alpha and keeping gray. */
1613
static int
1614
pixelformat_test_from_ag88(void)
×
1615
{
1616
    unsigned char dst[3];
×
1617
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1618
    int src_pixelformat = SIXEL_PIXELFORMAT_AG88;
×
1619
    unsigned char src[] = { 0x47, 0x88 };
×
1620
    int ret = 0;
×
1621

1622
    int nret = EXIT_FAILURE;
×
1623

1624
    ret = sixel_helper_normalize_pixelformat(dst,
×
1625
                                             &dst_pixelformat,
1626
                                             src,
1627
                                             src_pixelformat,
1628
                                             1,
1629
                                             1);
1630
    if (ret != 0) {
×
1631
        goto error;
×
1632
    }
1633
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1634
        goto error;
×
1635
    }
1636
    if (dst[0] != src[1]) {
×
1637
        goto error;
×
1638
    }
1639
    return EXIT_SUCCESS;
1640

1641
error:
×
1642
    perror("pixelformat_test_from_ag88");
×
1643
    return nret;
×
1644
}
1645

1646

1647
/* Convert GA88 data by duplicating gray channel into RGB. */
1648
static int
1649
pixelformat_test_from_ga88(void)
×
1650
{
1651
    unsigned char dst[3];
×
1652
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1653
    int src_pixelformat = SIXEL_PIXELFORMAT_GA88;
×
1654
    unsigned char src[] = { 0x47, 0x88 };
×
1655
    int ret = 0;
×
1656

1657
    int nret = EXIT_FAILURE;
×
1658

1659
    ret = sixel_helper_normalize_pixelformat(dst,
×
1660
                                             &dst_pixelformat,
1661
                                             src,
1662
                                             src_pixelformat,
1663
                                             1,
1664
                                             1);
1665
    if (ret != 0) {
×
1666
        goto error;
×
1667
    }
1668
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1669
        goto error;
×
1670
    }
1671
    if (dst[0] != src[0]) {
×
1672
        goto error;
×
1673
    }
1674
    return EXIT_SUCCESS;
1675

1676
error:
×
1677
    perror("pixelformat_test_from_ga88");
×
1678
    return nret;
×
1679
}
1680

1681

1682
/* Normalize RGBA8888 by dropping alpha. */
1683
static int
1684
pixelformat_test_from_rgba8888(void)
×
1685
{
1686
    unsigned char dst[3];
×
1687
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1688
    int src_pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
×
1689
    unsigned char src[] = { 0x46, 0xf3, 0xe5, 0xf0 };
×
1690
    int ret = 0;
×
1691

1692
    int nret = EXIT_FAILURE;
×
1693

1694
    ret = sixel_helper_normalize_pixelformat(dst,
×
1695
                                             &dst_pixelformat,
1696
                                             src,
1697
                                             src_pixelformat,
1698
                                             1,
1699
                                             1);
1700
    if (ret != 0) {
×
1701
        goto error;
×
1702
    }
1703
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1704
        goto error;
×
1705
    }
1706
    if (dst[0] != src[0]) {
×
1707
        goto error;
×
1708
    }
1709
    if (dst[1] != src[1]) {
×
1710
        goto error;
×
1711
    }
1712
    if (dst[2] != src[2]) {
×
1713
        goto error;
×
1714
    }
1715
    return EXIT_SUCCESS;
1716

1717
error:
×
1718
    perror("pixelformat_test_from_rgba8888");
×
1719
    return nret;
×
1720
}
1721

1722

1723
/* Normalize ARGB8888 while skipping the leading alpha byte. */
1724
static int
1725
pixelformat_test_from_argb8888(void)
×
1726
{
1727
    unsigned char dst[3];
×
1728
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1729
    int src_pixelformat = SIXEL_PIXELFORMAT_ARGB8888;
×
1730
    unsigned char src[] = { 0x46, 0xf3, 0xe5, 0xf0 };
×
1731
    int ret = 0;
×
1732

1733
    int nret = EXIT_FAILURE;
×
1734

1735
    ret = sixel_helper_normalize_pixelformat(dst,
×
1736
                                             &dst_pixelformat,
1737
                                             src,
1738
                                             src_pixelformat,
1739
                                             1,
1740
                                             1);
1741
    if (ret != 0) {
×
1742
        goto error;
×
1743
    }
1744
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1745
        goto error;
×
1746
    }
1747
    if (dst[0] != src[1]) {
×
1748
        goto error;
×
1749
    }
1750
    if (dst[1] != src[2]) {
×
1751
        goto error;
×
1752
    }
1753
    if (dst[2] != src[3]) {
×
1754
        goto error;
×
1755
    }
1756
    return EXIT_SUCCESS;
1757

1758
error:
×
1759
    perror("pixelformat_test_from_argb8888");
×
1760
    return nret;
×
1761
}
1762

1763

1764
/* Convert floating point RGB data to normalized 8-bit output. */
1765
static int
1766
pixelformat_test_from_rgbfloat32(void)
×
1767
{
1768
    unsigned char dst[3];
×
1769
    int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1770
    int src_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
×
1771
    float srcf[] = { 0.0f, 0.5f, 1.0f };
×
1772
    unsigned char const *src = (unsigned char const *)srcf;
×
1773
    int ret = 0;
×
1774
    int depth;
×
1775

1776
    int nret = EXIT_FAILURE;
×
1777

1778
    ret = sixel_helper_normalize_pixelformat(dst,
×
1779
                                             &dst_pixelformat,
1780
                                             src,
1781
                                             src_pixelformat,
1782
                                             1,
1783
                                             1);
1784
    if (ret != 0) {
×
1785
        goto error;
×
1786
    }
1787
    if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1788
        goto error;
×
1789
    }
1790
    if (dst[0] != 0 || dst[1] != 128 || dst[2] != 255) {
×
1791
        goto error;
×
1792
    }
1793
    depth = sixel_helper_compute_depth(src_pixelformat);
×
1794
    if (depth != (int)(sizeof(float) * 3)) {
×
1795
        goto error;
×
1796
    }
1797
    return EXIT_SUCCESS;
1798

1799
error:
×
1800
    perror("pixelformat_test_from_rgbfloat32");
×
1801
    return nret;
×
1802
}
1803

1804

1805
SIXELAPI int
1806
sixel_pixelformat_tests_main(void)
×
1807
{
1808
    int nret = EXIT_FAILURE;
×
1809
    size_t i;
×
1810
    typedef int (* testcase)(void);
×
1811

1812
    static testcase const testcases[] = {
×
1813
        pixelformat_test_rgb888_passthrough,
1814
        pixelformat_test_from_rgb555,
1815
        pixelformat_test_from_rgb565,
1816
        pixelformat_test_from_bgr888,
1817
        pixelformat_test_from_bgr555,
1818
        pixelformat_test_from_bgr565,
1819
        pixelformat_test_from_ag88,
1820
        pixelformat_test_from_ga88,
1821
        pixelformat_test_from_rgba8888,
1822
        pixelformat_test_from_argb8888,
1823
        pixelformat_test_from_rgbfloat32,
1824
    };
1825

1826
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
1827
        nret = testcases[i]();
×
1828
        if (nret != EXIT_SUCCESS) {
×
1829
            goto error;
×
1830
        }
1831
    }
1832

1833
    nret = EXIT_SUCCESS;
1834

1835
error:
×
1836
    return nret;
×
1837
}
1838
#endif  /* HAVE_TESTS */
1839

1840
/* emacs Local Variables:      */
1841
/* emacs mode: c               */
1842
/* emacs tab-width: 4          */
1843
/* emacs indent-tabs-mode: nil */
1844
/* emacs c-basic-offset: 4     */
1845
/* emacs End:                  */
1846
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1847
/* 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