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

saitoha / libsixel / 24098577013

07 Apr 2026 06:44PM UTC coverage: 84.535% (+0.009%) from 84.526%
24098577013

push

github

saitoha
frompsd: make alpha edge neighbor reads analyzer-safe

86518 of 181475 branches covered (47.67%)

50 of 51 new or added lines in 1 file covered. (98.04%)

3816 existing lines in 26 files now uncovered.

105494 of 124793 relevant lines covered (84.54%)

4942727.5 hits per line

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

84.15
/src/loader-common.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
 * Shared loader helpers used across backend implementations.  This module
26
 * centralizes trace logging, thumbnail size hints, and small detection
27
 * helpers so backend files stay narrow and platform headers remain isolated.
28
 */
29

30
#if defined(HAVE_CONFIG_H)
31
#include "config.h"
32
#endif
33

34
#include <stdio.h>
35
#include <stdlib.h>
36

37
#if HAVE_STRING_H
38
# include <string.h>
39
#endif
40
#if HAVE_STDARG_H
41
# include <stdarg.h>
42
#endif
43
#if HAVE_LIMITS_H
44
# include <limits.h>
45
#endif
46
#if HAVE_STDINT_H
47
# include <stdint.h>
48
#endif
49
#if HAVE_ERRNO_H
50
# include <errno.h>
51
#endif
52

53
/* Keep SIZE_MAX available even on strict C99 environments. */
54
#ifndef SIZE_MAX
55
# define SIZE_MAX ((size_t)-1)
56
#endif
57

58
#include <sixel.h>
59

60
#include "cms.h"
61
#include "compat_stub.h"
62
#include "frame.h"
63
#include "loader-common.h"
64
#include "logger.h"
65

66
#if defined(_MSC_VER)
67
# define SIXEL_LOADER_TLS __declspec(thread)
68
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L \
69
    && !defined(__PCC__)
70
# define SIXEL_LOADER_TLS _Thread_local
71
#elif (defined(__GNUC__) || defined(__clang__)) && !defined(__PCC__)
72
# define SIXEL_LOADER_TLS __thread
73
#else
74
# define SIXEL_LOADER_TLS
75
#endif
76

77
static int loader_trace_enabled;
78
static int thumbnailer_default_size_hint = SIXEL_THUMBNAILER_DEFAULT_SIZE;
79
static int thumbnailer_size_hint = SIXEL_THUMBNAILER_DEFAULT_SIZE;
80
static int thumbnailer_size_hint_initialized;
81
static int wic_ico_minsize_default;
82
static int wic_ico_minsize;
83
static int wic_ico_minsize_initialized;
84
static int libpng_enable_cms_default = 0;
85
static int libpng_enable_cms = 0;
86
static int builtin_enable_cms_default = 0;
87
static int builtin_enable_cms = 0;
88
static int loader_background_colorspace_initialized;
89
static int loader_background_colorspace_value = SIXEL_COLORSPACE_GAMMA;
90
/*
91
 * Per-thread temporary override used by loader.c when OSC11 provides
92
 * a terminal UI background color.
93
 */
94
static SIXEL_LOADER_TLS int loader_background_colorspace_override = -1;
95
static int loader_cms_target_initialized;
96
static int loader_cms_prefer_8bit_flag;
97
static int loader_cms_target_colorspace_value = SIXEL_COLORSPACE_LINEAR;
98

99
#undef SIXEL_LOADER_TLS
100

101
#define SIXEL_ENV_WIC_ICO_MINSIZE "SIXEL_LOADER_WIC_ICO_MINSIZE"
102
#define SIXEL_ENV_WIC_ICO_MINSIZE_LEGACY "SIXEL_LODER_WIC_ICO_MINSIZE"
103

104
static void
105
loader_wic_initialize_ico_minsize(void)
106
{
107
    char const *env_value;
108
    char *endptr;
109
    long parsed;
110

111
    if (wic_ico_minsize_initialized) {
×
UNCOV
112
        return;
×
113
    }
114

115
    wic_ico_minsize_initialized = 1;
×
116
    wic_ico_minsize_default = 0;
×
UNCOV
117
    wic_ico_minsize = 0;
×
118

119
    env_value = sixel_compat_getenv(SIXEL_ENV_WIC_ICO_MINSIZE);
×
120
    if (env_value == NULL || env_value[0] == '\0') {
×
UNCOV
121
        env_value = sixel_compat_getenv(SIXEL_ENV_WIC_ICO_MINSIZE_LEGACY);
×
122
    }
123
    if (env_value == NULL || env_value[0] == '\0') {
×
UNCOV
124
        return;
×
125
    }
126

127
    errno = 0;
×
128
    parsed = strtol(env_value, &endptr, 10);
×
UNCOV
129
    if (errno != 0) {
×
130
        return;
131
    }
UNCOV
132
    if (endptr == env_value || *endptr != '\0') {
×
133
        return;
134
    }
UNCOV
135
    if (parsed <= 0) {
×
136
        return;
137
    }
138
    if (parsed > (long)INT_MAX) {
×
139
        parsed = (long)INT_MAX;
140
    }
141

142
    wic_ico_minsize_default = (int)parsed;
×
UNCOV
143
    wic_ico_minsize = wic_ico_minsize_default;
×
144
}
×
145

146
int
147
loader_wic_get_ico_minsize(void)
148
{
UNCOV
149
    loader_wic_initialize_ico_minsize();
×
150

UNCOV
151
    return wic_ico_minsize;
×
152
}
153

