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

saitoha / libsixel / 21254372888

22 Jan 2026 03:32PM UTC coverage: 66.513% (+0.6%) from 65.908%
21254372888

push

github

saitoha
tests: change PSNR_Y base-line value on TGA regression tests

26721 of 40174 relevant lines covered (66.51%)

2936029.03 hits per line

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

82.22
/src/loader.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 deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * 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 FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
 * SOFTWARE.
24
 */
25

26
#if defined(HAVE_CONFIG_H)
27
#include "config.h"
28
#endif
29
#if !defined(_POSIX_C_SOURCE)
30
# define _POSIX_C_SOURCE 200809L
31
#endif
32

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

37
#if HAVE_STRING_H
38
# include <string.h>
39
#endif
40
#if HAVE_CTYPE_H
41
# include <ctype.h>
42
#endif
43
#if HAVE_STDARG_H
44
# include <stdarg.h>
45
#endif
46
#if HAVE_SYS_TYPES_H
47
# include <sys/types.h>
48
#endif
49
#if HAVE_UNISTD_H
50
# include <unistd.h>
51
#endif
52
#if !defined(PATH_MAX)
53
#define PATH_MAX 4096
54
#endif
55
#if HAVE_FCNTL_H
56
# include <fcntl.h>
57
#endif
58
#if HAVE_SYS_TIME_H
59
# include <sys/time.h>
60
#elif HAVE_TIME_H
61
# include <time.h>
62
#endif  /* HAVE_SYS_TIME_H HAVE_TIME_H */
63
#if defined(_WIN32)
64
# if !defined(UNICODE)
65
#  define UNICODE
66
# endif
67
# if !defined(_UNICODE)
68
#  define _UNICODE
69
# endif
70
# if !defined(WIN32_LEAN_AND_MEAN)
71
#  define WIN32_LEAN_AND_MEAN
72
# endif
73
# include <windows.h>
74
#endif
75
#if HAVE_ERRNO_H
76
# include <errno.h>
77
#endif
78
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
79
# include <CoreServices/CoreServices.h>
80
#endif
81
#if HAVE_SYS_WAIT_H
82
# include <sys/wait.h>
83
#endif
84
#if HAVE_SPAWN_H
85
# include <spawn.h>
86
#endif
87
#if HAVE_LIMITS_H
88
# include <limits.h>
89
#endif
90
#if HAVE_DIRENT_H
91
# include <dirent.h>
92
#endif
93

94
#if !defined(HAVE_MEMCPY)
95
# define memcpy(d, s, n) (bcopy ((s), (d), (n)))
96
#endif
97

98
#include <sixel.h>
99
#include "loader.h"
100
#include "loader-builtin.h"
101
#include "loader-common.h"
102
#include "loader-coregraphics.h"
103
#include "loader-gd.h"
104
#include "loader-gdk-pixbuf2.h"
105
#include "loader-gnome-thumbnailer.h"
106
#include "loader-libjpeg.h"
107
#include "loader-libpng.h"
108
#include "loader-quicklook.h"
109
#include "loader-registry.h"
110
#include "loader-wic.h"
111
#include "compat_stub.h"
112
#include "frame.h"
113
#include "chunk.h"
114
#include "allocator.h"
115
#include "assessment.h"
116
#include "encoder.h"
117
#include "logger.h"
118
#include "sixel_atomic.h"
119

120
/*
121
 * Internal loader state carried across backends.  The fields mirror the
122
 * original `loader.c` layout to keep statistics, logging, and allocator
123
 * ownership centralized while implementations move into per-backend files.
124
 */
125
struct sixel_loader {
126
    sixel_atomic_u32_t ref;
127
    int fstatic;
128
    int fuse_palette;
129
    int reqcolors;
130
    unsigned char bgcolor[3];
131
    int has_bgcolor;
132
    int loop_control;
133
    int finsecure;
134
    int const *cancel_flag;
135
    void *context;
136
    sixel_logger_t logger;
137
    sixel_assessment_t *assessment;
138
    char *loader_order;
139
    sixel_allocator_t *allocator;
140
    char last_loader_name[64];
141
    char last_source_path[PATH_MAX];
142
    size_t last_input_bytes;
143
    int callback_failed;
144
    int log_loader_finished;
145
    char log_path[PATH_MAX];
146
    char log_loader_name[64];
147
    size_t log_input_bytes;
148
};
149

150
typedef struct sixel_loader_callback_state {
151
    sixel_loader_t *loader;
152
    sixel_load_image_function fn;
153
    void *context;
154
} sixel_loader_callback_state_t;
155

156

157
#if HAVE_POSIX_SPAWNP
158
extern char **environ;
159
#endif
160

161
static char *
162
loader_strdup(char const *text, sixel_allocator_t *allocator)
22✔
163
{
164
    char *copy;
22✔
165
    size_t length;
22✔
166

167
    if (text == NULL) {
22✔
168
        return NULL;
169
    }
170

171
    length = strlen(text) + 1;
22✔
172
    copy = (char *)sixel_allocator_malloc(allocator, length);
22✔
173
    if (copy == NULL) {
22✔
174
        return NULL;
175
    }
176

177
#if HAVE_STRCPY_S
178
    (void)strcpy_s(copy, (rsize_t)(length - 1), text);
179
#else
180
    memcpy(copy, text, length);
22✔
181
#endif
182

183
    return copy;
22✔
184
}
185

186

187

188
/*
189
 * Emit loader stage markers.
190
 *
191
 * Loader callbacks run the downstream pipeline synchronously, so the finish
192
 * marker must be issued before invoking fn_load() to avoid inflating the
193
 * loader span. The helper keeps the formatting consistent with
194
 * sixel_encoder_log_stage() without depending on encoder internals.
195
 */
196
static void
197
loader_log_stage(sixel_loader_t *loader,
1,441✔
198
                 char const *event,
199
                 char const *fmt,
200
                 ...)
