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

saitoha / libsixel / 19918707358

04 Dec 2025 05:12AM UTC coverage: 38.402% (-4.0%) from 42.395%
19918707358

push

github

saitoha
tests: fix meson msys dll lookup

9738 of 38220 branches covered (25.48%)

12841 of 33438 relevant lines covered (38.4%)

782420.02 hits per line

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

28.49
/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
#include "config.h"
26

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

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

42
#include "frame.h"
43
#include "pixelformat.h"
44
#include "compat_stub.h"
45
#include "scale.h"
46

47
#if !defined(HAVE_MEMMOVE)
48
# define memmove(d, s, n) (bcopy ((s), (d), (n)))
49
#endif
50

51
static SIXELSTATUS
52
sixel_frame_convert_to_rgb888(sixel_frame_t /*in */ *frame);
53
static SIXELSTATUS
54
sixel_frame_promote_to_float32(sixel_frame_t *frame);
55
static int
56
sixel_frame_colorspace_from_pixelformat(int pixelformat);
57
static void
58
sixel_frame_apply_pixelformat(sixel_frame_t *frame, int pixelformat);
59

60
/* constructor of frame object */
61
SIXELAPI SIXELSTATUS
62
sixel_frame_new(
450✔
63
    sixel_frame_t       /* out */ **ppframe,    /* frame object to be created */
64
    sixel_allocator_t   /* in */  *allocator)   /* allocator, null if you use
65
                                                   default allocator */
66
{
67
    SIXELSTATUS status = SIXEL_FALSE;
450✔
68

69
    if (allocator == NULL) {
450!
70
        status = sixel_allocator_new(&allocator, malloc, calloc, realloc, free);
×
71
        if (SIXEL_FAILED(status)) {
×
72
            goto end;
×
73
        }
74
    }
75

76
    *ppframe = (sixel_frame_t *)sixel_allocator_malloc(
450✔
77
        allocator,
78
        sizeof(sixel_frame_t));
79
    if (*ppframe == NULL) {
450!
80
        sixel_helper_set_additional_message(
×
81
            "sixel_frame_resize: sixel_allocator_malloc() failed.");
82
        status = SIXEL_BAD_ALLOCATION;
×
83
        goto end;
×
84
    }
85

86
    (*ppframe)->ref = 1;
450✔
87
    (*ppframe)->pixels.u8ptr = NULL;
450✔
88
    (*ppframe)->palette = NULL;
450✔
89
    (*ppframe)->width = 0;
450✔
90
    (*ppframe)->height = 0;
450✔
91
    (*ppframe)->ncolors = (-1);
450✔
92
    (*ppframe)->pixelformat = SIXEL_PIXELFORMAT_RGB888;
450✔
93
    (*ppframe)->delay = 0;
450✔
94
    (*ppframe)->frame_no = 0;
450✔
95
    (*ppframe)->loop_count = 0;
450✔
96
    (*ppframe)->multiframe = 0;
450✔
97
    (*ppframe)->transparent = (-1);
450✔
98
    (*ppframe)->allocator = allocator;
450✔
99

100
    sixel_allocator_ref(allocator);
450✔
101

102
    /* Normalize between byte and float pipelines when buffers are present. */
103
    status = SIXEL_OK;
450✔
104

105
end:
450✔
106
    return status;
450✔
107
}
108

109

110
SIXELAPI /* deprecated */ sixel_frame_t *
111
sixel_frame_create(void)
×
112
{
113
    SIXELSTATUS status = SIXEL_FALSE;
×
114
    sixel_frame_t *frame = NULL;
×
115

116
    status = sixel_frame_new(&frame, NULL);
×
117
    if (SIXEL_FAILED(status)) {
×
118
        goto end;
119
    }
120

121
end:
×
122
    return frame;
×
123
}
124

125

126
static void
127
sixel_frame_destroy(sixel_frame_t /* in */ *frame)
450✔
128
{
129
    sixel_allocator_t *allocator = NULL;
450✔
130

131
    if (frame) {
450!
132
        allocator = frame->allocator;
450✔
133
        sixel_allocator_free(allocator, frame->pixels.u8ptr);
450✔
134
        sixel_allocator_free(allocator, frame->palette);
450✔
135
        sixel_allocator_free(allocator, frame);
450✔
136
        sixel_allocator_unref(allocator);
450✔
137
    }
138
}
450✔
139

140

141
/* increase reference count of frame object (thread-unsafe) */
142
SIXELAPI void
143
sixel_frame_ref(sixel_frame_t *frame)
633✔
144
{
145
    /* TODO: be thread safe */
146
    ++frame->ref;
633✔
147
}
424✔
148

149

150
/* decrease reference count of frame object (thread-unsafe) */
151
SIXELAPI void
152
sixel_frame_unref(sixel_frame_t *frame)
1,158✔
153
{
154
    /* TODO: be thread safe */
155
    if (frame != NULL && --frame->ref == 0) {
1,158✔
156
        sixel_frame_destroy(frame);
450✔
157
    }
158
}
1,158✔
159

160

161
/*
162
 * Shared initializer for both byte and float32 buffers.  Keeping the
163
 * validation and field updates in one place avoids diverging logic
164
 * between the two entry points while making the stored pointer type
165
 * explicit.
166
 */
167
static SIXELSTATUS
168
sixel_frame_init_common(
×
169
    sixel_frame_t   *frame,
170
    void            *pixels,
171
    int              width,
172
    int              height,
173
    int              pixelformat,
174
    unsigned char   *palette,
175
    int              ncolors,
176
    int              is_float)
177
{
178
    SIXELSTATUS status = SIXEL_FALSE;
×
179

180
    sixel_frame_ref(frame);
×
181

182
    /* check parameters */
183
    if (width <= 0) {
×
184
        sixel_helper_set_additional_message(
×
185
            "sixel_frame_init: an invalid width parameter detected.");
186
        status = SIXEL_BAD_INPUT;
×
187
        goto end;
×
188
    }
189
    if (height <= 0) {
×
190
        sixel_helper_set_additional_message(
×
191
            "sixel_frame_init: an invalid width parameter detected.");
192
        status = SIXEL_BAD_INPUT;
×
193
        goto end;
×
194
    }
195
    if (width > SIXEL_WIDTH_LIMIT) {
×
196
        sixel_helper_set_additional_message(
×
197
            "sixel_frame_init: given width parameter is too huge.");
198
        status = SIXEL_BAD_INPUT;
×
199
        goto end;
×
200
    }
201
    if (height > SIXEL_HEIGHT_LIMIT) {
×
202
        sixel_helper_set_additional_message(
×
203
            "sixel_frame_init: given height parameter is too huge.");
204
        status = SIXEL_BAD_INPUT;
×
205
        goto end;
×
206
    }
207
    if (is_float != 0 && !SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)) {
×
208
        sixel_helper_set_additional_message(
×
209
            "sixel_frame_init: pixelformat must be float32 when "
210
            "supplying float pixels.");
211
        status = SIXEL_BAD_INPUT;
×
212
        goto end;
×
213
    }
214

215
    if (is_float != 0) {
×
216
        frame->pixels.f32ptr = (float *)pixels;
×
217
    } else {
218
        frame->pixels.u8ptr = (unsigned char *)pixels;
×
219
    }
220
    frame->width = width;
×
221
    frame->height = height;
×
222
    sixel_frame_apply_pixelformat(frame, pixelformat);
×
223
    frame->palette = palette;
×
224
    frame->ncolors = ncolors;
×
225
    status = SIXEL_OK;
×
226

227
end:
×
228
    sixel_frame_unref(frame);
×
229

230
    return status;
×
231
}
232

233
/* initialize frame object with a pixel buffer */
234
SIXELAPI SIXELSTATUS
235
sixel_frame_init(
×
236
    sixel_frame_t   /* in */ *frame,
237
    unsigned char   /* in */ *pixels,
238
    int             /* in */ width,
239
    int             /* in */ height,
240
    int             /* in */ pixelformat,
241
    unsigned char   /* in */ *palette,
242
    int             /* in */ ncolors)
243
{
244
    return sixel_frame_init_common(frame,
×
245
                                   pixels,
246
                                   width,
247
                                   height,
248
                                   pixelformat,
249
                                   palette,
250
                                   ncolors,
251
                                   0);
252
}
253

254
/* initialize frame object with a float32 pixel buffer */
255
SIXELAPI SIXELSTATUS
256
sixel_frame_init_float32(
×
257
    sixel_frame_t   /* in */ *frame,
258
    float           /* in */ *pixels,
259
    int             /* in */ width,
260
    int             /* in */ height,
261
    int             /* in */ pixelformat,
262
    unsigned char   /* in */ *palette,
263
    int             /* in */ ncolors)