154
void
155
sixel_helper_set_wic_ico_minsize(int size)
156
{
UNCOV
157
    loader_wic_initialize_ico_minsize();
×
158

159
    if (size > 0) {
×
UNCOV
160
        wic_ico_minsize = size;
×
161
    } else {
UNCOV
162
        wic_ico_minsize = wic_ico_minsize_default;
×
163
    }
UNCOV
164
}
×
165

166
int
167
loader_libpng_get_enable_cms(void)
168
{
UNCOV
169
    return libpng_enable_cms;
×
170
}
171

172
void
173
sixel_helper_set_libpng_enable_cms(int enable)
174
{
175
    if (enable >= 0) {
×
UNCOV
176
        libpng_enable_cms = enable != 0 ? 1 : 0;
×
177
    } else {
UNCOV
178
        libpng_enable_cms = libpng_enable_cms_default;
×
179
    }
UNCOV
180
}
×
181

182
int
183
loader_builtin_get_enable_cms(void)
184
{
UNCOV
185
    return builtin_enable_cms;
×
186
}
187

188
void
189
sixel_helper_set_builtin_enable_cms(int enable)
190
{
191
    if (enable >= 0) {
×
UNCOV
192
        builtin_enable_cms = enable != 0 ? 1 : 0;
×
193
    } else {
UNCOV
194
        builtin_enable_cms = builtin_enable_cms_default;
×
195
    }
UNCOV
196
}
×
197

198
SIXEL_INTERNAL_API void
199
sixel_helper_set_loader_background_colorspace(int colorspace)
50✔
200
{
201
    if (colorspace == SIXEL_COLORSPACE_GAMMA ||
50!
202
            colorspace == SIXEL_COLORSPACE_LINEAR) {
22!
203
        loader_background_colorspace_override = colorspace;
25✔
204
    } else {
11✔
205
        loader_background_colorspace_override = -1;
25✔
206
    }
207
}
50✔
208

209
static void
210
loader_background_initialize_colorspace(void)
3,995✔
211
{
212
    char const *env_value;
2,352✔
213

214
    if (loader_background_colorspace_initialized) {
3,995✔
215
        return;
133✔
216
    }
217

218
    loader_background_colorspace_initialized = 1;
3,855✔
219
    loader_background_colorspace_value = SIXEL_COLORSPACE_GAMMA;
3,855✔
220

221
    env_value = sixel_compat_getenv("SIXEL_LOADER_BACKGROUND_COLORSPACE");
3,855✔
222
    if (env_value == NULL || env_value[0] == '\0') {
3,855!
223
        return;
3,475✔
224
    }
225

226
    if (strcmp(env_value, "linear") == 0) {
240✔
227
        loader_background_colorspace_value = SIXEL_COLORSPACE_LINEAR;
72✔
228
    } else if (strcmp(env_value, "gamma") == 0) {
212!
229
        loader_background_colorspace_value = SIXEL_COLORSPACE_GAMMA;
138✔
230
    }
82✔
231
}
3,603!
232

233
SIXEL_INTERNAL_API int
234
loader_background_colorspace(void)
4,020✔
235
{
236
    int override_value;
2,365✔
237

238
    override_value = loader_background_colorspace_override;
4,020✔
239
    if (override_value == SIXEL_COLORSPACE_GAMMA ||
4,020!
240
            override_value == SIXEL_COLORSPACE_LINEAR) {
3,614!
241
        return override_value;
18✔
242
    }
243

244
    loader_background_initialize_colorspace();
3,995✔
245

246
    return loader_background_colorspace_value;
3,995✔
247
}
3,614✔
248

249
static void
250
loader_cms_initialize_target(void)
9,451✔
251
{
252
    char const *prefer8_env;
4,997✔
253
    char const *target_env;
4,997✔
254

255
    if (loader_cms_target_initialized) {
9,451✔
256
        return;
2,047✔
257
    }
258
    loader_cms_target_initialized = 1;
6,634✔
259
    loader_cms_prefer_8bit_flag = 0;
6,634✔
260
    loader_cms_target_colorspace_value = SIXEL_COLORSPACE_LINEAR;
6,634✔
261

262
    prefer8_env = sixel_compat_getenv("SIXEL_LOADER_PREFER_8BIT");
6,634✔
263
    if (prefer8_env != NULL && strcmp(prefer8_env, "1") == 0) {
6,634!
264
        loader_cms_prefer_8bit_flag = 1;
25✔
265
    }
11✔
266

267
    target_env = sixel_compat_getenv("SIXEL_LOADER_CMS_TARGET_COLORSPACE");
6,583✔
268
    if (target_env == NULL || target_env[0] == '\0') {
6,583!
269
        return;
993✔
270
    }
271
    if (strcmp(target_env, "gamma") == 0) {
5,487✔
272
        loader_cms_target_colorspace_value = SIXEL_COLORSPACE_GAMMA;
2,681✔
273
    } else if (strcmp(target_env, "linear") == 0) {
3,989!
274
        loader_cms_target_colorspace_value = SIXEL_COLORSPACE_LINEAR;
2,756✔
275
    } else if (strcmp(target_env, "cielab") == 0) {
1,266!
UNCOV
276
        loader_cms_target_colorspace_value = SIXEL_COLORSPACE_CIELAB;
×
277
    } else if (strcmp(target_env, "oklab") == 0) {
50!
278
        loader_cms_target_colorspace_value = SIXEL_COLORSPACE_OKLAB;
25✔
279
    } else if (strcmp(target_env, "din99d") == 0) {
36!
280
        loader_cms_target_colorspace_value = SIXEL_COLORSPACE_DIN99D;
25✔
281
    }
11✔
282
}
4,537!
283

