• 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

82.74
/src/loader-builtin.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2021-2025 libsixel developers. See `AUTHORS`.
5
 * Copyright (c) 2014-2019 Hayaki Saito
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to
9
 * deal in the Software without restriction, including without limitation the
10
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
11
 * sell copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all 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,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
 * FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23
 * DEALINGS IN THE SOFTWARE.
24
 *
25
 * Builtin loader covering SIXEL, PNM, GIF, and stb_image fallbacks.  This
26
 * module keeps the heavyweight stb_image implementation isolated from the
27
 * registry so other backends avoid pulling in its macros and includes.
28
 */
29

30
#include "config.h"
31

32
/* STDC_HEADERS */
33
#include <stdio.h>
34
#include <stdlib.h>
35

36
#if HAVE_STRING_H
37
# include <string.h>
38
#endif
39
#if HAVE_CTYPE_H
40
# include <ctype.h>
41
#endif
42
#if HAVE_LIMITS_H
43
# include <limits.h>
44
#endif
45

46
#include <sixel.h>
47

48
#include "allocator.h"
49
#include "chunk.h"
50
#include "frame.h"
51
#include "fromgif.h"
52
#include "frompnm.h"
53
#include "pixelformat.h"
54
#include "loader-builtin.h"
55
#include "loader-common.h"
56
#include "loader.h"
57

58
static sixel_allocator_t *stbi_allocator;
59

60
void *
61
stbi_malloc(size_t n)
1,206✔
62
{
63
    return sixel_allocator_malloc(stbi_allocator, n);
1,206✔
64
}
65

66
void *
67
stbi_realloc(void *p, size_t n)
318✔
68
{
69
    return sixel_allocator_realloc(stbi_allocator, p, n);
318✔
70
}
71

72
void
73
stbi_free(void *p)
1,590✔
74
{
75
    sixel_allocator_free(stbi_allocator, p);
1,486✔
76
}
1,060✔
77

78
#define STBI_MALLOC stbi_malloc
79
#define STBI_REALLOC stbi_realloc
80
#define STBI_FREE stbi_free
81

82
#define STBI_NO_STDIO 1
83
#define STB_IMAGE_IMPLEMENTATION 1
84
#define STBI_FAILURE_USERMSG 1
85
#if defined(_WIN32)
86
# define STBI_NO_THREAD_LOCALS 1  /* no tls */
87
#endif
88
#define STBI_NO_GIF
89
#define STBI_NO_PNM
90

91
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
92
# pragma GCC diagnostic push
93
# pragma GCC diagnostic ignored "-Wsign-conversion"
94
#endif
95
#if HAVE_DIAGNOSTIC_STRICT_OVERFLOW
96
# pragma GCC diagnostic push
97
# pragma GCC diagnostic ignored "-Wstrict-overflow"
98
#endif
99
#if HAVE_DIAGNOSTIC_SWITCH_DEFAULT
100
# pragma GCC diagnostic push
101
# pragma GCC diagnostic ignored "-Wswitch-default"
102
#endif
103
#if HAVE_DIAGNOSTIC_SHADOW
104
# pragma GCC diagnostic push
105
# pragma GCC diagnostic ignored "-Wshadow"
106
#endif
107
#if HAVE_DIAGNOSTIC_DOUBLE_PROMOTION
108
# pragma GCC diagnostic push
109
# pragma GCC diagnostic ignored "-Wdouble-promotion"
110
#endif
111
#if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
112
# pragma GCC diagnostic push
113
# pragma GCC diagnostic ignored "-Wunused-function"
114
#endif
115
#if HAVE_DIAGNOSTIC_UNUSED_BUT_SET_VARIABLE
116
# pragma GCC diagnostic push
117
# pragma GCC diagnostic ignored "-Wunused-but-set-variable"
118
#endif
119
#include "stb_image.h"
120
#if HAVE_DIAGNOSTIC_UNUSED_BUT_SET_VARIABLE
121
# pragma GCC diagnostic pop
122
#endif
123
#if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
124
# pragma GCC diagnostic pop
125
#endif
126
#if HAVE_DIAGNOSTIC_DOUBLE_PROMOTION
127
# pragma GCC diagnostic pop
128
#endif
129
#if HAVE_DIAGNOSTIC_SHADOW
130
# pragma GCC diagnostic pop
131
#endif
132
#if HAVE_DIAGNOSTIC_SWITCH_DEFAULT
133
# pragma GCC diagnostic pop
134
#endif
135
#if HAVE_DIAGNOSTIC_STRICT_OVERFLOW
136
# pragma GCC diagnostic pop
137
#endif
138
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
139
# pragma GCC diagnostic pop
140
#endif
141

