• 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

48.21
/src/frame.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2025 libsixel developers. See `AUTHORS`.
5
 * Copyright (c) 2014-2020 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 <string.h>
31
#include <stdlib.h>
32
#include <stdio.h>
33

34
#if HAVE_MATH_H
35
# include <math.h>
36
#endif  /* HAVE_MATH_H */
37
#if HAVE_LIMITS_H
38
# include <limits.h>
39
#endif  /* HAVE_LIMITS_H */
40
#if HAVE_INTTYPES_H
41
# include <inttypes.h>
42
#endif  /* HAVE_INTTYPES_H */
43

44
#include "frame.h"
45
#include "pixelformat.h"
46
#include "compat_stub.h"
47
#include "scale.h"
48

49
static SIXELSTATUS
50
sixel_frame_convert_to_rgb888(sixel_frame_t /*in */ *frame);
51
static SIXELSTATUS
52
sixel_frame_promote_to_float32(sixel_frame_t *frame);
53
static int
54
sixel_frame_colorspace_from_pixelformat(int pixelformat);
55
static void
56
sixel_frame_apply_pixelformat(sixel_frame_t *frame, int pixelformat);
57
static SIXELSTATUS
58
sixel_frame_validate_size(char const *context,
59
                          int width,
60
                          int height,
61
                          int pixelformat,
62
                          size_t *pixel_total,
63
                          size_t *byte_total,
64
                          int *depth_bytes);
65

66

67
/*
68
 * Validate that a frame of the requested size stays within allocation
69
 * limits. Callers receive optional pixel and byte counts so they can reuse
70
 * the calculation without recomputing the totals when allocating buffers.
71
 */
72
static SIXELSTATUS
73
sixel_frame_validate_size(char const *context,
222✔
74
                          int width,
75
                          int height,
76
                          int pixelformat,
77
                          size_t *pixel_total,
78
                          size_t *byte_total,
79
                          int *depth_bytes)
80
{
81
    SIXELSTATUS status;
222✔
82
    char message[128];
222✔
83
    size_t pixels;
222✔
84
    size_t bytes;
222✔
85
    int depth;
222✔
86

87
    status = SIXEL_FALSE;
222✔
88
    pixels = 0u;
222✔
89
    bytes = 0u;
222✔
90
    depth = 0;
222✔
91
    if (context == NULL) {
222!
92
        context = "sixel frame validation";
×
93
    }
94

95
    depth = sixel_helper_compute_depth(pixelformat);
222✔
96
    if (depth <= 0) {
222!
97
        sixel_compat_snprintf(message,
×
98
                              sizeof(message),
99
                              "%s: pixelformat depth is invalid.",
100
                              context);
101
        sixel_helper_set_additional_message(message);
×
102
        status = SIXEL_BAD_ARGUMENT;
×
103
        goto end;
×
104
    }
105

106
    pixels = (size_t)width * (size_t)height;
222✔
107
    if (pixels > SIZE_MAX / (size_t)depth) {
222!
108
        sixel_compat_snprintf(message,
×
109
                              sizeof(message),
110
                              "%s: buffer size overflow.",
111
                              context);
112
        sixel_helper_set_additional_message(message);
×
113
        status = SIXEL_BAD_INPUT;
×
114
        goto end;
×
115
    }
116

117
    bytes = pixels * (size_t)depth;
222✔
118
    if (bytes > SIXEL_ALLOCATE_BYTES_MAX) {
222!
119
        sixel_compat_snprintf(message,
×
120
                              sizeof(message),
121
                              "%s: frame exceeds "
122
                              "SIXEL_ALLOCATE_BYTES_MAX.",
123
                              context);
124
        sixel_helper_set_additional_message(message);
×
125
        status = SIXEL_BAD_ALLOCATION;
×
126
        goto end;
×
127
    }
128

129
    if (pixel_total != NULL) {
222!
130
        *pixel_total = pixels;
222✔
131
    }
132
    if (byte_total != NULL) {
222!
133
        *byte_total = bytes;
222✔
134
    }
135
    if (depth_bytes != NULL) {
222!
136
        *depth_bytes = depth;
222✔
137
    }
138

139
    status = SIXEL_OK;
140

141
end:
222✔
142
    return status;
222✔
143
}
144

145
/* constructor of frame object */
146
SIXELAPI SIXELSTATUS
147
sixel_frame_new(
950✔
148
    sixel_frame_t       /* out */ **ppframe,    /* frame object to be created */
149
    sixel_allocator_t   /* in */  *allocator)   /* allocator, null if you use
150
                                                   default allocator */
151
{
152
    SIXELSTATUS status = SIXEL_FALSE;
950✔
153

154
    if (allocator == NULL) {
950!
155
        status = sixel_allocator_new(&allocator, malloc, calloc, realloc, free);
×
156
        if (SIXEL_FAILED(status)) {
×
157
            goto end;
×
158
        }
159
    }
160

161
    *ppframe = (sixel_frame_t *)sixel_allocator_malloc(
950✔
162
        allocator,
163
        sizeof(sixel_frame_t));
164
    if (*ppframe == NULL) {
950!
165
        sixel_helper_set_additional_message(
×
166
            "sixel_frame_resize: sixel_allocator_malloc() failed.");
167
        status = SIXEL_BAD_ALLOCATION;
×
168
        goto end;
×
169
    }
170

171
    (*ppframe)->ref = 1;
950✔
172
    (*ppframe)->pixels.u8ptr = NULL;
950✔
173
    (*ppframe)->palette = NULL;
950✔
174
    (*ppframe)->width = 0;
950✔
175
    (*ppframe)->height = 0;
950✔
176
    (*ppframe)->ncolors = (-1);
950✔
177
    (*ppframe)->pixelformat = SIXEL_PIXELFORMAT_RGB888;
950✔
178
    (*ppframe)->delay = 0;
950✔
179
    (*ppframe)->frame_no = 0;
950✔
180
    (*ppframe)->loop_count = 0;
950✔
181
    (*ppframe)->multiframe = 0;
950✔
182
    (*ppframe)->transparent = (-1);
950✔
183
    (*ppframe)->allocator = allocator;
950✔
184

185
    sixel_allocator_ref(allocator);
950✔
186

187
    /* Normalize between byte and float pipelines when buffers are present. */
188
    status = SIXEL_OK;
950✔
189

190
end:
950✔
191
    return status;
950✔
192
}
193

194

195
SIXELAPI /* deprecated */ sixel_frame_t *
196
sixel_frame_create(void)
×
197
{
198
    SIXELSTATUS status = SIXEL_FALSE;
×
199
    sixel_frame_t *frame = NULL;
×
200

201
    status = sixel_frame_new(&frame, NULL);
×
202
    if (SIXEL_FAILED(status)) {
×
203
        goto end;
204
    }
205

206
end:
×
207
    return frame;
×
208
}
209

210

211
static void
212
sixel_frame_destroy(sixel_frame_t /* in */ *frame)
946✔
213
{
214
    sixel_allocator_t *allocator = NULL;
946✔
215

216
    if (frame) {
946!
217
        allocator = frame->allocator;
946✔
218
        sixel_allocator_free(allocator, frame->pixels.u8ptr);
946✔
219
        sixel_allocator_free(allocator, frame->palette);
946✔
220
        sixel_allocator_free(allocator, frame);
946✔
221
        sixel_allocator_unref(allocator);
946✔
222
    }
223
}
946✔
224

225

226
/* increase reference count of frame object (thread-unsafe) */
227
SIXELAPI void
228
sixel_frame_ref(sixel_frame_t *frame)
1,781✔
229
{
230
    /* TODO: be thread safe */
231
    ++frame->ref;
1,781✔
232
}
1,406✔
233

234

235
/* decrease reference count of frame object (thread-unsafe) */
236
SIXELAPI void
237
sixel_frame_unref(sixel_frame_t *frame)
2,865✔
238
{
239
    /* TODO: be thread safe */
240
    if (frame != NULL && --frame->ref == 0) {
2,865✔
241
        sixel_frame_destroy(frame);
946✔
242
    }
243
}
2,865✔
244

245

246
/*
247
 * Shared initializer for both byte and float32 buffers.  Keeping the
248
 * validation and field updates in one place avoids diverging logic
249
 * between the two entry points while making the stored pointer type
250
 * explicit.
251
 */
252
static SIXELSTATUS
253
sixel_frame_init_common(
64✔
254
    sixel_frame_t   *frame,
255
    void            *pixels,
256
    int              width,
257
    int              height,
258
    int              pixelformat,
259
    unsigned char   *palette,
260
    int              ncolors,
261
    int              is_float)
262
{
263
    SIXELSTATUS status = SIXEL_FALSE;
64✔
264
    size_t unused_pixel_total;
64✔
265
    size_t unused_byte_total;
64✔
266
    int unused_depth_bytes;
64✔
267

268
    sixel_frame_ref(frame);
64✔
269

270
    unused_pixel_total = 0u;
64✔
271
    unused_byte_total = 0u;
64✔
272
    unused_depth_bytes = 0;
64✔
273

274
    /* check parameters */
275
    if (width <= 0) {
64!
276
        sixel_helper_set_additional_message(
×
277
            "sixel_frame_init: an invalid width parameter detected.");
278
        status = SIXEL_BAD_INPUT;
×
279
        goto end;
×
280
    }
281
    status = sixel_frame_validate_size("sixel_frame_init",
64✔
282
                                       width,
283
                                       height,
284
                                       pixelformat,
285
                                       &unused_pixel_total,
286
                                       &unused_byte_total,
287
                                       &unused_depth_bytes);
288
    if (SIXEL_FAILED(status)) {
64!
289
        goto end;
×
290
    }
291
    if (height <= 0) {
64!
292
        sixel_helper_set_additional_message(
×
293
            "sixel_frame_init: an invalid width parameter detected.");
294
        status = SIXEL_BAD_INPUT;
×
295
        goto end;
×
296
    }
297
    if (width > SIXEL_WIDTH_LIMIT) {
64!
298
        sixel_helper_set_additional_message(
×
299
            "sixel_frame_init: given width parameter is too huge.");
300
        status = SIXEL_BAD_INPUT;
×
301
        goto end;
×
302
    }
303
    if (height > SIXEL_HEIGHT_LIMIT) {
64!
304
        sixel_helper_set_additional_message(
×
305
            "sixel_frame_init: given height parameter is too huge.");
306
        status = SIXEL_BAD_INPUT;
×
307
        goto end;
×
308
    }
309
    if (is_float != 0 && !SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)) {
64!
310
        sixel_helper_set_additional_message(
×
311
            "sixel_frame_init: pixelformat must be float32 when "
312
            "supplying float pixels.");
313
        status = SIXEL_BAD_INPUT;
×
314
        goto end;
×
315
    }