284
int
285
loader_cms_prefer_8bit(void)
286
{
UNCOV
287
    loader_cms_initialize_target();
×
288

UNCOV
289
    return loader_cms_prefer_8bit_flag;
×
290
}
291

292
int
293
loader_cms_target_colorspace(void)
398✔
294
{
295
    loader_cms_initialize_target();
398✔
296

297
    return loader_cms_target_colorspace_value;
398✔
298
}
299

300
SIXELAPI int
301
loader_cms_target_pixelformat(void)
9,053✔
302
{
303
    loader_cms_initialize_target();
9,053✔
304

305
    if (loader_cms_prefer_8bit_flag) {
9,053✔
306
        return SIXEL_PIXELFORMAT_RGB888;
18✔
307
    }
308
    switch (loader_cms_target_colorspace_value) {
9,028!
309
    case SIXEL_COLORSPACE_GAMMA:
1,085!
310
        return SIXEL_PIXELFORMAT_RGBFLOAT32;
2,802✔
311
    case SIXEL_COLORSPACE_CIELAB:
×
312
        return SIXEL_PIXELFORMAT_CIELABFLOAT32;
313
    case SIXEL_COLORSPACE_OKLAB:
7!
314
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
18✔
315
    case SIXEL_COLORSPACE_DIN99D:
7!
316
        return SIXEL_PIXELFORMAT_DIN99DFLOAT32;
18✔
317
    case SIXEL_COLORSPACE_LINEAR:
3,824!
318
    default:
×
319
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
3,824✔
320
    }
321
}
4,307✔
322

323
void
324
sixel_helper_set_loader_cms_engine(int engine)
193,394✔
325
{
326
    if (engine < 0) {
193,394✔
327
        sixel_cms_set_engine(SIXEL_CMS_ENGINE_AUTO);
3,675✔
328
        return;
3,675✔
329
    }
330

331
    sixel_cms_set_engine((sixel_cms_engine_t)engine);
189,719✔
332
}
92,564✔
333

334
static unsigned short
335
loader_exif_read_u16(unsigned char const *data, int little_endian)
396✔
336
{
337
    if (data == NULL) {
396!
338
        return 0u;
339
    }
340
    if (little_endian) {
396!
341
        return (unsigned short)((unsigned short)data[0] |
×
UNCOV
342
                                (unsigned short)data[1] << 8u);
×
343
    }
344

345
    return (unsigned short)((unsigned short)data[0] << 8u |
792✔
346
                            (unsigned short)data[1]);
396✔
347
}
396✔
348

349
static unsigned int
350
loader_exif_read_u32(unsigned char const *data, int little_endian)
297✔
351
{
352
    if (data == NULL) {
297!
353
        return 0u;
354
    }
355
    if (little_endian) {
297!
356
        return (unsigned int)data[0] |
×
357
               ((unsigned int)data[1] << 8u) |
×
358
               ((unsigned int)data[2] << 16u) |
×
UNCOV
359
               ((unsigned int)data[3] << 24u);
×
360
    }
361

362
    return ((unsigned int)data[0] << 24u) |
891✔
363
           ((unsigned int)data[1] << 16u) |
594✔
364
           ((unsigned int)data[2] << 8u) |
594✔
365
           (unsigned int)data[3];
297✔
366
}
297✔
367

368
int
369
loader_exif_parse_orientation(unsigned char const *data,
99✔
370
                              size_t size,
371
                              int *orientation)
372
{
373
    int little_endian;
61✔
374
    unsigned short magic;
61✔
375
    unsigned int ifd_offset;
61✔
376
    unsigned short entry_count;
61✔
377
    size_t entries_offset;
61✔
378
    size_t index;
61✔
379
    unsigned short tag;
61✔
380
    unsigned short type;
61✔
381
    unsigned int count;
61✔
382
    unsigned int value_field;
61✔
383
    int parsed_orientation;
61✔
384

385
    little_endian = 0;
99✔
386
    magic = 0u;
99✔
387
    ifd_offset = 0u;
99✔
388
    entry_count = 0u;
99✔
389
    entries_offset = 0u;
99✔
390
    index = 0u;
99✔
391
    tag = 0u;
99✔
392
    type = 0u;
99✔
393
    count = 0u;
99✔
394
    value_field = 0u;
99✔
395
    parsed_orientation = 0;
99✔
396

397
    if (data == NULL || orientation == NULL) {
99!
398
        return 0;
399
    }
400

401
    if (size >= 6u && memcmp(data, "Exif\0\0", 6u) == 0) {
99!
402
        data += 6u;
34✔
403
        size -= 6u;
34✔
404
    }
34✔
405

406
    if (size < 8u) {
99!
407
        return 0;
408
    }
409
    if (data[0] == (unsigned char)'I' && data[1] == (unsigned char)'I') {
99!
410
        little_endian = 1;
411
    } else if (data[0] == (unsigned char)'M' &&
99!
412
               data[1] == (unsigned char)'M') {
99!
413
        little_endian = 0;
99✔
414
    } else {
99✔
415
        return 0;
416
    }
417

418
    magic = loader_exif_read_u16(data + 2u, little_endian);
99✔
419
    if (magic != 42u) {
99!
420
        return 0;
421
    }
422

423
    ifd_offset = loader_exif_read_u32(data + 4u, little_endian);
99✔
424
    if ((size_t)ifd_offset > size - 2u) {
99!
425
        return 0;
426
    }
427
    entry_count = loader_exif_read_u16(data + ifd_offset, little_endian);
99!
428
    entries_offset = (size_t)ifd_offset + 2u;
99✔
429
    if (entry_count > 0u &&
99!
430
        entries_offset > size - (size_t)entry_count * 12u) {
99!
431
        return 0;
432
    }
433

434
    for (index = 0u; index < (size_t)entry_count; ++index) {
99!
435
        unsigned char const *entry;
61✔
436

437
        entry = data + entries_offset + index * 12u;
99✔
438
        tag = loader_exif_read_u16(entry, little_endian);
99!
439
        if (tag != 0x0112u) {
99!
UNCOV
440
            continue;
×
441
        }
442

443
        type = loader_exif_read_u16(entry + 2u, little_endian);
99!
444
        count = loader_exif_read_u32(entry + 4u, little_endian);
99✔
445
        value_field = loader_exif_read_u32(entry + 8u, little_endian);
99✔
446
        if (type != 3u || count == 0u) {
99!
447
            return 0;
448
        }
449

450
        if (count == 1u) {
99!
451
            if (little_endian) {
99!
UNCOV
452
                parsed_orientation = (int)(value_field & 0xffffu);
×
453
            } else {
454
                parsed_orientation = (int)((value_field >> 16u) & 0xffffu);
99✔
455
            }
456
        } else {
99✔
UNCOV
457
            if ((size_t)value_field > size - 2u) {
×
458
                return 0;
459
            }
UNCOV
460
            parsed_orientation = (int)loader_exif_read_u16(
×
461
                data + value_field,
462
                little_endian);
463
        }
464

465
        if (parsed_orientation < 1 || parsed_orientation > 8) {
99!
466
            return 0;
467
        }
468

469
        *orientation = parsed_orientation;
99✔
470
        return 1;
99✔
471
    }
61!
472

473
    return 0;
474
}
99✔
475