201
{
202
    sixel_logger_t *logger;
1,441✔
203
    char message[256];
1,441✔
204
    va_list args;
1,441✔
205

206
    logger = NULL;
1,441✔
207
    if (loader != NULL) {
1,441✔
208
        logger = &loader->logger;
1,441✔
209
    }
210
    if (logger == NULL || logger->file == NULL || !logger->active) {
1,441✔
211
        return;
1,441✔
212
    }
213

214
    message[0] = '\0';
×
215
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
216
# if defined(__clang__)
217
#  pragma clang diagnostic push
218
#  pragma clang diagnostic ignored "-Wformat-nonliteral"
219
# elif defined(__GNUC__) && !defined(__PCC__)
220
#  pragma GCC diagnostic push
221
#  pragma GCC diagnostic ignored "-Wformat-nonliteral"
222
# endif
223
#endif
224
    va_start(args, fmt);
×
225
    if (fmt != NULL) {
×
226
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
227
# if defined(__clang__)
228
#  pragma clang diagnostic push
229
#  pragma clang diagnostic ignored "-Wformat-nonliteral"
230
# elif defined(__GNUC__) && !defined(__PCC__)
231
#  pragma GCC diagnostic push
232
#  pragma GCC diagnostic ignored "-Wformat-nonliteral"
233
# endif
234
#endif
235
        (void)sixel_compat_vsnprintf(message, sizeof(message), fmt, args);
×
236
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
237
# if defined(__clang__)
238
#  pragma clang diagnostic pop
239
# elif defined(__GNUC__) && !defined(__PCC__)
240
#  pragma GCC diagnostic pop
241
# endif
242
#endif
243
    }
244
    va_end(args);
×
245
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
246
# if defined(__clang__)
247
#  pragma clang diagnostic pop
248
# elif defined(__GNUC__) && !defined(__PCC__)
249
#  pragma GCC diagnostic pop
250
# endif
251
#endif
252

253
    sixel_logger_logf(logger,
×
254
                      "worker",
255
                      "loader",
256
                      event,
257
                      -1,
258
                      -1,
259
                      0,
260
                      0,
261
                      0,
262
                      0,
263
                      "%s",
264
                      message);
265
}
266

267
static SIXELSTATUS
268
loader_callback_trampoline(sixel_frame_t *frame, void *data)
780✔
269
{
270
    sixel_loader_callback_state_t *state;
780✔
271
    SIXELSTATUS status;
780✔
272
    sixel_loader_t *loader;
780✔
273

274
    state = (sixel_loader_callback_state_t *)data;
780✔
275
    loader = NULL;
780✔
276
    if (state == NULL || state->fn == NULL) {
780✔
277
        return SIXEL_BAD_ARGUMENT;
278
    }
279

280
    loader = state->loader;
780✔
281
    if (loader != NULL && loader->log_loader_finished == 0) {
780✔
282
        loader_log_stage(loader,
714✔
283
                         "finish",
284
                         "path=%s loader=%s bytes=%zu",
285
                         loader->log_path,
714✔
286
                         loader->log_loader_name,
714✔
287
                         loader->log_input_bytes);
288
        loader->log_loader_finished = 1;
714✔
289
    }
290

291
    status = state->fn(frame, state->context);
780✔
292
    if (SIXEL_FAILED(status) && state->loader != NULL) {
780✔
293
        state->loader->callback_failed = 1;
4✔
294
    }
295

296
    return status;
297
}
298

299
static int
300
loader_plan_contains(sixel_loader_entry_t const **plan,
301
                     size_t plan_length,
302
                     sixel_loader_entry_t const *entry)
303
{
304
    size_t index;
305

306
    for (index = 0; index < plan_length; ++index) {
2,188✔
307
        if (plan[index] == entry) {
744✔
308
            return 1;
309
        }
310
    }
311

312
    return 0;
313
}
314

315
static int
316
loader_token_matches(char const *token,
22✔
317
                     size_t token_length,
318
                     char const *name)
319
{
320
    size_t index;
22✔
321
    unsigned char left;
22✔
322
    unsigned char right;
22✔
323

324
    for (index = 0; index < token_length && name[index] != '\0'; ++index) {
176✔
325
        left = (unsigned char)token[index];
154✔
326
        right = (unsigned char)name[index];
154✔
327
        if (tolower(left) != tolower(right)) {
154✔
328
            return 0;
329
        }
330
    }
331

332
    if (index != token_length || name[index] != '\0') {
22✔
333
        return 0;
334
    }
335

336
    return 1;
337
}
338

339
static sixel_loader_entry_t const *
340
loader_lookup_token(char const *token,
22✔
341
                    size_t token_length,
342
                    sixel_loader_entry_t const *entries,
343
                    size_t entry_count)
344
{
345
    size_t index;
22✔
346

347
    for (index = 0; index < entry_count; ++index) {
22✔
348
        if (loader_token_matches(token,
22✔
349
                                 token_length,
350
                                 entries[index].name)) {
22✔
351
            return &entries[index];
352
        }
353
    }
354

355
    return NULL;
356
}
357

358
/*
359
 * loader_build_plan
360
 *
361
 * Translate a comma separated list into an execution plan that reorders the
362
 * runtime loader chain.  Tokens are matched case-insensitively.  Unknown names
363
 * are ignored so that new builds remain forward compatible.
364
 *
365
 *    user input "gd,coregraphics"
366
 *                |
367
 *                v
368
 *        +-------------------+
369
 *        | prioritized list  |
370
 *        +-------------------+
371
 *                |
372
 *                v
373
 *        +-------------------+
374
 *        | default fallbacks |
375
 *        +-------------------+
376
 */
377
static size_t
378
loader_build_plan(char const *order,
722✔
379
                  sixel_loader_entry_t const *entries,
380
                  size_t entry_count,
381
                  sixel_loader_entry_t const **plan,
382
                  size_t plan_capacity)
383
{
384
    size_t plan_length;
722✔
385
    size_t index;
722✔
386
    char const *cursor;
722✔
387
    char const *token_start;
722✔
388
    char const *token_end;
722✔
389
    size_t token_length;
722✔
390
    sixel_loader_entry_t const *entry;
722✔
391
    size_t limit;
722✔
392

393
    plan_length = 0;
722✔
394
    index = 0;
722✔
395
    cursor = order;
722✔
396
    token_start = order;
722✔
397
    token_end = order;
722✔
398
    token_length = 0;
722✔
399
    entry = NULL;
722✔
400
    limit = plan_capacity;
722✔
401

402
    if (order != NULL && plan != NULL && plan_capacity > 0) {
722✔
403
        token_start = order;
404
        cursor = order;
405
        while (*cursor != '\0') {
176✔
406
            if (*cursor == ',') {
154✔
407
                token_end = cursor;
×
408
                while (token_start < token_end &&
×
409
                       isspace((unsigned char)*token_start)) {
×
410
                    ++token_start;
×
411
                }
412
                while (token_end > token_start &&
×
413
                       isspace((unsigned char)token_end[-1])) {
×
414
                    --token_end;
×
415
                }
416
                token_length = (size_t)(token_end - token_start);
×
417
                if (token_length > 0) {
×
418
                    entry = loader_lookup_token(token_start,
×
419
                                                token_length,
420
                                                entries,
421
                                                entry_count);
422
                    if (entry != NULL &&
×
423
                        !loader_plan_contains(plan,
×
424
                                              plan_length,
425
                                              entry) &&
×
426
                        plan_length < limit) {
427
                        plan[plan_length] = entry;
×
428
                        ++plan_length;
×
429
                    }
430
                }
431
                token_start = cursor + 1;
×
432
            }
433
            ++cursor;
154✔
434
        }
435

436
        token_end = cursor;
22✔
437
        while (token_start < token_end &&
22✔
438
               isspace((unsigned char)*token_start)) {
22✔
439
            ++token_start;
×
440
        }
441
        while (token_end > token_start &&
22✔
442
               isspace((unsigned char)token_end[-1])) {
22✔
443
            --token_end;
×
444
        }
445
        token_length = (size_t)(token_end - token_start);
22✔
446
        if (token_length > 0) {
22✔
447
            entry = loader_lookup_token(token_start,
22✔
448
                                        token_length,
449
                                        entries,
450
                                        entry_count);
451
            if (entry != NULL &&
22✔
452
                !loader_plan_contains(plan, plan_length, entry) &&
22✔
453
                plan_length < limit) {
454
                plan[plan_length] = entry;
22✔
455
                ++plan_length;
22✔
456
            }
457
        }
458
    }
459

460
    for (index = 0; index < entry_count && plan_length < limit; ++index) {
2,521✔
461
        entry = &entries[index];
1,799✔
462
        if (!entry->default_enabled) {
1,799✔
463
            continue;
355✔
464
        }
465
        if (!loader_plan_contains(plan, plan_length, entry)) {
1,444✔
466
            plan[plan_length] = entry;
1,422✔
467
            ++plan_length;
1,422✔
468
        }
469
    }
470

471
    return plan_length;
722✔
472
}
473