316

317
    if (is_float != 0) {
10✔
318
        frame->pixels.f32ptr = (float *)pixels;
10✔
319
    } else {
320
        frame->pixels.u8ptr = (unsigned char *)pixels;
54✔
321
    }
322
    frame->width = width;
64✔
323
    frame->height = height;
64✔
324
    sixel_frame_apply_pixelformat(frame, pixelformat);
128✔
325
    frame->palette = palette;
64✔
326
    frame->ncolors = ncolors;
64✔
327
    status = SIXEL_OK;
64✔
328

329
end:
64✔
330
    sixel_frame_unref(frame);
64✔
331

332
    return status;
64✔
333
}
334

335
/* initialize frame object with a pixel buffer */
336
SIXELAPI SIXELSTATUS
337
sixel_frame_init(
54✔
338
    sixel_frame_t   /* in */ *frame,
339
    unsigned char   /* in */ *pixels,
340
    int             /* in */ width,
341
    int             /* in */ height,
342
    int             /* in */ pixelformat,
343
    unsigned char   /* in */ *palette,
344
    int             /* in */ ncolors)
345
{
346
    return sixel_frame_init_common(frame,
54✔
347
                                   pixels,
348
                                   width,
349
                                   height,
350
                                   pixelformat,
351
                                   palette,
352
                                   ncolors,
353
                                   0);
354
}
355

356
/* initialize frame object with a float32 pixel buffer */
357
SIXELAPI SIXELSTATUS
358
sixel_frame_init_float32(
10✔
359
    sixel_frame_t   /* in */ *frame,
360
    float           /* in */ *pixels,
361
    int             /* in */ width,
362
    int             /* in */ height,
363
    int             /* in */ pixelformat,
364
    unsigned char   /* in */ *palette,
365
    int             /* in */ ncolors)
366
{
367
    return sixel_frame_init_common(frame,
10✔
368
                                   pixels,
369
                                   width,
370
                                   height,
371
                                   pixelformat,
372
                                   palette,
373
                                   ncolors,
374
                                   1);
375
}
376

377

378
/* get pixels */
379
SIXELAPI unsigned char *
380
sixel_frame_get_pixels(sixel_frame_t /* in */ *frame)  /* frame object */
1,805✔
381
{
382
    return frame->pixels.u8ptr;
1,805✔
383
}
384

385

386
SIXELAPI float *
387
sixel_frame_get_pixels_float32(sixel_frame_t /* in */ *frame)
×
388
{
389
    return frame->pixels.f32ptr;
×
390
}
391

392

393
/* set pixels */
394
SIXELAPI void
395
sixel_frame_set_pixels(
944✔
396
    sixel_frame_t  /* in */ *frame,
397
    unsigned char  /* in */ *pixels)
398
{
399
    frame->pixels.u8ptr = pixels;
944✔
400
}
944✔
401

402

403
SIXELAPI void
404
sixel_frame_set_pixels_float32(
71✔
405
    sixel_frame_t  /* in */ *frame,
406
    float          /* in */ *pixels)
407
{
408
    frame->pixels.f32ptr = pixels;
71✔
409
}
54✔
410

411

412
/* get palette */
413
SIXELAPI unsigned char *
414
sixel_frame_get_palette(sixel_frame_t /* in */ *frame)  /* frame object */
164✔
415
{
416
    return frame->palette;
164✔
417
}
418

419

420
/* set palette */
421
SIXELAPI void
422
sixel_frame_set_palette(
32✔
423
    sixel_frame_t  /* in */ *frame,
424
    unsigned char  /* in */ *palette)
425
{
426
    frame->palette = palette;
32✔
427
}
32✔
428

429

430
/* get width */
431
SIXELAPI int
432
sixel_frame_get_width(sixel_frame_t /* in */ *frame)  /* frame object */
4,573✔
433
{
434
    return frame->width;
4,573✔
435
}
436

437

438
/* set width */
439
SIXELAPI void
440
sixel_frame_set_width(sixel_frame_t /* in */ *frame, int /* in */ width)
164✔
441
{
442
    frame->width = width;
164✔
443
}
164✔
444

445

446
/* get height */
447
SIXELAPI int
448
sixel_frame_get_height(sixel_frame_t /* in */ *frame)  /* frame object */
9,244✔
449
{
450
    return frame->height;
9,244✔
451
}
452

453

454
/* set height */
455
SIXELAPI void
456
sixel_frame_set_height(sixel_frame_t /* in */ *frame, int /* in */ height)
164✔
457
{
458
    frame->height = height;
164✔
459
}
164✔
460

461

462
/* get ncolors */
463
SIXELAPI int
464
sixel_frame_get_ncolors(sixel_frame_t /* in */ *frame)  /* frame object */
164✔
465
{
466
    return frame->ncolors;
164✔
467
}
468

469

470
/* set ncolors */
471
SIXELAPI void
472
sixel_frame_set_ncolors(
164✔
473
    sixel_frame_t  /* in */ *frame,
474
    int            /* in */ ncolors)
475
{
476
    frame->ncolors = ncolors;
164✔
477
}
164✔
478

479

480
/* get pixelformat */
481
SIXELAPI int
482
sixel_frame_get_pixelformat(sixel_frame_t /* in */ *frame)  /* frame object */
11,457✔
483
{
484
    return frame->pixelformat;
11,457✔
485
}
486

487

488
/* set pixelformat */
489
SIXELAPI SIXELSTATUS
490
sixel_frame_set_pixelformat(
680✔
491
    sixel_frame_t  /* in */ *frame,
492
    int            /* in */ pixelformat)
493
{
494
    SIXELSTATUS status;
680✔
495
    int source_colorspace;
680✔
496
    int target_colorspace;
680✔
497
    int working_pixelformat;
680✔
498
    int depth;
680✔
499
    int float_depth;
680✔
500
    size_t pixel_total;
680✔
501
    size_t pixel_size;
680✔
502
    size_t float_pixels;
680✔
503
    size_t float_bytes;
680✔
504
    size_t float_limit;
680✔
505
    unsigned char *pixels;
680✔
506

507
    if (frame == NULL) {
680!
508
        sixel_helper_set_additional_message(
×
509
            "sixel_frame_set_pixelformat: frame is null.");
510
        return SIXEL_BAD_ARGUMENT;
×
511
    }
512
    if (pixelformat == frame->pixelformat) {
680✔
513
        return SIXEL_OK;
514
    }
515
    if (frame->pixels.u8ptr == NULL) {
597✔
516
        sixel_frame_apply_pixelformat(frame, pixelformat);
40✔
517
        return SIXEL_OK;
20✔
518
    }
519

520
    status = SIXEL_OK;
577✔
521
    working_pixelformat = frame->pixelformat;
577✔
522
    source_colorspace = frame->colorspace;
577✔
523
    float_depth = 0;
577✔
524
    float_pixels = 0U;
577✔
525
    float_bytes = 0U;
577✔
526
    float_limit = SIXEL_ALLOCATE_BYTES_MAX / 2U;
577✔
527

528
    /*
529
     * Palette and byte-form buffers need to be normalized before any
530
     * colorspace adjustments so that channel ordering matches the
531
     * converter's expectations.
532
     */
533
    if (pixelformat == SIXEL_PIXELFORMAT_RGBFLOAT32
577!
534
            || pixelformat == SIXEL_PIXELFORMAT_LINEARRGBFLOAT32
1!
535
            || pixelformat == SIXEL_PIXELFORMAT_OKLABFLOAT32
2✔
536
            || pixelformat == SIXEL_PIXELFORMAT_CIELABFLOAT32
2✔
537
            || pixelformat == SIXEL_PIXELFORMAT_DIN99DFLOAT32
1!
538
            || pixelformat == SIXEL_PIXELFORMAT_YUVFLOAT32) {
577!
539
        if (working_pixelformat & SIXEL_FORMATTYPE_PALETTE) {
79!
540
            status = sixel_frame_convert_to_rgb888(frame);
×
541
        }
542
        if (SIXEL_SUCCEEDED(status)
×
543
                && !SIXEL_PIXELFORMAT_IS_FLOAT32(frame->pixelformat)) {
79!
544
            float_depth = sixel_helper_compute_depth(pixelformat);
79✔
545
            if (float_limit != 0U
79!
546
                    && float_depth > 0
1!
547
                    && frame->width > 0
79!
548
                    && frame->height > 0
71!
549
                    && (size_t)frame->width
1!
550
                           <= SIZE_MAX / (size_t)frame->height) {
71✔
551
                float_pixels = (size_t)frame->width
71✔
552
                    * (size_t)frame->height;
553
                if (float_pixels <= SIZE_MAX / (size_t)float_depth) {
71!
554
                    float_bytes = float_pixels
71✔
555
                        * (size_t)float_depth;
556
                }
557
            }
558
            if (float_limit != 0U
71!
559
                    && (float_bytes == 0U || float_bytes > float_limit)) {
71!
560
                pixelformat = SIXEL_PIXELFORMAT_RGB888;
561
            } else {
562
                status = sixel_frame_promote_to_float32(frame);
71✔
563
            }
564
        }
565
        if (pixelformat == SIXEL_PIXELFORMAT_RGB888
8!
566
                && SIXEL_SUCCEEDED(status)
2✔
567
                && frame->pixelformat != SIXEL_PIXELFORMAT_RGB888) {
8!
568
            status = sixel_frame_convert_to_rgb888(frame);
×
569
        }
570
    } else if (pixelformat == SIXEL_PIXELFORMAT_RGB888
498!
571
            && working_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
498✔
572
        status = sixel_frame_convert_to_rgb888(frame);
366✔
573
    } else if (!SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)
132!
574
            && !SIXEL_PIXELFORMAT_IS_FLOAT32(working_pixelformat)
132!
575
            && (working_pixelformat & SIXEL_FORMATTYPE_PALETTE)) {
132!
576
        status = sixel_frame_convert_to_rgb888(frame);
×
577
    }