476
static void
477
loader_exif_map_coordinates(int orientation,
15,264✔
478
                            int src_width,
479
                            int src_height,
480
                            int dst_x,
481
                            int dst_y,
482
                            int *src_x,
483
                            int *src_y)
484
{
485
    int mapped_x;
9,312✔
486
    int mapped_y;
9,312✔
487

488
    mapped_x = dst_x;
15,264✔
489
    mapped_y = dst_y;
15,264✔
490
    switch (orientation) {
15,264!
491
    case 2:
×
492
        mapped_x = src_width - 1 - dst_x;
×
493
        mapped_y = dst_y;
×
UNCOV
494
        break;
×
495
    case 3:
×
496
        mapped_x = src_width - 1 - dst_x;
×
497
        mapped_y = src_height - 1 - dst_y;
×
UNCOV
498
        break;
×
499
    case 4:
×
500
        mapped_x = dst_x;
×
501
        mapped_y = src_height - 1 - dst_y;
×
UNCOV
502
        break;
×
503
    case 5:
×
504
        mapped_x = dst_y;
505
        mapped_y = dst_x;
506
        break;
507
    case 6:
×
508
        mapped_x = dst_y;
15,264✔
509
        mapped_y = src_height - 1 - dst_x;
15,264✔
510
        break;
15,264✔
511
    case 7:
×
512
        mapped_x = src_width - 1 - dst_y;
×
513
        mapped_y = src_height - 1 - dst_x;
×
UNCOV
514
        break;
×
515
    case 8:
×
516
        mapped_x = src_width - 1 - dst_y;
×
517
        mapped_y = dst_x;
×
518
        break;
×
UNCOV
519
    case 1:
×
520
    default:
×
521
        mapped_x = dst_x;
×
522
        mapped_y = dst_y;
×
UNCOV
523
        break;
×
524
    }
525

526
    if (src_x != NULL) {
15,264!
527
        *src_x = mapped_x;
15,264✔
528
    }
15,264✔
529
    if (src_y != NULL) {
15,264!
530
        *src_y = mapped_y;
15,264✔
531
    }
15,264✔
532
}
15,264✔
533

534
SIXELSTATUS
535
loader_frame_apply_orientation(sixel_frame_t *frame,
139✔
536
                               int orientation)
