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

saitoha / libsixel / 22030802165

15 Feb 2026 06:01AM UTC coverage: 82.477% (-0.002%) from 82.479%
22030802165

push

github

saitoha
build: keep emscripten -s settings out of compile flags

24564 of 48140 branches covered (51.03%)

42941 of 52064 relevant lines covered (82.48%)

3425295.8 hits per line

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

73.44
/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
#include "sixel_atomic.h"
49

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

67

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

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

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

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

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

130
    if (pixel_total != NULL) {
1,583!
131
        *pixel_total = pixels;
1,583✔
132
    }
755✔
133
    if (byte_total != NULL) {
1,583!
134
        *byte_total = bytes;
1,583✔
135
    }
755✔
136
    if (depth_bytes != NULL) {
1,583!
137
        *depth_bytes = depth;
1,583✔
138
    }
755✔
139

140
    status = SIXEL_OK;
1,031✔
141

142
end:
828✔
143
    return status;
1,962✔
144
}
379✔
145

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

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

162
    /*
163
     * Zero-initialize the frame to avoid MSan reports when fields are read
164
     * before explicit assignment in loader paths.
165
     */
166
    *ppframe = (sixel_frame_t *)sixel_allocator_calloc(
15,831✔
167
        allocator,
8,161✔
168
        1,
169
        sizeof(sixel_frame_t));
170
    if (*ppframe == NULL) {
15,831!
171
        sixel_helper_set_additional_message(
×
172
            "sixel_frame_resize: sixel_allocator_malloc() failed.");
173
        status = SIXEL_BAD_ALLOCATION;
×
174
        goto end;
×
175
    }
176

177
    (*ppframe)->ref = 1U;
15,831✔
178
    (*ppframe)->pixels.u8ptr = NULL;
15,831✔
179
    (*ppframe)->palette = NULL;
15,831✔
180
    (*ppframe)->width = 0;
15,831✔
181
    (*ppframe)->height = 0;
15,831✔
182
    (*ppframe)->ncolors = (-1);
15,831✔
183
    (*ppframe)->pixelformat = SIXEL_PIXELFORMAT_RGB888;
15,831✔
184
    /*
185
     * Initialize colorspace metadata to match the default pixelformat so
186
     * getters do not read uninitialized memory before initialization.
187
     */
188
    (*ppframe)->colorspace =
18,371✔
189
        sixel_frame_colorspace_from_pixelformat((*ppframe)->pixelformat);
15,831✔
190
    (*ppframe)->delay = 0;
15,831✔
191
    (*ppframe)->frame_no = 0;
15,831✔
192
    (*ppframe)->loop_count = 0;
15,831✔
193
    (*ppframe)->multiframe = 0;
15,831✔
194
    (*ppframe)->transparent = (-1);
15,831✔
195
    (*ppframe)->allocator = allocator;
15,831✔
196

197
    sixel_allocator_ref(allocator);
15,831✔
198

199
    /* Normalize between byte and float pipelines when buffers are present. */
200
    status = SIXEL_OK;
15,831✔
201

202
end:
7,670✔
203
    return status;
20,033✔
204
}
4,202✔
205

206

207
SIXELAPI /* deprecated */ sixel_frame_t *
208
sixel_frame_create(void)
×
209
{
210
    SIXELSTATUS status = SIXEL_FALSE;
×
211
    sixel_frame_t *frame = NULL;
×
212

213
    status = sixel_frame_new(&frame, NULL);
×
214
    if (SIXEL_FAILED(status)) {
×
215
        goto end;
216
    }
217

218
end:
219
    return frame;
×
220
}
221

222

223
static void
224
sixel_frame_destroy(sixel_frame_t /* in */ *frame)
15,831✔
225
{
226
    sixel_allocator_t *allocator = NULL;
15,831✔
227

228
    if (frame) {
15,831!
229
        allocator = frame->allocator;
15,831✔
230
        sixel_allocator_free(allocator, frame->pixels.u8ptr);
15,831✔
231
        sixel_allocator_free(allocator, frame->palette);
15,831✔
232
        sixel_allocator_free(allocator, frame);
15,831✔
233
        sixel_allocator_unref(allocator);
15,831✔
234
    }
8,161✔
235
}
15,831✔
236

237

238
/* increase reference count of frame object (thread-safe) */
239
SIXELAPI void
240
sixel_frame_ref(sixel_frame_t *frame)
26,739✔
241
{
242
    if (frame == NULL) {
26,739!
243
        return;
244
    }
245

246
    (void)sixel_atomic_fetch_add_u32(&frame->ref, 1U);
26,739✔
247
}
13,567✔
248

249

250
/* decrease reference count of frame object (thread-safe) */
251
SIXELAPI void
252
sixel_frame_unref(sixel_frame_t *frame)
43,220✔
253
{
254
    unsigned int previous;
25,579✔
255

256
    if (frame == NULL) {
43,220✔
257
        return;
416✔
258
    }
259

260
    previous = sixel_atomic_fetch_sub_u32(&frame->ref, 1U);
42,570✔
261
    if (previous == 1U) {
42,570✔
262
        sixel_frame_destroy(frame);
15,831✔
263
    }
8,161✔
264
}
22,021!
265

266

267
/*
268
 * Shared initializer for both byte and float32 buffers.  Keeping the
269
 * validation and field updates in one place avoids diverging logic
270
 * between the two entry points while making the stored pointer type
271
 * explicit.
272
 */
273
static SIXELSTATUS
274
sixel_frame_init_common(
357✔
275
    sixel_frame_t   *frame,
276
    void            *pixels,
277
    int              width,
278
    int              height,
279
    int              pixelformat,
280
    unsigned char   *palette,
281
    int              ncolors,
282
    int              is_float)
283
{
284
    SIXELSTATUS status = SIXEL_FALSE;
357✔
285
    size_t unused_pixel_total;
210✔
286
    size_t unused_byte_total;
210✔
287
    int unused_depth_bytes;
210✔
288

289
    sixel_frame_ref(frame);
357✔
290

291
    unused_pixel_total = 0u;
357✔
292
    unused_byte_total = 0u;
357✔
293
    unused_depth_bytes = 0;
357✔
294

295
    /* check parameters */
296
    if (width <= 0) {
357!
297
        sixel_helper_set_additional_message(
×
298
            "sixel_frame_init: an invalid width parameter detected.");
299
        status = SIXEL_BAD_INPUT;
×
300
        goto end;
×
301
    }
302
    status = sixel_frame_validate_size("sixel_frame_init",
357✔
303
                                       width,
168✔
304
                                       height,
168✔
305
                                       pixelformat,
168✔
306
                                       &unused_pixel_total,
307
                                       &unused_byte_total,
308
                                       &unused_depth_bytes);
309
    if (SIXEL_FAILED(status)) {
357!
310
        goto end;
×
311
    }
312
    if (height <= 0) {
357!
313
        sixel_helper_set_additional_message(
×
314
            "sixel_frame_init: an invalid width parameter detected.");
315
        status = SIXEL_BAD_INPUT;
×
316
        goto end;
×
317
    }
318
    if (width > SIXEL_WIDTH_LIMIT) {
357!
319
        sixel_helper_set_additional_message(
×
320
            "sixel_frame_init: given width parameter is too huge.");
321
        status = SIXEL_BAD_INPUT;
×
322
        goto end;
×
323
    }
324
    if (height > SIXEL_HEIGHT_LIMIT) {
357!
325
        sixel_helper_set_additional_message(
×
326
            "sixel_frame_init: given height parameter is too huge.");
327
        status = SIXEL_BAD_INPUT;
×
328
        goto end;
×
329
    }
330
    if (is_float != 0 && !SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)) {
357!
331
        sixel_helper_set_additional_message(
×
332
            "sixel_frame_init: pixelformat must be float32 when "
333
            "supplying float pixels.");
334
        status = SIXEL_BAD_INPUT;
×
335
        goto end;
×
336
    }
337

338
    if (is_float != 0) {
243✔
339
        frame->pixels.f32ptr = (float *)pixels;
34✔
340
    } else {
16✔
341
        frame->pixels.u8ptr = (unsigned char *)pixels;
323✔
342
    }