474
static void
475
loader_append_chunk(char *dest,
56✔
476
                    size_t capacity,
477
                    size_t *offset,
478
                    char const *chunk)
479
{
480
    size_t available;
56✔
481
    size_t length;
56✔
482

483
    if (dest == NULL || offset == NULL || chunk == NULL) {
56✔
484
        return;
485
    }
486

487
    if (*offset >= capacity) {
56✔
488
        return;
489
    }
490

491
    available = capacity - *offset;
56✔
492
    if (available == 0) {
56✔
493
        return;
494
    }
495

496
    length = strlen(chunk);
56✔
497
    if (length >= available) {
56✔
498
        if (available == 0) {
×
499
            return;
500
        }
501
        length = available - 1u;
×
502
    }
503

504
    if (length > 0) {
56✔
505
        memcpy(dest + *offset, chunk, length);
56✔
506
        *offset += length;
56✔
507
    }
508

509
    if (*offset < capacity) {
56✔
510
        dest[*offset] = '\0';
56✔
511
    } else {
512
        dest[capacity - 1u] = '\0';
×
513
    }
514
}
515

516
static void
517
loader_append_key_value(char *dest,
24✔
518
                        size_t capacity,
519
                        size_t *offset,
520
                        char const *label,
521
                        char const *value)
522
{
523
    char line[128];
24✔
524
    int written;
24✔
525

526
    if (value == NULL || value[0] == '\0') {
24✔
527
        return;
×
528
    }
529

530
    written = sixel_compat_snprintf(line,
24✔
531
                                    sizeof(line),
532
                                    "  %-10s: %s\n",
533
                                    label,
534
                                    value);
535
    if (written < 0) {
24✔
536
        return;
537
    }
538

539
    if ((size_t)written >= sizeof(line)) {
24✔
540
        line[sizeof(line) - 1u] = '\0';
×
541
    }
542

543
    loader_append_chunk(dest, capacity, offset, line);
24✔
544
}
545

546
static void
547
loader_extract_extension(char const *path, char *buffer, size_t capacity)
8✔
548
{
549
    char const *dot;
8✔
550
    size_t index;
8✔
551

552
    if (buffer == NULL || capacity == 0) {
8✔
553
        return;
554
    }
555

556
    buffer[0] = '\0';
8✔
557

558
    if (path == NULL) {
8✔
559
        return;
560
    }
561

562
    dot = strrchr(path, '.');
6✔
563
    if (dot == NULL || dot[1] == '\0') {
6✔
564
        return;
565
    }
566

567
#if defined(_WIN32)
568
    {
569
        char const *slash;
4✔
570
        char const *backslash;
4✔
571

572
        slash = strrchr(path, '/');
4✔
573
        backslash = strrchr(path, '\\');
4✔
574
        if ((slash != NULL && dot < slash) ||
4✔
575
                (backslash != NULL && dot < backslash)) {
576
            return;
577
        }
578
    }
579
#else
580
    {
581
        char const *slash;
2✔
582

583
        slash = strrchr(path, '/');
2✔
584
        if (slash != NULL && dot < slash) {
2✔
585
            return;
586
        }
587
    }
588
#endif
589

590
    if (dot[1] == '\0') {
591
        return;
592
    }
593

594
    dot += 1;
24✔
595

596
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
24✔
597
        buffer[index] = (char)tolower((unsigned char)dot[index]);
18✔
598
    }
599
    buffer[index] = '\0';
6✔
600
}
601

602

603

604

605

606

607

608

609

610

611

612
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
613
static void
614
loader_copy_cfstring(CFStringRef source, char *buffer, size_t capacity)
615
{
616
    if (buffer == NULL || capacity == 0) {
617
        return;
618
    }
619

620
    buffer[0] = '\0';
621
    if (source == NULL) {
622
        return;
623
    }
624

625
    if (!CFStringGetCString(source,
626
                             buffer,
627
                             (CFIndex)capacity,
628
                             kCFStringEncodingUTF8)) {
629
        buffer[0] = '\0';
630
    }
631
}
632
#endif
633

634

635
static void
636
loader_publish_diagnostic(sixel_chunk_t const *pchunk,
8✔
637
                          char const *filename)