537
{
538
    SIXELSTATUS status;
85✔
539
    sixel_allocator_t *allocator;
85✔
540
    int src_width;
85✔
541
    int src_height;
85✔
542
    int dst_width;
85✔
543
    int dst_height;
85✔
544
    size_t src_pixel_count;
85✔
545
    size_t dst_pixel_count;
85✔
546
    int depth;
85✔
547
    int channels;
85✔
548
    int x;
85✔
549
    int y;
85✔
550
    int src_x;
85✔
551
    int src_y;
85✔
552
    size_t src_index;
85✔
553
    size_t dst_index;
85✔
554
    unsigned char *src_bytes;
85✔
555
    unsigned char *dst_bytes;
85✔
556
    float *src_floats;
85✔
557
    float *dst_floats;
85✔
558
    unsigned char *src_mask;
85✔
559
    unsigned char *dst_mask;
85✔
560
    size_t pixel_stride;
85✔
561

562
    status = SIXEL_OK;
139✔
563
    allocator = NULL;
139✔
564
    src_width = 0;
139✔
565
    src_height = 0;
139✔
566
    dst_width = 0;
139✔
567
    dst_height = 0;
139✔
568
    src_pixel_count = 0u;
139✔
569
    dst_pixel_count = 0u;
139✔
570
    depth = 0;
139✔
571
    channels = 0;
139✔
572
    x = 0;
139✔
573
    y = 0;
139✔
574
    src_x = 0;
139✔
575
    src_y = 0;
139✔
576
    src_index = 0u;
139✔
577
    dst_index = 0u;
139✔
578
    src_bytes = NULL;
139✔
579
    dst_bytes = NULL;
139✔
580
    src_floats = NULL;
139✔
581
    dst_floats = NULL;
139✔
582
    src_mask = NULL;
139✔
583
    dst_mask = NULL;
139✔
584
    pixel_stride = 0u;
139✔
585

586
    if (frame == NULL) {
139!
587
        return SIXEL_BAD_ARGUMENT;
588
    }
589
    if (orientation <= 1 || orientation > 8) {
139!
590
        return SIXEL_OK;
591
    }
592
    if (frame->width <= 0 || frame->height <= 0 || frame->allocator == NULL) {
139!
593
        return SIXEL_BAD_ARGUMENT;
594
    }
595

596
    src_width = frame->width;
139✔
597
    src_height = frame->height;
139✔
598
    if ((size_t)src_width > SIZE_MAX / (size_t)src_height) {
139!
599
        return SIXEL_BAD_INTEGER_OVERFLOW;
600
    }
601
    src_pixel_count = (size_t)src_width * (size_t)src_height;
139✔
602
    if (orientation >= 5 && orientation <= 8) {
139!
603
        dst_width = src_height;
139✔
604
        dst_height = src_width;
139✔
605
    } else {
139✔
606
        dst_width = src_width;
×
UNCOV
607
        dst_height = src_height;
×
608
    }
609
    if ((size_t)dst_width > SIZE_MAX / (size_t)dst_height) {
139!
610
        return SIXEL_BAD_INTEGER_OVERFLOW;
611
    }
612
    dst_pixel_count = (size_t)dst_width * (size_t)dst_height;
139✔
613
    allocator = frame->allocator;
139✔
614
    depth = sixel_helper_compute_depth(frame->pixelformat);
139✔
615
    if (depth <= 0) {
139!
616
        return SIXEL_BAD_ARGUMENT;
617
    }
618

619
    if (SIXEL_PIXELFORMAT_IS_FLOAT32(frame->pixelformat)) {
139!
620
        if (depth <= 0 || depth % (int)sizeof(float) != 0) {
10!
621
            return SIXEL_BAD_ARGUMENT;
622
        }
623
        channels = depth / (int)sizeof(float);
10✔
624
        if (channels <= 0) {
10!
625
            return SIXEL_BAD_ARGUMENT;
626
        }
627
        if (frame->pixels.f32ptr == NULL) {
10!
628
            return SIXEL_BAD_ARGUMENT;
629
        }
630
        if ((size_t)channels > SIZE_MAX / sizeof(float)) {
10!
631
            return SIXEL_BAD_INTEGER_OVERFLOW;
632
        }
633
        pixel_stride = (size_t)depth;
10✔
634
        if (dst_pixel_count > SIZE_MAX / pixel_stride) {
10!
635
            return SIXEL_BAD_INTEGER_OVERFLOW;
636
        }
637

638
        dst_floats = (float *)sixel_allocator_malloc(allocator,
20✔
639
                                                     dst_pixel_count *
20✔
640
                                                     pixel_stride);
10✔
641
        if (dst_floats == NULL) {
10!
642
            return SIXEL_BAD_ALLOCATION;
643
        }
644
        src_floats = frame->pixels.f32ptr;
10✔
645
        for (y = 0; y < dst_height; ++y) {
130!
646
            for (x = 0; x < dst_width; ++x) {
1,080!
647
                loader_exif_map_coordinates(orientation,
1,920✔
648
                                            src_width,
960✔
649
                                            src_height,
960✔
650
                                            x,
960✔
651
                                            y,
960✔
652
                                            &src_x,
653
                                            &src_y);
654
                src_index = (size_t)src_y * (size_t)src_width + (size_t)src_x;
960✔
655
                dst_index = (size_t)y * (size_t)dst_width + (size_t)x;
960✔
656
                memcpy((unsigned char *)(dst_floats + dst_index * channels),
960✔
657
                       (unsigned char const *)(src_floats +
658
                                               src_index * channels),
659
                       pixel_stride);
660
            }
960✔
661
        }
120✔
662
        frame->pixels.f32ptr = dst_floats;
10✔
663
        sixel_allocator_free(allocator, src_floats);
10✔
664
    } else {
10✔
665
        if (frame->pixelformat == SIXEL_PIXELFORMAT_PAL1 ||
258!
666
            frame->pixelformat == SIXEL_PIXELFORMAT_PAL2 ||
129!
667
            frame->pixelformat == SIXEL_PIXELFORMAT_PAL4 ||
129!
668
            frame->pixelformat == SIXEL_PIXELFORMAT_G1 ||
129!
669
            frame->pixelformat == SIXEL_PIXELFORMAT_G2 ||
129!
670
            frame->pixelformat == SIXEL_PIXELFORMAT_G4) {
129!
671
            return SIXEL_BAD_ARGUMENT;
672
        }
673
        if (depth <= 0) {
129!
674
            return SIXEL_BAD_ARGUMENT;
675
        }
676
        if (frame->pixels.u8ptr == NULL) {
129!
677
            return SIXEL_BAD_ARGUMENT;
678
        }
679
        pixel_stride = (size_t)depth;
129✔
680
        if (dst_pixel_count > SIZE_MAX / pixel_stride) {
129!
681
            return SIXEL_BAD_INTEGER_OVERFLOW;
682
        }
683

684
        dst_bytes = (unsigned char *)sixel_allocator_malloc(allocator,
258✔
685
                                                             dst_pixel_count *
258✔
686
                                                             pixel_stride);
129✔
687
        if (dst_bytes == NULL) {
129!
688
            return SIXEL_BAD_ALLOCATION;
689
        }
690
        src_bytes = frame->pixels.u8ptr;
129✔
691
        for (y = 0; y < dst_height; ++y) {
1,677!
692
            for (x = 0; x < dst_width; ++x) {
13,932!
693
                loader_exif_map_coordinates(orientation,
24,768✔
694
                                            src_width,
12,384✔
695
                                            src_height,
12,384✔
696
                                            x,
12,384✔
697
                                            y,
12,384✔
698
                                            &src_x,
699
                                            &src_y);
700
                src_index = (size_t)src_y * (size_t)src_width + (size_t)src_x;
12,384✔
701
                dst_index = (size_t)y * (size_t)dst_width + (size_t)x;
12,384✔
702
                memcpy(dst_bytes + dst_index * pixel_stride,
12,384✔
703
                       src_bytes + src_index * pixel_stride,
704
                       pixel_stride);
705
            }
12,384✔
706
        }
1,548✔
707
        frame->pixels.u8ptr = dst_bytes;
129✔
708
        sixel_allocator_free(allocator, src_bytes);
129✔
709
    }
710

711
    src_mask = frame->transparent_mask;
139✔
712
    dst_mask = NULL;
139✔
713
    if (src_mask != NULL) {
139!
714
        if (frame->transparent_mask_size != src_pixel_count) {
20!
715
            sixel_allocator_free(allocator, src_mask);
×
716
            frame->transparent_mask = NULL;
×
UNCOV
717
            frame->transparent_mask_size = 0u;
×
718
        } else {
719
            dst_mask = (unsigned char *)sixel_allocator_malloc(allocator,
40✔
720
                                                               dst_pixel_count);
20✔
721
            if (dst_mask == NULL) {
20!
722
                return SIXEL_BAD_ALLOCATION;
723
            }
724
            for (y = 0; y < dst_height; ++y) {
260!
725
                for (x = 0; x < dst_width; ++x) {
2,160!
726
                    loader_exif_map_coordinates(orientation,
3,840✔
727
                                                src_width,
1,920✔
728
                                                src_height,
1,920✔
729
                                                x,
1,920✔
730
                                                y,
1,920✔
731
                                                &src_x,
732
                                                &src_y);
733
                    src_index = (size_t)src_y * (size_t)src_width +
3,840✔
734
                                (size_t)src_x;
1,920✔
735
                    dst_index = (size_t)y * (size_t)dst_width + (size_t)x;
1,920✔
736
                    dst_mask[dst_index] = src_mask[src_index];
1,920✔
737
                }
1,920✔
738
            }
240✔
739
            frame->transparent_mask = dst_mask;
20✔
740
            frame->transparent_mask_size = dst_pixel_count;
20✔
741
            sixel_allocator_free(allocator, src_mask);
20✔
742
        }
743
    }
20✔
744

745
    frame->width = dst_width;
139✔
746
    frame->height = dst_height;
139✔
747

748
    return status;
139✔
749
}
139✔
750