264
{
265
    return sixel_frame_init_common(frame,
×
266
                                   pixels,
267
                                   width,
268
                                   height,
269
                                   pixelformat,
270
                                   palette,
271
                                   ncolors,
272
                                   1);
273
}
274

275

276
/* get pixels */
277
SIXELAPI unsigned char *
278
sixel_frame_get_pixels(sixel_frame_t /* in */ *frame)  /* frame object */
855✔
279
{
280
    return frame->pixels.u8ptr;
855✔
281
}
282

283

284
SIXELAPI float *
285
sixel_frame_get_pixels_float32(sixel_frame_t /* in */ *frame)
×
286
{
287
    return frame->pixels.f32ptr;
×
288
}
289

290

291
/* set pixels */
292
SIXELAPI void
293
sixel_frame_set_pixels(
546✔
294
    sixel_frame_t  /* in */ *frame,
295
    unsigned char  /* in */ *pixels)
296
{
297
    frame->pixels.u8ptr = pixels;
546✔
298
}
546✔
299

300

301
SIXELAPI void
302
sixel_frame_set_pixels_float32(
×
303
    sixel_frame_t  /* in */ *frame,
304
    float          /* in */ *pixels)
305
{
306
    frame->pixels.f32ptr = pixels;
×
307
}
×
308

309

310
/* get palette */
311
SIXELAPI unsigned char *
312
sixel_frame_get_palette(sixel_frame_t /* in */ *frame)  /* frame object */
576✔
313
{
314
    return frame->palette;
576✔
315
}
316

317

318
/* set palette */
319
SIXELAPI void
320
sixel_frame_set_palette(
21✔
321
    sixel_frame_t  /* in */ *frame,
322
    unsigned char  /* in */ *palette)
323
{
324
    frame->palette = palette;
21✔
325
}
21✔
326

327

328
/* get width */
329
SIXELAPI int
330
sixel_frame_get_width(sixel_frame_t /* in */ *frame)  /* frame object */
5,022✔
331
{
332
    return frame->width;
5,022✔
333
}
334

335

336
/* set width */
337
SIXELAPI void
338
sixel_frame_set_width(sixel_frame_t /* in */ *frame, int /* in */ width)
120✔
339
{
340
    frame->width = width;
120✔
341
}
120✔
342

343

344
/* get height */
345
SIXELAPI int
346
sixel_frame_get_height(sixel_frame_t /* in */ *frame)  /* frame object */
5,130✔
347
{
348
    return frame->height;
5,130✔
349
}
350

351

352
/* set height */
353
SIXELAPI void
354
sixel_frame_set_height(sixel_frame_t /* in */ *frame, int /* in */ height)
120✔
355
{
356
    frame->height = height;
120✔
357
}
120✔
358

359

360
/* get ncolors */
361
SIXELAPI int
362
sixel_frame_get_ncolors(sixel_frame_t /* in */ *frame)  /* frame object */
348✔
363
{
364
    return frame->ncolors;
348✔
365
}
366

367

368
/* set ncolors */
369
SIXELAPI void
370
sixel_frame_set_ncolors(
120✔
371
    sixel_frame_t  /* in */ *frame,
372
    int            /* in */ ncolors)
373
{
374
    frame->ncolors = ncolors;
120✔
375
}
120✔
376

377

378
/* get pixelformat */
379
SIXELAPI int
380
sixel_frame_get_pixelformat(sixel_frame_t /* in */ *frame)  /* frame object */
1,845✔
381
{
382
    return frame->pixelformat;
1,845✔
383
}
384

385

386
/* set pixelformat */
387
SIXELAPI SIXELSTATUS
388
sixel_frame_set_pixelformat(
141✔
389
    sixel_frame_t  /* in */ *frame,
390
    int            /* in */ pixelformat)
391
{
392
    SIXELSTATUS status;
141✔
393
    int source_colorspace;
141✔
394
    int target_colorspace;
141✔
395
    int working_pixelformat;
141✔
396
    int depth;
141✔
397
    size_t pixel_total;
141✔
398
    size_t pixel_size;
141✔
399
    unsigned char *pixels;
141✔
400

401
    if (frame == NULL) {
141!
402
        sixel_helper_set_additional_message(
×
403
            "sixel_frame_set_pixelformat: frame is null.");
404
        return SIXEL_BAD_ARGUMENT;
×
405
    }
406
    if (pixelformat == frame->pixelformat) {
141✔
407
        return SIXEL_OK;
408
    }
409
    if (frame->pixels.u8ptr == NULL) {
15✔
410
        sixel_frame_apply_pixelformat(frame, pixelformat);
24✔
411
        return SIXEL_OK;
12✔
412
    }
413

414
    status = SIXEL_OK;
3✔
415
    working_pixelformat = frame->pixelformat;
3✔
416
    source_colorspace = frame->colorspace;
3✔
417

418
    /*
419
     * Palette and byte-form buffers need to be normalized before any
420
     * colorspace adjustments so that channel ordering matches the
421
     * converter's expectations.
422
     */
423
    if (pixelformat == SIXEL_PIXELFORMAT_RGBFLOAT32
3!
424
            || pixelformat == SIXEL_PIXELFORMAT_LINEARRGBFLOAT32
1!
425
            || pixelformat == SIXEL_PIXELFORMAT_OKLABFLOAT32
1!
426
            || pixelformat == SIXEL_PIXELFORMAT_CIELABFLOAT32
1!
427
            || pixelformat == SIXEL_PIXELFORMAT_DIN99DFLOAT32) {
3!
428
        if (working_pixelformat & SIXEL_FORMATTYPE_PALETTE) {
×
429
            status = sixel_frame_convert_to_rgb888(frame);
×
430
        }
431
        if (SIXEL_SUCCEEDED(status)
×
432
                && !SIXEL_PIXELFORMAT_IS_FLOAT32(frame->pixelformat)) {
×
433
            status = sixel_frame_promote_to_float32(frame);
×
434
        }
435
    } else if (pixelformat == SIXEL_PIXELFORMAT_RGB888
3!
436
            && working_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
3!
437
        status = sixel_frame_convert_to_rgb888(frame);
3✔
438
    } else if (!SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)
×
439
            && !SIXEL_PIXELFORMAT_IS_FLOAT32(working_pixelformat)
×
440
            && (working_pixelformat & SIXEL_FORMATTYPE_PALETTE)) {
×
441
        status = sixel_frame_convert_to_rgb888(frame);
×
442
    }
443

444
    if (SIXEL_FAILED(status)) {
3!
445
        return status;
×
446
    }
447

448
    working_pixelformat = frame->pixelformat;
3✔
449
    source_colorspace = frame->colorspace;
3✔
450
    target_colorspace = sixel_frame_colorspace_from_pixelformat(pixelformat);
6✔
451

452
    if (target_colorspace != source_colorspace) {
3!
453
        /*
454
         * Convert in-place so callers can request alternate transfer
455
         * curves or OKLab buffers without mutating the frame twice.
456
         */
457
        if (frame->width <= 0 || frame->height <= 0) {
×
458
            sixel_helper_set_additional_message(
×
459
                "sixel_frame_set_pixelformat: invalid frame size.");
460
            return SIXEL_BAD_INPUT;
×
461
        }
462

463
        pixel_total = (size_t)frame->width * (size_t)frame->height;
×
464
        if (pixel_total / (size_t)frame->width != (size_t)frame->height) {
×
465
            sixel_helper_set_additional_message(
×
466
                "sixel_frame_set_pixelformat: buffer overflow risk.");
467
            return SIXEL_BAD_INPUT;
×
468
        }
469

470
        depth = sixel_helper_compute_depth(working_pixelformat);
×
471
        if (depth <= 0) {
×
472
            sixel_helper_set_additional_message(
×
473
                "sixel_frame_set_pixelformat: invalid pixelformat depth.");
474
            return SIXEL_BAD_INPUT;
×
475
        }
476
        if (pixel_total > SIZE_MAX / (size_t)depth) {
×
477
            sixel_helper_set_additional_message(
×
478
                "sixel_frame_set_pixelformat: buffer size overflow.");
479
            return SIXEL_BAD_INPUT;
×
480
        }
481
        pixel_size = pixel_total * (size_t)depth;
×
482

483
        pixels = frame->pixels.u8ptr;
×
484
        if (SIXEL_PIXELFORMAT_IS_FLOAT32(working_pixelformat)) {
×
485
            pixels = (unsigned char *)frame->pixels.f32ptr;
×
486
        }
487

488
        status = sixel_helper_convert_colorspace(pixels,
×
489
                                                 pixel_size,
490
                                                 working_pixelformat,
491
                                                 source_colorspace,
492
                                                 target_colorspace);
493
        if (SIXEL_FAILED(status)) {
×
494
            return status;
495
        }
496
    }