142
static SIXELSTATUS
143
load_sixel(unsigned char        /* out */ **result,
156✔
144
           unsigned char        /* in */  *buffer,
145
           int                  /* in */  size,
146
           int                  /* out */ *psx,
147
           int                  /* out */ *psy,
148
           unsigned char        /* out */ **ppalette,
149
           int                  /* out */ *pncolors,
150
           int                  /* in */  reqcolors,
151
           int                  /* out */ *ppixelformat,
152
           sixel_allocator_t    /* in */  *allocator)
153
{
154
    SIXELSTATUS status;
156✔
155
    unsigned char *decoded_pixels;
156✔
156
    unsigned char *decoded_palette;
156✔
157
    size_t image_bytes;
156✔
158

159
    if (result == NULL || pncolors == NULL || ppixelformat == NULL ||
156!
160
            allocator == NULL) {
156!
161
        return SIXEL_BAD_ARGUMENT;
162
    }
163

164
    status = SIXEL_FALSE;
156✔
165
    decoded_pixels = NULL;
156✔
166
    decoded_palette = NULL;
156✔
167
    image_bytes = 0;
156✔
168

169
    status = sixel_decode_raw(buffer,
156✔
170
                              size,
171
                              &decoded_pixels,
172
                              psx,
173
                              psy,
174
                              &decoded_palette,
175
                              pncolors,
176
                              allocator);
177
    if (loader_trace_is_enabled()) {
156✔
178
        loader_trace_message("load_sixel: sixel_decode_raw -> %d",
3✔
179
                             (int)status);
180
    }
181
    if (SIXEL_FAILED(status)) {
156!
182
        goto end;
×
183
    }
184

185
    image_bytes = (size_t)(*psx) * (size_t)(*psy);
156✔
186

187
    if (ppalette == NULL ||
156!
188
            (reqcolors > 0 && *pncolors > reqcolors)) {
120!
189
        size_t rgb_bytes;
36✔
190
        size_t index;
36✔
191

192
        if (decoded_palette == NULL) {
36!
193
            status = SIXEL_BAD_INPUT;
×
194
            goto end;
×
195
        }
196
        rgb_bytes = image_bytes * 3u;
36✔
197
        *result = sixel_allocator_malloc(allocator, rgb_bytes);
36✔
198
        if (*result == NULL) {
36!
199
            sixel_helper_set_additional_message(
×
200
                "load_sixel: sixel_allocator_malloc() failed.");
201
            status = SIXEL_BAD_ALLOCATION;
×
202
            goto end;
×
203
        }
204
        for (index = 0; index < image_bytes; ++index) {
5,670,576✔
205
            size_t palette_index;
5,670,540✔
206
            size_t pixel_offset;
5,670,540✔
207

208
            palette_index = decoded_pixels[index] * 3u;
5,670,540✔
209
            pixel_offset = index * 3u;
5,670,540✔
210
            (*result)[pixel_offset + 0] = decoded_palette[palette_index + 0];
5,670,540✔
211
            (*result)[pixel_offset + 1] = decoded_palette[palette_index + 1];
5,670,540✔
212
            (*result)[pixel_offset + 2] = decoded_palette[palette_index + 2];
5,670,540✔
213
        }
214
        *ppixelformat = SIXEL_PIXELFORMAT_RGB888;
36✔
215
    } else {
1!
216
        *result = decoded_pixels;
120✔
217
        decoded_pixels = NULL;
120✔
218
        *ppalette = decoded_palette;
120✔
219
        decoded_palette = NULL;
120✔
220
        *ppixelformat = SIXEL_PIXELFORMAT_PAL8;
120✔
221
    }
222
    status = SIXEL_OK;
223

224
end:
156✔
225
    if (decoded_pixels != NULL) {
156✔
226
        sixel_allocator_free(allocator, decoded_pixels);
36✔
227
        decoded_pixels = NULL;
36✔
228
    }
229
    if (decoded_palette != NULL) {
156✔
230
        sixel_allocator_free(allocator, decoded_palette);
36✔
231
        decoded_palette = NULL;
36✔
232
    }
233

234
    return status;
235
}
236