751
void
752
loader_thumbnailer_initialize_size_hint(void)
45,830✔
753
{
754
    char const *env_value;
24,659✔
755
    char *endptr;
24,659✔
756
    long parsed;
24,659✔
757

758
    if (thumbnailer_size_hint_initialized) {
45,830✔
759
        return;
23,518✔
760
    }
761

762
    thumbnailer_size_hint_initialized = 1;
42,644✔
763
    thumbnailer_default_size_hint = SIXEL_THUMBNAILER_DEFAULT_SIZE;
42,644✔
764
    thumbnailer_size_hint = thumbnailer_default_size_hint;
42,644✔
765

766
    env_value = sixel_compat_getenv("SIXEL_THUMBNAILER_HINT_SIZE");
42,644✔
767
    if (env_value == NULL || env_value[0] == '\0') {
42,644!
768
        return;
31,859✔
769
    }
770

771
    errno = 0;
589✔
772
    parsed = strtol(env_value, &endptr, 10);
589✔
773
    if (errno != 0) {
589!
774
        return;
10✔
775
    }
776
    if (endptr == env_value || *endptr != '\0') {
579!
777
        return;
4✔
778
    }
779
    if (parsed <= 0) {
572✔
780
        return;
14✔
781
    }
782
    if (parsed > (long)INT_MAX) {
555!
783
        parsed = (long)INT_MAX;
784
    }
785

786
    thumbnailer_default_size_hint = (int)parsed;
555✔
787
    thumbnailer_size_hint = thumbnailer_default_size_hint;
555✔
788
}
24,959!
789

790
int
791
loader_thumbnailer_get_size_hint(void)
1,375✔
792
{
793
    loader_thumbnailer_initialize_size_hint();
1,375✔
794

795
    return thumbnailer_size_hint;
1,375✔
796
}
797

798
int
799
loader_thumbnailer_get_default_size_hint(void)
800
{
UNCOV
801
    loader_thumbnailer_initialize_size_hint();
×
802

UNCOV
803
    return thumbnailer_default_size_hint;
×
804
}
805

806
void
807
sixel_helper_set_loader_trace(int enable)
45,421✔
808
{
809
    loader_trace_enabled = enable ? 1 : 0;
45,421!
810
}
45,421✔
811

812
void
813
sixel_helper_set_thumbnail_size_hint(int size)
43,070✔
814
{
815
    loader_thumbnailer_initialize_size_hint();
43,070✔
816

817
    if (size > 0) {
43,070✔
818
        thumbnailer_size_hint = size;
1,266✔
819
    } else {
626✔
820
        thumbnailer_size_hint = thumbnailer_default_size_hint;
41,804✔
821
    }
822
}
43,070✔
823