497

498
    sixel_frame_apply_pixelformat(frame, pixelformat);
3✔
499
    return SIXEL_OK;
3✔
500
}
501

502

503
SIXELAPI int
504
sixel_frame_get_colorspace(sixel_frame_t /* in */ *frame)  /* frame object */
1,560✔
505
{
506
    return frame->colorspace;
1,560✔
507
}
508

509

510
/* set colorspace */
511
SIXELAPI void
512
sixel_frame_set_colorspace(
×
513
    sixel_frame_t  /* in */ *frame,
514
    int            /* in */ colorspace)
515
{
516
    frame->colorspace = colorspace;
×
517
}
×
518

519

520
/* get transparent */
521
SIXELAPI int
522
sixel_frame_get_transparent(sixel_frame_t /* in */ *frame)  /* frame object */
228✔
523
{
524
    return frame->transparent;
228✔
525
}
526

527

528
/* set transparent */
529
SIXELAPI void
530
sixel_frame_set_transparent(
×
531
    sixel_frame_t  /* in */ *frame,
532
    int            /* in */ transparent)
533
{
534
    frame->transparent = transparent;
×
535
}
×
536

537

538
/* get transparent */
539
SIXELAPI int
540
sixel_frame_get_multiframe(sixel_frame_t /* in */ *frame)  /* frame object */
519✔
541
{
542
    return frame->multiframe;
519✔
543
}
544

545

546
/* set multiframe */
547
SIXELAPI void
548
sixel_frame_set_multiframe(
120✔
549
    sixel_frame_t  /* in */ *frame,
550
    int            /* in */ multiframe)
551
{
552
    frame->multiframe = multiframe;
120✔
553
}
120✔
554

555

556
/* get delay */
557
SIXELAPI int
558
sixel_frame_get_delay(sixel_frame_t /* in */ *frame)  /* frame object */
516✔
559
{
560
    return frame->delay;
516✔
561
}
562

563

564
/* set delay */
565
SIXELAPI void
566
sixel_frame_set_delay(sixel_frame_t /* in */ *frame, int /* in */ delay)
120✔
567
{
568
    frame->delay = delay;
120✔
569
}
120✔
570

571

572
/* get frame no */
573
SIXELAPI int
574
sixel_frame_get_frame_no(sixel_frame_t /* in */ *frame)  /* frame object */
222✔
575
{
576
    return frame->frame_no;
222✔
577
}
578

579

580
/* set frame index */
581
SIXELAPI void
582
sixel_frame_set_frame_no(
24✔
583
    sixel_frame_t  /* in */ *frame,
584
    int            /* in */ frame_no)
585
{
586
    frame->frame_no = frame_no;
24✔
587
}
24✔
588

589

590
/* increment frame index */
591
SIXELAPI void
592
sixel_frame_increment_frame_no(sixel_frame_t /* in */ *frame)
111✔
593
{
594
    ++frame->frame_no;
111✔
595
}
111✔
596

597

598
/* reset frame index */
599
SIXELAPI void
600
sixel_frame_reset_frame_no(sixel_frame_t /* in */ *frame)
×
601
{
602
    frame->frame_no = 0;
×
603
}
×
604

605

606
/* get loop no */
607
SIXELAPI int
608
sixel_frame_get_loop_no(sixel_frame_t /* in */ *frame)  /* frame object */
198✔
609
{
610
    return frame->loop_count;
198✔
611
}
612

613

614
/* set loop count */
615
SIXELAPI void
616
sixel_frame_set_loop_count(
21✔
617
    sixel_frame_t  /* in */ *frame,
618
    int            /* in */ loop_count)
619
{
620
    frame->loop_count = loop_count;
21✔
621
}
21✔
622

623

624
/* increment loop count */
625
SIXELAPI void
626
sixel_frame_increment_loop_count(sixel_frame_t /* in */ *frame)
15✔
627
{
628
    ++frame->loop_count;
15✔
629
}
15✔
630

631

632
/* get allocator */
633
SIXELAPI sixel_allocator_t *
634
sixel_frame_get_allocator(sixel_frame_t /* in */ *frame)
120✔
635
{
636
    return frame->allocator;
120✔
637
}
638

639
/* strip alpha from RGBA/ARGB/BGRA/ABGR formatted pixbuf */
640
SIXELAPI SIXELSTATUS
641
sixel_frame_strip_alpha(
426✔
642
    sixel_frame_t  /* in */ *frame,
643
    unsigned char  /* in */ *bgcolor
644
)
645
{
646
    SIXELSTATUS status = SIXEL_FALSE;
426✔
647
    int i;
426✔
648
    unsigned char *src;
426✔
649
    unsigned char *dst;
426✔
650
    unsigned char alpha;
426✔
651

652
    sixel_frame_ref(frame);
426✔
653

654
    src = dst = frame->pixels.u8ptr;
426✔
655

656
    if (bgcolor) {
426✔
657
        switch (frame->pixelformat) {
15!
658
        case SIXEL_PIXELFORMAT_ARGB8888:
659
            for (i = 0; i < frame->height * frame->width; i++) {
×
660
                alpha = src[0];
×
661
                *dst++ = (*src++ * alpha + bgcolor[0] * (0xff - alpha)) >> 8;
×
662
                *dst++ = (*src++ * alpha + bgcolor[1] * (0xff - alpha)) >> 8;
×
663
                *dst++ = (*src++ * alpha + bgcolor[2] * (0xff - alpha)) >> 8;
×
664
                src++;
×
665
            }
666
            sixel_frame_apply_pixelformat(
×
667
                frame,
668
                SIXEL_PIXELFORMAT_RGB888);
669
            break;
×
670
        case SIXEL_PIXELFORMAT_RGBA8888:
671
            for (i = 0; i < frame->height * frame->width; i++) {
×
672
                alpha = src[3];
×
673
                *dst++ = (*src++ * alpha + bgcolor[0] * (0xff - alpha)) >> 8;
×
674
                *dst++ = (*src++ * alpha + bgcolor[1] * (0xff - alpha)) >> 8;
×
675
                *dst++ = (*src++ * alpha + bgcolor[2] * (0xff - alpha)) >> 8;
×
676
                src++;
×
677
            }
678
            sixel_frame_apply_pixelformat(
×
679
                frame,
680
                SIXEL_PIXELFORMAT_RGB888);
681
            break;
×
682
        case SIXEL_PIXELFORMAT_ABGR8888:
683
            for (i = 0; i < frame->height * frame->width; i++) {
×
684
                alpha = src[0];
×
685
                *dst++ = (src[3] * alpha + bgcolor[0] * (0xff - alpha)) >> 8;
×
686
                *dst++ = (src[2] * alpha + bgcolor[1] * (0xff - alpha)) >> 8;
×
687
                *dst++ = (src[1] * alpha + bgcolor[2] * (0xff - alpha)) >> 8;
×
688
                src += 4;
×
689
            }
690
            sixel_frame_apply_pixelformat(
×
691
                frame,
692
                SIXEL_PIXELFORMAT_RGB888);
693
            break;
×
694
        case SIXEL_PIXELFORMAT_BGRA8888:
695
            for (i = 0; i < frame->height * frame->width; i++) {
×
696
                alpha = src[3];
×
697
                *dst++ = (src[2] * alpha + bgcolor[0] * (0xff - alpha)) >> 8;
×
698
                *dst++ = (src[1] * alpha + bgcolor[1] * (0xff - alpha)) >> 8;
×
699
                *dst++ = (src[0] * alpha + bgcolor[2] * (0xff - alpha)) >> 8;
×
700
                src += 4;
×
701
            }
702
            sixel_frame_apply_pixelformat(
×
703
                frame,
704
                SIXEL_PIXELFORMAT_RGB888);
705
            break;
×
706
        default:
707
            break;
708
        }
709
    } else {
710
        switch (frame->pixelformat) {
411!
711
        case SIXEL_PIXELFORMAT_ARGB8888:
712
            for (i = 0; i < frame->height * frame->width; i++) {
×
713
                src++;            /* A */
×
714
                *dst++ = *src++;  /* R */
×
715
                *dst++ = *src++;  /* G */
×
716
                *dst++ = *src++;  /* B */
×
717
            }
718
            sixel_frame_apply_pixelformat(
×
719
                frame,
720
                SIXEL_PIXELFORMAT_RGB888);
721
            break;
×
722
        case SIXEL_PIXELFORMAT_RGBA8888:
723
            for (i = 0; i < frame->height * frame->width; i++) {
×
724
                *dst++ = *src++;  /* R */
×
725
                *dst++ = *src++;  /* G */
×
726
                *dst++ = *src++;  /* B */
×
727
                src++;            /* A */
×
728
            }
729
            sixel_frame_apply_pixelformat(
×
730
                frame,
731
                SIXEL_PIXELFORMAT_RGB888);
732
            break;
×
733
        case SIXEL_PIXELFORMAT_ABGR8888:
734
            for (i = 0; i < frame->height * frame->width; i++) {
×
735
                *dst++ = src[3];  /* R */
×
736
                *dst++ = src[2];  /* G */
×
737
                *dst++ = src[1];  /* B */
×
738
                src += 4;
×
739
            }
740
            sixel_frame_apply_pixelformat(
×
741
                frame,
742
                SIXEL_PIXELFORMAT_RGB888);
743
            break;
×
744
        case SIXEL_PIXELFORMAT_BGRA8888:
745
            for (i = 0; i < frame->height * frame->width; i++) {
×
746
                *dst++ = src[2];  /* R */
×
747
                *dst++ = src[1];  /* G */
×
748
                *dst++ = src[0];  /* B */
×
749
                src += 4;
×
750
            }
751
            sixel_frame_apply_pixelformat(
×
752
                frame,
753
                SIXEL_PIXELFORMAT_RGB888);
754
            break;
×
755
        default:
756
            break;
757
        }
758
    }
759

760
    status = SIXEL_OK;
426✔
761

762
    sixel_frame_unref(frame);
426✔
763

764
    return status;
426✔
765
}
766