638
{
639
    enum { description_length = 128 };
8✔
640
    enum { uttype_length = 128 };
8✔
641
    enum { extension_length = 32 };
8✔
642
    enum { message_length = 768 };
8✔
643
    char message[message_length];
8✔
644
    char type_value[description_length];
8✔
645
    char extension_text[extension_length + 2];
8✔
646
    char uttype[uttype_length];
8✔
647
    char desc_buffer[description_length];
8✔
648
    char extension[extension_length];
8✔
649
    char const *path;
8✔
650
    char const *display_path;
8✔
651
    char const *metadata_path;
8✔
652
    char const *description_text;
8✔
653
    char *mime_string;
8✔
654
    char *description_string;
8✔
655
    size_t offset;
8✔
656
    int gnome_available;
8✔
657
    int gnome_has_dirs;
8✔
658
    int gnome_has_match;
8✔
659
    int suggestions;
8✔
660

661
    message[0] = '\0';
8✔
662
    type_value[0] = '\0';
8✔
663
    extension_text[0] = '\0';
8✔
664
    uttype[0] = '\0';
8✔
665
    desc_buffer[0] = '\0';
8✔
666
    extension[0] = '\0';
8✔
667
    path = NULL;
8✔
668
    display_path = "(stdin)";
8✔
669
    metadata_path = NULL;
8✔
670
    description_text = NULL;
8✔
671
    mime_string = NULL;
8✔
672
    description_string = NULL;
8✔
673
    offset = 0u;
8✔
674
    gnome_available = 0;
8✔
675
    gnome_has_dirs = 0;
8✔
676
    gnome_has_match = 0;
8✔
677
    suggestions = 0;
8✔
678

679
    if (pchunk != NULL && pchunk->source_path != NULL) {
8✔
680
        path = pchunk->source_path;
681
    } else if (filename != NULL) {
2✔
682
        path = filename;
683
    }
684

685
    if (path != NULL && strcmp(path, "-") != 0) {
6✔
686
        display_path = path;
6✔
687
    }
688

689
    if (path != NULL && strcmp(path, "-") != 0 &&
6✔
690
            strstr(path, "://") == NULL) {
2✔
691
        metadata_path = path;
3✔
692
    }
693

694
    loader_extract_extension(path, extension, sizeof(extension));
8✔
695

696
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
697
    if (metadata_path != NULL) {
3✔
698
        /*
699
         * Collect MIME metadata via file(1) when fork() and friends are
700
         * available.  Windows builds compiled with clang64 lack these
701
         * interfaces, so the thumbnail helpers remain disabled there.
702
         */
703
        mime_string = thumbnailer_guess_content_type(metadata_path);
2✔
704
        description_string = thumbnailer_run_file(metadata_path, NULL);
2✔
705
    }
706
#else
707
    (void)metadata_path;
5✔
708
#endif
709

710
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
711
#if defined(__clang__)
712
    /*
713
     * Allow use of legacy UTType C APIs when compiling with the
714
     * macOS 12 SDK.  The replacement interfaces are Objective-C only,
715
     * so we must intentionally silence the deprecation warnings here.
716
     */
717
#pragma clang diagnostic push
718
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
719
#endif
720
    {
721
        CFStringRef uti_ref;
722
        CFStringRef mime_ref;
723
        CFStringRef ext_ref;
724
        CFStringRef desc_ref;
725
        CFStringRef preferred_mime;
726
        char uti_local[uttype_length];
727
        char desc_local[description_length];
728
        char mime_local[64];
729

730
        uti_ref = NULL;
731
        mime_ref = NULL;
732
        ext_ref = NULL;
733
        desc_ref = NULL;
734
        preferred_mime = NULL;
735
        uti_local[0] = '\0';
736
        desc_local[0] = '\0';
737
        mime_local[0] = '\0';
738

739
        if (mime_string != NULL) {
740
            mime_ref = CFStringCreateWithCString(kCFAllocatorDefault,
741
                                                 mime_string,
742
                                                 kCFStringEncodingUTF8);
743
        }
744
        if (mime_ref != NULL) {
745
            uti_ref = UTTypeCreatePreferredIdentifierForTag(
746
                kUTTagClassMIMEType,
747
                mime_ref,
748
                NULL);
749
        }
750
        if (uti_ref == NULL && extension[0] != '\0') {
751
            ext_ref = CFStringCreateWithCString(kCFAllocatorDefault,
752
                                                extension,
753
                                                kCFStringEncodingUTF8);
754
            if (ext_ref != NULL) {
755
                uti_ref = UTTypeCreatePreferredIdentifierForTag(
756
                    kUTTagClassFilenameExtension,
757
                    ext_ref,
758
                    NULL);
759
            }
760
        }
761
        if (uti_ref != NULL) {
762
            loader_copy_cfstring(uti_ref, uti_local, sizeof(uti_local));
763
            desc_ref = UTTypeCopyDescription(uti_ref);
764
            if (desc_ref != NULL) {
765
                loader_copy_cfstring(desc_ref,
766
                                     desc_local,
767
                                     sizeof(desc_local));
768
                CFRelease(desc_ref);
769
                desc_ref = NULL;
770
            }
771
            if (mime_string == NULL) {
772
                preferred_mime = UTTypeCopyPreferredTagWithClass(
773
                    uti_ref,
774
                    kUTTagClassMIMEType);
775
                if (preferred_mime != NULL) {
776
                    loader_copy_cfstring(preferred_mime,
777
                                         mime_local,
778
                                         sizeof(mime_local));
779
                    CFRelease(preferred_mime);
780
                    preferred_mime = NULL;
781
                }
782
                if (mime_local[0] != '\0') {
783
                    mime_string = thumbnailer_strdup(mime_local);
784
                }
785
            }
786
        }
787
        if (mime_ref != NULL) {
788
            CFRelease(mime_ref);
789
        }
790
        if (ext_ref != NULL) {
791
            CFRelease(ext_ref);
792
        }
793
        if (uti_ref != NULL) {
794
            CFRelease(uti_ref);
795
        }
796
        if (uti_local[0] != '\0') {
797
            sixel_compat_snprintf(uttype,
798
                                  sizeof(uttype),
799
                                  "%s",
800
                                  uti_local);
801
        }
802
        if (desc_local[0] != '\0') {
803
            sixel_compat_snprintf(desc_buffer,
804
                                  sizeof(desc_buffer),
805
                                  "%s",
806
                                  desc_local);
807
        }
808
    }
809
#if defined(__clang__)
810
#pragma clang diagnostic pop
811
#endif
812
#endif
813

814
    if (description_string != NULL && description_string[0] != '\0') {
7✔
815
        description_text = description_string;
816
    } else if (desc_buffer[0] != '\0') {
6✔
817
        description_text = desc_buffer;
818
    } else {
819
        description_text = "unknown content";
6✔
820
    }
821

822
    sixel_compat_snprintf(type_value,
8✔
823
                          sizeof(type_value),
824
                          "%s",
825
                          description_text);
826

827
    loader_append_chunk(message,
8✔
828
                        sizeof(message),
829
                        &offset,
830
                        "diagnostic:\n");
831
    loader_append_key_value(message,
8✔
832
                            sizeof(message),
833
                            &offset,
834
                            "file",
835
                            display_path);
836
    loader_append_key_value(message,
8✔
837
                            sizeof(message),
838
                            &offset,
839
                            "type",
840
                            type_value);
841

842
    if (mime_string != NULL && mime_string[0] != '\0') {
8✔
843
        loader_append_key_value(message,
2✔
844
                                sizeof(message),
845
                                &offset,
846
                                "mime",
847
                                mime_string);
848
    }
849

850
    if (uttype[0] != '\0') {
8✔
851
        loader_append_key_value(message,
×
852
                                sizeof(message),
853
                                &offset,
854
                                "uti",
855
                                uttype);
856
    }
857

858
    if (extension[0] != '\0') {
8✔
859
        sixel_compat_snprintf(extension_text,
6✔
860
                              sizeof(extension_text),
861
                              ".%s",
862
                              extension);
863
        loader_append_key_value(message,
6✔
864
                                sizeof(message),
865
                                &offset,
866
                                "extension",
867
                                extension_text);
868
    }
869

870
    loader_append_chunk(message,
8✔
871
                        sizeof(message),
872
                        &offset,
873
                        "  suggestions:\n");
874

875
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
876
    int quicklook_available;
877
    int quicklook_supported;
878

879
    quicklook_available = 0;
880
    quicklook_supported = 0;
881

882
    quicklook_available = loader_registry_entry_available("quicklook");
883
    if (quicklook_available) {
884
        quicklook_supported = loader_quicklook_can_decode(pchunk, filename);
885
    }
886
    if (quicklook_supported) {
887
        loader_append_chunk(message,
888
                            sizeof(message),
889
                            &offset,
890
                            "    - QuickLook rendered a preview during "
891
                            "the probe; try -j quicklook.\n");
892
        suggestions += 1;
893
    }
894
#endif
895

896
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
897
    gnome_available = loader_registry_entry_available("gnome-thumbnailer");
3✔
898
    if (gnome_available) {
3✔
899
        loader_probe_gnome_thumbnailers(mime_string,
3✔
900
                                        &gnome_has_dirs,
901
                                        &gnome_has_match);
902
        if (gnome_has_dirs && gnome_has_match) {
3✔
903
            loader_append_chunk(message,
904
                                sizeof(message),
905
                                &offset,
906
                                "    - GNOME thumbnailer definitions match "
907
                                "this MIME type; try -j gnome-thumbnailer.\n"
908
                                );
909
            suggestions += 1;
910
        }
911
    }
912
#else
913
    (void)gnome_available;
5✔
914
    (void)gnome_has_dirs;
5✔
915
    (void)gnome_has_match;
5✔
916
#endif
917

918
    if (suggestions == 0) {
5✔
919
        loader_append_chunk(message,
8✔
920
                            sizeof(message),
921
                            &offset,
922
                            "    (no thumbnail helper hints)\n");
923
    }
924

925
    if (suggestions > 0) {
8✔
926
        loader_append_chunk(message,
927
                            sizeof(message),
928
                            &offset,
929
                            "  hint       : Enable one of the suggested "
930
                            "loaders with -j.\n");
931
    } else {
932
        loader_append_chunk(message,
8✔
933
                            sizeof(message),
934
                            &offset,
935
                            "  hint       : Convert the file to PNG or "
936
                            "enable optional loaders.\n");
937
    }
938

939
    sixel_helper_set_additional_message(message);
8✔
940

941
    free(mime_string);
8✔
942
    free(description_string);
8✔
943
}
8✔
944