578

579
    if (SIXEL_FAILED(status)) {
445!
580
        return status;
581
    }
582

583
    working_pixelformat = frame->pixelformat;
577✔
584
    source_colorspace = frame->colorspace;
577✔
585
    target_colorspace = sixel_frame_colorspace_from_pixelformat(pixelformat);
1,154✔
586

587
    if (target_colorspace != source_colorspace) {
577✔
588
        /*
589
         * Convert in-place so callers can request alternate transfer
590
         * curves or OKLab buffers without mutating the frame twice.
591
         */
592
        if (frame->width <= 0 || frame->height <= 0) {
125!
593
            sixel_helper_set_additional_message(
×
594
                "sixel_frame_set_pixelformat: invalid frame size.");
595
            return SIXEL_BAD_INPUT;
×
596
        }
597

598
        pixel_total = (size_t)frame->width * (size_t)frame->height;
125✔
599
        if (pixel_total / (size_t)frame->width != (size_t)frame->height) {
125!
600
            sixel_helper_set_additional_message(
×
601
                "sixel_frame_set_pixelformat: buffer overflow risk.");
602
            return SIXEL_BAD_INPUT;
×
603
        }
604

605
        depth = sixel_helper_compute_depth(working_pixelformat);
125✔
606
        if (depth <= 0) {
125!
607
            sixel_helper_set_additional_message(
×
608
                "sixel_frame_set_pixelformat: invalid pixelformat depth.");
609
            return SIXEL_BAD_INPUT;
×
610
        }
611
        if (pixel_total > SIZE_MAX / (size_t)depth) {
125!
612
            sixel_helper_set_additional_message(
×
613
                "sixel_frame_set_pixelformat: buffer size overflow.");
614
            return SIXEL_BAD_INPUT;
×
615
        }
616
        pixel_size = pixel_total * (size_t)depth;
125✔
617

618
        pixels = frame->pixels.u8ptr;
125✔
619
        if (SIXEL_PIXELFORMAT_IS_FLOAT32(working_pixelformat)) {
125!
620
            pixels = (unsigned char *)frame->pixels.f32ptr;
125✔
621
        }
622

623
        status = sixel_helper_convert_colorspace(pixels,
125✔
624
                                                 pixel_size,
625
                                                 working_pixelformat,
626
                                                 source_colorspace,
627
                                                 target_colorspace);
628
        if (SIXEL_FAILED(status)) {
125!
629
            return status;
630
        }
631
    }
632

633
    sixel_frame_apply_pixelformat(frame, pixelformat);
577✔
634
    return SIXEL_OK;
577✔
635
}
636

637

638
SIXELAPI int
639
sixel_frame_get_colorspace(sixel_frame_t /* in */ *frame)  /* frame object */
4,819✔
640
{
641
    return frame->colorspace;
4,819✔
642
}
643

644

645
/* set colorspace */
646
SIXELAPI void
647
sixel_frame_set_colorspace(
×
648
    sixel_frame_t  /* in */ *frame,
649
    int            /* in */ colorspace)
650
{
651
    frame->colorspace = colorspace;
×
652
}
×
653

654

655
/* get transparent */
656
SIXELAPI int
657
sixel_frame_get_transparent(sixel_frame_t /* in */ *frame)  /* frame object */
×
658
{
659
    return frame->transparent;
×
660
}
661

662

663
/* set transparent */
664
SIXELAPI void
665
sixel_frame_set_transparent(
×
666
    sixel_frame_t  /* in */ *frame,
667
    int            /* in */ transparent)
668
{
669
    frame->transparent = transparent;
×
670
}
×
671

672

673
/* get transparent */
674
SIXELAPI int
675
sixel_frame_get_multiframe(sixel_frame_t /* in */ *frame)  /* frame object */
772✔
676
{
677
    return frame->multiframe;
772✔
678
}
679

680

681
/* set multiframe */
682
SIXELAPI void
683
sixel_frame_set_multiframe(
164✔
684
    sixel_frame_t  /* in */ *frame,
685
    int            /* in */ multiframe)
686
{
687
    frame->multiframe = multiframe;
164✔
688
}
164✔
689

690

691
/* get delay */
692
SIXELAPI int
693
sixel_frame_get_delay(sixel_frame_t /* in */ *frame)  /* frame object */
768✔
694
{
695
    return frame->delay;
768✔
696
}
697

698

699
/* set delay */
700
SIXELAPI void
701
sixel_frame_set_delay(sixel_frame_t /* in */ *frame, int /* in */ delay)
164✔
702
{
703
    frame->delay = delay;
164✔
704
}
164✔
705

706

707
/* get frame no */
708
SIXELAPI int
709
sixel_frame_get_frame_no(sixel_frame_t /* in */ *frame)  /* frame object */
296✔
710
{
711
    return frame->frame_no;
296✔
712
}
713

714

715
/* set frame index */
716
SIXELAPI void
717
sixel_frame_set_frame_no(
36✔
718
    sixel_frame_t  /* in */ *frame,
719
    int            /* in */ frame_no)
720
{
721
    frame->frame_no = frame_no;
36✔
722
}
36✔
723

724

725
/* increment frame index */
726
SIXELAPI void
727
sixel_frame_increment_frame_no(sixel_frame_t /* in */ *frame)
152✔
728
{
729
    ++frame->frame_no;
152✔
730
}
152✔
731

732

733
/* reset frame index */
734
SIXELAPI void
735
sixel_frame_reset_frame_no(sixel_frame_t /* in */ *frame)
×
736
{
737
    frame->frame_no = 0;
×
738
}
×
739

740

741
/* get loop no */
742
SIXELAPI int
743
sixel_frame_get_loop_no(sixel_frame_t /* in */ *frame)  /* frame object */
264✔
744
{
745
    return frame->loop_count;
264✔
746
}
747

748

749
/* set loop count */
750
SIXELAPI void
751
sixel_frame_set_loop_count(
32✔
752
    sixel_frame_t  /* in */ *frame,
753
    int            /* in */ loop_count)
754
{
755
    frame->loop_count = loop_count;
32✔
756
}
32✔
757

758

759
/* increment loop count */
760
SIXELAPI void
761
sixel_frame_increment_loop_count(sixel_frame_t /* in */ *frame)
24✔
762
{
763
    ++frame->loop_count;
24✔
764
}
24✔
765

766

767
/* get allocator */
768
SIXELAPI sixel_allocator_t *
769
sixel_frame_get_allocator(sixel_frame_t /* in */ *frame)
164✔
770
{
771
    return frame->allocator;
164✔
772
}
773