767

768
static SIXELSTATUS
769
sixel_frame_convert_to_rgb888(sixel_frame_t /*in */ *frame)
96✔
770
{
771
    SIXELSTATUS status = SIXEL_FALSE;
96✔
772
    unsigned char *normalized_pixels = NULL;
96✔
773
    size_t size;
96✔
774
    unsigned char *dst;
96✔
775
    unsigned char *src;
96✔
776
    unsigned char *p;
96✔
777
    unsigned char *raw_pixels;
96✔
778
    unsigned char const *source_pixels;
96✔
779
    float *float_pixels;
96✔
780

781
    sixel_frame_ref(frame);
96✔
782

783
    raw_pixels = frame->pixels.u8ptr;
96✔
784
    float_pixels = frame->pixels.f32ptr;
96✔
785
    source_pixels = raw_pixels;
96✔
786

787
    switch (frame->pixelformat) {
96!
788
    case SIXEL_PIXELFORMAT_PAL1:
×
789
    case SIXEL_PIXELFORMAT_PAL2:
790
    case SIXEL_PIXELFORMAT_PAL4:
791
        size = (size_t)(frame->width * frame->height * 4);
×
792
        normalized_pixels = (unsigned char *)
×
793
            sixel_allocator_malloc(frame->allocator, size);
×
794
        if (normalized_pixels == NULL) {
×
795
            sixel_helper_set_additional_message(
×
796
                "sixel_frame_convert_to_rgb888: "
797
                "sixel_allocator_malloc() failed.");
798
            status = SIXEL_BAD_ALLOCATION;
×
799
            goto end;
×
800
        }
801
        src = normalized_pixels + frame->width * frame->height * 3;
×
802
        dst = normalized_pixels;
×
803
        status = sixel_helper_normalize_pixelformat(src,
×
804
                                                    &frame->pixelformat,
805
                                                    source_pixels,
806
                                                    frame->pixelformat,
807
                                                    frame->width,
808
                                                    frame->height);
809
        if (SIXEL_FAILED(status)) {
×
810
            sixel_allocator_free(frame->allocator, normalized_pixels);
×
811
            goto end;
×
812
        }
813
        for (p = src; dst < src; ++p) {
×
814
            *dst++ = *(frame->palette + *p * 3 + 0);
×
815
            *dst++ = *(frame->palette + *p * 3 + 1);
×
816
            *dst++ = *(frame->palette + *p * 3 + 2);
×
817
        }
818
        sixel_allocator_free(frame->allocator, raw_pixels);
×
819
        frame->pixels.u8ptr = normalized_pixels;
×
820
        sixel_frame_apply_pixelformat(
×
821
            frame,
822
            SIXEL_PIXELFORMAT_RGB888);
823
        break;
×
824
    case SIXEL_PIXELFORMAT_PAL8:
3✔
825
        size = (size_t)(frame->width * frame->height * 3);
3✔
826
        normalized_pixels = (unsigned char *)
3✔
827
            sixel_allocator_malloc(frame->allocator, size);
3✔
828
        if (normalized_pixels == NULL) {
3!
829
            sixel_helper_set_additional_message(
×
830
                "sixel_frame_convert_to_rgb888: "
831
                "sixel_allocator_malloc() failed.");
832
            status = SIXEL_BAD_ALLOCATION;
×
833
            goto end;
×
834
        }
835
        src = raw_pixels;
836
        dst = normalized_pixels;
837
        for (; dst != normalized_pixels + size; ++src) {
3,909✔
838
            *dst++ = frame->palette[*src * 3 + 0];
3,906✔
839
            *dst++ = frame->palette[*src * 3 + 1];
3,906✔
840
            *dst++ = frame->palette[*src * 3 + 2];
3,906✔
841
        }
842
        sixel_allocator_free(frame->allocator, raw_pixels);
3✔
843
        frame->pixels.u8ptr = normalized_pixels;
3✔
844
        sixel_frame_apply_pixelformat(
3✔
845
            frame,
846
            SIXEL_PIXELFORMAT_RGB888);
847
        break;
3✔
848
    case SIXEL_PIXELFORMAT_RGB888:
849
        break;
850
    case SIXEL_PIXELFORMAT_G8:
×
851
    case SIXEL_PIXELFORMAT_GA88:
852
    case SIXEL_PIXELFORMAT_AG88:
853
    case SIXEL_PIXELFORMAT_RGB555:
854
    case SIXEL_PIXELFORMAT_RGB565:
855
    case SIXEL_PIXELFORMAT_BGR555:
856
    case SIXEL_PIXELFORMAT_BGR565:
857
    case SIXEL_PIXELFORMAT_RGBA8888:
858
    case SIXEL_PIXELFORMAT_ARGB8888:
859
    case SIXEL_PIXELFORMAT_RGBFLOAT32:
860
    case SIXEL_PIXELFORMAT_LINEARRGBFLOAT32:
861
    case SIXEL_PIXELFORMAT_OKLABFLOAT32:
862
    case SIXEL_PIXELFORMAT_CIELABFLOAT32:
863
    case SIXEL_PIXELFORMAT_DIN99DFLOAT32:
864
        /* normalize pixelformat */
865
        size = (size_t)(frame->width * frame->height * 3);
×
866
        normalized_pixels = (unsigned char *)
×
867
            sixel_allocator_malloc(frame->allocator, size);
×
868
        if (normalized_pixels == NULL) {
×
869
            sixel_helper_set_additional_message(
×
870
                "sixel_frame_convert_to_rgb888: "
871
                "sixel_allocator_malloc() failed.");
872
            status = SIXEL_BAD_ALLOCATION;
×
873
            goto end;
×
874
        }
875
        if (SIXEL_PIXELFORMAT_IS_FLOAT32(frame->pixelformat)) {
×
876
            source_pixels = (unsigned char const *)float_pixels;
×
877
        }
878
        status = sixel_helper_normalize_pixelformat(normalized_pixels,
×
879
                                                    &frame->pixelformat,
880
                                                    source_pixels,
881
                                                    frame->pixelformat,
882
                                                    frame->width,
883
                                                    frame->height);
884
        if (SIXEL_FAILED(status)) {
×
885
            sixel_allocator_free(frame->allocator, normalized_pixels);
×
886
            goto end;
×
887
        }
888
        sixel_allocator_free(frame->allocator, raw_pixels);
×
889
        frame->pixels.u8ptr = normalized_pixels;
×
890
        break;
×
891
    default:
×
892
        status = SIXEL_LOGIC_ERROR;
×
893
        sixel_helper_set_additional_message(
×
894
            "sixel_frame_convert_to_rgb888: invalid pixelformat.");
895
        goto end;
×
896
    }
897

898
    status = SIXEL_OK;
899

900
end:
96✔
901
    sixel_frame_unref(frame);
96✔
902

903
    return status;
96✔
904
}
905

906
/*
907
 * Infer colorspace metadata from the pixelformat.  Float formats encode
908
 * their transfer characteristics directly, while byte-oriented formats
909
 * default to gamma encoded RGB.
910
 */