824
void
825
loader_trace_message(char const *format, ...)
113,617✔
826
{
827
    va_list args;
59,459✔
828

829
    if (!loader_trace_enabled) {
113,617✔
830
        return;
107,773✔
831
    }
832

833
    fprintf(stderr, "libsixel: ");
5,844✔
834

835
    va_start(args, format);
5,844✔
836
    sixel_compat_vfprintf(stderr, format, args);
5,844✔
837
    va_end(args);
5,844✔
838

839
    fprintf(stderr, "\n");
5,844✔
840
}
53,169!
841

842

843
/*
844
 * Return non-zero when SIXEL_TRACE_TOPIC contains the given token.
845
 * Supported separators are comma, colon, semicolon, and whitespace.
846
 */
847
int
848
sixel_trace_topic_is_enabled(char const *topic)
1,014,006✔
849
{
850
    char const *topics;
543,272✔
851
    char const *cursor;
543,272✔
852
    char const *token_end;
543,272✔
853
    size_t topic_length;
543,272✔
854
    size_t token_length;
543,272✔
855

856
    topics = NULL;
1,014,006✔
857
    cursor = NULL;
1,014,006✔
858
    token_end = NULL;
1,014,006✔
859
    topic_length = 0u;
1,014,006✔
860
    token_length = 0u;
1,014,006✔
861

862
    if (topic == NULL || topic[0] == '\0') {
1,014,006!
863
        return 0;
12✔
864
    }
865

866
    topic_length = strlen(topic);
1,014,005✔
867
    if (topic_length == 0u) {
1,014,005!
868
        return 0;
869
    }
870

871
    topics = sixel_compat_getenv("SIXEL_TRACE_TOPIC");
1,014,005✔
872
    if (topics == NULL || topics[0] == '\0') {
1,014,005!
873
        return 0;
626,633✔
874
    }
875

876
    cursor = topics;
155,055✔
877
    while (*cursor != '\0') {
372,956!
878
        while (*cursor != '\0' &&
358,272!
879
               (*cursor == ' ' || *cursor == '\t' || *cursor == ',' ||
244,304!
880
                *cursor == ':' || *cursor == ';')) {
170,358!
881
            ++cursor;
18,899✔
882
        }
883
        if (*cursor == '\0') {
225,403!
884
            break;
885
        }
886

887
        token_end = cursor;
169,802✔
888
        while (*token_end != '\0' &&
2,133,007!
889
               *token_end != ' ' && *token_end != '\t' &&
1,837,823!
890
               *token_end != ',' && *token_end != ':' &&
3,113,248!
891
               *token_end != ';') {
1,807,207!
892
            ++token_end;
2,387,757✔
893
        }
894

895
        token_length = (size_t)(token_end - cursor);
225,403✔
896
        if (token_length == topic_length &&
225,403!
897
                strncmp(cursor, topic, token_length) == 0) {
82,985!
898
            return 1;
44,916✔
899
        }
900

901
        cursor = token_end;
124,886✔
902
    }
903

904
    return 0;
110,135✔
905
}
549,746✔
906

907
/* Emit topic-scoped diagnostics selected through SIXEL_TRACE_TOPIC. */
908
void
909
sixel_trace_topic_message(
1,014,001✔
910
    char const *topic,
911
    char const *format,
912
    ...)
913
{
914
    va_list args;
543,271✔
915

916
    if (!sixel_trace_topic_is_enabled(topic)) {
1,014,001✔
917
        return;
955,049✔
918
    }
919

920
    fprintf(stderr,
117,908!
921
            "libsixel[%s]: ",
922
            topic != NULL && topic[0] != '\0' ? topic : "trace");
58,954!
923

924
    va_start(args, format);
58,954✔
925
    sixel_compat_vfprintf(stderr, format, args);
58,954✔
926
    va_end(args);
58,954✔
927

928
    fprintf(stderr, "\n");
58,954✔
929
}
549,744!
930

931
void
932
loader_trace_try(char const *name)
95,245✔
933
{
934
    if (loader_trace_enabled) {
95,245✔
935
        fprintf(stderr, "libsixel: trying %s loader\n", name);
2,361✔
936
    }
1,529✔
937
}
95,245✔
938

939
void
940
loader_trace_result(char const *name, SIXELSTATUS status)
95,238✔
941
{
942
    if (!loader_trace_enabled) {
95,238✔
943
        return;
69,817✔
944
    }
945
    if (SIXEL_SUCCEEDED(status)) {
2,361✔
946
        fprintf(stderr, "libsixel: loader %s succeeded\n", name);
2,301✔
947
    } else {
1,483✔
948
        fprintf(stderr, "libsixel: loader %s failed (%s)\n",
106✔
949
                name, sixel_helper_format_error(status));
46✔
950
    }
951
}
48,548✔
952

953
int
954
loader_trace_is_enabled(void)
73,983✔
955
{
956
    return loader_trace_enabled;
73,983✔
957
}
958

959
int
960
chunk_is_png(sixel_chunk_t const *chunk)
95,607✔
961
{
962
    if (chunk == NULL || chunk->size < 8) {
95,607!
963
        return 0;
43✔
964
    }
965

966
    /*
967
     * PNG streams begin with an 8-byte signature.  Checking the fixed magic
968
     * sequence keeps the detection fast and avoids depending on libpng
969
     * helpers when only the signature is needed.
970
     */
971
    if (chunk->buffer[0] == (unsigned char)0x89 &&
101,215!
972
        chunk->buffer[1] == 'P' &&
16,347!
973
        chunk->buffer[2] == 'N' &&
16,347!
974
        chunk->buffer[3] == 'G' &&
16,347!
975
        chunk->buffer[4] == (unsigned char)0x0d &&
16,347!
976
        chunk->buffer[5] == (unsigned char)0x0a &&
16,347!
977
        chunk->buffer[6] == (unsigned char)0x1a &&
16,347!
978
        chunk->buffer[7] == (unsigned char)0x0a) {
16,347!
979
        return 1;
16,347✔
980
    }
981

982
    return 0;
58,384✔
983
}
44,093✔
984