343
    frame->width = width;
357✔
344
    frame->height = height;
357✔
345
    sixel_frame_apply_pixelformat(frame, pixelformat);
483✔
346
    frame->palette = palette;
357✔
347
    frame->ncolors = ncolors;
357✔
348
    status = SIXEL_OK;
357✔
349

350
end:
189✔
351
    sixel_frame_unref(frame);
357✔
352

353
    return status;
441✔
354
}
84✔
355

356
/* initialize frame object with a pixel buffer */
357
SIXELAPI SIXELSTATUS
358
sixel_frame_init(
323✔
359
    sixel_frame_t   /* in */ *frame,
360
    unsigned char   /* 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,
475✔
368
                                   pixels,
152✔
369
                                   width,
152✔
370
                                   height,
152✔
371
                                   pixelformat,
152✔
372
                                   palette,
152✔
373
                                   ncolors,
152✔
374
                                   0);
375
}
376

377
/* initialize frame object with a float32 pixel buffer */
378
SIXELAPI SIXELSTATUS
379
sixel_frame_init_float32(
34✔
380
    sixel_frame_t   /* in */ *frame,
381
    float           /* in */ *pixels,
382
    int             /* in */ width,
383
    int             /* in */ height,
384
    int             /* in */ pixelformat,
385
    unsigned char   /* in */ *palette,
386
    int             /* in */ ncolors)
387
{
388
    return sixel_frame_init_common(frame,
50✔
389
                                   pixels,
16✔
390
                                   width,
16✔
391
                                   height,
16✔
392
                                   pixelformat,
16✔
393
                                   palette,
16✔
394
                                   ncolors,
16✔
395
                                   1);
396
}
397

398

399
/* get pixels */
400
SIXELAPI unsigned char *
401
sixel_frame_get_pixels(sixel_frame_t /* in */ *frame)  /* frame object */
20,965✔
402
{
403
    return frame->pixels.u8ptr;
20,965✔
404
}
405

406

407
SIXELAPI float *
408
sixel_frame_get_pixels_float32(sixel_frame_t /* in */ *frame)
×
409
{
410
    return frame->pixels.f32ptr;
×
411
}
412

413

414
/* set pixels */
415
SIXELAPI void
416
sixel_frame_set_pixels(
12,360✔
417
    sixel_frame_t  /* in */ *frame,
418
    unsigned char  /* in */ *pixels)
419
{
420
    frame->pixels.u8ptr = pixels;
12,360✔
421
}
12,360✔
422

423

424
SIXELAPI void
425
sixel_frame_set_pixels_float32(
4,341✔
426
    sixel_frame_t  /* in */ *frame,
427
    float          /* in */ *pixels)
428
{
429
    frame->pixels.f32ptr = pixels;
4,341✔
430
}
3,368✔
431

432

433
/* get palette */
434
SIXELAPI unsigned char *
435
sixel_frame_get_palette(sixel_frame_t /* in */ *frame)  /* frame object */
2,501✔
436
{
437
    return frame->palette;
2,501✔
438
}
439

440

441
/* set palette */
442
SIXELAPI void
443
sixel_frame_set_palette(
170✔
444
    sixel_frame_t  /* in */ *frame,
445
    unsigned char  /* in */ *palette)
446
{
447
    frame->palette = palette;
170✔
448
}
170✔
449

450

451
/* get width */
452
SIXELAPI int
453
sixel_frame_get_width(sixel_frame_t /* in */ *frame)  /* frame object */
44,371✔
454
{
455
    return frame->width;
44,371✔
456
}
457

458

459
/* set width */
460
SIXELAPI void
461
sixel_frame_set_width(sixel_frame_t /* in */ *frame, int /* in */ width)
629✔
462
{
463
    frame->width = width;
629✔
464
}
629✔
465

466

467
/* get height */
468
SIXELAPI int
469
sixel_frame_get_height(sixel_frame_t /* in */ *frame)  /* frame object */
86,068✔
470
{
471
    return frame->height;
86,068✔
472
}
473

474

475
/* set height */
476
SIXELAPI void
477
sixel_frame_set_height(sixel_frame_t /* in */ *frame, int /* in */ height)
629✔
478
{
479
    frame->height = height;
629✔
480
}
629✔
481

482

483
/* get ncolors */
484
SIXELAPI int
485
sixel_frame_get_ncolors(sixel_frame_t /* in */ *frame)  /* frame object */
7,474✔
486
{
487
    return frame->ncolors;
7,474✔
488
}
489

490

491
/* set ncolors */
492
SIXELAPI void
493
sixel_frame_set_ncolors(
629✔
494
    sixel_frame_t  /* in */ *frame,
495
    int            /* in */ ncolors)
496
{
497
    frame->ncolors = ncolors;
629✔
498
}
629✔
499

500

501
/* get pixelformat */
502
SIXELAPI int
503
sixel_frame_get_pixelformat(sixel_frame_t /* in */ *frame)  /* frame object */
118,309✔
504
{
505
    return frame->pixelformat;
118,309✔
506
}
507

508

509
/* set pixelformat */
510
SIXELAPI SIXELSTATUS
511
sixel_frame_set_pixelformat(
8,570✔
512
    sixel_frame_t  /* in */ *frame,
513
    int            /* in */ pixelformat)
514
{
515
    SIXELSTATUS status;
5,022✔
516
    int source_colorspace;
5,022✔
517
    int target_colorspace;
5,022✔
518
    int working_pixelformat;
5,022✔
519
    int depth;
5,022✔
520
    int float_depth;
5,022✔
521
    size_t pixel_total;
5,022✔
522
    size_t pixel_size;
5,022✔
523
    size_t float_pixels;
5,022✔
524
    size_t float_bytes;
5,022✔
525
    size_t float_limit;
5,022✔
526
    unsigned char *pixels;
5,022✔
527

528
    if (frame == NULL) {
8,570!
529
        sixel_helper_set_additional_message(
×
530
            "sixel_frame_set_pixelformat: frame is null.");
531
        return SIXEL_BAD_ARGUMENT;
×
532
    }
533
    if (pixelformat == frame->pixelformat) {
8,570✔
534
        return SIXEL_OK;
1,739✔
535
    }
536
    if (frame->pixels.u8ptr == NULL) {
6,003✔
537
        sixel_frame_apply_pixelformat(frame, pixelformat);
92✔
538
        return SIXEL_OK;
68✔
539
    }
540

541
    status = SIXEL_OK;
5,935✔
542
    working_pixelformat = frame->pixelformat;
5,935✔
543
    source_colorspace = frame->colorspace;
5,935✔
544
    float_depth = 0;
5,935✔
545
    float_pixels = 0U;
5,935✔
546
    float_bytes = 0U;
5,935✔
547
    float_limit = SIXEL_ALLOCATE_BYTES_MAX / 2U;
5,935✔
548

549
    /*
550
     * Palette and byte-form buffers need to be normalized before any
551
     * colorspace adjustments so that channel ordering matches the
552
     * converter's expectations.
553
     */
554
    if (pixelformat == SIXEL_PIXELFORMAT_RGBFLOAT32
6,763✔
555
            || pixelformat == SIXEL_PIXELFORMAT_LINEARRGBFLOAT32
3,814✔
556
            || pixelformat == SIXEL_PIXELFORMAT_OKLABFLOAT32
2,950✔
557
            || pixelformat == SIXEL_PIXELFORMAT_CIELABFLOAT32
1,944✔
558
            || pixelformat == SIXEL_PIXELFORMAT_DIN99DFLOAT32) {
3,316✔
559
        if (working_pixelformat & SIXEL_FORMATTYPE_PALETTE) {
4,514!
560
            status = sixel_frame_convert_to_rgb888(frame);
×
561
        }
562
        if (SIXEL_SUCCEEDED(status)
5,021!
563
                && !SIXEL_PIXELFORMAT_IS_FLOAT32(frame->pixelformat)) {
4,514!
564
            float_depth = sixel_helper_compute_depth(pixelformat);
4,391✔
565
            if (float_limit != 0U
6,349!
566
                    && float_depth > 0
2,971!
567
                    && frame->width > 0
4,391!
568
                    && frame->height > 0
4,376!
569
                    && (size_t)frame->width
2,931!
570
                           <= SIZE_MAX / (size_t)frame->height) {
4,341✔
571
                float_pixels = (size_t)frame->width
6,567✔
572
                    * (size_t)frame->height;
2,931✔
573
                if (float_pixels <= SIZE_MAX / (size_t)float_depth) {
4,341!
574
                    float_bytes = float_pixels
6,567✔
575
                        * (size_t)float_depth;
2,931✔
576
                }
2,226✔
577
            }
2,226✔
578
            if (float_limit != 0U
6,339!
579
                    && (float_bytes == 0U || float_bytes > float_limit)) {
4,381!
580
                pixelformat = SIXEL_PIXELFORMAT_RGB888;
40✔
581
            } else {
35✔
582
                status = sixel_frame_promote_to_float32(frame);
4,341✔
583
            }
584
        }
2,261✔
585
        if (pixelformat == SIXEL_PIXELFORMAT_RGB888
3,073!
586
                && SIXEL_SUCCEEDED(status)
2,290✔
587
                && frame->pixelformat != SIXEL_PIXELFORMAT_RGB888) {
50!
588
            status = sixel_frame_convert_to_rgb888(frame);
×
589
        }
590
    } else if (pixelformat == SIXEL_PIXELFORMAT_RGB888
3,706!
591
            && working_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
1,421!
592
        status = sixel_frame_convert_to_rgb888(frame);
1,421✔
593
    } else if (!SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)
755!
594
            && !SIXEL_PIXELFORMAT_IS_FLOAT32(working_pixelformat)
×
595
            && (working_pixelformat & SIXEL_FORMATTYPE_PALETTE)) {
×
596
        status = sixel_frame_convert_to_rgb888(frame);
×
597
    }
598

599
    if (SIXEL_FAILED(status)) {
5,935!
600
        return status;
601
    }
602

603
    working_pixelformat = frame->pixelformat;
5,935✔
604
    source_colorspace = frame->colorspace;
5,935✔
605
    target_colorspace = sixel_frame_colorspace_from_pixelformat(pixelformat);
7,865✔
606

607
    if (target_colorspace != source_colorspace) {
5,935✔
608
        /*
609
         * Convert in-place so callers can request alternate transfer
610
         * curves or OKLab buffers without mutating the frame twice.
611
         */
612
        if (frame->width <= 0 || frame->height <= 0) {
4,515!
613
            sixel_helper_set_additional_message(
×
614
                "sixel_frame_set_pixelformat: invalid frame size.");
615
            return SIXEL_BAD_INPUT;
×
616
        }
617

618
        pixel_total = (size_t)frame->width * (size_t)frame->height;
4,515✔
619
        if (pixel_total / (size_t)frame->width != (size_t)frame->height) {
4,515!
620
            sixel_helper_set_additional_message(
×
621
                "sixel_frame_set_pixelformat: buffer overflow risk.");
622
            return SIXEL_BAD_INPUT;
×
623
        }
624

625
        depth = sixel_helper_compute_depth(working_pixelformat);
4,515✔
626
        if (depth <= 0) {
4,515!
627
            sixel_helper_set_additional_message(
×
628
                "sixel_frame_set_pixelformat: invalid pixelformat depth.");
629
            return SIXEL_BAD_INPUT;
×
630
        }
631
        if (pixel_total > SIZE_MAX / (size_t)depth) {
4,515!
632
            sixel_helper_set_additional_message(
×
633
                "sixel_frame_set_pixelformat: buffer size overflow.");
634
            return SIXEL_BAD_INPUT;
×
635
        }
636
        pixel_size = pixel_total * (size_t)depth;
4,515✔
637

638
        pixels = frame->pixels.u8ptr;
4,515✔
639
        if (SIXEL_PIXELFORMAT_IS_FLOAT32(working_pixelformat)) {
4,515!
640
            pixels = (unsigned char *)frame->pixels.f32ptr;
3,707✔
641
        }
1,658✔
642

643
        status = sixel_helper_convert_colorspace(pixels,
6,783✔
644
                                                 pixel_size,
2,268✔
645
                                                 working_pixelformat,
2,268✔
646
                                                 source_colorspace,
2,268✔
647
                                                 target_colorspace);
2,268✔
648
        if (SIXEL_FAILED(status)) {
4,515!
649
            return status;
650
        }
651
    }
2,268✔
652

653
    sixel_frame_apply_pixelformat(frame, pixelformat);
5,935✔
654
    return SIXEL_OK;
5,935✔
655
}
4,407✔
656