911
static int
912
sixel_frame_colorspace_from_pixelformat(int pixelformat)
15✔
913
{
914
    switch (pixelformat) {
3!
915
    case SIXEL_PIXELFORMAT_LINEARRGBFLOAT32:
916
        return SIXEL_COLORSPACE_LINEAR;
917
    case SIXEL_PIXELFORMAT_OKLABFLOAT32:
918
        return SIXEL_COLORSPACE_OKLAB;
919
    case SIXEL_PIXELFORMAT_CIELABFLOAT32:
920
        return SIXEL_COLORSPACE_CIELAB;
921
    case SIXEL_PIXELFORMAT_DIN99DFLOAT32:
922
        return SIXEL_COLORSPACE_DIN99D;
923
    default:
924
        return SIXEL_COLORSPACE_GAMMA;
925
    }
926
}
927

928
static void
929
sixel_frame_apply_pixelformat(sixel_frame_t *frame, int pixelformat)
18✔
930
{
931
    frame->pixelformat = pixelformat;
18✔
932
    frame->colorspace = sixel_frame_colorspace_from_pixelformat(pixelformat);
15!
933
}
3✔
934

935
#if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
936
# pragma GCC diagnostic push
937
# pragma GCC diagnostic ignored "-Wunused-function"
938
#endif
939

940
/*
941
 * Select the float pixelformat that matches the frame's current colorspace
942
 * so downstream conversions interpret each channel correctly.  OKLab uses
943
 * a [-0.5, 0.5] range for a/b, while gamma/linear share the 0-1 interval.
944
 */
945
static int
946
sixel_frame_float_pixelformat_for_colorspace(int colorspace)
×
947
{
948
    switch (colorspace) {
×
949
    case SIXEL_COLORSPACE_LINEAR:
950
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
951
    case SIXEL_COLORSPACE_OKLAB:
952
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
953
    case SIXEL_COLORSPACE_CIELAB:
954
        return SIXEL_PIXELFORMAT_CIELABFLOAT32;
955
    case SIXEL_COLORSPACE_DIN99D:
956
        return SIXEL_PIXELFORMAT_DIN99DFLOAT32;
957
    default:
958
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
959
    }
960
}
961

962
static SIXELSTATUS
963
sixel_frame_promote_to_float32(sixel_frame_t *frame)
×
964
{
965
    float *float_pixels;
×
966
    unsigned char *byte_pixels;
×
967
    unsigned char const *pixel;
×
968
    size_t pixel_total;
×
969
    size_t bytes;
×
970
    size_t index;
×
971
    size_t base;
×
972
    int float_pixelformat;
×
973
    int step;
×
974
    int index_r;
×
975
    int index_g;
×
976
    int index_b;
×
977

978
    step = 0;
×
979
    index_r = 0;
×
980
    index_g = 0;
×
981
    index_b = 0;
×
982

983
    /*
984
     * Derive the byte stride and per-channel offsets instead of coercing the
985
     * frame to RGB888 first.  OKLab buffers keep their signed A/B buckets in
986
     * the order dictated by pixelformat, so preserving that layout avoids the
987
     * blue casts reported when we reinterpreted them as gamma RGB.
988
     */
989
    switch (frame->pixelformat) {
×
990
    case SIXEL_PIXELFORMAT_RGB888:
991
        step = 3;
992
        index_r = 0;
993
        index_g = 1;
994
        index_b = 2;
995
        break;
996
    case SIXEL_PIXELFORMAT_BGR888:
×
997
        step = 3;
×
998
        index_r = 2;
×
999
        index_g = 1;
×
1000
        index_b = 0;
×
1001
        break;
×
1002
    case SIXEL_PIXELFORMAT_RGBA8888:
×
1003
        step = 4;
×
1004
        index_r = 0;
×
1005
        index_g = 1;
×
1006
        index_b = 2;
×
1007
        break;
×
1008
    case SIXEL_PIXELFORMAT_ARGB8888:
×
1009
        step = 4;
×
1010
        index_r = 1;
×
1011
        index_g = 2;
×
1012
        index_b = 3;
×
1013
        break;
×
1014
    case SIXEL_PIXELFORMAT_BGRA8888:
×
1015
        step = 4;
×
1016
        index_r = 2;
×
1017
        index_g = 1;
×
1018
        index_b = 0;
×
1019
        break;
×
1020
    case SIXEL_PIXELFORMAT_ABGR8888:
×
1021
        step = 4;
×
1022
        index_r = 3;
×
1023
        index_g = 2;
×
1024
        index_b = 1;
×
1025
        break;
×
1026
    case SIXEL_PIXELFORMAT_G8:
×
1027
        step = 1;
×
1028
        index_r = 0;
×
1029
        index_g = 0;
×
1030
        index_b = 0;
×
1031
        break;
×
1032
    case SIXEL_PIXELFORMAT_GA88:
×
1033
        step = 2;
×
1034
        index_r = 0;
×
1035
        index_g = 0;
×
1036
        index_b = 0;
×
1037
        break;
×
1038
    case SIXEL_PIXELFORMAT_AG88:
×
1039
        step = 2;
×
1040
        index_r = 1;
×
1041
        index_g = 1;
×
1042
        index_b = 1;
×
1043
        break;
×
1044
    default:
×
1045
        sixel_helper_set_additional_message(
×
1046
            "sixel_frame_promote_to_float32: unsupported pixelformat.");
1047
        return SIXEL_BAD_INPUT;
×
1048
    }
1049

1050
    if ((size_t)frame->width > SIZE_MAX / (size_t)frame->height) {
×
1051
        sixel_helper_set_additional_message(
×
1052
            "sixel_frame_promote_to_float32: overflow.");
1053
        return SIXEL_BAD_INPUT;
×
1054
    }
1055

1056
    pixel_total = (size_t)frame->width * (size_t)frame->height;
×
1057
    if (pixel_total > SIZE_MAX / (3U * sizeof(float))) {
×
1058
        sixel_helper_set_additional_message(
×
1059
            "sixel_frame_promote_to_float32: buffer too large.");
1060
        return SIXEL_BAD_INPUT;
×
1061
    }
1062
    bytes = pixel_total * 3U * sizeof(float);
×
1063
    float_pixels = (float *)sixel_allocator_malloc(frame->allocator, bytes);
×
1064
    if (float_pixels == NULL) {
×
1065
        sixel_helper_set_additional_message(
×
1066
            "sixel_frame_promote_to_float32: "
1067
            "sixel_allocator_malloc() failed.");
1068
        return SIXEL_BAD_ALLOCATION;
×
1069
    }
1070

1071
    byte_pixels = frame->pixels.u8ptr;
×
1072
    float_pixelformat =
×
1073
        sixel_frame_float_pixelformat_for_colorspace(frame->colorspace);
×
1074

1075
    for (index = 0U; index < pixel_total; ++index) {
×
1076
        unsigned char r8;
×
1077
        unsigned char g8;
×
1078
        unsigned char b8;
×
1079

1080
        pixel = byte_pixels + index * (size_t)step;
×
1081
        r8 = *(pixel + (size_t)index_r);
×
1082
        g8 = *(pixel + (size_t)index_g);
×
1083
        b8 = *(pixel + (size_t)index_b);
×
1084

1085
        base = index * 3U;
×
1086
        float_pixels[base + 0U] =
×
1087
            sixel_pixelformat_byte_to_float(float_pixelformat, 0, r8);
×
1088
        float_pixels[base + 1U] =
×
1089
            sixel_pixelformat_byte_to_float(float_pixelformat, 1, g8);
×
1090
        float_pixels[base + 2U] =
×
1091
            sixel_pixelformat_byte_to_float(float_pixelformat, 2, b8);
×
1092
    }
1093

1094
    sixel_allocator_free(frame->allocator, byte_pixels);
×
1095
    sixel_frame_set_pixels_float32(frame, float_pixels);
×
1096
    sixel_frame_apply_pixelformat(frame, float_pixelformat);
×
1097
    return SIXEL_OK;
×
1098
}
1099

1100
#if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
1101
# pragma GCC diagnostic pop
1102
#endif
1103