945
SIXELAPI SIXELSTATUS
946
sixel_loader_new(
727✔
947
    sixel_loader_t   /* out */ **pploader,
948
    sixel_allocator_t/* in */  *allocator)
949
{
950
    SIXELSTATUS status = SIXEL_FALSE;
727✔
951
    sixel_loader_t *loader;
727✔
952
    sixel_allocator_t *local_allocator;
727✔
953

954
    loader = NULL;
727✔
955
    local_allocator = allocator;
727✔
956

957
    if (pploader == NULL) {
727✔
958
        sixel_helper_set_additional_message(
×
959
            "sixel_loader_new: pploader is null.");
960
        status = SIXEL_BAD_ARGUMENT;
×
961
        goto end;
×
962
    }
963

964
    if (local_allocator == NULL) {
727✔
965
        status = sixel_allocator_new(&local_allocator,
×
966
                                     NULL,
967
                                     NULL,
968
                                     NULL,
969
                                     NULL);
970
        if (SIXEL_FAILED(status)) {
×
971
            goto end;
×
972
        }
973
    } else {
974
        sixel_allocator_ref(local_allocator);
727✔
975
    }
976

977
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
727✔
978
                                                      sizeof(*loader));
979
    if (loader == NULL) {
727✔
980
        sixel_helper_set_additional_message(
×
981
            "sixel_loader_new: sixel_allocator_malloc() failed.");
982
        status = SIXEL_BAD_ALLOCATION;
×
983
        sixel_allocator_unref(local_allocator);
×
984
        goto end;
×
985
    }
986

987
    loader->ref = 1U;
727✔
988
    loader->fstatic = 0;
727✔
989
    loader->fuse_palette = 0;
727✔
990
    loader->reqcolors = SIXEL_PALETTE_MAX;
727✔
991
    loader->bgcolor[0] = 0;
727✔
992
    loader->bgcolor[1] = 0;
727✔
993
    loader->bgcolor[2] = 0;
727✔
994
    loader->has_bgcolor = 0;
727✔
995
    loader->loop_control = SIXEL_LOOP_AUTO;
727✔
996
    loader->finsecure = 0;
727✔
997
    loader->cancel_flag = NULL;
727✔
998
    loader->context = NULL;
727✔
999
    /*
1000
     * Initialize a private logger. The helper reuses an existing global
1001
     * logger sink when present so loader markers share the timeline with
1002
     * upstream stages without requiring sixel_loader_setopt().
1003
     */
1004
    sixel_logger_init(&loader->logger);
727✔
1005
    (void)sixel_logger_prepare_env(&loader->logger);
727✔
1006
    loader->assessment = NULL;
727✔
1007
    loader->loader_order = NULL;
727✔
1008
    loader->allocator = local_allocator;
727✔
1009
    loader->last_loader_name[0] = '\0';
727✔
1010
    loader->last_source_path[0] = '\0';
727✔
1011
    loader->last_input_bytes = 0u;
727✔
1012
    loader->callback_failed = 0;
727✔
1013
    loader->log_loader_finished = 0;
727✔
1014
    loader->log_path[0] = '\0';
727✔
1015
    loader->log_loader_name[0] = '\0';
727✔
1016
    loader->log_input_bytes = 0u;
727✔
1017

1018
    *pploader = loader;
727✔
1019
    status = SIXEL_OK;
727✔
1020

1021
end:
727✔
1022
    return status;
727✔
1023
}
1024

1025
SIXELAPI void
1026
sixel_loader_ref(
6,975✔
1027
    sixel_loader_t /* in */ *loader)
1028
{
1029
    if (loader == NULL) {
6,975✔
1030
        return;
1031
    }
1032

1033
    (void)sixel_atomic_fetch_add_u32(&loader->ref, 1U);
6,975✔
1034
}
1035

1036
SIXELAPI void
1037
sixel_loader_unref(
7,702✔
1038
    sixel_loader_t /* in */ *loader)
1039
{
1040
    sixel_allocator_t *allocator;
7,702✔
1041
    unsigned int previous;
7,702✔
1042

1043
    if (loader == NULL) {
7,702✔
1044
        return;
1045
    }
1046

1047
    previous = sixel_atomic_fetch_sub_u32(&loader->ref, 1U);
7,702✔
1048
    if (previous == 1U) {
7,702✔
1049
        allocator = loader->allocator;
727✔
1050
        sixel_logger_close(&loader->logger);
727✔
1051
        sixel_allocator_free(allocator, loader->loader_order);
727✔
1052
        sixel_allocator_free(allocator, loader);
727✔
1053
        sixel_allocator_unref(allocator);
727✔
1054
    }
1055
}
1056