657

658
SIXELAPI int
659
sixel_frame_get_colorspace(sixel_frame_t /* in */ *frame)  /* frame object */
47,474✔
660
{
661
    return frame->colorspace;
47,474✔
662
}
663

664

665
/* set colorspace */
666
SIXELAPI void
667
sixel_frame_set_colorspace(
153✔
668
    sixel_frame_t  /* in */ *frame,
669
    int            /* in */ colorspace)
670
{
671
    frame->colorspace = colorspace;
153✔
672
}
153✔
673

674

675
/* get transparent */
676
SIXELAPI int
677
sixel_frame_get_transparent(sixel_frame_t /* in */ *frame)  /* frame object */
573✔
678
{
679
    return frame->transparent;
573✔
680
}
681

682

683
/* set transparent */
684
SIXELAPI void
685
sixel_frame_set_transparent(
×
686
    sixel_frame_t  /* in */ *frame,
687
    int            /* in */ transparent)
688
{
689
    frame->transparent = transparent;
×
690
}
×
691

692

693
/* get transparent */
694
SIXELAPI int
695
sixel_frame_get_multiframe(sixel_frame_t /* in */ *frame)  /* frame object */
6,034✔
696
{
697
    return frame->multiframe;
6,034✔
698
}
699

700

701
/* set multiframe */
702
SIXELAPI void
703
sixel_frame_set_multiframe(
629✔
704
    sixel_frame_t  /* in */ *frame,
705
    int            /* in */ multiframe)
706
{
707
    frame->multiframe = multiframe;
629✔
708
}
629✔
709

710

711
/* get delay */
712
SIXELAPI int
713
sixel_frame_get_delay(sixel_frame_t /* in */ *frame)  /* frame object */
6,017✔
714
{
715
    return frame->delay;
6,017✔
716
}
717

718

719
/* set delay */
720
SIXELAPI void
721
sixel_frame_set_delay(sixel_frame_t /* in */ *frame, int /* in */ delay)
629✔
722
{
723
    frame->delay = delay;
629✔
724
}
629✔
725

726

727
/* get frame no */
728
SIXELAPI int
729
sixel_frame_get_frame_no(sixel_frame_t /* in */ *frame)  /* frame object */
1,197✔
730
{
731
    return frame->frame_no;
1,197✔
732
}
733

734

735
/* set frame index */
736
SIXELAPI void
737
sixel_frame_set_frame_no(
204✔
738
    sixel_frame_t  /* in */ *frame,
739
    int            /* in */ frame_no)
740
{
741
    frame->frame_no = frame_no;
204✔
742
}
204✔
743

744

745
/* increment frame index */
746
SIXELAPI void
747
sixel_frame_increment_frame_no(sixel_frame_t /* in */ *frame)
527✔
748
{
749
    ++frame->frame_no;
527✔
750
}
527✔
751

752

753
/* reset frame index */
754
SIXELAPI void
755
sixel_frame_reset_frame_no(sixel_frame_t /* in */ *frame)
×
756
{
757
    frame->frame_no = 0;
×
758
}
×
759

760

761
/* get loop no */
762
SIXELAPI int
763
sixel_frame_get_loop_no(sixel_frame_t /* in */ *frame)  /* frame object */
881✔
764
{
765
    return frame->loop_count;
881✔
766
}
767

768

769
/* set loop count */
770
SIXELAPI void
771
sixel_frame_set_loop_count(
170✔
772
    sixel_frame_t  /* in */ *frame,
773
    int            /* in */ loop_count)