237
static int
238
chunk_is_sixel(sixel_chunk_t const *chunk)
450✔
239
{
240
    unsigned char *p;
450✔
241
    unsigned char *end;
450✔
242

243
    p = chunk->buffer;
450✔
244
    end = p + chunk->size;
450✔
245

246
    if (chunk->size < 3) {
450!
247
        return 0;
248
    }
249

250
    p++;
447✔
251
    if (p >= end) {
447!
252
        return 0;
253
    }
254
    if (*(p - 1) == 0x90 || (*(p - 1) == 0x1b && *p == 0x50)) {
447!
255
        while (p++ < end) {
570!
256
            if (*p == 0x71) {
570✔
257
                return 1;
258
            } else if (*p == 0x18 || *p == 0x1a) {
414!
259
                return 0;
260
            } else if (*p < 0x20) {
414✔
261
                continue;
6✔
262
            } else if (*p < 0x30) {
408!
263
                return 0;
264
            } else if (*p < 0x40) {
2✔
265
                continue;
266
            }
267
        }
268
    }
269
    return 0;
270
}
271

272
static int
273
chunk_is_pnm(sixel_chunk_t const *chunk)
294✔
274
{
275
    if (chunk->size < 2) {
294!
276
        return 0;
277
    }
278
    if (chunk->buffer[0] == 'P' &&
291!
279
        chunk->buffer[1] >= '1' &&
30!
280
        chunk->buffer[1] <= '6') {
281
        return 1;
30✔
282
    }
283
    return 0;
284
}
285

286
typedef union _fn_pointer {
287
    sixel_load_image_function fn;
288
    void *                    p;
289
} fn_pointer;
290

291
SIXELSTATUS
292
load_with_builtin(
450✔
293
    sixel_chunk_t const *pchunk,
294
    int fstatic,
295
    int fuse_palette,
296
    int reqcolors,
297
    unsigned char *bgcolor,
298
    int loop_control,
299
    sixel_load_image_function fn_load,
300
    void *context)