1104
/* resize a frame to given size with specified resampling filter */
1105
SIXELAPI SIXELSTATUS
1106
sixel_frame_resize(
93✔
1107
    sixel_frame_t *frame,
1108
    int width,
1109
    int height,
1110
    int method_for_resampling
1111
)
1112
{
1113
    SIXELSTATUS status = SIXEL_FALSE;
93✔
1114
    size_t size;
93✔
1115
    unsigned char *scaled_frame = NULL;
93✔
1116

1117
    sixel_frame_ref(frame);
93✔
1118

1119
    /* check parameters */
1120
    if (width <= 0) {
93!
1121
        sixel_helper_set_additional_message(
×
1122
            "sixel_frame_resize: an invalid width parameter detected.");
1123
        status = SIXEL_BAD_INPUT;
×
1124
        goto end;
×
1125
    }
1126
    if (height <= 0) {
93!
1127
        sixel_helper_set_additional_message(
×
1128
            "sixel_frame_resize: an invalid width parameter detected.");
1129
        status = SIXEL_BAD_INPUT;
×
1130
        goto end;
×
1131
    }
1132
    if (width > SIXEL_WIDTH_LIMIT) {
93!
1133
        sixel_helper_set_additional_message(
×
1134
            "sixel_frame_resize: given width parameter is too huge.");
1135
        status = SIXEL_BAD_INPUT;
×
1136
        goto end;
×
1137
    }
1138
    if (height > SIXEL_HEIGHT_LIMIT) {
93!
1139
        sixel_helper_set_additional_message(
×
1140
            "sixel_frame_resize: given height parameter is too huge.");
1141
        status = SIXEL_BAD_INPUT;
×
1142
        goto end;
×
1143
    }
1144

1145
    if (width == frame->width && height == frame->height) {
93!
1146
        /* nothing to do */
1147
        goto out;
×
1148
    }
1149

1150
    status = sixel_frame_convert_to_rgb888(frame);
93✔
1151
    if (SIXEL_FAILED(status)) {
93!
1152
        goto end;
×
1153
    }
1154

1155
    size = (size_t)width * (size_t)height * 3UL;
93✔
1156
    scaled_frame = (unsigned char *)
93✔
1157
        sixel_allocator_malloc(frame->allocator, size);
93✔
1158
    if (scaled_frame == NULL) {
93!
1159
        sixel_helper_set_additional_message(
×
1160
            "sixel_frame_resize: sixel_allocator_malloc() failed.");
1161
        status = SIXEL_BAD_ALLOCATION;
×
1162
        goto end;
×
1163
    }
1164

1165
    status = sixel_helper_scale_image(
186✔
1166
        scaled_frame,
1167
        frame->pixels.u8ptr,
93✔
1168
        frame->width,
1169
        frame->height,
1170
        3,
1171
        width,
1172
        height,
1173
        method_for_resampling,
1174
        frame->allocator);
1175
    if (SIXEL_FAILED(status)) {
93!
1176
        goto end;
×
1177
    }
1178
    sixel_allocator_free(frame->allocator, frame->pixels.u8ptr);
93✔
1179
    frame->pixels.u8ptr = scaled_frame;
93✔
1180
    frame->width = width;
93✔
1181
    frame->height = height;
93✔
1182

1183
out:
1184
    status = SIXEL_OK;
1185

1186
end:
93✔
1187
    sixel_frame_unref(frame);
93✔
1188

1189
    return status;
93✔
1190
}
1191

1192
/*
1193
 * Resize a frame using float buffers. Non-nearest filters run in linear RGB
1194
 * so interpolation blends radiometrically instead of on gamma encoded values.
1195
 * Callers can still request nearest-neighbor sampling, in which case the
1196
 * routine preserves the frame's current colorspace while scaling.
1197
 */
1198
SIXELAPI SIXELSTATUS
1199
sixel_frame_resize_float32(
×
1200
    sixel_frame_t *frame,
1201
    int width,
1202
    int height,
1203
    int method_for_resampling)
1204
{
1205
    SIXELSTATUS status;
×
1206
    size_t pixel_total;
×
1207
    size_t size;
×
1208
    float *scaled_frame;
×
1209
    int depth;
×
1210
    int depth_bytes;
×
1211
    int target_pixelformat;
×
1212

1213
    status = SIXEL_FALSE;
×
1214
    scaled_frame = NULL;
×
1215
    pixel_total = 0u;
×
1216
    size = 0u;
×
1217
    depth = 0;
×
1218
    depth_bytes = 0;
×
1219
    target_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
×
1220

1221
    sixel_frame_ref(frame);
×
1222

1223
    if (width <= 0) {
×
1224
        sixel_helper_set_additional_message(
×
1225
            "sixel_frame_resize_float32: "
1226
            "an invalid width parameter detected.");
1227
        status = SIXEL_BAD_INPUT;
×
1228
        goto end;
×
1229
    }
1230
    if (height <= 0) {
×
1231
        sixel_helper_set_additional_message(
×
1232
            "sixel_frame_resize_float32: "
1233
            "an invalid width parameter detected.");
1234
        status = SIXEL_BAD_INPUT;
×
1235
        goto end;
×
1236
    }
1237
    if (width > SIXEL_WIDTH_LIMIT) {
×
1238
        sixel_helper_set_additional_message(
×
1239
            "sixel_frame_resize_float32: "
1240
            "given width parameter is too huge.");
1241
        status = SIXEL_BAD_INPUT;
×
1242
        goto end;
×
1243
    }
1244
    if (height > SIXEL_HEIGHT_LIMIT) {
×
1245
        sixel_helper_set_additional_message(
×
1246
            "sixel_frame_resize_float32: "
1247
            "given height parameter is too huge.");
1248
        status = SIXEL_BAD_INPUT;
×
1249
        goto end;
×
1250
    }
1251

1252
    if (width == frame->width && height == frame->height) {
×
1253
        goto out;
×
1254
    }
1255

1256
    if (method_for_resampling == SIXEL_RES_NEAREST) {
×
1257
        target_pixelformat =
×
1258
            sixel_frame_float_pixelformat_for_colorspace(
×
1259
                frame->colorspace);
1260
    } else {
1261
        target_pixelformat = SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
1262
    }
1263

1264
    status = sixel_frame_set_pixelformat(frame, target_pixelformat);
×
1265
    if (SIXEL_FAILED(status)) {
×
1266
        goto end;
×
1267
    }
1268

1269
    depth_bytes = sixel_helper_compute_depth(frame->pixelformat);
×
1270
    if (depth_bytes <= 0) {
×
1271
        sixel_helper_set_additional_message(
×
1272
            "sixel_frame_resize_float32: "
1273
            "invalid pixelformat depth.");
1274
        status = SIXEL_BAD_ARGUMENT;
×
1275
        goto end;
×
1276
    }
1277

1278
    /*
1279
     * sixel_helper_compute_depth() returns bytes per pixel. Convert the
1280
     * value to channels for validation and reuse the byte count for buffer
1281
     * sizing to avoid overflow on float formats.
1282
     */
1283
    if (depth_bytes % (int)sizeof(float) != 0) {
×
1284
        sixel_helper_set_additional_message(
×
1285
            "sixel_frame_resize_float32: "
1286
            "pixelformat depth is not float-aligned.");
1287
        status = SIXEL_BAD_ARGUMENT;
×
1288
        goto end;
×
1289
    }
1290
    depth = depth_bytes / (int)sizeof(float);
×
1291

1292
    if (depth != 3) {
×
1293
        sixel_helper_set_additional_message(
×
1294
            "sixel_frame_resize_float32: "
1295
            "unsupported channel count.");
1296
        status = SIXEL_BAD_ARGUMENT;
×
1297
        goto end;
×
1298
    }
1299

1300
    pixel_total = (size_t)width * (size_t)height;
×
1301
    if (pixel_total > SIZE_MAX / (size_t)depth_bytes) {
×
1302
        sixel_helper_set_additional_message(
1303
            "sixel_frame_resize_float32: buffer size overflow.");
1304
        status = SIXEL_BAD_INPUT;
1305
        goto end;
1306
    }
1307
    size = pixel_total * (size_t)depth_bytes;
×
1308

1309
    scaled_frame = (float *)sixel_allocator_malloc(frame->allocator, size);
×
1310
    if (scaled_frame == NULL) {
×
1311
        sixel_helper_set_additional_message(
×
1312
            "sixel_frame_resize_float32: "
1313
            "sixel_allocator_malloc() failed.");
1314
        status = SIXEL_BAD_ALLOCATION;
×
1315
        goto end;
×
1316
    }
1317

1318
    status = sixel_helper_scale_image_float32(
×
1319
        scaled_frame,
1320
        frame->pixels.f32ptr,
×
1321
        frame->width,
1322
        frame->height,
1323
        frame->pixelformat,
1324
        width,
1325
        height,
1326
        method_for_resampling,
1327
        frame->allocator);
1328
    if (SIXEL_FAILED(status)) {
×
1329
        goto end;
×
1330
    }
1331

1332
    sixel_allocator_free(frame->allocator, frame->pixels.f32ptr);
×
1333
    frame->pixels.f32ptr = scaled_frame;
×
1334
    frame->width = width;
×
1335
    frame->height = height;
×
1336

1337
out:
1338
    status = SIXEL_OK;
1339

1340
end:
1341
    if (SIXEL_FAILED(status) && scaled_frame != NULL) {
×
1342
        sixel_allocator_free(frame->allocator, scaled_frame);
×
1343
    }
1344
    sixel_frame_unref(frame);
×
1345

1346
    return status;
×
1347
}
1348