774
/* strip alpha from RGBA/ARGB/BGRA/ABGR formatted pixbuf */
775
SIXELAPI SIXELSTATUS
776
sixel_frame_strip_alpha(
920✔
777
    sixel_frame_t  /* in */ *frame,
778
    unsigned char  /* in */ *bgcolor
779
)
780
{
781
    SIXELSTATUS status = SIXEL_FALSE;
920✔
782
    int i;
920✔
783
    unsigned char *src;
920✔
784
    unsigned char *dst;
920✔
785
    unsigned char alpha;
920✔
786

787
    sixel_frame_ref(frame);
920✔
788

789
    src = dst = frame->pixels.u8ptr;
920✔
790

791
    if (bgcolor) {
920✔
792
        switch (frame->pixelformat) {
24!
793
        case SIXEL_PIXELFORMAT_ARGB8888:
794
            for (i = 0; i < frame->height * frame->width; i++) {
×
795
                alpha = src[0];
×
796
                *dst++ = (*src++ * alpha + bgcolor[0] * (0xff - alpha)) >> 8;
×
797
                *dst++ = (*src++ * alpha + bgcolor[1] * (0xff - alpha)) >> 8;
×
798
                *dst++ = (*src++ * alpha + bgcolor[2] * (0xff - alpha)) >> 8;
×
799
                src++;
×
800
            }
801
            sixel_frame_apply_pixelformat(
×
802
                frame,
803
                SIXEL_PIXELFORMAT_RGB888);
804
            break;
×
805
        case SIXEL_PIXELFORMAT_RGBA8888:
806
            for (i = 0; i < frame->height * frame->width; i++) {
×
807
                alpha = src[3];
×
808
                *dst++ = (*src++ * alpha + bgcolor[0] * (0xff - alpha)) >> 8;
×
809
                *dst++ = (*src++ * alpha + bgcolor[1] * (0xff - alpha)) >> 8;
×
810
                *dst++ = (*src++ * alpha + bgcolor[2] * (0xff - alpha)) >> 8;
×
811
                src++;
×
812
            }
813
            sixel_frame_apply_pixelformat(
×
814
                frame,
815
                SIXEL_PIXELFORMAT_RGB888);
816
            break;
×
817
        case SIXEL_PIXELFORMAT_ABGR8888:
818
            for (i = 0; i < frame->height * frame->width; i++) {
×
819
                alpha = src[0];
×
820
                *dst++ = (src[3] * alpha + bgcolor[0] * (0xff - alpha)) >> 8;
×
821
                *dst++ = (src[2] * alpha + bgcolor[1] * (0xff - alpha)) >> 8;
×
822
                *dst++ = (src[1] * alpha + bgcolor[2] * (0xff - alpha)) >> 8;
×
823
                src += 4;
×
824
            }
825
            sixel_frame_apply_pixelformat(
×
826
                frame,
827
                SIXEL_PIXELFORMAT_RGB888);
828
            break;
×
829
        case SIXEL_PIXELFORMAT_BGRA8888:
830
            for (i = 0; i < frame->height * frame->width; i++) {
×
831
                alpha = src[3];
×
832
                *dst++ = (src[2] * alpha + bgcolor[0] * (0xff - alpha)) >> 8;
×
833
                *dst++ = (src[1] * alpha + bgcolor[1] * (0xff - alpha)) >> 8;
×
834
                *dst++ = (src[0] * alpha + bgcolor[2] * (0xff - alpha)) >> 8;
×
835
                src += 4;
×
836
            }
837
            sixel_frame_apply_pixelformat(
×
838
                frame,
839
                SIXEL_PIXELFORMAT_RGB888);
840
            break;
×
841
        default:
842
            break;
843
        }
844
    } else {
845
        switch (frame->pixelformat) {
896!
846
        case SIXEL_PIXELFORMAT_ARGB8888:
847
            for (i = 0; i < frame->height * frame->width; i++) {
×
848
                src++;            /* A */
×
849
                *dst++ = *src++;  /* R */
×
850
                *dst++ = *src++;  /* G */
×
851
                *dst++ = *src++;  /* B */
×
852
            }
853
            sixel_frame_apply_pixelformat(
×
854
                frame,
855
                SIXEL_PIXELFORMAT_RGB888);
856
            break;
×
857
        case SIXEL_PIXELFORMAT_RGBA8888:
858
            for (i = 0; i < frame->height * frame->width; i++) {
×
859
                *dst++ = *src++;  /* R */
×
860
                *dst++ = *src++;  /* G */
×
861
                *dst++ = *src++;  /* B */
×
862
                src++;            /* A */
×
863
            }
864
            sixel_frame_apply_pixelformat(
×
865
                frame,
866
                SIXEL_PIXELFORMAT_RGB888);
867
            break;
×
868
        case SIXEL_PIXELFORMAT_ABGR8888:
869
            for (i = 0; i < frame->height * frame->width; i++) {
×
870
                *dst++ = src[3];  /* R */
×
871
                *dst++ = src[2];  /* G */
×
872
                *dst++ = src[1];  /* B */
×
873
                src += 4;
×
874
            }
875
            sixel_frame_apply_pixelformat(
×
876
                frame,
877
                SIXEL_PIXELFORMAT_RGB888);
878
            break;
×
879
        case SIXEL_PIXELFORMAT_BGRA8888:
880
            for (i = 0; i < frame->height * frame->width; i++) {
×
881
                *dst++ = src[2];  /* R */
×
882
                *dst++ = src[1];  /* G */
×
883
                *dst++ = src[0];  /* B */
×
884
                src += 4;
×
885
            }
886
            sixel_frame_apply_pixelformat(
×
887
                frame,
888
                SIXEL_PIXELFORMAT_RGB888);
889
            break;
×
890
        default:
891
            break;
892
        }
893
    }
894

895
    status = SIXEL_OK;
920✔
896

897
    sixel_frame_unref(frame);
920✔
898

899
    return status;
920✔
900
}
901

902

903
static SIXELSTATUS
904
sixel_frame_convert_to_rgb888(sixel_frame_t /*in */ *frame)
465✔
905
{
906
    SIXELSTATUS status = SIXEL_FALSE;
465✔
907
    unsigned char *normalized_pixels = NULL;
465✔
908
    size_t size;
465✔
909
    unsigned char *dst;
465✔
910
    unsigned char *src;
465✔
911
    unsigned char *p;
465✔
912
    unsigned char *raw_pixels;
465✔
913
    unsigned char const *source_pixels;
465✔
914
    float *float_pixels;
465✔
915

916
    sixel_frame_ref(frame);
465✔
917

918
    raw_pixels = frame->pixels.u8ptr;
465✔
919
    float_pixels = frame->pixels.f32ptr;
465✔
920
    source_pixels = raw_pixels;
465✔
921

922
    switch (frame->pixelformat) {
465!
923
    case SIXEL_PIXELFORMAT_PAL1:
×
924
    case SIXEL_PIXELFORMAT_PAL2:
925
    case SIXEL_PIXELFORMAT_PAL4:
926
        size = (size_t)(frame->width * frame->height * 4);
×
927
        normalized_pixels = (unsigned char *)
×
928
            sixel_allocator_malloc(frame->allocator, size);
×
929
        if (normalized_pixels == NULL) {
×
930
            sixel_helper_set_additional_message(
×
931
                "sixel_frame_convert_to_rgb888: "
932
                "sixel_allocator_malloc() failed.");
933
            status = SIXEL_BAD_ALLOCATION;
×
934
            goto end;
×
935
        }
936
        src = normalized_pixels + frame->width * frame->height * 3;
×
937
        dst = normalized_pixels;
×
938
        status = sixel_helper_normalize_pixelformat(src,
×
939
                                                    &frame->pixelformat,
940
                                                    source_pixels,
941
                                                    frame->pixelformat,
942
                                                    frame->width,
943
                                                    frame->height);
944
        if (SIXEL_FAILED(status)) {
×
945
            sixel_allocator_free(frame->allocator, normalized_pixels);
×
946
            goto end;
×
947
        }
948
        for (p = src; dst < src; ++p) {
×
949
            *dst++ = *(frame->palette + *p * 3 + 0);
×
950
            *dst++ = *(frame->palette + *p * 3 + 1);
×
951
            *dst++ = *(frame->palette + *p * 3 + 2);
×
952
        }
953
        sixel_allocator_free(frame->allocator, raw_pixels);
×
954
        frame->pixels.u8ptr = normalized_pixels;
×
955
        sixel_frame_apply_pixelformat(
×
956
            frame,
957
            SIXEL_PIXELFORMAT_RGB888);
958
        break;
×
959
    case SIXEL_PIXELFORMAT_PAL8:
312✔
960
        size = (size_t)(frame->width * frame->height * 3);
312✔
961
        normalized_pixels = (unsigned char *)
312✔
962
            sixel_allocator_malloc(frame->allocator, size);
312✔
963
        if (normalized_pixels == NULL) {
312!
964
            sixel_helper_set_additional_message(
×
965
                "sixel_frame_convert_to_rgb888: "
966
                "sixel_allocator_malloc() failed.");
967
            status = SIXEL_BAD_ALLOCATION;
×
968
            goto end;
×
969
        }
970
        src = raw_pixels;
971
        dst = normalized_pixels;
972
        for (; dst != normalized_pixels + size; ++src) {
28,228,472✔
973
            *dst++ = frame->palette[*src * 3 + 0];
28,228,160✔
974
            *dst++ = frame->palette[*src * 3 + 1];
28,228,160✔
975
            *dst++ = frame->palette[*src * 3 + 2];
28,228,160✔
976
        }
977
        sixel_allocator_free(frame->allocator, raw_pixels);
312✔
978
        frame->pixels.u8ptr = normalized_pixels;
312✔
979
        sixel_frame_apply_pixelformat(
312✔
980
            frame,
981
            SIXEL_PIXELFORMAT_RGB888);
982
        break;
312✔
983
    case SIXEL_PIXELFORMAT_RGB888:
984
        break;
985
    case SIXEL_PIXELFORMAT_G8:
54✔
986
    case SIXEL_PIXELFORMAT_GA88:
987
    case SIXEL_PIXELFORMAT_AG88:
988
    case SIXEL_PIXELFORMAT_RGB555:
989
    case SIXEL_PIXELFORMAT_RGB565:
990
    case SIXEL_PIXELFORMAT_BGR555:
991
    case SIXEL_PIXELFORMAT_BGR565:
992
    case SIXEL_PIXELFORMAT_RGBA8888:
993
    case SIXEL_PIXELFORMAT_ARGB8888:
994
    case SIXEL_PIXELFORMAT_RGBFLOAT32:
995
    case SIXEL_PIXELFORMAT_LINEARRGBFLOAT32:
996
    case SIXEL_PIXELFORMAT_OKLABFLOAT32:
997
    case SIXEL_PIXELFORMAT_CIELABFLOAT32:
998
    case SIXEL_PIXELFORMAT_DIN99DFLOAT32:
999
        /* normalize pixelformat */
1000
        size = (size_t)(frame->width * frame->height * 3);
54✔
1001
        normalized_pixels = (unsigned char *)
54✔
1002
            sixel_allocator_malloc(frame->allocator, size);
54✔
1003
        if (normalized_pixels == NULL) {
54!
1004
            sixel_helper_set_additional_message(
×
1005
                "sixel_frame_convert_to_rgb888: "
1006
                "sixel_allocator_malloc() failed.");
1007
            status = SIXEL_BAD_ALLOCATION;
×
1008
            goto end;
×
1009
        }
1010
        if (SIXEL_PIXELFORMAT_IS_FLOAT32(frame->pixelformat)) {
54!
1011
            source_pixels = (unsigned char const *)float_pixels;
54✔
1012
        }
1013
        status = sixel_helper_normalize_pixelformat(normalized_pixels,
54✔
1014
                                                    &frame->pixelformat,
1015
                                                    source_pixels,
1016
                                                    frame->pixelformat,
1017
                                                    frame->width,
1018
                                                    frame->height);
1019
        if (SIXEL_FAILED(status)) {
54!
1020
            sixel_allocator_free(frame->allocator, normalized_pixels);
×
1021
            goto end;
×
1022
        }
1023
        sixel_allocator_free(frame->allocator, raw_pixels);
54✔
1024
        frame->pixels.u8ptr = normalized_pixels;
54✔
1025
        break;
54✔
1026
    default:
×
1027
        status = SIXEL_LOGIC_ERROR;
×
1028
        sixel_helper_set_additional_message(
×
1029
            "sixel_frame_convert_to_rgb888: invalid pixelformat.");
1030
        goto end;
×
1031
    }
1032

1033
    status = SIXEL_OK;
1034

1035
end:
465✔
1036
    sixel_frame_unref(frame);
465✔
1037

1038
    return status;
465✔
1039
}
1040

