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

saitoha / libsixel / 20466639304

23 Dec 2025 04:53PM UTC coverage: 51.46% (-6.3%) from 57.773%
20466639304

push

github

saitoha
build: fix windows find path in images meson build

14511 of 44933 branches covered (32.29%)

21089 of 40981 relevant lines covered (51.46%)

3915123.44 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
#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 "compat_stub.h"
42
#include "threading.h"
43
#include "pixelformat.h"
44

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

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

76
    if (value <= 0.0f) {
2,086,800✔
77
        return 0;
78
    }
79
    if (value >= 1.0f) {
176,512!
80
        return 255;
81
    }
82

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

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

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

102
    return (unsigned char)(value * 255.0f + 0.5f);
36✔
103
}
104

105
static unsigned char
106
sixel_pixelformat_oklab_ab_to_byte(float value)
72✔
107
{
108
    float encoded;
72✔
109

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

116
    encoded = value + 0.5f;
144✔
117
    if (encoded <= 0.0f) {
72!
118
        return 0;
119
    }
120
    if (encoded >= 1.0f) {
72!
121
        return 255;
122
    }
123

124
    return (unsigned char)(encoded * 255.0f + 0.5f);
72✔
125
}
126

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

335
    return value;
336
}
337

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

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

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

383
float
384
sixel_pixelformat_byte_to_float(int pixelformat,
25,922,508✔
385
                                int channel,
386
                                unsigned char value)
387
{
388
    float decoded;
25,922,508✔
389

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

428
    (void)channel;
25,922,328✔
429
    return (float)value / 255.0f;
25,922,328✔
430
}
431

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

437

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

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

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

455
    return pixels;
×
456
}
457

458

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

467
    pixels = sixel_rgb_read16(data);
×
468

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

474

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

483
    pixels = sixel_rgb_read16(data);
×
484

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

490

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

499
    pixels = sixel_rgb_read16(data);
×
500

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

506

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

515
    pixels = sixel_rgb_read16(data);
×
516

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

522

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

531
    pixels = sixel_rgb_read16(data);
×
532

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

538

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

547
    pixels = sixel_rgb_read16(data);
×
548

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

554

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

566

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

578

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

590

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

602

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

614

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

626

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

638

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

647
    fpixels = (float const *)(void const *)data;
695,600✔
648

649
    *r = sixel_pixelformat_float_to_byte(fpixels[0]);
695,600✔
650
    *g = sixel_pixelformat_float_to_byte(fpixels[1]);
695,600✔
651
    *b = sixel_pixelformat_float_to_byte(fpixels[2]);
695,600✔
652
}
695,600✔
653

654

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

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

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

670

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

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

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

686

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

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

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

702

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

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

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

720

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

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

734

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

780
    return sixel_rgb_from_unknown;
×
781
}
782

783

784
SIXELAPI int
785
sixel_helper_compute_depth(int pixelformat)
4,223✔
786
{
787
    int depth = (-1);  /* unknown */
4,223✔
788

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

830
    return depth;
4,223✔
831
}
832

833

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

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

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

871
    for (y = 0; y < height; y++) {
4,252✔
872
        src_pixel = src_row;
873
        dst_pixel = dst_row;
874
        for (x = 0; x < width; x++) {
699,800✔
875
            reader(src_pixel, &r, &g, &b);
695,600✔
876

877
            dst_pixel[0] = r;
695,600✔
878
            dst_pixel[1] = g;
695,600✔
879
            dst_pixel[2] = b;
695,600✔
880

881
            src_pixel += depth;
695,600✔
882
            dst_pixel += 3;
695,600✔
883
        }
884

885
        src_row += src_stride;
4,200✔
886
        dst_row += dst_stride;
4,200✔
887
    }
888
}
52✔
889

890

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

903

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

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

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

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

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

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

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

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

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

976
    palette_table_initialized = 1;
×
977

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

984
    return 1;
×
985
}
986

987

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

1004
    byte_count = width / 8;
×
1005
    remainder = width - byte_count * 8;
×
1006

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

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

1042

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

1059
    byte_count = width / 4;
×
1060
    remainder = width - byte_count * 4;
×
1061

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

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

1096

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

1113
    byte_count = width / 2;
×
1114
    remainder = width - byte_count * 2;
×
1115

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

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

1150

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

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

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

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

1196

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

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

1220
    use_palette_tables = 0;
×
1221
    tables_ready = 0;
×
1222

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

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

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

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

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

1303
end:
×
1304
    return status;
×
1305
}
1306

1307

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

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

1386
    status = SIXEL_OK;
1387

1388
end:
52✔
1389
    return status;
52✔
1390
}
1391

1392

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

1404
    int nret = EXIT_FAILURE;
×
1405

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

1424
error:
×
1425
    perror("pixelformat_test_rgb888_passthrough");
×
1426
    return nret;
×
1427
}
1428

1429

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

1440
    int nret = EXIT_FAILURE;
×
1441

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

1460
error:
×
1461
    perror("pixelformat_test_from_rgb555");
×
1462
    return nret;
×
1463
}
1464

1465

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

1476
    int nret = EXIT_FAILURE;
×
1477

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

1496
error:
×
1497
    perror("pixelformat_test_from_rgb565");
×
1498
    return nret;
×
1499
}
1500

1501

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

1512
    int nret = EXIT_FAILURE;
×
1513

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

1532
error:
×
1533
    perror("pixelformat_test_from_bgr888");
×
1534
    return nret;
×
1535
}
1536

1537

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

1548
    int nret = EXIT_FAILURE;
×
1549

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

1568
error:
×
1569
    perror("pixelformat_test_from_bgr555");
×
1570
    return nret;
×
1571
}
1572

1573

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

1584
    int nret = EXIT_FAILURE;
×
1585

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

1604
error:
×
1605
    perror("pixelformat_test_from_bgr565");
×
1606
    return nret;
×
1607
}
1608

1609

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

1620
    int nret = EXIT_FAILURE;
×
1621

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

1639
error:
×
1640
    perror("pixelformat_test_from_ag88");
×
1641
    return nret;
×
1642
}
1643

1644

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

1655
    int nret = EXIT_FAILURE;
×
1656

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

1674
error:
×
1675
    perror("pixelformat_test_from_ga88");
×
1676
    return nret;
×
1677
}
1678

1679

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

1690
    int nret = EXIT_FAILURE;
×
1691

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

1715
error:
×
1716
    perror("pixelformat_test_from_rgba8888");
×
1717
    return nret;
×
1718
}
1719

1720

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

1731
    int nret = EXIT_FAILURE;
×
1732

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

1756
error:
×
1757
    perror("pixelformat_test_from_argb8888");
×
1758
    return nret;
×
1759
}
1760

1761

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

1774
    int nret = EXIT_FAILURE;
×
1775

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

1797
error:
×
1798
    perror("pixelformat_test_from_rgbfloat32");
×
1799
    return nret;
×
1800
}
1801

1802

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

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

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

1831
    nret = EXIT_SUCCESS;
1832

1833
error:
×
1834
    return nret;
×
1835
}
1836
#endif  /* HAVE_TESTS */
1837

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