1057
SIXELAPI SIXELSTATUS
1058
sixel_loader_setopt(
6,248✔
1059
    sixel_loader_t /* in */ *loader,
1060
    int            /* in */ option,
1061
    void const     /* in */ *value)
1062
{
1063
    SIXELSTATUS status = SIXEL_FALSE;
6,248✔
1064
    int const *flag;
6,248✔
1065
    unsigned char const *color;
6,248✔
1066
    char const *order;
6,248✔
1067
    char *copy;
6,248✔
1068
    sixel_allocator_t *allocator;
6,248✔
1069

1070
    flag = NULL;
6,248✔
1071
    color = NULL;
6,248✔
1072
    order = NULL;
6,248✔
1073
    copy = NULL;
6,248✔
1074
    allocator = NULL;
6,248✔
1075

1076
    if (loader == NULL) {
6,248✔
1077
        sixel_helper_set_additional_message(
×
1078
            "sixel_loader_setopt: loader is null.");
1079
        status = SIXEL_BAD_ARGUMENT;
×
1080
        goto end0;
×
1081
    }
1082

1083
    sixel_loader_ref(loader);
6,248✔
1084

1085
    switch (option) {
6,248✔
1086
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
727✔
1087
        flag = (int const *)value;
727✔
1088
        loader->fstatic = flag != NULL ? *flag : 0;
727✔
1089
        status = SIXEL_OK;
727✔
1090
        break;
727✔
1091
    case SIXEL_LOADER_OPTION_USE_PALETTE:
727✔
1092
        flag = (int const *)value;
727✔
1093
        loader->fuse_palette = flag != NULL ? *flag : 0;
727✔
1094
        status = SIXEL_OK;
727✔
1095
        break;
727✔
1096
    case SIXEL_LOADER_OPTION_REQCOLORS:
727✔
1097
        flag = (int const *)value;
727✔
1098
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
727✔
1099
        if (loader->reqcolors < 1) {
727✔
1100
            sixel_helper_set_additional_message(
×
1101
                "sixel_loader_setopt: reqcolors must be 1 or greater.");
1102
            status = SIXEL_BAD_ARGUMENT;
×
1103
            goto end;
×
1104
        }
1105
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
727✔
1106
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
1107
        }
1108
        status = SIXEL_OK;
1109
        break;
1110
    case SIXEL_LOADER_OPTION_BGCOLOR:
475✔
1111
        if (value == NULL) {
475✔
1112
            loader->has_bgcolor = 0;
462✔
1113
        } else {
1114
            color = (unsigned char const *)value;
13✔
1115
            loader->bgcolor[0] = color[0];
13✔
1116
            loader->bgcolor[1] = color[1];
13✔
1117
            loader->bgcolor[2] = color[2];
13✔
1118
            loader->has_bgcolor = 1;
13✔
1119
        }
1120
        status = SIXEL_OK;
1121
        break;
1122
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
727✔
1123
        flag = (int const *)value;
727✔
1124
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
727✔
1125
        status = SIXEL_OK;
727✔
1126
        break;
727✔
1127
    case SIXEL_LOADER_OPTION_INSECURE:
727✔
1128
        flag = (int const *)value;
727✔
1129
        loader->finsecure = flag != NULL ? *flag : 0;
727✔
1130
        status = SIXEL_OK;
727✔
1131
        break;
727✔
1132
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
475✔
1133
        loader->cancel_flag = (int const *)value;
475✔
1134
        status = SIXEL_OK;
475✔
1135
        break;
475✔
1136
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
475✔
1137
        allocator = loader->allocator;
475✔
1138
        sixel_allocator_free(allocator, loader->loader_order);
475✔
1139
        loader->loader_order = NULL;
475✔
1140
        if (value != NULL) {
475✔
1141
            order = (char const *)value;
22✔
1142
            copy = loader_strdup(order, allocator);
22✔
1143
            if (copy == NULL) {
22✔
1144
                sixel_helper_set_additional_message(
×
1145
                    "sixel_loader_setopt: loader_strdup() failed.");
1146
                status = SIXEL_BAD_ALLOCATION;
×
1147
                goto end;
×
1148
            }
1149
            loader->loader_order = copy;
22✔
1150
        }
1151
        status = SIXEL_OK;
1152
        break;
1153
    case SIXEL_LOADER_OPTION_CONTEXT:
727✔
1154
        loader->context = (void *)value;
727✔
1155
        loader->assessment = NULL;
727✔
1156
        status = SIXEL_OK;
727✔
1157
        break;
727✔
1158
    case SIXEL_LOADER_OPTION_ASSESSMENT:
461✔
1159
        loader->assessment = (sixel_assessment_t *)value;
461✔
1160
        status = SIXEL_OK;
461✔
1161
        break;
461✔
1162
    default:
×
1163
        sixel_helper_set_additional_message(
×
1164
            "sixel_loader_setopt: unknown option.");
1165
        status = SIXEL_BAD_ARGUMENT;
×
1166
        goto end;
×
1167
    }
1168

1169
end:
6,248✔
1170
    sixel_loader_unref(loader);
6,248✔
1171

1172
end0:
6,248✔
1173
    return status;
6,248✔
1174
}
1175

1176
SIXELAPI char const *
1177
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
896✔
1178
{
1179
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
896✔
1180
        return NULL;
1181
    }
1182
    return loader->last_loader_name;
896✔
1183
}
1184

1185
SIXELAPI char const *
1186
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
803✔
1187
{
1188
    if (loader == NULL || loader->last_source_path[0] == '\0') {
803✔
1189
        return NULL;
1190
    }
1191
    return loader->last_source_path;
710✔
1192
}
1193

1194
SIXELAPI size_t
1195
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
448✔
1196
{
1197
    if (loader == NULL) {
448✔
1198
        return 0u;
1199
    }
1200
    return loader->last_input_bytes;
448✔
1201
}
1202

1203
SIXELAPI SIXELSTATUS
1204
sixel_loader_load_file(
727✔
1205
    sixel_loader_t         /* in */ *loader,
1206
    char const             /* in */ *filename,
1207
    sixel_load_image_function /* in */ fn_load)