1349

1350
static SIXELSTATUS
1351
clip(unsigned char *pixels,
12✔
1352
     int sx,
1353
     int sy,
1354
     int pixelformat,
1355
     int cx,
1356
     int cy,
1357
     int cw,
1358
     int ch)
1359
{
1360
    SIXELSTATUS status = SIXEL_FALSE;
12✔
1361
    int y;
12✔
1362
    unsigned char *src;
12✔
1363
    unsigned char *dst;
12✔
1364
    int depth;
12✔
1365
    char message[256];
12✔
1366
    int nwrite;
12✔
1367

1368
    /* unused */ (void) sx;
12✔
1369
    /* unused */ (void) sy;
12✔
1370
    /* unused */ (void) cx;
12✔
1371

1372
    switch (pixelformat) {
12!
1373
    case SIXEL_PIXELFORMAT_PAL8:
12✔
1374
    case SIXEL_PIXELFORMAT_G8:
1375
    case SIXEL_PIXELFORMAT_RGB888:
1376
    case SIXEL_PIXELFORMAT_RGBFLOAT32:
1377
        depth = sixel_helper_compute_depth(pixelformat);
12✔
1378
        if (depth < 0) {
12!
1379
            status = SIXEL_LOGIC_ERROR;
×
1380
            /*
1381
             * We funnel formatting through the compat helper so that MSVC
1382
             * receives explicit bounds information.
1383
             */
1384
            nwrite = sixel_compat_snprintf(
×
1385
                message,
1386
                sizeof(message),
1387
                "clip: sixel_helper_compute_depth(%08x) failed.",
1388
                pixelformat);
1389
            if (nwrite > 0) {
×
1390
                sixel_helper_set_additional_message(message);
×
1391
            }
1392
            goto end;
×
1393
        }
1394

1395
        dst = pixels;
12✔
1396
        src = pixels + cy * sx * depth + cx * depth;
12✔
1397
        for (y = 0; y < ch; y++) {
1,902✔
1398
            memmove(dst, src, (size_t)(cw * depth));
1,890✔
1399
            dst += (cw * depth);
1,890✔
1400
            src += (sx * depth);
1,890✔
1401
        }
1402

1403
        status = SIXEL_OK;
1404

1405
        break;
1406
    default:
×
1407
        status = SIXEL_BAD_ARGUMENT;
×
1408
        nwrite = sixel_compat_snprintf(
×
1409
            message,
1410
            sizeof(message),
1411
            "clip: invalid pixelformat(%08x) is specified.",
1412
            pixelformat);
1413
        if (nwrite > 0) {
×
1414
            sixel_helper_set_additional_message(message);
×
1415
        }
1416
        break;
1417
    }
1418

1419
end:
12✔
1420
    return status;
12✔
1421
}
1422

1423

1424
/* clip frame */
1425
SIXELAPI SIXELSTATUS
1426
sixel_frame_clip(
12✔
1427
    sixel_frame_t *frame,
1428
    int x,
1429
    int y,
1430
    int width,
1431
    int height
1432
)
1433
{
1434
    SIXELSTATUS status = SIXEL_FALSE;
12✔
1435
    unsigned char *normalized_pixels;
12✔
1436
    unsigned char *raw_pixels;
12✔
1437

1438
    sixel_frame_ref(frame);
12✔
1439

1440
    raw_pixels = frame->pixels.u8ptr;
12✔
1441

1442
    /* check parameters */
1443
    if (width <= 0) {
12!
1444
        sixel_helper_set_additional_message(
×
1445
            "sixel_frame_clip: an invalid width parameter detected.");
1446
        status = SIXEL_BAD_INPUT;
×
1447
        goto end;
×
1448
    }
1449
    if (height <= 0) {
12!
1450
        sixel_helper_set_additional_message(
×
1451
            "sixel_frame_clip: an invalid width parameter detected.");
1452
        status = SIXEL_BAD_INPUT;
×
1453
        goto end;
×
1454
    }
1455
    if (width > SIXEL_WIDTH_LIMIT) {
12!
1456
        sixel_helper_set_additional_message(
×
1457
            "sixel_frame_clip: given width parameter is too huge.");
1458
        status = SIXEL_BAD_INPUT;
×
1459
        goto end;
×
1460
    }
1461
    if (height > SIXEL_HEIGHT_LIMIT) {
12!
1462
        sixel_helper_set_additional_message(
×
1463
            "sixel_frame_clip: given height parameter is too huge.");
1464
        status = SIXEL_BAD_INPUT;
×
1465
        goto end;
×
1466
    }
1467

1468
    switch (frame->pixelformat) {
12!
1469
    case SIXEL_PIXELFORMAT_PAL1:
×
1470
    case SIXEL_PIXELFORMAT_PAL2:
1471
    case SIXEL_PIXELFORMAT_PAL4:
1472
    case SIXEL_PIXELFORMAT_G1:
1473
    case SIXEL_PIXELFORMAT_G2:
1474
    case SIXEL_PIXELFORMAT_G4:
1475
        normalized_pixels = (unsigned char *)
×
1476
            sixel_allocator_malloc(frame->allocator,
×
1477
                                   (size_t)(frame->width * frame->height));
×
1478
        status = sixel_helper_normalize_pixelformat(normalized_pixels,
×
1479
                                                    &frame->pixelformat,
1480
                                                    raw_pixels,
1481
                                                    frame->pixelformat,
1482
                                                    frame->width,
1483
                                                    frame->height);
1484
        if (SIXEL_FAILED(status)) {
×
1485
            sixel_allocator_free(frame->allocator, normalized_pixels);
×
1486
            goto end;
×
1487
        }
1488
        sixel_allocator_free(frame->allocator, raw_pixels);
×
1489
        frame->pixels.u8ptr = normalized_pixels;
×
1490
        raw_pixels = normalized_pixels;
×
1491
        break;
×
1492
    default:
1493
        break;
1494
    }
1495

1496
    status = clip(raw_pixels,
12✔
1497
                  frame->width,
1498
                  frame->height,
1499
                  frame->pixelformat,
1500
                  x,
1501
                  y,
1502
                  width,
1503
                  height);
1504
    if (SIXEL_FAILED(status)) {
12!
1505
        goto end;
×
1506
    }
1507
    frame->width = width;
12✔
1508
    frame->height = height;
12✔
1509

1510
    status = SIXEL_OK;
12✔
1511

1512
end:
12✔
1513
    sixel_frame_unref(frame);
12✔
1514

1515
    return status;
12✔
1516
}
1517

1518

1519
#if HAVE_TESTS
1520
static int
1521
test1(void)
×
1522
{
1523
    sixel_frame_t *frame = NULL;
×
1524
    int nret = EXIT_FAILURE;
×
1525

1526
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1527
#  pragma GCC diagnostic push
1528
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1529
#endif
1530
    frame = sixel_frame_create();
×
1531
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1532
#  pragma GCC diagnostic pop
1533
#endif
1534
    if (frame == NULL) {
×
1535
        goto error;
×
1536
    }
1537
    sixel_frame_ref(frame);
×
1538
    sixel_frame_unref(frame);
×
1539
    nret = EXIT_SUCCESS;
×
1540

1541
error:
×
1542
    sixel_frame_unref(frame);
×
1543
    return nret;
×
1544
}
1545

1546