774
{
775
    frame->loop_count = loop_count;
170✔
776
}
170✔
777

778

779
/* increment loop count */
780
SIXELAPI void
781
sixel_frame_increment_loop_count(sixel_frame_t /* in */ *frame)
102✔
782
{
783
    ++frame->loop_count;
102✔
784
}
102✔
785

786

787
/* get allocator */
788
SIXELAPI sixel_allocator_t *
789
sixel_frame_get_allocator(sixel_frame_t /* in */ *frame)
629✔
790
{
791
    return frame->allocator;
629✔
792
}
793

794
/* strip alpha from RGBA/ARGB/BGRA/ABGR formatted pixbuf */
795
SIXELAPI SIXELSTATUS
796
sixel_frame_strip_alpha(
17,609✔
797
    sixel_frame_t  /* in */ *frame,
798
    unsigned char  /* in */ *bgcolor
799
)
800
{
801
    SIXELSTATUS status = SIXEL_FALSE;
17,609✔
802
    int i;
10,449✔
803
    unsigned char *src;
10,449✔
804
    unsigned char *dst;
10,449✔
805
    unsigned char alpha;
10,449✔
806

807
    sixel_frame_ref(frame);
17,609✔
808

809
    src = dst = frame->pixels.u8ptr;
17,609✔
810

811
    if (bgcolor) {
17,609✔
812
        switch (frame->pixelformat) {
119!
813
        case SIXEL_PIXELFORMAT_ARGB8888:
814
            for (i = 0; i < frame->height * frame->width; i++) {
×
815
                alpha = src[0];
×
816
                *dst++ = (*src++ * alpha + bgcolor[0] * (0xff - alpha)) >> 8;
×
817
                *dst++ = (*src++ * alpha + bgcolor[1] * (0xff - alpha)) >> 8;
×
818
                *dst++ = (*src++ * alpha + bgcolor[2] * (0xff - alpha)) >> 8;
×
819
                src++;
×
820
            }
821
            sixel_frame_apply_pixelformat(
×
822
                frame,
823
                SIXEL_PIXELFORMAT_RGB888);
824
            break;
×
825
        case SIXEL_PIXELFORMAT_RGBA8888:
826
            for (i = 0; i < frame->height * frame->width; i++) {
12,291!
827
                alpha = src[3];
12,288✔
828
                *dst++ = (*src++ * alpha + bgcolor[0] * (0xff - alpha)) >> 8;
12,288✔
829
                *dst++ = (*src++ * alpha + bgcolor[1] * (0xff - alpha)) >> 8;
12,288✔
830
                *dst++ = (*src++ * alpha + bgcolor[2] * (0xff - alpha)) >> 8;
12,288✔
831
                src++;
12,288✔
832
            }
12,288✔
833
            sixel_frame_apply_pixelformat(
3✔
834
                frame,
3✔
835
                SIXEL_PIXELFORMAT_RGB888);
836
            break;
3✔
837
        case SIXEL_PIXELFORMAT_ABGR8888:
838
            for (i = 0; i < frame->height * frame->width; i++) {
×
839
                alpha = src[0];
×
840
                *dst++ = (src[3] * alpha + bgcolor[0] * (0xff - alpha)) >> 8;
×
841
                *dst++ = (src[2] * alpha + bgcolor[1] * (0xff - alpha)) >> 8;
×
842
                *dst++ = (src[1] * alpha + bgcolor[2] * (0xff - alpha)) >> 8;
×
843
                src += 4;
×
844
            }
845
            sixel_frame_apply_pixelformat(
×
846
                frame,
847
                SIXEL_PIXELFORMAT_RGB888);
848
            break;
×
849
        case SIXEL_PIXELFORMAT_BGRA8888:
850
            for (i = 0; i < frame->height * frame->width; i++) {
×
851
                alpha = src[3];
×
852
                *dst++ = (src[2] * alpha + bgcolor[0] * (0xff - alpha)) >> 8;
×
853
                *dst++ = (src[1] * alpha + bgcolor[1] * (0xff - alpha)) >> 8;
×
854
                *dst++ = (src[0] * alpha + bgcolor[2] * (0xff - alpha)) >> 8;
×
855
                src += 4;
×
856
            }
857
            sixel_frame_apply_pixelformat(
×
858
                frame,
859
                SIXEL_PIXELFORMAT_RGB888);
860
            break;
×
861
        default:
12✔
862
            break;
92✔
863
        }
864
    } else {
83✔
865
        switch (frame->pixelformat) {
17,490!
866
        case SIXEL_PIXELFORMAT_ARGB8888:
867
            for (i = 0; i < frame->height * frame->width; i++) {
×
868
                src++;            /* A */
×
869
                *dst++ = *src++;  /* R */
×
870
                *dst++ = *src++;  /* G */
×
871
                *dst++ = *src++;  /* B */
×
872
            }
873
            sixel_frame_apply_pixelformat(
×
874
                frame,
875
                SIXEL_PIXELFORMAT_RGB888);
876
            break;
×
877
        case SIXEL_PIXELFORMAT_RGBA8888:
878
            for (i = 0; i < frame->height * frame->width; i++) {
73,746!
879
                *dst++ = *src++;  /* R */
73,728✔
880
                *dst++ = *src++;  /* G */
73,728✔
881
                *dst++ = *src++;  /* B */
73,728✔
882
                src++;            /* A */
73,728✔
883
            }
73,728✔
884
            sixel_frame_apply_pixelformat(
18✔
885
                frame,
18✔
886
                SIXEL_PIXELFORMAT_RGB888);
887
            break;
18✔
888
        case SIXEL_PIXELFORMAT_ABGR8888:
889
            for (i = 0; i < frame->height * frame->width; i++) {
×
890
                *dst++ = src[3];  /* R */
×
891
                *dst++ = src[2];  /* G */
×
892
                *dst++ = src[1];  /* B */
×
893
                src += 4;
×
894
            }
895
            sixel_frame_apply_pixelformat(
×
896
                frame,
897
                SIXEL_PIXELFORMAT_RGB888);
898
            break;
×
899
        case SIXEL_PIXELFORMAT_BGRA8888:
900
            for (i = 0; i < frame->height * frame->width; i++) {
×
901
                *dst++ = src[2];  /* R */
×
902
                *dst++ = src[1];  /* G */
×
903
                *dst++ = src[0];  /* B */
×
904
                src += 4;
×
905
            }
906
            sixel_frame_apply_pixelformat(
×
907
                frame,
908
                SIXEL_PIXELFORMAT_RGB888);
909
            break;
×
910
        default:
2,880✔
911
            break;
11,680✔
912
        }
913
    }
914

915
    status = SIXEL_OK;
17,609✔
916

917
    sixel_frame_unref(frame);
17,609✔
918

919
    return status;
22,242✔
920
}
4,633✔
921

922