1208
{
1209
    SIXELSTATUS status = SIXEL_FALSE;
727✔
1210
    sixel_chunk_t *pchunk;
727✔
1211
    sixel_loader_entry_t const **plan;
727✔
1212
    sixel_loader_entry_t const *entries;
727✔
1213
    size_t entry_count;
727✔
1214
    size_t plan_length;
727✔
1215
    size_t plan_index;
727✔
1216
    unsigned char *bgcolor;
727✔
1217
    int reqcolors;
727✔
1218
    sixel_assessment_t *assessment;
727✔
1219
    char const *order_override;
727✔
1220
    char const *env_order;
727✔
1221
    sixel_loader_callback_state_t callback_state;
727✔
1222

1223
    pchunk = NULL;
727✔
1224
    plan = NULL;
727✔
1225
    entries = NULL;
727✔
1226
    entry_count = 0;
727✔
1227
    plan_length = 0;
727✔
1228
    plan_index = 0;
727✔
1229
    bgcolor = NULL;
727✔
1230
    reqcolors = 0;
727✔
1231
    assessment = NULL;
727✔
1232
    order_override = NULL;
727✔
1233
    env_order = NULL;
727✔
1234

1235
    if (loader == NULL) {
727✔
1236
        sixel_helper_set_additional_message(
×
1237
            "sixel_loader_load_file: loader is null.");
1238
        status = SIXEL_BAD_ARGUMENT;
×
1239
        goto end0;
×
1240
    }
1241

1242
    sixel_loader_ref(loader);
727✔
1243

1244
    loader->log_loader_finished = 0;
727✔
1245
    loader->log_loader_name[0] = '\0';
727✔
1246
    loader->log_input_bytes = 0u;
727✔
1247
    loader->log_path[0] = '\0';
727✔
1248
    if (filename != NULL) {
727✔
1249
        (void)sixel_compat_snprintf(loader->log_path,
633✔
1250
                                    sizeof(loader->log_path),
1251
                                    "%s",
1252
                                    filename);
1253
    }
1254
    loader_log_stage(loader, "start", "path=%s", loader->log_path);
727✔
1255

1256
    memset(&callback_state, 0, sizeof(callback_state));
727✔
1257
    callback_state.loader = loader;
727✔
1258
    callback_state.fn = fn_load;
727✔
1259
    callback_state.context = loader->context;
727✔
1260
    loader->callback_failed = 0;
727✔
1261

1262
    entry_count = loader_registry_get_entries(&entries);
727✔
1263

1264
    reqcolors = loader->reqcolors;
727✔
1265
    if (reqcolors > SIXEL_PALETTE_MAX) {
727✔
1266
        reqcolors = SIXEL_PALETTE_MAX;
1267
    }
1268

1269
    assessment = loader->assessment;
727✔
1270

1271
    /*
1272
     *  Assessment pipeline sketch:
1273
     *
1274
     *      +-------------+        +--------------+
1275
     *      | chunk read  | -----> | image decode |
1276
     *      +-------------+        +--------------+
1277
     *
1278
     *  The loader owns the hand-off.  Chunk I/O ends before any decoder runs,
1279
     *  so we time the read span in the encoder and pivot to decode once the
1280
     *  chunk is populated.
1281
     */
1282
    status = sixel_chunk_new(&pchunk,
727✔
1283
                             filename,
1284
                             loader->finsecure,
1285
                             loader->cancel_flag,
1286
                             loader->allocator);
1287
    if (status != SIXEL_OK) {
727✔
1288
        goto end;
5✔
1289
    }
1290

1291
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
722✔
1292
        status = SIXEL_OK;
×
1293
        goto end;
×
1294
    }
1295

1296
    if (pchunk->source_path != NULL && pchunk->source_path[0] != '\0') {
722✔
1297
        (void)sixel_compat_snprintf(loader->log_path,
611✔
1298
                                    sizeof(loader->log_path),
1299
                                    "%s",
1300
                                    pchunk->source_path);
1301
    }
1302

1303
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
722✔
1304
        status = SIXEL_LOGIC_ERROR;
×
1305
        goto end;
×
1306
    }
1307

1308
    if (loader->has_bgcolor) {
722✔
1309
        bgcolor = loader->bgcolor;
13✔
1310
    }
1311

1312
    status = SIXEL_FALSE;
722✔
1313
    if (assessment != NULL) {
722✔
1314
        sixel_assessment_stage_transition(
2✔
1315
            assessment,
1316
            SIXEL_ASSESSMENT_STAGE_IMAGE_DECODE);
1317
    }
1318
    order_override = loader->loader_order;
722✔
1319
    /*
1320
     * Honour SIXEL_LOADER_PRIORITY_LIST when callers do not supply
1321
     * a loader order via -L/--loaders or sixel_loader_setopt().
1322
     */
1323
    if (order_override == NULL) {
722✔
1324
        env_order = sixel_compat_getenv("SIXEL_LOADER_PRIORITY_LIST");
700✔
1325
        if (env_order != NULL && env_order[0] != '\0') {
700✔
1326
            order_override = env_order;
722✔
1327
        }
1328
    }
1329

1330
    plan = sixel_allocator_malloc(loader->allocator,
1,444✔
1331
                                  entry_count * sizeof(*plan));
722✔
1332
    if (plan == NULL) {
722✔
1333
        status = SIXEL_BAD_ALLOCATION;
×
1334
        goto end;
×
1335
    }
1336

1337
    plan_length = loader_build_plan(order_override,
722✔
1338
                                    entries,
1339
                                    entry_count,
1340
                                    plan,
1341
                                    entry_count);
1342

1343
    for (plan_index = 0; plan_index < plan_length; ++plan_index) {
1,468✔
1344
        if (plan[plan_index] == NULL) {
734✔
1345
            continue;
×
1346
        }
1347
        if (plan[plan_index]->predicate != NULL &&
746✔
1348
            plan[plan_index]->predicate(pchunk) == 0) {
12✔
1349
            continue;
4✔
1350
        }
1351
        loader->log_input_bytes = pchunk != NULL ? pchunk->size : 0u;
730✔
1352
        if (plan[plan_index]->name != NULL) {
730✔
1353
            (void)sixel_compat_snprintf(loader->log_loader_name,
730✔
1354
                                        sizeof(loader->log_loader_name),
1355
                                        "%s",
1356
                                        plan[plan_index]->name);
1357
        } else {
1358
            loader->log_loader_name[0] = '\0';
×
1359
        }
1360
        loader_trace_try(plan[plan_index]->name);
730✔
1361
        status = plan[plan_index]->backend(pchunk,
730✔
1362
                                           loader->fstatic,
1363
                                           loader->fuse_palette,
1364
                                           reqcolors,
1365
                                           bgcolor,
1366
                                           loader->loop_control,
1367
                                           loader_callback_trampoline,
1368
                                           &callback_state);
1369
        loader_trace_result(plan[plan_index]->name, status);
730✔
1370
        if (SIXEL_SUCCEEDED(status)) {
730✔
1371
            break;
1372
        }
1373
    }
1374

1375
    if (SIXEL_FAILED(status)) {
722✔
1376
        if (!loader->callback_failed &&
12✔
1377
                plan_length > 0u &&
8✔
1378
                plan_index >= plan_length &&
8✔
1379
                pchunk != NULL) {
8✔
1380
            status = SIXEL_LOADER_FAILED;
8✔
1381
            loader_publish_diagnostic(pchunk, filename);
8✔
1382
        }
1383
        goto end;
12✔
1384
    }
1385