1547
static int
1548
test2(void)
×
1549
{
1550
    sixel_frame_t *frame = NULL;
×
1551
    int nret = EXIT_FAILURE;
×
1552
    unsigned char *pixels = malloc(4);
×
1553
    unsigned char *bgcolor = malloc(3);
×
1554
    unsigned char *u8pixels;
×
1555
    SIXELSTATUS status;
×
1556

1557
    pixels[0] = 0x43;
×
1558
    pixels[1] = 0x89;
×
1559
    pixels[2] = 0x97;
×
1560
    pixels[3] = 0x32;
×
1561

1562
    memset(bgcolor, 0x10, 3);
×
1563

1564
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1565
#  pragma GCC diagnostic push
1566
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1567
#endif
1568
    frame = sixel_frame_create();
×
1569
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1570
#  pragma GCC diagnostic pop
1571
#endif
1572

1573
    if (frame == NULL) {
×
1574
        goto error;
×
1575
    }
1576

1577
    status = sixel_frame_init(frame,
×
1578
                              pixels,
1579
                              1,
1580
                              1,
1581
                              SIXEL_PIXELFORMAT_RGBA8888,
1582
                              NULL,
1583
                              0);
1584
    if (SIXEL_FAILED(status)) {
×
1585
        goto error;
×
1586
    }
1587

1588
    status = sixel_frame_strip_alpha(frame, bgcolor);
×
1589
    if (SIXEL_FAILED(status)) {
×
1590
        goto error;
1591
    }
1592

1593
    if (frame->pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1594
        goto error;
×
1595
    }
1596

1597
    u8pixels = frame->pixels.u8ptr;
×
1598

1599
    if (u8pixels[0] != (0x43 * 0x32 + 0x10 * (0xff - 0x32)) >> 8) {
×
1600
        goto error;
×
1601
    }
1602

1603
    if (u8pixels[1] != (0x89 * 0x32 + 0x10 * (0xff - 0x32)) >> 8) {
×
1604
        goto error;
×
1605
    }
1606

1607
    if (u8pixels[2] != (0x97 * 0x32 + 0x10 * (0xff - 0x32)) >> 8) {
×
1608
        goto error;
×
1609
    }
1610

1611
    nret = EXIT_SUCCESS;
1612

1613
error:
×
1614
    sixel_frame_unref(frame);
×
1615
    return nret;
×
1616
}
1617

1618

1619
static int
1620
test3(void)
×
1621
{
1622
    sixel_frame_t *frame = NULL;
×
1623
    int nret = EXIT_FAILURE;
×
1624
    unsigned char *pixels = malloc(4);
×
1625
    unsigned char *u8pixels;
×
1626
    SIXELSTATUS status;
×
1627

1628
    pixels[0] = 0x43;
×
1629
    pixels[1] = 0x89;
×
1630
    pixels[2] = 0x97;
×
1631
    pixels[3] = 0x32;
×
1632

1633
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1634
#  pragma GCC diagnostic push
1635
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1636
#endif
1637
    frame = sixel_frame_create();
×
1638
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1639
#  pragma GCC diagnostic pop
1640
#endif
1641
    if (frame == NULL) {
×
1642
        goto error;
×
1643
    }
1644

1645
    status = sixel_frame_init(frame,
×
1646
                              pixels,
1647
                              1,
1648
                              1,
1649
                              SIXEL_PIXELFORMAT_RGBA8888,
1650
                              NULL,
1651
                              0);
1652
    if (SIXEL_FAILED(status)) {
×
1653
        goto error;
×
1654
    }
1655

1656
    status = sixel_frame_strip_alpha(frame, NULL);
×
1657
    if (SIXEL_FAILED(status)) {
×
1658
        goto error;
1659
    }
1660

1661
    if (frame->pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1662
        goto error;
×
1663
    }
1664

1665
    u8pixels = frame->pixels.u8ptr;
×
1666

1667
    if (u8pixels[0] != 0x43) {
×
1668
        goto error;
×
1669
    }
1670

1671
    if (u8pixels[1] != 0x89) {
×
1672
        goto error;
×
1673
    }
1674

1675
    if (u8pixels[2] != 0x97) {
×
1676
        goto error;
×
1677
    }
1678

1679
    nret = EXIT_SUCCESS;
1680

1681
error:
×
1682
    sixel_frame_unref(frame);
×
1683
    return nret;
×
1684
}
1685

1686

1687
static int
1688
test4(void)
×
1689
{
1690
    sixel_frame_t *frame = NULL;
×
1691
    int nret = EXIT_FAILURE;
×
1692
    unsigned char *pixels = malloc(4);
×
1693
    unsigned char *u8pixels;
×
1694
    SIXELSTATUS status;
×
1695

1696
    pixels[0] = 0x43;
×
1697
    pixels[1] = 0x89;
×
1698
    pixels[2] = 0x97;
×
1699
    pixels[3] = 0x32;
×
1700

1701
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1702
#  pragma GCC diagnostic push
1703
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1704
#endif
1705
    frame = sixel_frame_create();
×
1706
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1707
#  pragma GCC diagnostic pop
1708
#endif
1709
    if (frame == NULL) {
×
1710
        goto error;
×
1711
    }
1712

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

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

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

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

1735
    if (u8pixels[0] != 0x89) {
×
1736
        goto error;
×
1737
    }
1738

1739
    if (u8pixels[1] != 0x97) {
×
1740
        goto error;
×
1741
    }
1742

1743
    if (u8pixels[2] != 0x32) {
×
1744
        goto error;
×
1745
    }
1746

1747
    nret = EXIT_SUCCESS;
1748

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

1754

1755
static int
1756
test5(void)
×
1757
{
1758
    sixel_frame_t *frame = NULL;
×
1759
    int nret = EXIT_FAILURE;
×
1760
    unsigned char *pixels = malloc(1);
×
1761
    unsigned char *palette = malloc(3);
×
1762
    unsigned char *u8pixels;
×
1763
    SIXELSTATUS status;
×
1764

1765
    palette[0] = 0x43;
×
1766
    palette[1] = 0x89;
×
1767
    palette[2] = 0x97;
×
1768

1769
    pixels[0] = 0;
×
1770

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

1783
    status = sixel_frame_init(frame,
×
1784
                              pixels,
1785
                              1,
1786
                              1,
1787
                              SIXEL_PIXELFORMAT_PAL8,
1788
                              palette,
1789
                              1);
1790
    if (SIXEL_FAILED(status)) {
×
1791
        goto error;
×
1792
    }
1793

1794
    status = sixel_frame_convert_to_rgb888(frame);
×
1795
    if (SIXEL_FAILED(status)) {
×
1796
        goto error;
×
1797
    }
1798

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

1803
    u8pixels = frame->pixels.u8ptr;
×
1804

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

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

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

1817
    nret = EXIT_SUCCESS;
1818

1819
error:
×
1820
    sixel_frame_unref(frame);
×
1821
    return nret;
×
1822
}
1823

1824

1825
static int
1826
test6(void)
×
1827
{
1828
    sixel_frame_t *frame = NULL;
×
1829
    int nret = EXIT_FAILURE;
×
1830
    unsigned char *pixels = malloc(6);
×
1831
    unsigned char *palette = malloc(3);
×
1832
    unsigned char *u8pixels;
×
1833
    SIXELSTATUS status;
×
1834

1835
    palette[0] = 0x43;
×
1836
    palette[1] = 0x89;
×
1837
    palette[2] = 0x97;
×
1838

1839
    pixels[0] = 0;
×
1840

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

1853
    status = sixel_frame_init(frame,
×
1854
                              pixels,
1855
                              1,
1856
                              1,
1857
                              SIXEL_PIXELFORMAT_PAL1,
1858
                              palette,
1859
                              1);
1860
    if (SIXEL_FAILED(status)) {
×
1861
        goto error;
×
1862
    }
1863

1864
    status = sixel_frame_convert_to_rgb888(frame);
×
1865
    if (SIXEL_FAILED(status)) {
×
1866
        goto error;
×
1867
    }
1868

1869
    if (frame->pixelformat != SIXEL_PIXELFORMAT_RGB888) {
×
1870
        goto error;
×
1871
    }
1872

1873
    u8pixels = frame->pixels.u8ptr;
×
1874

1875
    if (u8pixels[0] != 0x43) {
×
1876
        goto error;
×
1877
    }
1878

1879
    if (u8pixels[1] != 0x89) {
×
1880
        goto error;
×
1881
    }
1882

1883
    if (u8pixels[2] != 0x97) {
×
1884
        goto error;
×
1885
    }
1886

1887
    nret = EXIT_SUCCESS;
1888

1889
error:
×
1890
    sixel_frame_unref(frame);
×
1891
    return nret;
×
1892
}
1893

1894

1895
SIXELAPI int
1896
sixel_frame_tests_main(void)
×
1897
{
1898
    int nret = EXIT_FAILURE;
×
1899
    size_t i;
×
1900
    typedef int (* testcase)(void);
×
1901

1902
    static testcase const testcases[] = {
×
1903
        test1,
1904
        test2,
1905
        test3,
1906
        test4,
1907
        test5,
1908
        test6,
1909
    };
1910

1911
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
1912
        nret = testcases[i]();
×
1913
        if (nret != EXIT_SUCCESS) {
×
1914
            goto error;
×
1915
        }
1916
    }
1917

1918
    nret = EXIT_SUCCESS;
1919

1920
error:
×
1921
    return nret;
×
1922
}
1923
#endif  /* HAVE_TESTS */
1924

1925
/* emacs Local Variables:      */
1926
/* emacs mode: c               */
1927
/* emacs tab-width: 4          */
1928
/* emacs indent-tabs-mode: nil */
1929
/* emacs c-basic-offset: 4     */
1930
/* emacs End:                  */
1931
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1932
/* 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