923
static SIXELSTATUS
924
sixel_frame_convert_to_rgb888(sixel_frame_t /*in */ *frame)
1,438✔
925
{
926
    SIXELSTATUS status = SIXEL_FALSE;
1,438✔
927
    unsigned char *normalized_pixels = NULL;
1,438✔
928
    size_t size;
855✔
929
    unsigned char *dst;
855✔
930
    unsigned char *src;
855✔
931
    unsigned char *p;
855✔
932
    unsigned char *raw_pixels;
855✔
933
    unsigned char const *source_pixels;
855✔
934
    float *float_pixels;
855✔
935

936
    sixel_frame_ref(frame);
1,438✔
937

938
    raw_pixels = frame->pixels.u8ptr;
1,438✔
939
    float_pixels = frame->pixels.f32ptr;
1,438✔
940
    source_pixels = raw_pixels;
1,438✔
941

942
    switch (frame->pixelformat) {
1,438!
943
    case SIXEL_PIXELFORMAT_PAL1:
944
    case SIXEL_PIXELFORMAT_PAL2:
945
    case SIXEL_PIXELFORMAT_PAL4:
946
        size = (size_t)(frame->width * frame->height * 4);
18✔
947
        normalized_pixels = (unsigned char *)
18✔
948
            sixel_allocator_malloc(frame->allocator, size);
18✔
949
        if (normalized_pixels == NULL) {
18!
950
            sixel_helper_set_additional_message(
×
951
                "sixel_frame_convert_to_rgb888: "
952
                "sixel_allocator_malloc() failed.");
953
            status = SIXEL_BAD_ALLOCATION;
×
954
            goto end;
×
955
        }
956
        src = normalized_pixels + frame->width * frame->height * 3;
18✔
957
        dst = normalized_pixels;
18✔
958
        status = sixel_helper_normalize_pixelformat(src,
36✔
959
                                                    &frame->pixelformat,
18✔
960
                                                    source_pixels,
18✔
961
                                                    frame->pixelformat,
18✔
962
                                                    frame->width,
18✔
963
                                                    frame->height);
18✔
964
        if (SIXEL_FAILED(status)) {
18!
965
            sixel_allocator_free(frame->allocator, normalized_pixels);
×
966
            goto end;
×
967
        }
968
        for (p = src; dst < src; ++p) {
92,394!
969
            *dst++ = *(frame->palette + *p * 3 + 0);
92,376✔
970
            *dst++ = *(frame->palette + *p * 3 + 1);
92,376✔
971
            *dst++ = *(frame->palette + *p * 3 + 2);
92,376✔
972
        }
92,376✔
973
        sixel_allocator_free(frame->allocator, raw_pixels);
18✔
974
        frame->pixels.u8ptr = normalized_pixels;
18✔
975
        sixel_frame_apply_pixelformat(
18✔
976
            frame,
18✔
977
            SIXEL_PIXELFORMAT_RGB888);
978
        break;
18✔
979
    case SIXEL_PIXELFORMAT_PAL8:
72✔
980
        size = (size_t)(frame->width * frame->height * 3);
118✔
981
        normalized_pixels = (unsigned char *)
94✔
982
            sixel_allocator_malloc(frame->allocator, size);
118✔
983
        if (normalized_pixels == NULL) {
118!
984
            sixel_helper_set_additional_message(
×
985
                "sixel_frame_convert_to_rgb888: "
986
                "sixel_allocator_malloc() failed.");
987
            status = SIXEL_BAD_ALLOCATION;
×
988
            goto end;
×
989
        }
990
        src = raw_pixels;
70✔
991
        dst = normalized_pixels;
70✔
992
        for (; dst != normalized_pixels + size; ++src) {
470,748✔
993
            *dst++ = frame->palette[*src * 3 + 0];
470,630✔
994
            *dst++ = frame->palette[*src * 3 + 1];
470,630✔
995
            *dst++ = frame->palette[*src * 3 + 2];
470,630✔
996
        }
172,568✔
997
        sixel_allocator_free(frame->allocator, raw_pixels);
118✔
998
        frame->pixels.u8ptr = normalized_pixels;
118✔
999
        sixel_frame_apply_pixelformat(
118✔
1000
            frame,
46✔
1001
            SIXEL_PIXELFORMAT_RGB888);
1002
        break;
118✔
1003
    case SIXEL_PIXELFORMAT_RGB888:
3✔
1004
        break;
7✔
1005
    case SIXEL_PIXELFORMAT_G8:
594✔
1006
    case SIXEL_PIXELFORMAT_GA88:
1007
    case SIXEL_PIXELFORMAT_AG88:
1008
    case SIXEL_PIXELFORMAT_RGB555:
1009
    case SIXEL_PIXELFORMAT_RGB565:
1010
    case SIXEL_PIXELFORMAT_BGR555:
1011
    case SIXEL_PIXELFORMAT_BGR565:
1012
    case SIXEL_PIXELFORMAT_RGBA8888:
1013
    case SIXEL_PIXELFORMAT_ARGB8888:
1014
    case SIXEL_PIXELFORMAT_RGBFLOAT32:
1015
    case SIXEL_PIXELFORMAT_LINEARRGBFLOAT32:
1016
    case SIXEL_PIXELFORMAT_OKLABFLOAT32:
1017
    case SIXEL_PIXELFORMAT_CIELABFLOAT32:
1018
    case SIXEL_PIXELFORMAT_DIN99DFLOAT32:
1019
        /* normalize pixelformat */
1020
        size = (size_t)(frame->width * frame->height * 3);
1,285✔
1021
        normalized_pixels = (unsigned char *)
1,087✔
1022
            sixel_allocator_malloc(frame->allocator, size);
1,285✔
1023
        if (normalized_pixels == NULL) {
1,285!
1024
            sixel_helper_set_additional_message(
×
1025
                "sixel_frame_convert_to_rgb888: "
1026
                "sixel_allocator_malloc() failed.");
1027
            status = SIXEL_BAD_ALLOCATION;
×
1028
            goto end;
×
1029
        }
1030
        if (SIXEL_PIXELFORMAT_IS_FLOAT32(frame->pixelformat)) {
1,285!
1031
            source_pixels = (unsigned char const *)float_pixels;
1,204✔
1032
        }
610✔
1033
        status = sixel_helper_normalize_pixelformat(normalized_pixels,
1,976✔
1034
                                                    &frame->pixelformat,
691✔
1035
                                                    source_pixels,
691✔
1036
                                                    frame->pixelformat,
691✔
1037
                                                    frame->width,
691✔
1038
                                                    frame->height);
691✔
1039
        if (SIXEL_FAILED(status)) {
1,285!
1040
            sixel_allocator_free(frame->allocator, normalized_pixels);
×
1041
            goto end;
×
1042
        }
1043
        sixel_allocator_free(frame->allocator, raw_pixels);
1,285✔
1044
        frame->pixels.u8ptr = normalized_pixels;
1,285✔
1045
        break;
1,285✔
1046
    default:
1047
        status = SIXEL_LOGIC_ERROR;
×
1048
        sixel_helper_set_additional_message(
×
1049
            "sixel_frame_convert_to_rgb888: invalid pixelformat.");
1050
        goto end;
×
1051
    }
1052

1053
    status = SIXEL_OK;
988✔
1054

1055
end:
675✔
1056
    sixel_frame_unref(frame);
1,438✔
1057

1058
    return status;
1,843✔
1059
}
405✔
1060

1061
/*
1062
 * Infer colorspace metadata from the pixelformat.  Float formats encode
1063
 * their transfer characteristics directly, while byte-oriented formats
1064
 * default to gamma encoded RGB.
1065
 */
1066
static int
1067
sixel_frame_colorspace_from_pixelformat(int pixelformat)
25,516✔
1068
{
1069
    switch (pixelformat) {
23,956✔
1070
    case SIXEL_PIXELFORMAT_LINEARRGBFLOAT32:
540✔
1071
        return SIXEL_COLORSPACE_LINEAR;
2,176✔
1072
    case SIXEL_PIXELFORMAT_OKLABFLOAT32:
322✔
1073
        return SIXEL_COLORSPACE_OKLAB;
1,314✔
1074
    case SIXEL_PIXELFORMAT_CIELABFLOAT32:
124✔
1075
        return SIXEL_COLORSPACE_CIELAB;
492✔
1076
    case SIXEL_PIXELFORMAT_DIN99DFLOAT32:
114✔
1077
        return SIXEL_COLORSPACE_DIN99D;
450✔
1078
    default:
4,174✔
1079
        return SIXEL_COLORSPACE_GAMMA;
17,594✔
1080
    }
1081
}
16,752✔
1082

1083
static void
1084
sixel_frame_apply_pixelformat(sixel_frame_t *frame, int pixelformat)
10,858✔
1085
{
1086
    frame->pixelformat = pixelformat;
10,858✔
1087
    frame->colorspace = sixel_frame_colorspace_from_pixelformat(pixelformat);
9,412!
1088
}
7,368✔
1089

1090
#if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
1091
# if defined(__GNUC__) && !defined(__PCC__)
1092
#  pragma GCC diagnostic push
1093
#  pragma GCC diagnostic ignored "-Wunused-function"
1094
# endif
1095
#endif
1096