1386
    if (plan_index < plan_length &&
710✔
1387
            plan[plan_index] != NULL &&
710✔
1388
            plan[plan_index]->name != NULL) {
710✔
1389
        (void)sixel_compat_snprintf(loader->last_loader_name,
710✔
1390
                                    sizeof(loader->last_loader_name),
1391
                                    "%s",
1392
                                    plan[plan_index]->name);
1393
    } else {
1394
        loader->last_loader_name[0] = '\0';
×
1395
    }
1396
    loader->last_input_bytes = pchunk->size;
710✔
1397
    if (pchunk->source_path != NULL) {
710✔
1398
        size_t path_len;
601✔
1399

1400
        path_len = strlen(pchunk->source_path);
601✔
1401
        if (path_len >= sizeof(loader->last_source_path)) {
601✔
1402
            path_len = sizeof(loader->last_source_path) - 1u;
1403
        }
1404
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
601✔
1405
        loader->last_source_path[path_len] = '\0';
601✔
1406
    } else {
1407
        loader->last_source_path[0] = '\0';
109✔
1408
    }
1409

1410
end:
722✔
1411
    if (plan != NULL) {
5✔
1412
        sixel_allocator_free(loader->allocator, plan);
722✔
1413
        plan = NULL;
722✔
1414
    }
1415
    sixel_chunk_destroy(pchunk);
727✔
1416
    sixel_loader_unref(loader);
727✔
1417

1418
end0:
727✔
1419
    return status;
727✔
1420
}
1421

1422
/* load image from file */
1423

1424
SIXELAPI SIXELSTATUS
1425
sixel_helper_load_image_file(
×
1426
    char const                /* in */     *filename,     /* source file name */
1427
    int                       /* in */     fstatic,       /* whether to */
1428
                                                             /* extract a */
1429
                                                             /* static image */
1430
                                                             /* from an */
1431
                                                             /* animated gif */
1432
    int                       /* in */     fuse_palette,  /* whether to */
1433
                                                             /* use a */
1434
                                                             /* paletted */
1435
                                                             /* image; set */
1436
                                                             /* non-zero to */
1437
                                                             /* request one */
1438
    int                       /* in */     reqcolors,     /* requested */
1439
                                                             /* number of */
1440
                                                             /* colors; */
1441
                                                             /* should be */
1442
                                                             /* equal to or */
1443
                                                             /* less than */
1444
                                                             /* SIXEL_ */
1445
                                                             /* PALETTE_ */
1446
                                                             /* MAX */
1447
    unsigned char             /* in */     *bgcolor,      /* background */
1448
                                                             /* color, may */
1449
                                                             /* be NULL */
1450
    int                       /* in */     loop_control,  /* one of enum */
1451
                                                             /* loopControl */
1452
    sixel_load_image_function /* in */     fn_load,       /* callback */
1453
    int                       /* in */     finsecure,     /* true if do */
1454
                                                             /* not verify */
1455
                                                             /* SSL */
1456
    int const                 /* in */     *cancel_flag,  /* cancel flag, */
1457
                                                             /* may be */
1458
                                                             /* NULL */
1459
    void                      /* in/out */ *context,      /* private data */
1460
                                                             /* passed to */
1461
                                                             /* callback */
1462
                                                             /* function, */
1463
                                                             /* may be */
1464
                                                             /* NULL */
1465
    sixel_allocator_t         /* in */     *allocator     /* allocator */
1466
                                                             /* object, */
1467
                                                             /* may be */
1468
                                                             /* NULL */
1469
)
1470
{
1471
    SIXELSTATUS status = SIXEL_FALSE;
×
1472
    sixel_loader_t *loader;
×
1473

1474
    loader = NULL;
×
1475

1476
    status = sixel_loader_new(&loader, allocator);
×
1477
    if (SIXEL_FAILED(status)) {
×
1478
        goto end;
×
1479
    }
1480

1481
    status = sixel_loader_setopt(loader,
×
1482
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
1483
                                 &fstatic);
1484
    if (SIXEL_FAILED(status)) {
×
1485
        goto end;
×
1486
    }
1487

1488
    status = sixel_loader_setopt(loader,
×
1489
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
1490
                                 &fuse_palette);
1491
    if (SIXEL_FAILED(status)) {
×
1492
        goto end;
×
1493
    }
1494

1495
    status = sixel_loader_setopt(loader,
×
1496
                                 SIXEL_LOADER_OPTION_REQCOLORS,
1497
                                 &reqcolors);
1498
    if (SIXEL_FAILED(status)) {
×
1499
        goto end;
×
1500
    }
1501

1502
    status = sixel_loader_setopt(loader,
×
1503
                                 SIXEL_LOADER_OPTION_BGCOLOR,
1504
                                 bgcolor);
1505
    if (SIXEL_FAILED(status)) {
×
1506
        goto end;
×
1507
    }
1508

1509
    status = sixel_loader_setopt(loader,
×
1510
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
1511
                                 &loop_control);
1512
    if (SIXEL_FAILED(status)) {
×
1513
        goto end;
×
1514
    }
1515

1516
    status = sixel_loader_setopt(loader,
×
1517
                                 SIXEL_LOADER_OPTION_INSECURE,
1518
                                 &finsecure);
1519
    if (SIXEL_FAILED(status)) {
×
1520
        goto end;
×
1521
    }
1522

1523
    status = sixel_loader_setopt(loader,
×
1524
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
1525
                                 cancel_flag);
1526
    if (SIXEL_FAILED(status)) {
×
1527
        goto end;
×
1528
    }
1529

1530
    status = sixel_loader_setopt(loader,
×
1531
                                 SIXEL_LOADER_OPTION_CONTEXT,
1532
                                 context);
1533
    if (SIXEL_FAILED(status)) {
×
1534
        goto end;
×
1535
    }
1536

1537
    status = sixel_loader_load_file(loader, filename, fn_load);
×
1538

1539
end:
×
1540
    sixel_loader_unref(loader);
×
1541

1542
    return status;
×
1543
}
1544

1545

1546
SIXELAPI size_t
1547
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
4✔
1548
{
1549
    sixel_loader_entry_t const *entries;
4✔
1550
    size_t entry_count;
4✔
1551
    size_t limit;
4✔
1552
    size_t index;
4✔
1553

1554
    entries = NULL;
4✔
1555
    entry_count = loader_registry_get_entries(&entries);
4✔
1556

1557
    if (names != NULL && max_names > 0) {
4✔
1558
        limit = entry_count;
2✔
1559
        if (limit > max_names) {
2✔
1560
            limit = max_names;
1561
        }
1562
        for (index = 0; index < limit; ++index) {
7✔
1563
            names[index] = entries[index].name;
5✔
1564
        }
1565
    }
1566

1567
    return entry_count;
4✔
1568
}
1569

1570

1571
/* emacs Local Variables:      */
1572
/* emacs mode: c               */
1573
/* emacs tab-width: 4          */
1574
/* emacs indent-tabs-mode: nil */
1575
/* emacs c-basic-offset: 4     */
1576
/* emacs End:                  */
1577
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1578
/* 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