985
int
986
chunk_is_jpeg(sixel_chunk_t const *chunk)
95,539✔
987
{
988
    if (chunk == NULL || chunk->size < 2) {
95,539!
989
        return 0;
990
    }
991

992
    /*
993
     * JPEG files start with SOI (Start of Image) marker 0xFF 0xD8.  The GD
994
     * loader uses this to decide whether libgd should attempt JPEG decoding.
995
     */
996
    if (chunk->buffer[0] == (unsigned char)0xff &&
95,539!
997
        chunk->buffer[1] == (unsigned char)0xd8) {
1,209!
998
        return 1;
1,209✔
999
    }
1000

1001
    return 0;
68,900✔
1002
}
44,025✔
1003

1004
int
1005
chunk_is_webp(sixel_chunk_t const *chunk)
14✔
1006
{
1007
    if (chunk == NULL || chunk->size < 12) {
14!
1008
        return 0;
2✔
1009
    }
1010

1011
    /*
1012
     * WebP files use a RIFF container.  The stream starts with \"RIFF\",
1013
     * followed by a 32-bit size field, and then the literal \"WEBP\" tag.
1014
     */
1015
    if (chunk->buffer[0] == 'R' &&
12!
1016
        chunk->buffer[1] == 'I' &&
×
1017
        chunk->buffer[2] == 'F' &&
×
1018
        chunk->buffer[3] == 'F' &&
×
1019
        chunk->buffer[8] == 'W' &&
×
1020
        chunk->buffer[9] == 'E' &&
×
1021
        chunk->buffer[10] == 'B' &&
×
1022
        chunk->buffer[11] == 'P') {
×
UNCOV
1023
        return 1;
×
1024
    }
1025

1026
    return 0;
12✔
1027
}
14✔
1028

1029
int
1030
chunk_is_bmp(sixel_chunk_t const *chunk)
20✔
1031
{
1032
    if (chunk == NULL || chunk->size < 2) {
20!
1033
        return 0;
1034
    }
1035

1036
    /* BMP headers begin with the literal characters 'B' 'M'. */
1037
    if (chunk->buffer[0] == 'B' && chunk->buffer[1] == 'M') {
20!
UNCOV
1038
        return 1;
×
1039
    }
1040

1041
    return 0;
20✔
1042
}
20✔
1043

1044
int
1045
chunk_is_tiff(sixel_chunk_t const *chunk)
36,231✔
1046
{
1047
    if (chunk == NULL || chunk->size < 4) {
36,231!
1048
        return 0;
18✔
1049
    }
1050

1051
    /*
1052
     * TIFF headers begin with either "II*\0", "MM\0*", or the BigTIFF
1053
     * variants "II+\0"/"MM\0+". Checking the first four bytes is enough to
1054
     * decide whether the stream can be probed by libtiff.
1055
     */
1056
    if ((chunk->buffer[0] == 'I' && chunk->buffer[1] == 'I' &&
36,213!
1057
         chunk->buffer[2] == (unsigned char)0x2a &&
×
UNCOV
1058
         chunk->buffer[3] == (unsigned char)0x00) ||
×
1059
        (chunk->buffer[0] == 'M' && chunk->buffer[1] == 'M' &&
32,140!
1060
         chunk->buffer[2] == (unsigned char)0x00 &&
×
UNCOV
1061
         chunk->buffer[3] == (unsigned char)0x2a) ||
×
1062
        (chunk->buffer[0] == 'I' && chunk->buffer[1] == 'I' &&
32,140!
1063
         chunk->buffer[2] == (unsigned char)0x2b &&
×
UNCOV
1064
         chunk->buffer[3] == (unsigned char)0x00) ||
×
1065
        (chunk->buffer[0] == 'M' && chunk->buffer[1] == 'M' &&
32,140!
1066
         chunk->buffer[2] == (unsigned char)0x00 &&
×
1067
         chunk->buffer[3] == (unsigned char)0x2b)) {
×
UNCOV
1068
        return 1;
×
1069
    }
1070

1071
    return 0;
36,213✔
1072
}
36,231✔
1073

1074
int
1075
chunk_is_gif(sixel_chunk_t const *chunk)
48,358✔
1076
{
1077
    if (chunk->size < 6) {
48,358✔
1078
        return 0;
38✔
1079
    }
1080
    if (chunk->buffer[0] == 'G' &&
49,348!
1081
        chunk->buffer[1] == 'I' &&
2,544!
1082
        chunk->buffer[2] == 'F' &&
2,544!
1083
        chunk->buffer[3] == '8' &&
2,544!
1084
        (chunk->buffer[4] == '7' || chunk->buffer[4] == '9') &&
2,544!
1085
        chunk->buffer[5] == 'a') {
2,544!
1086
        return 1;
2,544✔
1087
    }
1088
    return 0;
32,722✔
1089
}
20,832✔
1090

1091
/* emacs Local Variables:      */
1092
/* emacs mode: c               */
1093
/* emacs tab-width: 4          */
1094
/* emacs indent-tabs-mode: nil */
1095
/* emacs c-basic-offset: 4     */
1096
/* emacs End:                  */
1097
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1098
/* 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