1097
/*
1098
 * Select the float pixelformat that matches the frame's current colorspace
1099
 * so downstream conversions interpret each channel correctly.  OKLab uses
1100
 * a [-0.5, 0.5] range for a/b, while gamma/linear share the 0-1 interval.
1101
 */
1102
static int
1103
sixel_frame_float_pixelformat_for_colorspace(int colorspace)
4,341✔
1104
{
1105
    switch (colorspace) {
4,341!
1106
    case SIXEL_COLORSPACE_LINEAR:
1107
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
1108
    case SIXEL_COLORSPACE_OKLAB:
1109
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
1110
    case SIXEL_COLORSPACE_CIELAB:
1111
        return SIXEL_PIXELFORMAT_CIELABFLOAT32;
1112
    case SIXEL_COLORSPACE_DIN99D:
1113
        return SIXEL_PIXELFORMAT_DIN99DFLOAT32;
1114
    default:
705✔
1115
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
2,931✔
1116
    }
1117
}
2,226✔
1118

1119
static SIXELSTATUS
1120
sixel_frame_promote_to_float32(sixel_frame_t *frame)
4,341✔
1121
{
1122
    float *float_pixels;
2,535✔
1123
    unsigned char *byte_pixels;
2,535✔
1124
    unsigned char const *pixel;
2,535✔
1125
    size_t pixel_total;
2,535✔
1126
    size_t bytes;
2,535✔
1127
    size_t index;
2,535✔
1128
    size_t base;
2,535✔
1129
    int float_pixelformat;
2,535✔
1130
    int step;
2,535✔
1131
    int index_r;
2,535✔
1132
    int index_g;
2,535✔
1133
    int index_b;
2,535✔
1134

1135
    step = 0;
4,341✔
1136
    index_r = 0;
4,341✔
1137
    index_g = 0;
4,341✔
1138
    index_b = 0;
4,341✔
1139

1140
    /*
1141
     * Derive the byte stride and per-channel offsets instead of coercing the
1142
     * frame to RGB888 first.  OKLab buffers keep their signed A/B buckets in
1143
     * the order dictated by pixelformat, so preserving that layout avoids the
1144
     * blue casts reported when we reinterpreted them as gamma RGB.
1145
     */
1146
    switch (frame->pixelformat) {
4,341!
1147
    case SIXEL_PIXELFORMAT_RGB888:
705✔
1148
        step = 3;
2,915✔
1149
        index_r = 0;
2,915✔
1150
        index_g = 1;
2,915✔
1151
        index_b = 2;
2,915✔
1152
        break;
2,915✔
1153
    case SIXEL_PIXELFORMAT_BGR888:
1154
        step = 3;
×
1155
        index_r = 2;
×
1156
        index_g = 1;
×
1157
        index_b = 0;
×
1158
        break;
×
1159
    case SIXEL_PIXELFORMAT_RGBA8888:
1160
        step = 4;
16✔
1161
        index_r = 0;
16✔
1162
        index_g = 1;
16✔
1163
        index_b = 2;
16✔
1164
        break;
16✔
1165
    case SIXEL_PIXELFORMAT_ARGB8888:
1166
        step = 4;
×
1167
        index_r = 1;
×
1168
        index_g = 2;
×
1169
        index_b = 3;
×
1170
        break;
×
1171
    case SIXEL_PIXELFORMAT_BGRA8888:
1172
        step = 4;
×
1173
        index_r = 2;
×
1174
        index_g = 1;
×
1175
        index_b = 0;
×
1176
        break;
×
1177
    case SIXEL_PIXELFORMAT_ABGR8888:
1178
        step = 4;
×
1179
        index_r = 3;
×
1180
        index_g = 2;
×
1181
        index_b = 1;
×
1182
        break;
×
1183
    case SIXEL_PIXELFORMAT_G8:
1184
        step = 1;
×
1185
        index_r = 0;
×
1186
        index_g = 0;
×
1187
        index_b = 0;
×
1188
        break;
×
1189
    case SIXEL_PIXELFORMAT_GA88:
1190
        step = 2;
×
1191
        index_r = 0;
×
1192
        index_g = 0;
×
1193
        index_b = 0;
×
1194
        break;
×
1195
    case SIXEL_PIXELFORMAT_AG88:
1196
        step = 2;
×
1197
        index_r = 1;
×
1198
        index_g = 1;
×
1199
        index_b = 1;
×
1200
        break;
×
1201
    default:
1202
        sixel_helper_set_additional_message(
×
1203
            "sixel_frame_promote_to_float32: unsupported pixelformat.");
1204
        return SIXEL_BAD_INPUT;
×
1205
    }
1206

1207
    if ((size_t)frame->width > SIZE_MAX / (size_t)frame->height) {
4,341!
1208
        sixel_helper_set_additional_message(
×
1209
            "sixel_frame_promote_to_float32: overflow.");
1210
        return SIXEL_BAD_INPUT;
×
1211
    }
1212

1213
    pixel_total = (size_t)frame->width * (size_t)frame->height;
4,341✔
1214
    if (pixel_total > SIZE_MAX / (3U * sizeof(float))) {
4,341!
1215
        sixel_helper_set_additional_message(
×
1216
            "sixel_frame_promote_to_float32: buffer too large.");
1217
        return SIXEL_BAD_INPUT;
×
1218
    }
1219
    bytes = pixel_total * 3U * sizeof(float);
4,341✔
1220
    float_pixels = (float *)sixel_allocator_malloc(frame->allocator, bytes);
4,341✔
1221
    if (float_pixels == NULL) {
4,341!
1222
        sixel_helper_set_additional_message(
×
1223
            "sixel_frame_promote_to_float32: "
1224
            "sixel_allocator_malloc() failed.");
1225
        return SIXEL_BAD_ALLOCATION;
×
1226
    }
1227

1228
    byte_pixels = frame->pixels.u8ptr;
4,341✔
1229
    float_pixelformat =
3,636✔
1230
        sixel_frame_float_pixelformat_for_colorspace(frame->colorspace);
4,341!
1231

1232
    for (index = 0U; index < pixel_total; ++index) {
25,432,994✔
1233
        unsigned char r8;
14,919,438✔
1234
        unsigned char g8;
14,919,438✔
1235
        unsigned char b8;
14,919,438✔
1236

1237
        pixel = byte_pixels + index * (size_t)step;
25,428,653✔
1238
        r8 = *(pixel + (size_t)index_r);
25,428,653✔
1239
        g8 = *(pixel + (size_t)index_g);
25,428,653✔
1240
        b8 = *(pixel + (size_t)index_b);
25,428,653✔
1241

1242
        base = index * 3U;
25,428,653✔
1243
        float_pixels[base + 0U] =
38,436,379✔
1244
            sixel_pixelformat_byte_to_float(float_pixelformat, 0, r8);
25,428,653✔
1245
        float_pixels[base + 1U] =
38,435,967✔
1246
            sixel_pixelformat_byte_to_float(float_pixelformat, 1, g8);
25,427,222✔
1247
        float_pixels[base + 2U] =
25,428,653✔
1248
            sixel_pixelformat_byte_to_float(float_pixelformat, 2, b8);
25,428,241✔
1249
    }
12,419,496✔
1250

1251
    sixel_allocator_free(frame->allocator, byte_pixels);
4,341✔
1252
    sixel_frame_set_pixels_float32(frame, float_pixels);
4,341✔
1253
    sixel_frame_apply_pixelformat(frame, float_pixelformat);
4,341!
1254
    return SIXEL_OK;
4,341✔
1255
}
2,226✔
1256

1257
#if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
1258
# if defined(__GNUC__) && !defined(__PCC__)
1259
#  pragma GCC diagnostic pop
1260
# endif
1261
#endif
1262