1041
/*
1042
 * Infer colorspace metadata from the pixelformat.  Float formats encode
1043
 * their transfer characteristics directly, while byte-oriented formats
1044
 * default to gamma encoded RGB.
1045
 */
1046
static int
1047
sixel_frame_colorspace_from_pixelformat(int pixelformat)
732✔
1048
{
1049
    switch (pixelformat) {
577!
1050
    case SIXEL_PIXELFORMAT_LINEARRGBFLOAT32:
1051
        return SIXEL_COLORSPACE_LINEAR;
1052
    case SIXEL_PIXELFORMAT_OKLABFLOAT32:
1053
        return SIXEL_COLORSPACE_OKLAB;
1054
    case SIXEL_PIXELFORMAT_CIELABFLOAT32:
1055
        return SIXEL_COLORSPACE_CIELAB;
1056
    case SIXEL_PIXELFORMAT_DIN99DFLOAT32:
1057
        return SIXEL_COLORSPACE_DIN99D;
1058
    case SIXEL_PIXELFORMAT_YUVFLOAT32:
1059
        return SIXEL_COLORSPACE_YUV;
1060
    default:
1061
        return SIXEL_COLORSPACE_GAMMA;
1062
    }
1063
}
1064

1065
static void
1066
sixel_frame_apply_pixelformat(sixel_frame_t *frame, int pixelformat)
1,044✔
1067
{
1068
    frame->pixelformat = pixelformat;
1,044✔
1069
    frame->colorspace = sixel_frame_colorspace_from_pixelformat(pixelformat);
671!
1070
}
312✔
1071

1072
#if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
1073
# if defined(__GNUC__) && !defined(__PCC__)
1074
#  pragma GCC diagnostic push
1075
#  pragma GCC diagnostic ignored "-Wunused-function"
1076
# endif
1077
#endif
1078

1079
/*
1080
 * Select the float pixelformat that matches the frame's current colorspace
1081
 * so downstream conversions interpret each channel correctly.  OKLab uses
1082
 * a [-0.5, 0.5] range for a/b, while gamma/linear share the 0-1 interval.
1083
 */
1084
static int
1085
sixel_frame_float_pixelformat_for_colorspace(int colorspace)
71✔
1086
{
1087
    switch (colorspace) {
71!
1088
    case SIXEL_COLORSPACE_LINEAR:
1089
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
1090
    case SIXEL_COLORSPACE_OKLAB:
1091
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
1092
    case SIXEL_COLORSPACE_CIELAB:
1093
        return SIXEL_PIXELFORMAT_CIELABFLOAT32;
1094
    case SIXEL_COLORSPACE_DIN99D:
1095
        return SIXEL_PIXELFORMAT_DIN99DFLOAT32;
1096
    case SIXEL_COLORSPACE_YUV:
1097
        return SIXEL_PIXELFORMAT_YUVFLOAT32;
1098
    default:
1099
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
1100
    }
1101
}
1102

1103
static SIXELSTATUS
1104
sixel_frame_promote_to_float32(sixel_frame_t *frame)
71✔
1105
{
1106
    float *float_pixels;
71✔
1107
    unsigned char *byte_pixels;
71✔
1108
    unsigned char const *pixel;
71✔
1109
    size_t pixel_total;
71✔
1110
    size_t bytes;
71✔
1111
    size_t index;
71✔
1112
    size_t base;
71✔
1113
    int float_pixelformat;
71✔
1114
    int step;
71✔
1115
    int index_r;
71✔
1116
    int index_g;
71✔
1117
    int index_b;
71✔
1118

1119
    step = 0;
71✔
1120
    index_r = 0;
71✔
1121
    index_g = 0;
71✔
1122
    index_b = 0;
71✔
1123

1124
    /*
1125
     * Derive the byte stride and per-channel offsets instead of coercing the
1126
     * frame to RGB888 first.  OKLab buffers keep their signed A/B buckets in
1127
     * the order dictated by pixelformat, so preserving that layout avoids the
1128
     * blue casts reported when we reinterpreted them as gamma RGB.
1129
     */
1130
    switch (frame->pixelformat) {
71!
1131
    case SIXEL_PIXELFORMAT_RGB888:
1132
        step = 3;
1133
        index_r = 0;
1134
        index_g = 1;
1135
        index_b = 2;
1136
        break;
1137
    case SIXEL_PIXELFORMAT_BGR888:
×
1138
        step = 3;
×
1139
        index_r = 2;
×
1140
        index_g = 1;
×
1141
        index_b = 0;
×
1142
        break;
×
1143
    case SIXEL_PIXELFORMAT_RGBA8888:
×
1144
        step = 4;
×
1145
        index_r = 0;
×
1146
        index_g = 1;
×
1147
        index_b = 2;
×
1148
        break;
×
1149
    case SIXEL_PIXELFORMAT_ARGB8888:
×
1150
        step = 4;
×
1151
        index_r = 1;
×
1152
        index_g = 2;
×
1153
        index_b = 3;
×
1154
        break;
×
1155
    case SIXEL_PIXELFORMAT_BGRA8888:
×
1156
        step = 4;
×
1157
        index_r = 2;
×
1158
        index_g = 1;
×
1159
        index_b = 0;
×
1160
        break;
×
1161
    case SIXEL_PIXELFORMAT_ABGR8888:
×
1162
        step = 4;
×
1163
        index_r = 3;
×
1164
        index_g = 2;
×
1165
        index_b = 1;
×
1166
        break;
×
1167
    case SIXEL_PIXELFORMAT_G8:
×
1168
        step = 1;
×
1169
        index_r = 0;
×
1170
        index_g = 0;
×
1171
        index_b = 0;
×
1172
        break;
×
1173
    case SIXEL_PIXELFORMAT_GA88:
×
1174
        step = 2;
×
1175
        index_r = 0;
×
1176
        index_g = 0;
×
1177
        index_b = 0;
×
1178
        break;
×
1179
    case SIXEL_PIXELFORMAT_AG88:
×
1180
        step = 2;
×
1181
        index_r = 1;
×
1182
        index_g = 1;
×
1183
        index_b = 1;
×
1184
        break;
×
1185
    default:
×
1186
        sixel_helper_set_additional_message(
×
1187
            "sixel_frame_promote_to_float32: unsupported pixelformat.");
1188
        return SIXEL_BAD_INPUT;
×
1189
    }
1190

1191
    if ((size_t)frame->width > SIZE_MAX / (size_t)frame->height) {
71!
1192
        sixel_helper_set_additional_message(
×
1193
            "sixel_frame_promote_to_float32: overflow.");
1194
        return SIXEL_BAD_INPUT;
×
1195
    }
1196

1197
    pixel_total = (size_t)frame->width * (size_t)frame->height;
71✔
1198
    if (pixel_total > SIZE_MAX / (3U * sizeof(float))) {
71!
1199
        sixel_helper_set_additional_message(
×
1200
            "sixel_frame_promote_to_float32: buffer too large.");
1201
        return SIXEL_BAD_INPUT;
×
1202
    }
1203
    bytes = pixel_total * 3U * sizeof(float);
71✔
1204
    float_pixels = (float *)sixel_allocator_malloc(frame->allocator, bytes);
71✔
1205
    if (float_pixels == NULL) {
71!
1206
        sixel_helper_set_additional_message(
×
1207
            "sixel_frame_promote_to_float32: "
1208
            "sixel_allocator_malloc() failed.");
1209
        return SIXEL_BAD_ALLOCATION;
×
1210
    }
1211

1212
    byte_pixels = frame->pixels.u8ptr;
71✔
1213
    float_pixelformat =
71✔
1214
        sixel_frame_float_pixelformat_for_colorspace(frame->colorspace);
71!
1215

1216
    for (index = 0U; index < pixel_total; ++index) {
17,167,255✔
1217
        unsigned char r8;
17,167,184✔
1218
        unsigned char g8;
17,167,184✔
1219
        unsigned char b8;
17,167,184✔
1220

1221
        pixel = byte_pixels + index * (size_t)step;
17,167,184✔
1222
        r8 = *(pixel + (size_t)index_r);
17,167,184✔
1223
        g8 = *(pixel + (size_t)index_g);
17,167,184✔
1224
        b8 = *(pixel + (size_t)index_b);
17,167,184✔
1225

1226
        base = index * 3U;
17,167,184✔
1227
        float_pixels[base + 0U] =
34,334,368✔
1228
            sixel_pixelformat_byte_to_float(float_pixelformat, 0, r8);
17,167,184✔
1229
        float_pixels[base + 1U] =
34,334,368✔
1230
            sixel_pixelformat_byte_to_float(float_pixelformat, 1, g8);
17,167,184✔
1231
        float_pixels[base + 2U] =
17,167,184✔
1232
            sixel_pixelformat_byte_to_float(float_pixelformat, 2, b8);
17,167,184✔
1233
    }
1234

1235
    sixel_allocator_free(frame->allocator, byte_pixels);
71✔
1236
    sixel_frame_set_pixels_float32(frame, float_pixels);
71✔
1237
    sixel_frame_apply_pixelformat(frame, float_pixelformat);
71!
1238
    return SIXEL_OK;
71✔
1239
}
1240