301
{
302
    SIXELSTATUS status;
450✔
303
    unsigned char *pixels;
450✔
304
    int depth;
450✔
305
    sixel_frame_t *frame;
450✔
306
    fn_pointer fnp;
450✔
307
    stbi__context stb_context;
450✔
308
    char message[80];
450✔
309
    int nwrite;
450✔
310

311
    status = SIXEL_BAD_INPUT;
450✔
312
    pixels = NULL;
450✔
313
    depth = 0;
450✔
314
    frame = NULL;
450✔
315
    fnp.fn = NULL;
450✔
316
    stb_context = (stbi__context){ 0 };
450✔
317
    nwrite = 0;
450✔
318

319
    if (pchunk == NULL) {
450!
320
        status = SIXEL_BAD_ARGUMENT;
×
321
        goto end;
×
322
    }
323

324
    if (chunk_is_sixel(pchunk)) {
450✔
325
        status = sixel_frame_new(&frame, pchunk->allocator);
156✔
326
        if (SIXEL_FAILED(status)) {
156!
327
            goto end;
×
328
        }
329
        if (pchunk->size > INT_MAX) {
156!
330
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
331
            goto end;
×
332
        }
333
        status = load_sixel(&pixels,
120✔
334
                            pchunk->buffer,
156✔
335
                            (int)pchunk->size,
336
                            &frame->width,
337
                            &frame->height,
338
                            fuse_palette ? &frame->palette: NULL,
2✔
339
                            &frame->ncolors,
340
                            reqcolors,
341
                            &frame->pixelformat,
156✔
342
                            pchunk->allocator);
156✔
343
        if (SIXEL_FAILED(status)) {
156!
344
            goto end;
×
345
        }
346
        sixel_frame_set_pixels(frame, pixels);
156✔
347
    } else if (chunk_is_pnm(pchunk)) {
294✔
348
        status = sixel_frame_new(&frame, pchunk->allocator);
30✔
349
        if (SIXEL_FAILED(status)) {
30!
350
            goto end;
×
351
        }
352
        if (pchunk->size > INT_MAX) {
30!
353
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
354
            goto end;
×
355
        }
356
        status = load_pnm(pchunk->buffer,
30✔
357
                          (int)pchunk->size,
358
                          frame->allocator,
359
                          &pixels,
360
                          &frame->width,
361
                          &frame->height,
362
                          fuse_palette ? &frame->palette: NULL,
1!
363
                          &frame->ncolors,
364
                          &frame->pixelformat);
30!
365
        if (SIXEL_FAILED(status)) {
30!
366
            goto end;
×
367
        }
368
        sixel_frame_set_pixels(frame, pixels);
30✔
369
    } else if (chunk_is_gif(pchunk)) {
264✔
370
        fnp.fn = fn_load;
21✔
371
        if (pchunk->size > INT_MAX) {
21!
372
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
373
            goto end;
×
374
        }
375
        status = load_gif(pchunk->buffer,
42✔
376
                          (int)pchunk->size,
377
                          bgcolor,
378
                          reqcolors,
379
                          fuse_palette,
380
                          fstatic,
381
                          loop_control,
382
                          fnp.p,
383
                          context,
384
                          pchunk->allocator);
21✔
385
        if (SIXEL_FAILED(status)) {
21✔
386
            goto end;
387
        }
388
        goto end;
21✔
389
    } else {
390
        status = sixel_frame_new(&frame, pchunk->allocator);
243✔
391
        if (SIXEL_FAILED(status)) {
243!
392
            goto end;
×
393
        }
394
        if (pchunk->size > INT_MAX) {
243!
395
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
396
            goto end;
×
397
        }
398
        stbi_allocator = pchunk->allocator;
243✔
399
        stbi__start_mem(&stb_context,
243✔
400
                        pchunk->buffer,
243✔
401
                        (int)pchunk->size);
402
        pixels = stbi__load_and_postprocess_8bit(&stb_context,
486✔
403
                                                 &frame->width,
404
                                                 &frame->height,
243✔
405
                                                 &depth,
406
                                                 3);
407
        if (pixels == NULL) {
243✔
408
            sixel_helper_set_additional_message(stbi_failure_reason());
3✔
409
            status = SIXEL_STBI_ERROR;
3✔
410
            goto end;
3✔
411
        }
412
        sixel_frame_set_pixels(frame, pixels);
240✔
413
        frame->loop_count = 1;
240✔
414
        switch (depth) {
240!
415
        case 1:
240✔
416
        case 3:
417
        case 4:
418
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
240✔
419
            break;
240✔
420
        default:
×
421
            nwrite = snprintf(message,
×
422
                              sizeof(message),
423
                              "load_with_builtin() failed.\n"
424
                              "reason: unknown pixel-format.(depth: %d)\n",
425
                              depth);
426
            if (nwrite > 0) {
×
427
                sixel_helper_set_additional_message(message);
×
428
            }
429
            status = SIXEL_STBI_ERROR;
×
430
            goto end;
×
431
        }
432
    }
433

434
    status = sixel_frame_strip_alpha(frame, bgcolor);
426✔
435
    if (SIXEL_FAILED(status)) {
426!
436
        goto end;
×
437
    }
438

439
    status = fn_load(frame, context);
426✔
440
    if (SIXEL_FAILED(status)) {
426!
441
        goto end;
×
442
    }
443

444
    status = SIXEL_OK;
445

446
end:
450✔
447
    sixel_frame_unref(frame);
450✔
448

449
    return status;
450✔
450
}
451

452
/* emacs Local Variables:      */
453
/* emacs mode: c               */
454
/* emacs tab-width: 4          */
455
/* emacs indent-tabs-mode: nil */
456
/* emacs c-basic-offset: 4     */
457
/* emacs End:                  */
458
/* vim: set expandtab ts=4 sts=4 sw=4 : */
459
/* 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