1263
/* resize a frame to given size with specified resampling filter */
1264
SIXELAPI SIXELSTATUS
1265
sixel_frame_resize(
17✔
1266
    sixel_frame_t *frame,
1267
    int width,
1268
    int height,
1269
    int method_for_resampling
1270
)
1271
{
1272
    SIXELSTATUS status = SIXEL_FALSE;
17✔
1273
    size_t size;
10✔
1274
    unsigned char *scaled_frame = NULL;
17✔
1275
    size_t unused_pixel_total;
10✔
1276
    int unused_depth_bytes;
10✔
1277

1278
    sixel_frame_ref(frame);
17✔
1279

1280
    size = 0u;
17✔
1281
    unused_pixel_total = 0u;
17✔
1282
    unused_depth_bytes = 0;
17✔
1283

1284
    /* check parameters */
1285
    if (width <= 0) {
17!
1286
        sixel_helper_set_additional_message(
×
1287
            "sixel_frame_resize: an invalid width parameter detected.");
1288
        status = SIXEL_BAD_INPUT;
×
1289
        goto end;
×
1290
    }
1291
    if (height <= 0) {
17!
1292
        sixel_helper_set_additional_message(
×
1293
            "sixel_frame_resize: an invalid width parameter detected.");
1294
        status = SIXEL_BAD_INPUT;
×
1295
        goto end;
×
1296
    }
1297
    if (width > SIXEL_WIDTH_LIMIT) {
17!
1298
        sixel_helper_set_additional_message(
×
1299
            "sixel_frame_resize: given width parameter is too huge.");
1300
        status = SIXEL_BAD_INPUT;
×
1301
        goto end;
×
1302
    }
1303
    if (height > SIXEL_HEIGHT_LIMIT) {
17!
1304
        sixel_helper_set_additional_message(
×
1305
            "sixel_frame_resize: given height parameter is too huge.");
1306
        status = SIXEL_BAD_INPUT;
×
1307
        goto end;
×
1308
    }
1309

1310
    status = sixel_frame_validate_size("sixel_frame_resize",
17✔
1311
                                       width,
8✔
1312
                                       height,
8✔
1313
                                       SIXEL_PIXELFORMAT_RGB888,
1314
                                       &unused_pixel_total,
1315
                                       &size,
1316
                                       &unused_depth_bytes);
1317
    if (SIXEL_FAILED(status)) {
17!
1318
        goto end;
×
1319
    }
1320

1321
    if (width == frame->width && height == frame->height) {
17!
1322
        /* nothing to do */
1323
        goto out;
×
1324
    }
1325

1326
    status = sixel_frame_convert_to_rgb888(frame);
17✔
1327
    if (SIXEL_FAILED(status)) {
17!
1328
        goto end;
×
1329
    }
1330

1331
    scaled_frame = (unsigned char *)
14✔
1332
        sixel_allocator_malloc(frame->allocator, size);
17✔
1333
    if (scaled_frame == NULL) {
17!
1334
        sixel_helper_set_additional_message(
×
1335
            "sixel_frame_resize: sixel_allocator_malloc() failed.");
1336
        status = SIXEL_BAD_ALLOCATION;
×
1337
        goto end;
×
1338
    }
1339

1340
    status = sixel_helper_scale_image(
23✔
1341
        scaled_frame,
8✔
1342
        frame->pixels.u8ptr,
17✔
1343
        frame->width,
8✔
1344
        frame->height,
8✔
1345
        3,
1346
        width,
8✔
1347
        height,
8✔
1348
        method_for_resampling,
8✔
1349
        frame->allocator);
8✔
1350
    if (SIXEL_FAILED(status)) {
17!
1351
        goto end;
×
1352
    }
1353
    sixel_allocator_free(frame->allocator, frame->pixels.u8ptr);
17✔
1354
    frame->pixels.u8ptr = scaled_frame;
17✔
1355
    frame->width = width;
17✔
1356
    frame->height = height;
17✔
1357

1358
out:
3✔
1359
    status = SIXEL_OK;
11✔
1360

1361
end:
9✔
1362
    sixel_frame_unref(frame);
17✔
1363

1364
    return status;
21✔
1365
}
4✔
1366

1367
/*
1368
 * Resize a frame using float buffers. Callers must supply RGB float32 input
1369
 * that already matches the intended resampling basis (linear RGB). The
1370
 * planner is responsible for any required color conversions prior to calling
1371
 * this routine. Unexpected pixelformats are rejected so that order mistakes
1372
 * surface early.
1373
 */
1374
SIXELAPI SIXELSTATUS
1375
sixel_frame_resize_float32(
1,272✔
1376
    sixel_frame_t *frame,
1377
    int width,
1378
    int height,
1379
    int method_for_resampling)
1380
{
1381
    SIXELSTATUS status;
753✔
1382
    size_t pixel_total;
753✔
1383
    size_t size;
753✔
1384
    float *scaled_frame;
753✔
1385
    int depth;
753✔
1386
    int depth_bytes;
753✔
1387
    int target_pixelformat;
753✔
1388

1389
    status = SIXEL_FALSE;
1,272✔
1390
    scaled_frame = NULL;
1,272✔
1391
    pixel_total = 0u;
1,272✔
1392
    size = 0u;
1,272✔
1393
    depth = 0;
1,272✔
1394
    depth_bytes = 0;
1,272✔
1395
    target_pixelformat = frame->pixelformat;
1,272✔
1396

1397
    sixel_frame_ref(frame);
1,272✔
1398

1399
    if (width <= 0) {
1,272!
1400
        sixel_helper_set_additional_message(
×
1401
            "sixel_frame_resize_float32: "
1402
            "an invalid width parameter detected.");
1403
        status = SIXEL_BAD_INPUT;
×
1404
        goto end;
×
1405
    }
1406
    if (height <= 0) {
1,272!
1407
        sixel_helper_set_additional_message(
×
1408
            "sixel_frame_resize_float32: "
1409
            "an invalid width parameter detected.");
1410
        status = SIXEL_BAD_INPUT;
×
1411
        goto end;
×
1412
    }
1413
    if (width > SIXEL_WIDTH_LIMIT) {
1,272!
1414
        sixel_helper_set_additional_message(
×
1415
            "sixel_frame_resize_float32: "
1416
            "given width parameter is too huge.");
1417
        status = SIXEL_BAD_INPUT;
×
1418
        goto end;
×
1419
    }
1420
    if (height > SIXEL_HEIGHT_LIMIT) {
1,272!
1421
        sixel_helper_set_additional_message(
×
1422
            "sixel_frame_resize_float32: "
1423
            "given height parameter is too huge.");
1424
        status = SIXEL_BAD_INPUT;
×
1425
        goto end;
×
1426
    }
1427

1428
    if (target_pixelformat != SIXEL_PIXELFORMAT_RGBFLOAT32
1,272!
1429
        && target_pixelformat != SIXEL_PIXELFORMAT_LINEARRGBFLOAT32) {
1,272!
1430
        sixel_helper_set_additional_message(
×
1431
            "sixel_frame_resize_float32: "
1432
            "resize expects RGB float32 input.");
1433
        status = SIXEL_BAD_ARGUMENT;
×
1434
        goto end;
×
1435
    }
1436

1437
    if (width == frame->width && height == frame->height) {
1,272!
1438
        goto out;
63✔
1439
    }
1440

1441
    status = sixel_frame_validate_size("sixel_frame_resize_float32",
1,209✔
1442
                                       width,
579✔
1443
                                       height,
579✔
1444
                                       frame->pixelformat,
579✔
1445
                                       &pixel_total,
1446
                                       &size,
1447
                                       &depth_bytes);
1448
    if (SIXEL_FAILED(status)) {
1,209!
1449
        goto end;
×
1450
    }
1451

1452
    /*
1453
     * sixel_frame_validate_size() returns bytes per pixel. Convert the value
1454
     * to channels for validation and reuse the byte count for buffer sizing
1455
     * to avoid overflow on float formats.
1456
     */
1457
    if (depth_bytes % (int)sizeof(float) != 0) {
1,209!
1458
        sixel_helper_set_additional_message(
×
1459
            "sixel_frame_resize_float32: "
1460
            "pixelformat depth is not float-aligned.");
1461
        status = SIXEL_BAD_ARGUMENT;
×
1462
        goto end;
×
1463
    }
1464
    depth = depth_bytes / (int)sizeof(float);
1,209✔
1465

1466
    if (depth != 3) {
1,209!
1467
        sixel_helper_set_additional_message(
×
1468
            "sixel_frame_resize_float32: "
1469
            "unsupported channel count.");
1470
        status = SIXEL_BAD_ARGUMENT;
×
1471
        goto end;
×
1472
    }
1473

1474
    scaled_frame = (float *)sixel_allocator_malloc(frame->allocator, size);
1,209✔
1475
    if (scaled_frame == NULL) {
1,209!
1476
        sixel_helper_set_additional_message(
×
1477
            "sixel_frame_resize_float32: "
1478
            "sixel_allocator_malloc() failed.");
1479
        status = SIXEL_BAD_ALLOCATION;
×
1480
        goto end;
×
1481
    }
1482

1483
    status = sixel_helper_scale_image_float32(
1,629✔
1484
        scaled_frame,
579✔
1485
        frame->pixels.f32ptr,
1,209✔
1486
        frame->width,
579✔
1487
        frame->height,
579✔
1488
        frame->pixelformat,
579✔
1489
        width,
579✔
1490
        height,
579✔
1491
        method_for_resampling,
579✔
1492
        frame->allocator);
579✔
1493
    if (SIXEL_FAILED(status)) {
1,209!
1494
        goto end;
×
1495
    }
1496

1497
    sixel_allocator_free(frame->allocator, frame->pixels.f32ptr);
1,209✔
1498
    frame->pixels.f32ptr = scaled_frame;
1,209✔
1499
    frame->width = width;
1,209✔
1500
    frame->height = height;
1,209✔
1501

1502
out:
210✔
1503
    status = SIXEL_OK;
852✔
1504

1505
end:
210✔
1506
    if (SIXEL_FAILED(status) && scaled_frame != NULL) {
852!
1507
        sixel_allocator_free(frame->allocator, scaled_frame);
×
1508
    }
1509
    sixel_frame_unref(frame);
1,272✔
1510

1511
    return status;
1,605✔
1512
}
333✔
1513