1241
#if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
1242
# if defined(__GNUC__) && !defined(__PCC__)
1243
#  pragma GCC diagnostic pop
1244
# endif
1245
#endif
1246

1247
/* resize a frame to given size with specified resampling filter */
1248
SIXELAPI SIXELSTATUS
1249
sixel_frame_resize(
99✔
1250
    sixel_frame_t *frame,
1251
    int width,
1252
    int height,
1253
    int method_for_resampling
1254
)
1255
{
1256
    SIXELSTATUS status = SIXEL_FALSE;
99✔
1257
    size_t size;
99✔
1258
    unsigned char *scaled_frame = NULL;
99✔
1259
    size_t unused_pixel_total;
99✔
1260
    int unused_depth_bytes;
99✔
1261

1262
    sixel_frame_ref(frame);
99✔
1263

1264
    size = 0u;
99✔
1265
    unused_pixel_total = 0u;
99✔
1266
    unused_depth_bytes = 0;
99✔
1267

1268
    /* check parameters */
1269
    if (width <= 0) {
99!
1270
        sixel_helper_set_additional_message(
×
1271
            "sixel_frame_resize: an invalid width parameter detected.");
1272
        status = SIXEL_BAD_INPUT;
×
1273
        goto end;
×
1274
    }
1275
    if (height <= 0) {
99!
1276
        sixel_helper_set_additional_message(
×
1277
            "sixel_frame_resize: an invalid width parameter detected.");
1278
        status = SIXEL_BAD_INPUT;
×
1279
        goto end;
×
1280
    }
1281
    if (width > SIXEL_WIDTH_LIMIT) {
99!
1282
        sixel_helper_set_additional_message(
×
1283
            "sixel_frame_resize: given width parameter is too huge.");
1284
        status = SIXEL_BAD_INPUT;
×
1285
        goto end;
×
1286
    }
1287
    if (height > SIXEL_HEIGHT_LIMIT) {
99!
1288
        sixel_helper_set_additional_message(
×
1289
            "sixel_frame_resize: given height parameter is too huge.");
1290
        status = SIXEL_BAD_INPUT;
×
1291
        goto end;
×
1292
    }
1293

1294
    status = sixel_frame_validate_size("sixel_frame_resize",
99✔
1295
                                       width,
1296
                                       height,
1297
                                       SIXEL_PIXELFORMAT_RGB888,
1298
                                       &unused_pixel_total,
1299
                                       &size,
1300
                                       &unused_depth_bytes);
1301
    if (SIXEL_FAILED(status)) {
99!
1302
        goto end;
×
1303
    }
1304

1305
    if (width == frame->width && height == frame->height) {
99!
1306
        /* nothing to do */
1307
        goto out;
×
1308
    }
1309

1310
    status = sixel_frame_convert_to_rgb888(frame);
99✔
1311
    if (SIXEL_FAILED(status)) {
99!
1312
        goto end;
×
1313
    }
1314

1315
    scaled_frame = (unsigned char *)
99✔
1316
        sixel_allocator_malloc(frame->allocator, size);
99✔
1317
    if (scaled_frame == NULL) {
99!
1318
        sixel_helper_set_additional_message(
×
1319
            "sixel_frame_resize: sixel_allocator_malloc() failed.");
1320
        status = SIXEL_BAD_ALLOCATION;
×
1321
        goto end;
×
1322
    }
1323

1324
    status = sixel_helper_scale_image(
198✔
1325
        scaled_frame,
1326
        frame->pixels.u8ptr,
99✔
1327
        frame->width,
1328
        frame->height,
1329
        3,
1330
        width,
1331
        height,
1332
        method_for_resampling,
1333
        frame->allocator);
1334
    if (SIXEL_FAILED(status)) {
99!
1335
        goto end;
×
1336
    }
1337
    sixel_allocator_free(frame->allocator, frame->pixels.u8ptr);
99✔
1338
    frame->pixels.u8ptr = scaled_frame;
99✔
1339
    frame->width = width;
99✔
1340
    frame->height = height;
99✔
1341

1342
out:
1343
    status = SIXEL_OK;
1344

1345
end:
99✔
1346
    sixel_frame_unref(frame);
99✔
1347

1348
    return status;
99✔
1349
}
1350

1351
/*
1352
 * Resize a frame using float buffers. Callers must supply RGB float32 input
1353
 * that already matches the intended resampling basis (linear RGB). The
1354
 * planner is responsible for any required color conversions prior to calling
1355
 * this routine. Unexpected pixelformats are rejected so that order mistakes
1356
 * surface early.
1357
 */
1358
SIXELAPI SIXELSTATUS
1359
sixel_frame_resize_float32(
59✔
1360
    sixel_frame_t *frame,
1361
    int width,
1362
    int height,
1363
    int method_for_resampling)
1364
{
1365
    SIXELSTATUS status;
59✔
1366
    size_t pixel_total;
59✔
1367
    size_t size;
59✔
1368
    float *scaled_frame;
59✔
1369
    int depth;
59✔
1370
    int depth_bytes;
59✔
1371
    int target_pixelformat;
59✔
1372

1373
    status = SIXEL_FALSE;
59✔
1374
    scaled_frame = NULL;
59✔
1375
    pixel_total = 0u;
59✔
1376
    size = 0u;
59✔
1377
    depth = 0;
59✔
1378
    depth_bytes = 0;
59✔
1379
    target_pixelformat = frame->pixelformat;
59✔
1380

1381
    sixel_frame_ref(frame);
59✔
1382

1383
    if (width <= 0) {
59!
1384
        sixel_helper_set_additional_message(
×
1385
            "sixel_frame_resize_float32: "
1386
            "an invalid width parameter detected.");
1387
        status = SIXEL_BAD_INPUT;
×
1388
        goto end;
×
1389
    }
1390
    if (height <= 0) {
59!
1391
        sixel_helper_set_additional_message(
×
1392
            "sixel_frame_resize_float32: "
1393
            "an invalid width parameter detected.");
1394
        status = SIXEL_BAD_INPUT;
×
1395
        goto end;
×
1396
    }
1397
    if (width > SIXEL_WIDTH_LIMIT) {
59!
1398
        sixel_helper_set_additional_message(
×
1399
            "sixel_frame_resize_float32: "
1400
            "given width parameter is too huge.");
1401
        status = SIXEL_BAD_INPUT;
×
1402
        goto end;
×
1403
    }
1404
    if (height > SIXEL_HEIGHT_LIMIT) {
59!
1405
        sixel_helper_set_additional_message(
×
1406
            "sixel_frame_resize_float32: "
1407
            "given height parameter is too huge.");
1408
        status = SIXEL_BAD_INPUT;
×
1409
        goto end;
×
1410
    }
1411

1412
    if (target_pixelformat != SIXEL_PIXELFORMAT_RGBFLOAT32
59!
1413
        && target_pixelformat != SIXEL_PIXELFORMAT_LINEARRGBFLOAT32) {
59!
1414
        sixel_helper_set_additional_message(
×
1415
            "sixel_frame_resize_float32: "
1416
            "resize expects RGB float32 input.");
1417
        status = SIXEL_BAD_ARGUMENT;
×
1418
        goto end;
×
1419
    }
1420

1421
    if (width == frame->width && height == frame->height) {
59!
1422
        goto out;
×
1423
    }
1424

1425
    status = sixel_frame_validate_size("sixel_frame_resize_float32",
59✔
1426
                                       width,
1427
                                       height,
1428
                                       frame->pixelformat,
1429
                                       &pixel_total,
1430
                                       &size,
1431
                                       &depth_bytes);
1432
    if (SIXEL_FAILED(status)) {
59!
1433
        goto end;
×
1434
    }
1435

1436
    /*
1437
     * sixel_frame_validate_size() returns bytes per pixel. Convert the value
1438
     * to channels for validation and reuse the byte count for buffer sizing
1439
     * to avoid overflow on float formats.
1440
     */
1441
    if (depth_bytes % (int)sizeof(float) != 0) {
59!
1442
        sixel_helper_set_additional_message(
×
1443
            "sixel_frame_resize_float32: "
1444
            "pixelformat depth is not float-aligned.");
1445
        status = SIXEL_BAD_ARGUMENT;
×
1446
        goto end;
×
1447
    }
1448
    depth = depth_bytes / (int)sizeof(float);
59✔
1449

1450
    if (depth != 3) {
59!
1451
        sixel_helper_set_additional_message(
×
1452
            "sixel_frame_resize_float32: "
1453
            "unsupported channel count.");
1454
        status = SIXEL_BAD_ARGUMENT;
×
1455
        goto end;
×
1456
    }
1457

1458
    scaled_frame = (float *)sixel_allocator_malloc(frame->allocator, size);
59✔
1459
    if (scaled_frame == NULL) {
59!
1460
        sixel_helper_set_additional_message(
×
1461
            "sixel_frame_resize_float32: "
1462
            "sixel_allocator_malloc() failed.");
1463
        status = SIXEL_BAD_ALLOCATION;
×
1464
        goto end;
×
1465
    }
1466

1467
    status = sixel_helper_scale_image_float32(
118✔
1468
        scaled_frame,
1469
        frame->pixels.f32ptr,
59✔
1470
        frame->width,
1471
        frame->height,
1472
        frame->pixelformat,
1473
        width,
1474
        height,
1475
        method_for_resampling,
1476
        frame->allocator);
1477
    if (SIXEL_FAILED(status)) {
59!
1478
        goto end;
×
1479
    }
1480

1481
    sixel_allocator_free(frame->allocator, frame->pixels.f32ptr);
59✔
1482
    frame->pixels.f32ptr = scaled_frame;
59✔
1483
    frame->width = width;
59✔
1484
    frame->height = height;
59✔
1485

1486
out:
1487
    status = SIXEL_OK;
1488

1489
end:
1490
    if (SIXEL_FAILED(status) && scaled_frame != NULL) {
×
1491
        sixel_allocator_free(frame->allocator, scaled_frame);
×
1492
    }
1493
    sixel_frame_unref(frame);
59✔
1494

1495
    return status;
59✔
1496
}
1497

1498

1499
static SIXELSTATUS
1500
clip(unsigned char *pixels,
26✔
1501
     int sx,
1502
     int sy,
1503
     int pixelformat,
1504
     int cx,
1505
     int cy,
1506
     int cw,
1507
     int ch)
1508
{
1509
    SIXELSTATUS status = SIXEL_FALSE;
26✔
1510
    int y;
26✔
1511
    unsigned char *src;
26✔
1512
    unsigned char *dst;
26✔
1513
    int depth;
26✔
1514
    char message[256];
26✔
1515
    int nwrite;
26✔
1516

1517
    /* unused */ (void) sx;
26✔
1518
    /* unused */ (void) sy;
26✔
1519
    /* unused */ (void) cx;
26✔
1520

1521
    depth = sixel_helper_compute_depth(pixelformat);
26✔
1522
    if (depth < 0) {
26!
1523
        status = SIXEL_BAD_ARGUMENT;
×
1524
        /*
1525
         * The compat helper keeps the format string bounded for MSVC while
1526
         * reporting the offending pixelformat to the caller.
1527
         */
1528
        nwrite = sixel_compat_snprintf(
×
1529
            message,
1530
            sizeof(message),
1531
            "clip: invalid pixelformat(%08x) is specified.",
1532
            pixelformat);
1533
        if (nwrite > 0) {
×
1534
            sixel_helper_set_additional_message(message);
×
1535
        }
1536
        goto end;
×
1537
    }
1538

1539
    dst = pixels;
26✔
1540
    src = pixels + cy * sx * depth + cx * depth;
26✔
1541
    for (y = 0; y < ch; y++) {
2,561✔
1542
        memmove(dst, src, (size_t)(cw * depth));
2,535✔
1543
        dst += (cw * depth);
2,535✔
1544
        src += (sx * depth);
2,535✔
1545
    }
1546

1547
    status = SIXEL_OK;
1548

1549
end:
26✔
1550
    return status;
26✔
1551
}
1552

1553

1554
/* clip frame */
1555
SIXELAPI SIXELSTATUS
1556
sixel_frame_clip(
26✔
1557
    sixel_frame_t *frame,
1558
    int x,
1559
    int y,
1560
    int width,
1561
    int height
1562
)
1563
{
1564
    SIXELSTATUS status = SIXEL_FALSE;
26✔
1565
    unsigned char *normalized_pixels;
26✔
1566
    unsigned char *raw_pixels;
26✔
1567

1568
    sixel_frame_ref(frame);
26✔
1569

1570
    raw_pixels = frame->pixels.u8ptr;
26✔
1571

1572
    /* check parameters */
1573
    if (width <= 0) {
26!
1574
        sixel_helper_set_additional_message(
×
1575
            "sixel_frame_clip: an invalid width parameter detected.");
1576
        status = SIXEL_BAD_INPUT;
×
1577
        goto end;
×
1578
    }
1579
    if (height <= 0) {
26!
1580
        sixel_helper_set_additional_message(
×
1581
            "sixel_frame_clip: an invalid width parameter detected.");
1582
        status = SIXEL_BAD_INPUT;
×
1583
        goto end;
×
1584
    }
1585
    if (width > SIXEL_WIDTH_LIMIT) {
26!
1586
        sixel_helper_set_additional_message(
×
1587
            "sixel_frame_clip: given width parameter is too huge.");
1588
        status = SIXEL_BAD_INPUT;
×
1589
        goto end;
×
1590
    }
1591
    if (height > SIXEL_HEIGHT_LIMIT) {
26!
1592
        sixel_helper_set_additional_message(
×
1593
            "sixel_frame_clip: given height parameter is too huge.");
1594
        status = SIXEL_BAD_INPUT;
×
1595
        goto end;
×
1596
    }
1597

1598
    switch (frame->pixelformat) {
26!
1599
    case SIXEL_PIXELFORMAT_PAL1:
×
1600
    case SIXEL_PIXELFORMAT_PAL2:
1601
    case SIXEL_PIXELFORMAT_PAL4:
1602
    case SIXEL_PIXELFORMAT_G1:
1603
    case SIXEL_PIXELFORMAT_G2:
1604
    case SIXEL_PIXELFORMAT_G4:
1605
        normalized_pixels = (unsigned char *)
×
1606
            sixel_allocator_malloc(frame->allocator,
×
1607
                                   (size_t)(frame->width * frame->height));
×
1608
        status = sixel_helper_normalize_pixelformat(normalized_pixels,
×
1609
                                                    &frame->pixelformat,
1610
                                                    raw_pixels,
1611
                                                    frame->pixelformat,
1612
                                                    frame->width,
1613
                                                    frame->height);
1614
        if (SIXEL_FAILED(status)) {
×
1615
            sixel_allocator_free(frame->allocator, normalized_pixels);
×
1616
            goto end;
×
1617
        }
1618
        sixel_allocator_free(frame->allocator, raw_pixels);
×
1619
        frame->pixels.u8ptr = normalized_pixels;
×
1620
        raw_pixels = normalized_pixels;
×
1621
        break;
×
1622
    default:
1623
        break;
1624
    }
1625

1626
    status = clip(raw_pixels,
26✔
1627
                  frame->width,
1628
                  frame->height,
1629
                  frame->pixelformat,
1630
                  x,
1631
                  y,
1632
                  width,
1633
                  height);
1634
    if (SIXEL_FAILED(status)) {
26!
1635
        goto end;
×
1636
    }
1637
    frame->width = width;
26✔
1638
    frame->height = height;
26✔
1639

1640
    status = SIXEL_OK;
26✔
1641

1642
end:
26✔
1643
    sixel_frame_unref(frame);
26✔
1644

1645
    return status;
26✔
1646
}
1647

1648

1649
/* Ensure legacy frame constructor and refcounting work. */
1650
#if HAVE_TESTS
1651
static int
1652
frame_test_create_refcount(void)
×
1653
{
1654
    sixel_frame_t *frame = NULL;
×
1655
    int nret = EXIT_FAILURE;
×
1656

1657
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1658
#  pragma GCC diagnostic push
1659
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1660
#endif
1661
    frame = sixel_frame_create();
×
1662
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1663
#  pragma GCC diagnostic pop
1664
#endif
1665
    if (frame == NULL) {
×
1666
        goto error;
×
1667
    }
1668
    sixel_frame_ref(frame);
×
1669
    sixel_frame_unref(frame);
×
1670
    nret = EXIT_SUCCESS;
×
1671

1672
error:
×
1673
    sixel_frame_unref(frame);
×
1674
    return nret;
×
1675
}
1676

1677

1678
/*
1679
 * Verify alpha stripping blends using a provided background color when
1680
 * converting RGBA data.
1681
 */
1682
static int
1683
frame_test_strip_alpha_with_background(void)
×
1684
{
1685
    sixel_frame_t *frame = NULL;
×
1686
    int nret = EXIT_FAILURE;
×
1687
    unsigned char *pixels = malloc(4);
×
1688
    unsigned char *bgcolor = malloc(3);
×
1689
    unsigned char *u8pixels;
×
1690
    SIXELSTATUS status;
×
1691

1692
    pixels[0] = 0x43;
×
1693
    pixels[1] = 0x89;
×
1694
    pixels[2] = 0x97;
×
1695
    pixels[3] = 0x32;
×
1696

1697
    memset(bgcolor, 0x10, 3);
×
1698

1699
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1700
#  pragma GCC diagnostic push
1701
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1702
#endif
1703
    frame = sixel_frame_create();
×
1704
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1705
#  pragma GCC diagnostic pop
1706
#endif
1707

1708
    if (frame == NULL) {
×
1709
        goto error;
×
1710
    }
1711

1712
    status = sixel_frame_init(frame,
×
1713
                              pixels,
1714
                              1,
1715
                              1,
1716
                              SIXEL_PIXELFORMAT_RGBA8888,
1717
                              NULL,
1718
                              0);
1719
    if (SIXEL_FAILED(status)) {
×
1720
        goto error;
×
1721
    }
1722

1723
    status = sixel_frame_strip_alpha(frame, bgcolor);
×
1724
    if (SIXEL_FAILED(status)) {
×
1725
        goto error;
1726
    }
1727

1728
    if (frame->pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1729
        goto error;
×
1730
    }
1731

1732
    u8pixels = frame->pixels.u8ptr;
×
1733

1734
    if (u8pixels[0] != (0x43 * 0x32 + 0x10 * (0xff - 0x32)) >> 8) {
×
1735
        goto error;
×
1736
    }
1737

1738
    if (u8pixels[1] != (0x89 * 0x32 + 0x10 * (0xff - 0x32)) >> 8) {
×
1739
        goto error;
×
1740
    }
1741

1742
    if (u8pixels[2] != (0x97 * 0x32 + 0x10 * (0xff - 0x32)) >> 8) {
×
1743
        goto error;
×
1744
    }
1745

1746
    nret = EXIT_SUCCESS;
1747

1748
error:
×
1749
    sixel_frame_unref(frame);
×
1750
    return nret;
×
1751
}
1752

1753