1514

1515
static SIXELSTATUS
1516
clip(unsigned char *pixels,
124✔
1517
     int sx,
1518
     int sy,
1519
     int pixelformat,
1520
     int cx,
1521
     int cy,
1522
     int cw,
1523
     int ch)
1524
{
1525
    SIXELSTATUS status = SIXEL_FALSE;
124✔
1526
    int y;
76✔
1527
    unsigned char *src;
76✔
1528
    unsigned char *dst;
76✔
1529
    int depth;
76✔
1530
    char message[256];
76✔
1531
    int nwrite;
76✔
1532

1533
    /* unused */ (void) sx;
109✔
1534
    /* unused */ (void) sy;
109✔
1535
    /* unused */ (void) cx;
109✔
1536

1537
    depth = sixel_helper_compute_depth(pixelformat);
124✔
1538
    if (depth < 0) {
124!
1539
        status = SIXEL_BAD_ARGUMENT;
×
1540
        /*
1541
         * The compat helper keeps the format string bounded for MSVC while
1542
         * reporting the offending pixelformat to the caller.
1543
         */
1544
        nwrite = sixel_compat_snprintf(
×
1545
            message,
1546
            sizeof(message),
1547
            "clip: invalid pixelformat(%08x) is specified.",
1548
            pixelformat);
1549
        if (nwrite > 0) {
×
1550
            sixel_helper_set_additional_message(message);
×
1551
        }
1552
        goto end;
×
1553
    }
1554

1555
    dst = pixels;
124✔
1556
    src = pixels + cy * sx * depth + cx * depth;
124✔
1557
    for (y = 0; y < ch; y++) {
10,659✔
1558
        memmove(dst, src, (size_t)(cw * depth));
10,535✔
1559
        dst += (cw * depth);
10,535✔
1560
        src += (sx * depth);
10,535✔
1561
    }
5,288✔
1562

1563
    status = SIXEL_OK;
94✔
1564

1565
end:
45✔
1566
    return status;
170✔
1567
}
46✔
1568

1569

1570
/* clip frame */
1571
SIXELAPI SIXELSTATUS
1572
sixel_frame_clip(
124✔
1573
    sixel_frame_t *frame,
1574
    int x,
1575
    int y,
1576
    int width,
1577
    int height
1578
)
1579
{
1580
    SIXELSTATUS status = SIXEL_FALSE;
124✔
1581
    unsigned char *normalized_pixels;
76✔
1582
    unsigned char *raw_pixels;
76✔
1583

1584
    sixel_frame_ref(frame);
124✔
1585

1586
    raw_pixels = frame->pixels.u8ptr;
124✔
1587

1588
    /* check parameters */
1589
    if (width <= 0) {
124!
1590
        sixel_helper_set_additional_message(
×
1591
            "sixel_frame_clip: an invalid width parameter detected.");
1592
        status = SIXEL_BAD_INPUT;
×
1593
        goto end;
×
1594
    }
1595
    if (height <= 0) {
124!
1596
        sixel_helper_set_additional_message(
×
1597
            "sixel_frame_clip: an invalid width parameter detected.");
1598
        status = SIXEL_BAD_INPUT;
×
1599
        goto end;
×
1600
    }
1601
    if (width > SIXEL_WIDTH_LIMIT) {
124!
1602
        sixel_helper_set_additional_message(
×
1603
            "sixel_frame_clip: given width parameter is too huge.");
1604
        status = SIXEL_BAD_INPUT;
×
1605
        goto end;
×
1606
    }
1607
    if (height > SIXEL_HEIGHT_LIMIT) {
124!
1608
        sixel_helper_set_additional_message(
×
1609
            "sixel_frame_clip: given height parameter is too huge.");
1610
        status = SIXEL_BAD_INPUT;
×
1611
        goto end;
×
1612
    }
1613

1614
    switch (frame->pixelformat) {
124!
1615
    case SIXEL_PIXELFORMAT_PAL1:
1616
    case SIXEL_PIXELFORMAT_PAL2:
1617
    case SIXEL_PIXELFORMAT_PAL4:
1618
    case SIXEL_PIXELFORMAT_G1:
1619
    case SIXEL_PIXELFORMAT_G2:
1620
    case SIXEL_PIXELFORMAT_G4:
1621
        normalized_pixels = (unsigned char *)
9✔
1622
            sixel_allocator_malloc(frame->allocator,
18✔
1623
                                   (size_t)(frame->width * frame->height));
9✔
1624
        status = sixel_helper_normalize_pixelformat(normalized_pixels,
18✔
1625
                                                    &frame->pixelformat,
9✔
1626
                                                    raw_pixels,
9✔
1627
                                                    frame->pixelformat,
9✔
1628
                                                    frame->width,
9✔
1629
                                                    frame->height);
9✔
1630
        if (SIXEL_FAILED(status)) {
9!
1631
            sixel_allocator_free(frame->allocator, normalized_pixels);
×
1632
            goto end;
×
1633
        }
1634
        sixel_allocator_free(frame->allocator, raw_pixels);
9✔
1635
        frame->pixels.u8ptr = normalized_pixels;
9✔
1636
        raw_pixels = normalized_pixels;
9✔
1637
        break;
9✔
1638
    default:
15✔
1639
        break;
85✔
1640
    }
1641

1642
    status = clip(raw_pixels,
203✔
1643
                  frame->width,
79✔
1644
                  frame->height,
79✔
1645
                  frame->pixelformat,
79✔
1646
                  x,
79✔
1647
                  y,
79✔
1648
                  width,
79✔
1649
                  height);
79✔
1650
    if (SIXEL_FAILED(status)) {
124!
1651
        goto end;
×
1652
    }
1653
    frame->width = width;
124✔
1654
    frame->height = height;
124✔
1655

1656
    status = SIXEL_OK;
124✔
1657

1658
end:
45✔
1659
    sixel_frame_unref(frame);
124✔
1660

1661
    return status;
170✔
1662
}
46✔
1663

1664

1665
/* Ensure legacy frame constructor and refcounting work. */
1666

1667
/* emacs Local Variables:      */
1668
/* emacs mode: c               */
1669
/* emacs tab-width: 4          */
1670
/* emacs indent-tabs-mode: nil */
1671
/* emacs c-basic-offset: 4     */
1672
/* emacs End:                  */
1673
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1674
/* 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