1754
/* Ensure stripping alpha without background preserves RGB values. */
1755
static int
1756
frame_test_strip_alpha_preserves_rgb(void)
×
1757
{
1758
    sixel_frame_t *frame = NULL;
×
1759
    int nret = EXIT_FAILURE;
×
1760
    unsigned char *pixels = malloc(4);
×
1761
    unsigned char *u8pixels;
×
1762
    SIXELSTATUS status;
×
1763

1764
    pixels[0] = 0x43;
×
1765
    pixels[1] = 0x89;
×
1766
    pixels[2] = 0x97;
×
1767
    pixels[3] = 0x32;
×
1768

1769
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1770
#  pragma GCC diagnostic push
1771
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1772
#endif
1773
    frame = sixel_frame_create();
×
1774
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1775
#  pragma GCC diagnostic pop
1776
#endif
1777
    if (frame == NULL) {
×
1778
        goto error;
×
1779
    }
1780

1781
    status = sixel_frame_init(frame,
×
1782
                              pixels,
1783
                              1,
1784
                              1,
1785
                              SIXEL_PIXELFORMAT_RGBA8888,
1786
                              NULL,
1787
                              0);
1788
    if (SIXEL_FAILED(status)) {
×
1789
        goto error;
×
1790
    }
1791

1792
    status = sixel_frame_strip_alpha(frame, NULL);
×
1793
    if (SIXEL_FAILED(status)) {
×
1794
        goto error;
1795
    }
1796

1797
    if (frame->pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1798
        goto error;
×
1799
    }
1800

1801
    u8pixels = frame->pixels.u8ptr;
×
1802

1803
    if (u8pixels[0] != 0x43) {
×
1804
        goto error;
×
1805
    }
1806

1807
    if (u8pixels[1] != 0x89) {
×
1808
        goto error;
×
1809
    }
1810

1811
    if (u8pixels[2] != 0x97) {
×
1812
        goto error;
×
1813
    }
1814

1815
    nret = EXIT_SUCCESS;
1816

1817
error:
×
1818
    sixel_frame_unref(frame);
×
1819
    return nret;
×
1820
}
1821

1822

1823
/* Confirm ARGB input is reordered correctly when alpha is removed. */
1824
static int
1825
frame_test_strip_alpha_argb_input(void)
×
1826
{
1827
    sixel_frame_t *frame = NULL;
×
1828
    int nret = EXIT_FAILURE;
×
1829
    unsigned char *pixels = malloc(4);
×
1830
    unsigned char *u8pixels;
×
1831
    SIXELSTATUS status;
×
1832

1833
    pixels[0] = 0x43;
×
1834
    pixels[1] = 0x89;
×
1835
    pixels[2] = 0x97;
×
1836
    pixels[3] = 0x32;
×
1837

1838
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1839
#  pragma GCC diagnostic push
1840
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1841
#endif
1842
    frame = sixel_frame_create();
×
1843
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1844
#  pragma GCC diagnostic pop
1845
#endif
1846
    if (frame == NULL) {
×
1847
        goto error;
×
1848
    }
1849

1850
    status = sixel_frame_init(frame,
×
1851
                              pixels,
1852
                              1,
1853
                              1,
1854
                              SIXEL_PIXELFORMAT_ARGB8888,
1855
                              NULL,
1856
                              0);
1857
    if (SIXEL_FAILED(status)) {
×
1858
        goto error;
×
1859
    }
1860

1861
    status = sixel_frame_strip_alpha(frame, NULL);
×
1862
    if (SIXEL_FAILED(status)) {
×
1863
        goto error;
1864
    }
1865

1866
    if (frame->pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1867
        goto error;
×
1868
    }
1869

1870
    u8pixels = frame->pixels.u8ptr;
×
1871

1872
    if (u8pixels[0] != 0x89) {
×
1873
        goto error;
×
1874
    }
1875

1876
    if (u8pixels[1] != 0x97) {
×
1877
        goto error;
×
1878
    }
1879

1880
    if (u8pixels[2] != 0x32) {
×
1881
        goto error;
×
1882
    }
1883

1884
    nret = EXIT_SUCCESS;
1885

1886
error:
×
1887
    sixel_frame_unref(frame);
×
1888
    return nret;
×
1889
}
1890

1891

1892
/* Convert PAL8 frames to RGB888 using the provided palette. */
1893
static int
1894
frame_test_convert_pal8_to_rgb(void)
×
1895
{
1896
    sixel_frame_t *frame = NULL;
×
1897
    int nret = EXIT_FAILURE;
×
1898
    unsigned char *pixels = malloc(1);
×
1899
    unsigned char *palette = malloc(3);
×
1900
    unsigned char *u8pixels;
×
1901
    SIXELSTATUS status;
×
1902

1903
    palette[0] = 0x43;
×
1904
    palette[1] = 0x89;
×
1905
    palette[2] = 0x97;
×
1906

1907
    pixels[0] = 0;
×
1908

1909
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1910
#  pragma GCC diagnostic push
1911
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1912
#endif
1913
    frame = sixel_frame_create();
×
1914
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1915
#  pragma GCC diagnostic pop
1916
#endif
1917
    if (frame == NULL) {
×
1918
        goto error;
×
1919
    }
1920

1921
    status = sixel_frame_init(frame,
×
1922
                              pixels,
1923
                              1,
1924
                              1,
1925
                              SIXEL_PIXELFORMAT_PAL8,
1926
                              palette,
1927
                              1);
1928
    if (SIXEL_FAILED(status)) {
×
1929
        goto error;
×
1930
    }
1931

1932
    status = sixel_frame_convert_to_rgb888(frame);
×
1933
    if (SIXEL_FAILED(status)) {
×
1934
        goto error;
×
1935
    }
1936

1937
    if (frame->pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1938
        goto error;
×
1939
    }
1940

1941
    u8pixels = frame->pixels.u8ptr;
×
1942

1943
    if (u8pixels[0] != 0x43) {
×
1944
        goto error;
×
1945
    }
1946

1947
    if (u8pixels[1] != 0x89) {
×
1948
        goto error;
×
1949
    }
1950

1951
    if (u8pixels[2] != 0x97) {
×
1952
        goto error;
×
1953
    }
1954

1955
    nret = EXIT_SUCCESS;
1956

1957
error:
×
1958
    sixel_frame_unref(frame);
×
1959
    return nret;
×
1960
}
1961

1962

1963
/* Convert PAL1 frames to RGB888 using palette expansion. */
1964
static int
1965
frame_test_convert_pal1_to_rgb(void)
×
1966
{
1967
    sixel_frame_t *frame = NULL;
×
1968
    int nret = EXIT_FAILURE;
×
1969
    unsigned char *pixels = malloc(6);
×
1970
    unsigned char *palette = malloc(3);
×
1971
    unsigned char *u8pixels;
×
1972
    SIXELSTATUS status;
×
1973

1974
    palette[0] = 0x43;
×
1975
    palette[1] = 0x89;
×
1976
    palette[2] = 0x97;
×
1977

1978
    pixels[0] = 0;
×
1979

1980
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1981
#  pragma GCC diagnostic push
1982
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1983
#endif
1984
    frame = sixel_frame_create();
×
1985
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1986
#  pragma GCC diagnostic pop
1987
#endif
1988
    if (frame == NULL) {
×
1989
        goto error;
×
1990
    }
1991

1992
    status = sixel_frame_init(frame,
×
1993
                              pixels,
1994
                              1,
1995
                              1,
1996
                              SIXEL_PIXELFORMAT_PAL1,
1997
                              palette,
1998
                              1);
1999
    if (SIXEL_FAILED(status)) {
×
2000
        goto error;
×
2001
    }
2002

2003
    status = sixel_frame_convert_to_rgb888(frame);
×
2004
    if (SIXEL_FAILED(status)) {
×
2005
        goto error;
×
2006
    }
2007

2008
    if (frame->pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
2009
        goto error;
×
2010
    }
2011

2012
    u8pixels = frame->pixels.u8ptr;
×
2013

2014
    if (u8pixels[0] != 0x43) {
×
2015
        goto error;
×
2016
    }
2017

2018
    if (u8pixels[1] != 0x89) {
×
2019
        goto error;
×
2020
    }
2021

2022
    if (u8pixels[2] != 0x97) {
×
2023
        goto error;
×
2024
    }
2025

2026
    nret = EXIT_SUCCESS;
2027

2028
error:
×
2029
    sixel_frame_unref(frame);
×
2030
    return nret;
×
2031
}
2032

2033

2034
SIXELAPI int
2035
sixel_frame_tests_main(void)
×
2036
{
2037
    int nret = EXIT_FAILURE;
×
2038
    size_t i;
×
2039
    typedef int (* testcase)(void);
×
2040

2041
    static testcase const testcases[] = {
×
2042
        frame_test_create_refcount,
2043
        frame_test_strip_alpha_with_background,
2044
        frame_test_strip_alpha_preserves_rgb,
2045
        frame_test_strip_alpha_argb_input,
2046
        frame_test_convert_pal8_to_rgb,
2047
        frame_test_convert_pal1_to_rgb,
2048
    };
2049

2050
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
2051
        nret = testcases[i]();
×
2052
        if (nret != EXIT_SUCCESS) {
×
2053
            goto error;
×
2054
        }
2055
    }
2056

2057
    nret = EXIT_SUCCESS;
2058

2059
error:
×
2060
    return nret;
×
2061
}
2062
#endif  /* HAVE_TESTS */
2063

2064
/* emacs Local Variables:      */
2065
/* emacs mode: c               */
2066
/* emacs tab-width: 4          */
2067
/* emacs indent-tabs-mode: nil */
2068
/* emacs c-basic-offset: 4     */
2069
/* emacs End:                  */
2070
/* vim: set expandtab ts=4 sts=4 sw=4 : */
2071
/* 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