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

saitoha / libsixel / 18616099092

18 Oct 2025 01:12PM UTC coverage: 54.874% (+0.6%) from 54.284%
18616099092

push

github

saitoha
build: add --coverage to LDFLAGS if gcov option is enabled

5214 of 14205 branches covered (36.71%)

7189 of 13101 relevant lines covered (54.87%)

1144188.41 hits per line

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

91.26
/src/loader.c
1
/*
2
 * Copyright (c) 2021-2025 libsixel developers. See `AUTHORS`.
3
 * Copyright (c) 2014-2019 Hayaki Saito
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
6
 * this software and associated documentation files (the "Software"), to deal in
7
 * the Software without restriction, including without limitation the rights to
8
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
 * the Software, and to permit persons to whom the Software is furnished to do so,
10
 * subject to the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be included in all
13
 * copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
 */
22

23
#include "config.h"
24

25
/* STDC_HEADERS */
26
#include <stdio.h>
27
#include <stdlib.h>
28

29
#if HAVE_STRING_H
30
# include <string.h>
31
#endif
32
#if HAVE_UNISTD_H
33
# include <unistd.h>
34
#endif
35
#if HAVE_ERRNO_H
36
# include <errno.h>
37
#endif
38
#if HAVE_LIMITS_H
39
# include <limits.h>
40
#endif
41
#ifdef HAVE_GDK_PIXBUF2
42
# if HAVE_DIAGNOSTIC_TYPEDEF_REDEFINITION
43
#   pragma GCC diagnostic push
44
#   pragma GCC diagnostic ignored "-Wtypedef-redefinition"
45
# endif
46
# include <gdk-pixbuf/gdk-pixbuf.h>
47
# include <gdk-pixbuf/gdk-pixbuf-simple-anim.h>
48
# if HAVE_DIAGNOSTIC_TYPEDEF_REDEFINITION
49
#   pragma GCC diagnostic pop
50
# endif
51
#endif
52
#if HAVE_GD
53
# include <gd.h>
54
#endif
55
#if HAVE_LIBPNG
56
# include <png.h>
57
#endif  /* HAVE_LIBPNG */
58
#if HAVE_JPEG
59
# include <jpeglib.h>
60
#endif  /* HAVE_JPEG */
61
#if HAVE_COREGRAPHICS
62
# include <ApplicationServices/ApplicationServices.h>
63
# include <ImageIO/ImageIO.h>
64
#endif  /* HAVE_COREGRAPHICS */
65
#if HAVE_QUICKLOOK
66
# include <CoreServices/CoreServices.h>
67
# include <QuickLook/QuickLook.h>
68
#endif  /* HAVE_QUICKLOOK */
69

70
#if HAVE_QUICKLOOK_THUMBNAILING
71
CGImageRef
72
sixel_quicklook_thumbnail_create(CFURLRef url, CGSize max_size);
73
#endif
74

75
#if !defined(HAVE_MEMCPY)
76
# define memcpy(d, s, n) (bcopy ((s), (d), (n)))
77
#endif
78

79
#include <sixel.h>
80
#include "loader.h"
81
#include "frame.h"
82
#include "chunk.h"
83
#include "frompnm.h"
84
#include "fromgif.h"
85
#include "allocator.h"
86

87
static int loader_trace_enabled;
88

89
void
90
sixel_helper_set_loader_trace(int enable)
452✔
91
{
92
    loader_trace_enabled = enable ? 1 : 0;
452✔
93
}
452✔
94

95
static void
96
loader_trace_try(char const *name)
536✔
97
{
98
    if (loader_trace_enabled) {
536✔
99
        fprintf(stderr, "libsixel: trying %s loader\n", name);
11✔
100
    }
5✔
101
}
536✔
102

103
static void
104
loader_trace_result(char const *name, SIXELSTATUS status)
536✔
105
{
106
    if (!loader_trace_enabled) {
536✔
107
        return;
525✔
108
    }
109
    if (SIXEL_SUCCEEDED(status)) {
11!
110
        fprintf(stderr, "libsixel: loader %s succeeded\n", name);
9✔
111
    } else {
3✔
112
        fprintf(stderr, "libsixel: loader %s failed (%s)\n",
4✔
113
                name, sixel_helper_format_error(status));
2✔
114
    }
115
}
254✔
116

117
sixel_allocator_t *stbi_allocator;
118

119
void *
120
stbi_malloc(size_t n)
694✔
121
{
122
    return sixel_allocator_malloc(stbi_allocator, n);
694✔
123
}
124

125
void *
126
stbi_realloc(void *p, size_t n)
182✔
127
{
128
    return sixel_allocator_realloc(stbi_allocator, p, n);
182✔
129
}
130

131
void
132
stbi_free(void *p)
949✔
133
{
134
    sixel_allocator_free(stbi_allocator, p);
949✔
135
}
949✔
136

137
#define STBI_MALLOC stbi_malloc
138
#define STBI_REALLOC stbi_realloc
139
#define STBI_FREE stbi_free
140

141
#define STBI_NO_STDIO 1
142
#define STB_IMAGE_IMPLEMENTATION 1
143
#define STBI_FAILURE_USERMSG 1
144
#if defined(_WIN32)
145
# define STBI_NO_THREAD_LOCALS 1  /* no tls */
146
#endif
147
#define STBI_NO_GIF
148
#define STBI_NO_PNM
149

150
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
151
# pragma GCC diagnostic push
152
# pragma GCC diagnostic ignored "-Wsign-conversion"
153
#endif
154
#if HAVE_DIAGNOSTIC_STRICT_OVERFLOW
155
# pragma GCC diagnostic push
156
# pragma GCC diagnostic ignored "-Wstrict-overflow"
157
#endif
158
#if HAVE_DIAGNOSTIC_SWITCH_DEFAULT
159
# pragma GCC diagnostic push
160
# pragma GCC diagnostic ignored "-Wswitch-default"
161
#endif
162
#if HAVE_DIAGNOSTIC_SHADOW
163
# pragma GCC diagnostic push
164
# pragma GCC diagnostic ignored "-Wshadow"
165
#endif
166
#if HAVE_DIAGNOSTIC_DOUBLE_PROMOTION
167
# pragma GCC diagnostic push
168
# pragma GCC diagnostic ignored "-Wdouble-promotion"
169
#endif
170
# if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
171
# pragma GCC diagnostic push
172
# pragma GCC diagnostic ignored "-Wunused-function"
173
#endif
174
# if HAVE_DIAGNOSTIC_UNUSED_BUT_SET_VARIABLE
175
# pragma GCC diagnostic push
176
# pragma GCC diagnostic ignored "-Wunused-but-set-variable"
177
#endif
178
#include "stb_image.h"
179
#if HAVE_DIAGNOSTIC_UNUSED_BUT_SET_VARIABLE
180
# pragma GCC diagnostic pop
181
#endif
182
#if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
183
# pragma GCC diagnostic pop
184
#endif
185
#if HAVE_DIAGNOSTIC_DOUBLE_PROMOTION
186
# pragma GCC diagnostic pop
187
#endif
188
#if HAVE_DIAGNOSTIC_SHADOW
189
# pragma GCC diagnostic pop
190
#endif
191
#if HAVE_DIAGNOSTIC_SWITCH_DEFAULT
192
# pragma GCC diagnostic pop
193
#endif
194
#if HAVE_DIAGNOSTIC_STRICT_OVERFLOW
195
# pragma GCC diagnostic pop
196
#endif
197
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
198
# pragma GCC diagnostic pop
199
#endif
200

201

202
# if HAVE_JPEG
203
/* import from @uobikiemukot's sdump loader.h */
204
static SIXELSTATUS
205
load_jpeg(unsigned char **result,
206
          unsigned char *data,
207
          size_t datasize,
208
          int *pwidth,
209
          int *pheight,
210
          int *ppixelformat,
211
          sixel_allocator_t *allocator)
212
{
213
    SIXELSTATUS status = SIXEL_JPEG_ERROR;
214
    JDIMENSION row_stride;
215
    size_t size;
216
    JSAMPARRAY buffer;
217
    struct jpeg_decompress_struct cinfo;
218
    struct jpeg_error_mgr pub;
219

220
    cinfo.err = jpeg_std_error(&pub);
221

222
    jpeg_create_decompress(&cinfo);
223
    jpeg_mem_src(&cinfo, data, datasize);
224
    jpeg_read_header(&cinfo, TRUE);
225

226
    /* disable colormap (indexed color), grayscale -> rgb */
227
    cinfo.quantize_colors = FALSE;
228
    cinfo.out_color_space = JCS_RGB;
229
    jpeg_start_decompress(&cinfo);
230

231
    if (cinfo.output_components != 3) {
232
        sixel_helper_set_additional_message(
233
            "load_jpeg: unknown pixel format.");
234
        status = SIXEL_BAD_INPUT;
235
        goto end;
236
    }
237

238
    *ppixelformat = SIXEL_PIXELFORMAT_RGB888;
239

240
    if (cinfo.output_width > INT_MAX || cinfo.output_height > INT_MAX) {
241
        status = SIXEL_BAD_INTEGER_OVERFLOW;
242
        goto end;
243
    }
244
    *pwidth = (int)cinfo.output_width;
245
    *pheight = (int)cinfo.output_height;
246

247
    size = (size_t)(*pwidth * *pheight * cinfo.output_components);
248
    *result = (unsigned char *)sixel_allocator_malloc(allocator, size);
249
    if (*result == NULL) {
250
        sixel_helper_set_additional_message(
251
            "load_jpeg: sixel_allocator_malloc() failed.");
252
        status = SIXEL_BAD_ALLOCATION;
253
        goto end;
254
    }
255
    row_stride = cinfo.output_width * (unsigned int)cinfo.output_components;
256
    buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
257

258
    while (cinfo.output_scanline < cinfo.output_height) {
259
        jpeg_read_scanlines(&cinfo, buffer, 1);
260
        if (cinfo.err->num_warnings > 0) {
261
            sixel_helper_set_additional_message(
262
                "jpeg_read_scanlines: error/warining occuered.");
263
            status = SIXEL_BAD_INPUT;
264
            goto end;
265
        }
266
        memcpy(*result + (cinfo.output_scanline - 1) * row_stride, buffer[0], row_stride);
267
    }
268

269
    status = SIXEL_OK;
270

271
end:
272
    jpeg_finish_decompress(&cinfo);
273
    jpeg_destroy_decompress(&cinfo);
274

275
    return status;
276
}
277
# endif  /* HAVE_JPEG */
278

279

280
# if HAVE_LIBPNG
281
static void
282
read_png(png_structp png_ptr,
283
         png_bytep data,
284
         png_size_t length)
285
{
286
    sixel_chunk_t *pchunk = (sixel_chunk_t *)png_get_io_ptr(png_ptr);
287
    if (length > pchunk->size) {
288
        length = pchunk->size;
289
    }
290
    if (length > 0) {
291
        memcpy(data, pchunk->buffer, length);
292
        pchunk->buffer += length;
293
        pchunk->size -= length;
294
    }
295
}
296

297

298
static void
299
read_palette(png_structp png_ptr,
300
             png_infop info_ptr,
301
             unsigned char *palette,
302
             int ncolors,
303
             png_color *png_palette,
304
             png_color_16 *pbackground,
305
             int *transparent)
306
{
307
    png_bytep trans = NULL;
308
    int num_trans = 0;
309
    int i;
310

311
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
312
        png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
313
    }
314
    if (num_trans > 0) {
315
        *transparent = trans[0];
316
    }
317
    for (i = 0; i < ncolors; ++i) {
318
        if (pbackground && i < num_trans) {
319
            palette[i * 3 + 0] = ((0xff - trans[i]) * pbackground->red
320
                                   + trans[i] * png_palette[i].red) >> 8;
321
            palette[i * 3 + 1] = ((0xff - trans[i]) * pbackground->green
322
                                   + trans[i] * png_palette[i].green) >> 8;
323
            palette[i * 3 + 2] = ((0xff - trans[i]) * pbackground->blue
324
                                   + trans[i] * png_palette[i].blue) >> 8;
325
        } else {
326
            palette[i * 3 + 0] = png_palette[i].red;
327
            palette[i * 3 + 1] = png_palette[i].green;
328
            palette[i * 3 + 2] = png_palette[i].blue;
329
        }
330
    }
331
}
332

333
jmp_buf jmpbuf;
334

335
/* libpng error handler */
336
static void
337
png_error_callback(png_structp png_ptr, png_const_charp error_message)
338
{
339
    (void) png_ptr;
340

341
    sixel_helper_set_additional_message(error_message);
342
#if HAVE_SETJMP && HAVE_LONGJMP
343
    longjmp(jmpbuf, 1);
344
#endif  /* HAVE_SETJMP && HAVE_LONGJMP */
345
}
346

347

348
static SIXELSTATUS
349
load_png(unsigned char      /* out */ **result,
350
         unsigned char      /* in */  *buffer,
351
         size_t             /* in */  size,
352
         int                /* out */ *psx,
353
         int                /* out */ *psy,
354
         unsigned char      /* out */ **ppalette,
355
         int                /* out */ *pncolors,
356
         int                /* in */  reqcolors,
357
         int                /* out */ *pixelformat,
358
         unsigned char      /* out */ *bgcolor,
359
         int                /* out */ *transparent,
360
         sixel_allocator_t  /* in */  *allocator)
361
{
362
    SIXELSTATUS status;
363
    sixel_chunk_t read_chunk;
364
    png_uint_32 bitdepth;
365
    png_uint_32 png_status;
366
    png_structp png_ptr;
367
    png_infop info_ptr;
368
#ifdef HAVE_DIAGNOSTIC_CLOBBERED
369
# pragma GCC diagnostic push
370
# pragma GCC diagnostic ignored "-Wclobbered"
371
#endif
372
    unsigned char **rows = NULL;
373
    png_color *png_palette = NULL;
374
    png_color_16 background;
375
    png_color_16p default_background;
376
    png_uint_32 width;
377
    png_uint_32 height;
378
    int i;
379
    int depth;
380

381
#if HAVE_SETJMP && HAVE_LONGJMP
382
    if (setjmp(jmpbuf) != 0) {
383
        sixel_allocator_free(allocator, *result);
384
        *result = NULL;
385
        status = SIXEL_PNG_ERROR;
386
        goto cleanup;
387
    }
388
#endif  /* HAVE_SETJMP && HAVE_LONGJMP */
389

390
    status = SIXEL_FALSE;
391
    *result = NULL;
392

393
    png_ptr = png_create_read_struct(
394
        PNG_LIBPNG_VER_STRING, NULL, &png_error_callback, NULL);
395
    if (!png_ptr) {
396
        sixel_helper_set_additional_message(
397
            "png_create_read_struct() failed.");
398
        status = SIXEL_PNG_ERROR;
399
        goto cleanup;
400
    }
401

402
    /*
403
     * The minimum valid PNG is 67 bytes.
404
     * https://garethrees.org/2007/11/14/pngcrush/
405
     */
406
    if (size < 67) {
407
        sixel_helper_set_additional_message("PNG data too small to be valid!");
408
        status = SIXEL_PNG_ERROR;
409
        goto cleanup;
410
    }
411

412
#if HAVE_SETJMP
413
    if (setjmp(png_jmpbuf(png_ptr)) != 0) {
414
        sixel_allocator_free(allocator, *result);
415
        *result = NULL;
416
        status = SIXEL_PNG_ERROR;
417
        goto cleanup;
418
    }
419
#endif  /* HAVE_SETJMP */
420

421
    info_ptr = png_create_info_struct(png_ptr);
422
    if (!info_ptr) {
423
        sixel_helper_set_additional_message(
424
            "png_create_info_struct() failed.");
425
        status = SIXEL_PNG_ERROR;
426
        png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0);
427
        goto cleanup;
428
    }
429
    read_chunk.buffer = buffer;
430
    read_chunk.size = size;
431

432
    png_set_read_fn(png_ptr,(png_voidp)&read_chunk, read_png);
433
    png_read_info(png_ptr, info_ptr);
434

435
    width = png_get_image_width(png_ptr, info_ptr);
436
    height = png_get_image_height(png_ptr, info_ptr);
437

438
    if (width > INT_MAX || height > INT_MAX) {
439
        status = SIXEL_BAD_INTEGER_OVERFLOW;
440
        goto cleanup;
441
    }
442

443
    *psx = (int)width;
444
    *psy = (int)height;
445

446
    bitdepth = png_get_bit_depth(png_ptr, info_ptr);
447
    if (bitdepth == 16) {
448
#  if HAVE_DEBUG
449
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
450
        fprintf(stderr, "stripping to 8bit...\n");
451
#  endif
452
        png_set_strip_16(png_ptr);
453
        bitdepth = 8;
454
    }
455

456
    if (bgcolor) {
457
#  if HAVE_DEBUG
458
        fprintf(stderr, "background color is specified [%02x, %02x, %02x]\n",
459
                bgcolor[0], bgcolor[1], bgcolor[2]);
460
#  endif
461
        background.red = bgcolor[0];
462
        background.green = bgcolor[1];
463
        background.blue = bgcolor[2];
464
        background.gray = (bgcolor[0] + bgcolor[1] + bgcolor[2]) / 3;
465
    } else if (png_get_bKGD(png_ptr, info_ptr, &default_background) == PNG_INFO_bKGD) {
466
        memcpy(&background, default_background, sizeof(background));
467
#  if HAVE_DEBUG
468
        fprintf(stderr, "background color is found [%02x, %02x, %02x]\n",
469
                background.red, background.green, background.blue);
470
#  endif
471
    } else {
472
        background.red = 0;
473
        background.green = 0;
474
        background.blue = 0;
475
        background.gray = 0;
476
    }
477

478
    switch (png_get_color_type(png_ptr, info_ptr)) {
479
    case PNG_COLOR_TYPE_PALETTE:
480
#  if HAVE_DEBUG
481
        fprintf(stderr, "paletted PNG(PNG_COLOR_TYPE_PALETTE)\n");
482
#  endif
483
        png_status = png_get_PLTE(png_ptr, info_ptr,
484
                                  &png_palette, pncolors);
485
        if (png_status != PNG_INFO_PLTE || png_palette == NULL) {
486
            sixel_helper_set_additional_message(
487
                "PLTE chunk not found");
488
            status = SIXEL_PNG_ERROR;
489
            goto cleanup;
490
        }
491
#  if HAVE_DEBUG
492
        fprintf(stderr, "palette colors: %d\n", *pncolors);
493
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
494
#  endif
495
        if (ppalette == NULL || *pncolors > reqcolors) {
496
#  if HAVE_DEBUG
497
            fprintf(stderr, "detected more colors than reqired(>%d).\n",
498
                    reqcolors);
499
            fprintf(stderr, "expand to RGB format...\n");
500
#  endif
501
            png_set_background(png_ptr, &background,
502
                               PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
503
            png_set_palette_to_rgb(png_ptr);
504
            png_set_strip_alpha(png_ptr);
505
            *pixelformat = SIXEL_PIXELFORMAT_RGB888;
506
        } else {
507
            switch (bitdepth) {
508
            case 1:
509
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
510
                if (*ppalette == NULL) {
511
                    sixel_helper_set_additional_message(
512
                        "load_png: sixel_allocator_malloc() failed.");
513
                    status = SIXEL_BAD_ALLOCATION;
514
                    goto cleanup;
515
                }
516
                read_palette(png_ptr, info_ptr, *ppalette,
517
                             *pncolors, png_palette, &background, transparent);
518
                *pixelformat = SIXEL_PIXELFORMAT_PAL1;
519
                break;
520
            case 2:
521
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
522
                if (*ppalette == NULL) {
523
                    sixel_helper_set_additional_message(
524
                        "load_png: sixel_allocator_malloc() failed.");
525
                    status = SIXEL_BAD_ALLOCATION;
526
                    goto cleanup;
527
                }
528
                read_palette(png_ptr, info_ptr, *ppalette,
529
                             *pncolors, png_palette, &background, transparent);
530
                *pixelformat = SIXEL_PIXELFORMAT_PAL2;
531
                break;
532
            case 4:
533
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
534
                if (*ppalette == NULL) {
535
                    sixel_helper_set_additional_message(
536
                        "load_png: sixel_allocator_malloc() failed.");
537
                    status = SIXEL_BAD_ALLOCATION;
538
                    goto cleanup;
539
                }
540
                read_palette(png_ptr, info_ptr, *ppalette,
541
                             *pncolors, png_palette, &background, transparent);
542
                *pixelformat = SIXEL_PIXELFORMAT_PAL4;
543
                break;
544
            case 8:
545
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
546
                if (*ppalette == NULL) {
547
                    sixel_helper_set_additional_message(
548
                        "load_png: sixel_allocator_malloc() failed.");
549
                    status = SIXEL_BAD_ALLOCATION;
550
                    goto cleanup;
551
                }
552
                read_palette(png_ptr, info_ptr, *ppalette,
553
                             *pncolors, png_palette, &background, transparent);
554
                *pixelformat = SIXEL_PIXELFORMAT_PAL8;
555
                break;
556
            default:
557
                png_set_background(png_ptr, &background,
558
                                   PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
559
                png_set_palette_to_rgb(png_ptr);
560
                *pixelformat = SIXEL_PIXELFORMAT_RGB888;
561
                break;
562
            }
563
        }
564
        break;
565
    case PNG_COLOR_TYPE_GRAY:
566
#  if HAVE_DEBUG
567
        fprintf(stderr, "grayscale PNG(PNG_COLOR_TYPE_GRAY)\n");
568
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
569
#  endif
570
        if (1 << bitdepth > reqcolors) {
571
#  if HAVE_DEBUG
572
            fprintf(stderr, "detected more colors than reqired(>%d).\n",
573
                    reqcolors);
574
            fprintf(stderr, "expand into RGB format...\n");
575
#  endif
576
            png_set_background(png_ptr, &background,
577
                               PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
578
            png_set_gray_to_rgb(png_ptr);
579
            *pixelformat = SIXEL_PIXELFORMAT_RGB888;
580
        } else {
581
            switch (bitdepth) {
582
            case 1:
583
            case 2:
584
            case 4:
585
                if (ppalette) {
586
#  if HAVE_DECL_PNG_SET_EXPAND_GRAY_1_2_4_TO_8
587
#   if HAVE_DEBUG
588
                    fprintf(stderr, "expand %u bpp to 8bpp format...\n",
589
                            (unsigned int)bitdepth);
590
#   endif
591
                    png_set_expand_gray_1_2_4_to_8(png_ptr);
592
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
593
#  elif HAVE_DECL_PNG_SET_GRAY_1_2_4_TO_8
594
#   if HAVE_DEBUG
595
                    fprintf(stderr, "expand %u bpp to 8bpp format...\n",
596
                            (unsigned int)bitdepth);
597
#   endif
598
                    png_set_gray_1_2_4_to_8(png_ptr);
599
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
600
#  else
601
#   if HAVE_DEBUG
602
                    fprintf(stderr, "expand into RGB format...\n");
603
#   endif
604
                    png_set_background(png_ptr, &background,
605
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
606
                    png_set_gray_to_rgb(png_ptr);
607
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
608
#  endif
609
                } else {
610
                    png_set_background(png_ptr, &background,
611
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
612
                    png_set_gray_to_rgb(png_ptr);
613
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
614
                }
615
                break;
616
            case 8:
617
                if (ppalette) {
618
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
619
                } else {
620
#  if HAVE_DEBUG
621
                    fprintf(stderr, "expand into RGB format...\n");
622
#  endif
623
                    png_set_background(png_ptr, &background,
624
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
625
                    png_set_gray_to_rgb(png_ptr);
626
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
627
                }
628
                break;
629
            default:
630
#  if HAVE_DEBUG
631
                fprintf(stderr, "expand into RGB format...\n");
632
#  endif
633
                png_set_background(png_ptr, &background,
634
                                   PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
635
                png_set_gray_to_rgb(png_ptr);
636
                *pixelformat = SIXEL_PIXELFORMAT_RGB888;
637
                break;
638
            }
639
        }
640
        break;
641
    case PNG_COLOR_TYPE_GRAY_ALPHA:
642
#  if HAVE_DEBUG
643
        fprintf(stderr, "grayscale-alpha PNG(PNG_COLOR_TYPE_GRAY_ALPHA)\n");
644
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
645
        fprintf(stderr, "expand to RGB format...\n");
646
#  endif
647
        png_set_background(png_ptr, &background,
648
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
649
        png_set_gray_to_rgb(png_ptr);
650
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
651
        break;
652
    case PNG_COLOR_TYPE_RGB_ALPHA:
653
#  if HAVE_DEBUG
654
        fprintf(stderr, "RGBA PNG(PNG_COLOR_TYPE_RGB_ALPHA)\n");
655
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
656
        fprintf(stderr, "expand to RGB format...\n");
657
#  endif
658
        png_set_background(png_ptr, &background,
659
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
660
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
661
        break;
662
    case PNG_COLOR_TYPE_RGB:
663
#  if HAVE_DEBUG
664
        fprintf(stderr, "RGB PNG(PNG_COLOR_TYPE_RGB)\n");
665
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
666
#  endif
667
        png_set_background(png_ptr, &background,
668
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
669
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
670
        break;
671
    default:
672
        /* unknown format */
673
        goto cleanup;
674
    }
675
    depth = sixel_helper_compute_depth(*pixelformat);
676
    *result = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(*psx * *psy * depth));
677
    if (*result == NULL) {
678
        sixel_helper_set_additional_message(
679
            "load_png: sixel_allocator_malloc() failed.");
680
        status = SIXEL_BAD_ALLOCATION;
681
        goto cleanup;
682
    }
683
    rows = (unsigned char **)sixel_allocator_malloc(allocator, (size_t)*psy * sizeof(unsigned char *));
684
    if (rows == NULL) {
685
        sixel_helper_set_additional_message(
686
            "load_png: sixel_allocator_malloc() failed.");
687
        status = SIXEL_BAD_ALLOCATION;
688
        goto cleanup;
689
    }
690
    switch (*pixelformat) {
691
    case SIXEL_PIXELFORMAT_PAL1:
692
    case SIXEL_PIXELFORMAT_PAL2:
693
    case SIXEL_PIXELFORMAT_PAL4:
694
        for (i = 0; i < *psy; ++i) {
695
            rows[i] = *result + (depth * *psx * (int)bitdepth + 7) / 8 * i;
696
        }
697
        break;
698
    default:
699
        for (i = 0; i < *psy; ++i) {
700
            rows[i] = *result + depth * *psx * i;
701
        }
702
        break;
703
    }
704

705
    png_read_image(png_ptr, rows);
706

707
    status = SIXEL_OK;
708

709
cleanup:
710
    png_destroy_read_struct(&png_ptr, &info_ptr,(png_infopp)0);
711

712
    if (rows != NULL) {
713
        sixel_allocator_free(allocator, rows);
714
    }
715

716
    return status;
717
}
718
#ifdef HAVE_DIAGNOSTIC_CLOBBERED
719
# pragma GCC diagnostic pop
720
#endif
721

722
# endif  /* HAVE_LIBPNG */
723

724

725
static SIXELSTATUS
726
load_sixel(unsigned char        /* out */ **result,
153✔
727
           unsigned char        /* in */  *buffer,
728
           int                  /* in */  size,
729
           int                  /* out */ *psx,
730
           int                  /* out */ *psy,
731
           unsigned char        /* out */ **ppalette,
732
           int                  /* out */ *pncolors,
733
           int                  /* in */  reqcolors,
734
           int                  /* out */ *ppixelformat,
735
           sixel_allocator_t    /* in */  *allocator)
736
{
737
    SIXELSTATUS status = SIXEL_FALSE;
153✔
738
    unsigned char *p = NULL;
153✔
739
    unsigned char *palette = NULL;
153✔
740
    int colors;
741
    int i;
742

743
    /* sixel */
744
    status = sixel_decode_raw(buffer, size,
204✔
745
                              &p, psx, psy,
51✔
746
                              &palette, &colors, allocator);
51✔
747
    if (SIXEL_FAILED(status)) {
153!
748
        goto end;
×
749
    }
750
    if (ppalette == NULL || colors > reqcolors) {
204!
751
        *ppixelformat = SIXEL_PIXELFORMAT_RGB888;
36✔
752
        *result = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(*psx * *psy * 3));
36✔
753
        if (*result == NULL) {
36!
754
            sixel_helper_set_additional_message(
×
755
                "load_sixel: sixel_allocator_malloc() failed.");
756
            status = SIXEL_BAD_ALLOCATION;
×
757
            goto end;
×
758
        }
759
        for (i = 0; i < *psx * *psy; ++i) {
5,670,576✔
760
            (*result)[i * 3 + 0] = palette[p[i] * 3 + 0];
5,670,540✔
761
            (*result)[i * 3 + 1] = palette[p[i] * 3 + 1];
5,670,540✔
762
            (*result)[i * 3 + 2] = palette[p[i] * 3 + 2];
5,670,540✔
763
        }
1,890,180✔
764
    } else {
12✔
765
        *ppixelformat = SIXEL_PIXELFORMAT_PAL8;
117✔
766
        *result = p;
117✔
767
        *ppalette = palette;
117✔
768
        *pncolors = colors;
117✔
769
        p = NULL;
117✔
770
        palette = NULL;
117✔
771
    }
772

773
end:
102✔
774
    sixel_allocator_free(allocator, palette);
153✔
775
    sixel_allocator_free(allocator, p);
153✔
776

777
    return status;
153✔
778
}
779

780

781
/* detect whether given chunk is sixel stream */
782
static int
783
chunk_is_sixel(sixel_chunk_t const *chunk)
337✔
784
{
785
    unsigned char *p;
786
    unsigned char *end;
787

788
    p = chunk->buffer;
337✔
789
    end = p + chunk->size;
337✔
790

791
    if (chunk->size < 3) {
337!
792
        return 0;
2✔
793
    }
794

795
    p++;
335✔
796
    if (p >= end) {
335!
797
        return 0;
×
798
    }
799
    if (*(p - 1) == 0x90 || (*(p - 1) == 0x1b && *p == 0x50)) {
335!
800
        while (p++ < end) {
567!
801
            if (*p == 0x71) {
567✔
802
                return 1;
153✔
803
            } else if (*p == 0x18 || *p == 0x1a) {
414!
804
                return 0;
×
805
            } else if (*p < 0x20) {
414✔
806
                continue;
6✔
807
            } else if (*p < 0x30) {
408!
808
                return 0;
×
809
            } else if (*p < 0x40) {
408✔
810
                continue;
405✔
811
            }
812
        }
813
    }
814
    return 0;
182✔
815
}
55✔
816

817

818
/* detect whether given chunk is PNM stream */
819
static int
820
chunk_is_pnm(sixel_chunk_t const *chunk)
184✔
821
{
822
    if (chunk->size < 2) {
184!
823
        return 0;
2✔
824
    }
825
    if (chunk->buffer[0] == 'P' &&
182!
826
        chunk->buffer[1] >= '1' &&
20!
827
        chunk->buffer[1] <= '6') {
20!
828
        return 1;
20✔
829
    }
830
    return 0;
162✔
831
}
4✔
832

833

834
#if HAVE_LIBPNG
835
/* detect whether given chunk is PNG stream */
836
static int
837
chunk_is_png(sixel_chunk_t const *chunk)
838
{
839
    if (chunk->size < 8) {
840
        return 0;
841
    }
842
    if (png_check_sig(chunk->buffer, 8)) {
843
        return 1;
844
    }
845
    return 0;
846
}
847
#endif  /* HAVE_LIBPNG */
848

849

850
/* detect whether given chunk is GIF stream */
851
static int
852
chunk_is_gif(sixel_chunk_t const *chunk)
164✔
853
{
854
    if (chunk->size < 6) {
164✔
855
        return 0;
3✔
856
    }
857
    if (chunk->buffer[0] == 'G' &&
163!
858
        chunk->buffer[1] == 'I' &&
16!
859
        chunk->buffer[2] == 'F' &&
16!
860
        chunk->buffer[3] == '8' &&
16!
861
        (chunk->buffer[4] == '7' || chunk->buffer[4] == '9') &&
16!
862
        chunk->buffer[5] == 'a') {
16!
863
        return 1;
16✔
864
    }
865
    return 0;
145✔
866
}
4✔
867

868

869
#if HAVE_JPEG
870
/* detect whether given chunk is JPEG stream */
871
static int
872
chunk_is_jpeg(sixel_chunk_t const *chunk)
873
{
874
    if (chunk->size < 2) {
875
        return 0;
876
    }
877
    if (memcmp("\xFF\xD8", chunk->buffer, 2) == 0) {
878
        return 1;
879
    }
880
    return 0;
881
}
882
#endif  /* HAVE_JPEG */
883

884
typedef union _fn_pointer {
885
    sixel_load_image_function fn;
886
    void *                    p;
887
} fn_pointer;
888

889
/* load images using builtin image loaders */
890
static SIXELSTATUS
891
load_with_builtin(
337✔
892
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
893
    int                       /* in */     fstatic,      /* static */
894
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
895
    int                       /* in */     reqcolors,    /* reqcolors */
896
    unsigned char             /* in */     *bgcolor,     /* background color */
897
    int                       /* in */     loop_control, /* one of enum loop_control */
898
    sixel_load_image_function /* in */     fn_load,      /* callback */
899
    void                      /* in/out */ *context      /* private data for callback */
900
)
901
{
902
    SIXELSTATUS status = SIXEL_FALSE;
337✔
903
    sixel_frame_t *frame = NULL;
337✔
904
    char message[256];
905
    int nwrite;
906
    fn_pointer fnp;
907

908
    if (chunk_is_sixel(pchunk)) {
337✔
909
        status = sixel_frame_new(&frame, pchunk->allocator);
153✔
910
        if (SIXEL_FAILED(status)) {
153!
911
            goto end;
×
912
        }
913
        if (pchunk->size > INT_MAX) {
153!
914
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
915
            goto end;
×
916
        }
917
        status = load_sixel(&frame->pixels,
126✔
918
                            pchunk->buffer,
153✔
919
                            (int)pchunk->size,
153✔
920
                            &frame->width,
153✔
921
                            &frame->height,
153✔
922
                            fuse_palette ? &frame->palette: NULL,
129✔
923
                            &frame->ncolors,
153✔
924
                            reqcolors,
51✔
925
                            &frame->pixelformat,
153✔
926
                            pchunk->allocator);
153✔
927
        if (SIXEL_FAILED(status)) {
153!
928
            goto end;
×
929
        }
930
    } else if (chunk_is_pnm(pchunk)) {
235!
931
        status = sixel_frame_new(&frame, pchunk->allocator);
20✔
932
        if (SIXEL_FAILED(status)) {
20!
933
            goto end;
×
934
        }
935
        if (pchunk->size > INT_MAX) {
20!
936
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
937
            goto end;
×
938
        }
939
        /* pnm */
940
        status = load_pnm(pchunk->buffer,
20✔
941
                          (int)pchunk->size,
20✔
942
                          frame->allocator,
20✔
943
                          &frame->pixels,
20✔
944
                          &frame->width,
20✔
945
                          &frame->height,
20✔
946
                          fuse_palette ? &frame->palette: NULL,
×
947
                          &frame->ncolors,
20✔
948
                          &frame->pixelformat);
20!
949
        if (SIXEL_FAILED(status)) {
20!
950
            goto end;
×
951
        }
952
    }
953
#if HAVE_JPEG
954
    else if (chunk_is_jpeg(pchunk)) {
955
        status = sixel_frame_new(&frame, pchunk->allocator);
956
        if (SIXEL_FAILED(status)) {
957
            goto end;
958
        }
959
        status = load_jpeg(&frame->pixels,
960
                           pchunk->buffer,
961
                           pchunk->size,
962
                           &frame->width,
963
                           &frame->height,
964
                           &frame->pixelformat,
965
                           pchunk->allocator);
966

967
        if (SIXEL_FAILED(status)) {
968
            goto end;
969
        }
970
    }
971
#endif  /* HAVE_JPEG */
972
#if HAVE_LIBPNG
973
    else if (chunk_is_png(pchunk)) {
974
        status = sixel_frame_new(&frame, pchunk->allocator);
975
        if (SIXEL_FAILED(status)) {
976
            goto end;
977
        }
978
        status = load_png(&frame->pixels,
979
                          pchunk->buffer,
980
                          pchunk->size,
981
                          &frame->width,
982
                          &frame->height,
983
                          fuse_palette ? &frame->palette: NULL,
984
                          &frame->ncolors,
985
                          reqcolors,
986
                          &frame->pixelformat,
987
                          bgcolor,
988
                          &frame->transparent,
989
                          pchunk->allocator);
990
        if (SIXEL_FAILED(status)) {
991
            goto end;
992
        }
993
    }
994
#endif  /* HAVE_LIBPNG */
995
    else if (chunk_is_gif(pchunk)) {
164✔
996
        fnp.fn = fn_load;
16✔
997
        if (pchunk->size > INT_MAX) {
16!
998
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
999
            goto end;
×
1000
        }
1001
        status = load_gif(pchunk->buffer,
18✔
1002
                          (int)pchunk->size,
16✔
1003
                          bgcolor,
2✔
1004
                          reqcolors,
2✔
1005
                          fuse_palette,
2✔
1006
                          fstatic,
2✔
1007
                          loop_control,
2✔
1008
                          fnp.p,
2✔
1009
                          context,
2✔
1010
                          pchunk->allocator);
16✔
1011
        if (SIXEL_FAILED(status)) {
16!
1012
            goto end;
6✔
1013
        }
1014
        goto end;
10✔
1015
    } else {
1016
        stbi__context s;
1017
        int depth;
1018

1019
        status = sixel_frame_new(&frame, pchunk->allocator);
148✔
1020
        if (SIXEL_FAILED(status)) {
148!
1021
            goto end;
2✔
1022
        }
1023
        stbi_allocator = pchunk->allocator;
148✔
1024
        if (pchunk->size > INT_MAX) {
148!
1025
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1026
            goto end;
×
1027
        }
1028
        stbi__start_mem(&s, pchunk->buffer, (int)pchunk->size);
148✔
1029
        frame->pixels = stbi__load_and_postprocess_8bit(&s, &frame->width, &frame->height, &depth, 3);
148✔
1030
        if (!frame->pixels) {
148✔
1031
            sixel_helper_set_additional_message(stbi_failure_reason());
3✔
1032
            status = SIXEL_STBI_ERROR;
3✔
1033
            goto end;
3✔
1034
        }
1035
        frame->loop_count = 1;
145✔
1036

1037
        switch (depth) {
145!
1038
        case 1:
144✔
1039
        case 3:
1040
        case 4:
1041
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
145✔
1042
            break;
145✔
1043
        default:
1044
            nwrite = sprintf(message,
×
1045
                             "load_with_builtin() failed.\n"
1046
                             "reason: unknown pixel-format.(depth: %d)\n",
1047
                             depth);
1048
            if (nwrite > 0) {
×
1049
                sixel_helper_set_additional_message(message);
×
1050
            }
1051
            goto end;
×
1052
        }
1053
    }
1054

1055
    status = sixel_frame_strip_alpha(frame, bgcolor);
318✔
1056
    if (SIXEL_FAILED(status)) {
318!
1057
        goto end;
×
1058
    }
1059

1060
    status = fn_load(frame, context);
318✔
1061
    if (SIXEL_FAILED(status)) {
318✔
1062
        goto end;
3✔
1063
    }
1064

1065
    status = SIXEL_OK;
315✔
1066

1067
end:
282✔
1068
    sixel_frame_unref(frame);
337✔
1069

1070
    return status;
337✔
1071
}
1072

1073

1074
#ifdef HAVE_GDK_PIXBUF2
1075
/*
1076
 * Loader backed by gdk-pixbuf2. The entire animation is consumed via
1077
 * GdkPixbufLoader, each frame is copied into a temporary buffer and forwarded as
1078
 * a sixel_frame_t. Loop attributes provided by gdk-pixbuf are reconciled with
1079
 * libsixel's loop control settings.
1080
 */
1081
static SIXELSTATUS
1082
load_with_gdkpixbuf(
1083
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
1084
    int                       /* in */     fstatic,      /* static */
1085
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
1086
    int                       /* in */     reqcolors,    /* reqcolors */
1087
    unsigned char             /* in */     *bgcolor,     /* background color */
1088
    int                       /* in */     loop_control, /* one of enum loop_control */
1089
    sixel_load_image_function /* in */     fn_load,      /* callback */
1090
    void                      /* in/out */ *context      /* private data for callback */
1091
)
1092
{
1093
    SIXELSTATUS status = SIXEL_FALSE;
1094
    GdkPixbuf *pixbuf;
1095
    GdkPixbufAnimation *animation;
1096
    GdkPixbufLoader *loader = NULL;
1097
    GdkPixbufAnimationIter *it = NULL;
1098
    gboolean loader_closed = FALSE;  /* remember if loader was already closed */
1099
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1100
# pragma GCC diagnostic push
1101
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1102
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1103
    GTimeVal time_val;
1104
    GTimeVal start_time;
1105
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1106
# pragma GCC diagnostic pop
1107
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1108
    sixel_frame_t *frame = NULL;
1109
    int stride;
1110
    unsigned char *p;
1111
    int i;
1112
    int depth;
1113
    int anim_loop_count = (-1);  /* (-1): infinite, >=0: finite loop count */
1114
    int delay_ms;
1115

1116
    (void) fuse_palette;
1117
    (void) reqcolors;
1118
    (void) bgcolor;
1119

1120
    status = sixel_frame_new(&frame, pchunk->allocator);
1121
    if (SIXEL_FAILED(status)) {
1122
        goto end;
1123
    }
1124

1125
#if (! GLIB_CHECK_VERSION(2, 36, 0))
1126
    g_type_init();
1127
#endif
1128
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1129
# pragma GCC diagnostic push
1130
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1131
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1132
    g_get_current_time(&time_val);
1133
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1134
# pragma GCC diagnostic pop
1135
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1136
    start_time = time_val;
1137
    loader = gdk_pixbuf_loader_new();
1138
    if (loader == NULL) {
1139
        status = SIXEL_GDK_ERROR;
1140
        goto end;
1141
    }
1142
    /* feed the whole blob and close so the animation metadata becomes available */
1143
    if (! gdk_pixbuf_loader_write(loader, pchunk->buffer, pchunk->size, NULL)) {
1144
        status = SIXEL_GDK_ERROR;
1145
        goto end;
1146
    }
1147
    if (! gdk_pixbuf_loader_close(loader, NULL)) {
1148
        status = SIXEL_GDK_ERROR;
1149
        goto end;
1150
    }
1151
    loader_closed = TRUE;
1152
    animation = gdk_pixbuf_loader_get_animation(loader);
1153
    if (animation) {
1154
        /* inspect animation object to determine built-in loop semantics */
1155
        if (GDK_IS_PIXBUF_SIMPLE_ANIM(animation)) {
1156
            anim_loop_count = gdk_pixbuf_simple_anim_get_loop(
1157
                                 GDK_PIXBUF_SIMPLE_ANIM(animation)) ? (-1) : 1;
1158
        } else {
1159
            GParamSpec *loop_pspec = g_object_class_find_property(
1160
                G_OBJECT_GET_CLASS(animation), "loop");
1161
            if (loop_pspec == NULL) {
1162
                loop_pspec = g_object_class_find_property(
1163
                    G_OBJECT_GET_CLASS(animation), "loop-count");
1164
            }
1165
            if (loop_pspec) {
1166
                GValue loop_value = G_VALUE_INIT;
1167
                g_value_init(&loop_value, loop_pspec->value_type);
1168
                g_object_get_property(G_OBJECT(animation),
1169
                                      g_param_spec_get_name(loop_pspec),
1170
                                      &loop_value);
1171
                if (G_VALUE_HOLDS_BOOLEAN(&loop_value)) {
1172
                    /* TRUE means "loop forever" for these properties */
1173
                    anim_loop_count = g_value_get_boolean(&loop_value) ? (-1) : 1;
1174
                } else if (G_VALUE_HOLDS_INT(&loop_value)) {
1175
                    int loop_int = g_value_get_int(&loop_value);
1176
                    /* GIF spec treats zero as infinite repetition */
1177
                    anim_loop_count = (loop_int <= 0) ? (-1) : loop_int;
1178
                } else if (G_VALUE_HOLDS_UINT(&loop_value)) {
1179
                    guint loop_uint = g_value_get_uint(&loop_value);
1180
                    if (loop_uint == 0U) {
1181
                        anim_loop_count = (-1);
1182
                    } else {
1183
                        anim_loop_count = loop_uint > (guint)INT_MAX
1184
                                            ? INT_MAX
1185
                                            : (int)loop_uint;
1186
                    }
1187
                }
1188
                g_value_unset(&loop_value);
1189
            }
1190
        }
1191
    }
1192
    if (!animation || fstatic || gdk_pixbuf_animation_is_static_image(animation)) {
1193
        /* fall back to single frame decoding */
1194
        pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1195
        if (pixbuf == NULL) {
1196
            goto end;
1197
        }
1198
        frame->frame_no = 0;
1199
        frame->width = gdk_pixbuf_get_width(pixbuf);
1200
        frame->height = gdk_pixbuf_get_height(pixbuf);
1201
        stride = gdk_pixbuf_get_rowstride(pixbuf);
1202
        frame->pixels = sixel_allocator_malloc(pchunk->allocator, (size_t)(frame->height * stride));
1203
        if (frame->pixels == NULL) {
1204
            sixel_helper_set_additional_message(
1205
                "load_with_gdkpixbuf: sixel_allocator_malloc() failed.");
1206
            status = SIXEL_BAD_ALLOCATION;
1207
            goto end;
1208
        }
1209
        if (stride / frame->width == 4) {
1210
            frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
1211
            depth = 4;
1212
        } else {
1213
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
1214
            depth = 3;
1215
        }
1216
        p = gdk_pixbuf_get_pixels(pixbuf);
1217
        if (stride == frame->width * depth) {
1218
            memcpy(frame->pixels, gdk_pixbuf_get_pixels(pixbuf),
1219
                   (size_t)(frame->height * stride));
1220
        } else {
1221
            for (i = 0; i < frame->height; ++i) {
1222
                memcpy(frame->pixels + frame->width * depth * i,
1223
                       p + stride * i,
1224
                       (size_t)(frame->width * depth));
1225
            }
1226
        }
1227
        status = fn_load(frame, context);
1228
        if (status != SIXEL_OK) {
1229
            goto end;
1230
        }
1231
        /* scratch buffer no longer needed after callback */
1232
        sixel_allocator_free(pchunk->allocator, frame->pixels);
1233
        frame->pixels = NULL;
1234
    } else {
1235
        gboolean finished;
1236

1237
        /* reset iterator to the beginning of the timeline */
1238
        time_val = start_time;
1239
        frame->frame_no = 0;
1240
        frame->loop_count = 0;
1241

1242
        it = gdk_pixbuf_animation_get_iter(animation, &time_val);
1243
        if (it == NULL) {
1244
            status = SIXEL_GDK_ERROR;
1245
            goto end;
1246
        }
1247

1248
        for (;;) {
1249
            /* handle one logical loop of the animation */
1250
            finished = FALSE;
1251
            while (!gdk_pixbuf_animation_iter_on_currently_loading_frame(it)) {
1252

1253
                pixbuf = gdk_pixbuf_animation_iter_get_pixbuf(it);
1254
                if (pixbuf == NULL) {
1255
                    finished = TRUE;
1256
                    break;
1257
                }
1258
                /* allocate a scratch copy of the current frame */
1259
                frame->width = gdk_pixbuf_get_width(pixbuf);
1260
                frame->height = gdk_pixbuf_get_height(pixbuf);
1261
                stride = gdk_pixbuf_get_rowstride(pixbuf);
1262
                frame->pixels = sixel_allocator_malloc(
1263
                    pchunk->allocator,
1264
                    (size_t)(frame->height * stride));
1265
                if (frame->pixels == NULL) {
1266
                    sixel_helper_set_additional_message(
1267
                        "load_with_gdkpixbuf: sixel_allocator_malloc() failed.");
1268
                    status = SIXEL_BAD_ALLOCATION;
1269
                    goto end;
1270
                }
1271
                if (gdk_pixbuf_get_has_alpha(pixbuf)) {
1272
                    frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
1273
                    depth = 4;
1274
                } else {
1275
                    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
1276
                    depth = 3;
1277
                }
1278
                p = gdk_pixbuf_get_pixels(pixbuf);
1279
                if (stride == frame->width * depth) {
1280
                    memcpy(frame->pixels, p,
1281
                           (size_t)(frame->height * stride));
1282
                } else {
1283
                    for (i = 0; i < frame->height; ++i) {
1284
                        memcpy(frame->pixels + frame->width * depth * i,
1285
                               p + stride * i,
1286
                               (size_t)(frame->width * depth));
1287
                    }
1288
                }
1289
                delay_ms = gdk_pixbuf_animation_iter_get_delay_time(it);
1290
                if (delay_ms < 0) {
1291
                    delay_ms = 0;
1292
                }
1293
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1294
# pragma GCC diagnostic push
1295
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1296
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1297
                /* advance the synthetic clock before asking gdk to move forward */
1298
                g_time_val_add(&time_val, delay_ms * 1000);
1299
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1300
# pragma GCC diagnostic pop
1301
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1302
                frame->delay = delay_ms / 10;
1303
                frame->multiframe = 1;
1304

1305
                if (!gdk_pixbuf_animation_iter_advance(it, &time_val)) {
1306
                    finished = TRUE;
1307
                }
1308
                status = fn_load(frame, context);
1309
                if (status != SIXEL_OK) {
1310
                    goto end;
1311
                }
1312
                /* release scratch pixels before decoding the next frame */
1313
                sixel_allocator_free(pchunk->allocator, frame->pixels);
1314
                frame->pixels = NULL;
1315
                frame->frame_no++;
1316

1317
                if (finished) {
1318
                    break;
1319
                }
1320
            }
1321

1322
            if (frame->frame_no == 0) {
1323
                break;
1324
            }
1325

1326
            /* finished processing one full loop */
1327
            ++frame->loop_count;
1328

1329
            if (loop_control == SIXEL_LOOP_DISABLE || frame->frame_no == 1) {
1330
                break;
1331
            }
1332
            if (loop_control == SIXEL_LOOP_AUTO) {
1333
                /* obey header-provided loop count when AUTO */
1334
                if (anim_loop_count >= 0 &&
1335
                    frame->loop_count >= anim_loop_count) {
1336
                    break;
1337
                }
1338
            } else if (loop_control != SIXEL_LOOP_FORCE &&
1339
                       anim_loop_count > 0 &&
1340
                       frame->loop_count >= anim_loop_count) {
1341
                break;
1342
            }
1343

1344
            /* restart iteration from the beginning for the next pass */
1345
            g_object_unref(it);
1346
            time_val = start_time;
1347
            it = gdk_pixbuf_animation_get_iter(animation, &time_val);
1348
            if (it == NULL) {
1349
                status = SIXEL_GDK_ERROR;
1350
                goto end;
1351
            }
1352
            /* next pass starts counting frames from zero again */
1353
            frame->frame_no = 0;
1354
        }
1355
    }
1356

1357
    status = SIXEL_OK;
1358

1359
end:
1360
    if (frame) {
1361
        /* drop the reference we obtained from sixel_frame_new() */
1362
        sixel_frame_unref(frame);
1363
    }
1364
    if (it) {
1365
        g_object_unref(it);
1366
    }
1367
    if (loader) {
1368
        if (!loader_closed) {
1369
            /* ensure the incremental loader is finalized even on error paths */
1370
            gdk_pixbuf_loader_close(loader, NULL);
1371
        }
1372
        g_object_unref(loader);
1373
    }
1374

1375
    return status;
1376

1377
}
1378
#endif  /* HAVE_GDK_PIXBUF2 */
1379

1380
#if HAVE_COREGRAPHICS
1381
static SIXELSTATUS
1382
load_with_coregraphics(
141✔
1383
    sixel_chunk_t const       /* in */     *pchunk,
1384
    int                       /* in */     fstatic,
1385
    int                       /* in */     fuse_palette,
1386
    int                       /* in */     reqcolors,
1387
    unsigned char             /* in */     *bgcolor,
1388
    int                       /* in */     loop_control,
1389
    sixel_load_image_function /* in */     fn_load,
1390
    void                      /* in/out */ *context)
1391
{
1392
    SIXELSTATUS status = SIXEL_FALSE;
141✔
1393
    sixel_frame_t *frame = NULL;
141✔
1394
    CFDataRef data = NULL;
141✔
1395
    CGImageSourceRef source = NULL;
141✔
1396
    CGImageRef image = NULL;
141✔
1397
    CGColorSpaceRef color_space = NULL;
141✔
1398
    CGContextRef ctx = NULL;
141✔
1399
    size_t stride;
1400
    size_t frame_count;
1401
    int anim_loop_count = (-1);
141✔
1402
    CFDictionaryRef props = NULL;
141✔
1403
    CFDictionaryRef anim_dict;
1404
    CFNumberRef loop_num;
1405
    CFDictionaryRef frame_props;
1406
    CFDictionaryRef frame_anim_dict;
1407
    CFNumberRef delay_num;
1408
    double delay_sec;
1409
    size_t i;
1410

1411
    (void) fuse_palette;
141✔
1412
    (void) reqcolors;
141✔
1413
    (void) bgcolor;
141✔
1414

1415
    status = sixel_frame_new(&frame, pchunk->allocator);
141✔
1416
    if (SIXEL_FAILED(status)) {
141!
1417
        goto end;
1418
    }
1419

1420
    data = CFDataCreate(kCFAllocatorDefault,
282✔
1421
                        pchunk->buffer,
141✔
1422
                        (CFIndex)pchunk->size);
141✔
1423
    if (! data) {
141!
1424
        status = SIXEL_FALSE;
1425
        goto end;
1426
    }
1427

1428
    source = CGImageSourceCreateWithData(data, NULL);
141✔
1429
    if (! source) {
141!
1430
        status = SIXEL_FALSE;
1431
        goto end;
1432
    }
1433

1434
    frame_count = CGImageSourceGetCount(source);
141✔
1435
    if (! frame_count) {
141✔
1436
        status = SIXEL_FALSE;
54✔
1437
        goto end;
54✔
1438
    }
1439
    if (fstatic) {
87✔
1440
        frame_count = 1;
7✔
1441
    }
7✔
1442

1443
    props = CGImageSourceCopyProperties(source, NULL);
87✔
1444
    if (props) {
87!
1445
        anim_dict = (CFDictionaryRef)CFDictionaryGetValue(
87✔
1446
            props, kCGImagePropertyGIFDictionary);
87✔
1447
        if (anim_dict) {
87✔
1448
            loop_num = (CFNumberRef)CFDictionaryGetValue(
5✔
1449
                anim_dict, kCGImagePropertyGIFLoopCount);
5✔
1450
            if (loop_num) {
5!
1451
                CFNumberGetValue(loop_num, kCFNumberIntType, &anim_loop_count);
5✔
1452
            }
5✔
1453
        }
5✔
1454
        CFRelease(props);
87✔
1455
    }
87✔
1456

1457
    color_space = CGColorSpaceCreateDeviceRGB();
87✔
1458
    if (! color_space) {
87!
1459
        status = SIXEL_FALSE;
1460
        goto end;
1461
    }
1462

1463
    frame->loop_count = 0;
87✔
1464

1465
    for (;;) {
87✔
1466
        frame->frame_no = 0;
89✔
1467
        for (i = 0; i < frame_count; ++i) {
214✔
1468
            delay_sec = 0.0;
129✔
1469
            frame_props = CGImageSourceCopyPropertiesAtIndex(
129✔
1470
                source, (CFIndex)i, NULL);
129✔
1471
            if (frame_props) {
129!
1472
                frame_anim_dict = (CFDictionaryRef)CFDictionaryGetValue(
129✔
1473
                    frame_props, kCGImagePropertyGIFDictionary);
129✔
1474
                if (frame_anim_dict) {
129✔
1475
                    delay_num = (CFNumberRef)CFDictionaryGetValue(
47✔
1476
                        frame_anim_dict, kCGImagePropertyGIFUnclampedDelayTime);
47✔
1477
                    if (! delay_num) {
47!
1478
                        delay_num = (CFNumberRef)CFDictionaryGetValue(
1479
                            frame_anim_dict, kCGImagePropertyGIFDelayTime);
1480
                    }
1481
                    if (delay_num) {
47!
1482
                        CFNumberGetValue(delay_num,
47✔
1483
                                         kCFNumberDoubleType,
1484
                                         &delay_sec);
1485
                    }
47✔
1486
                }
47✔
1487
#if defined(kCGImagePropertyPNGDictionary) && \
1488
    defined(kCGImagePropertyAPNGUnclampedDelayTime) && \
1489
    defined(kCGImagePropertyAPNGDelayTime)
1490
                if (delay_sec <= 0.0) {
1491
                    CFDictionaryRef png_frame = (CFDictionaryRef)CFDictionaryGetValue(
1492
                        frame_props, kCGImagePropertyPNGDictionary);
1493
                    if (png_frame) {
1494
                        delay_num = (CFNumberRef)CFDictionaryGetValue(
1495
                            png_frame, kCGImagePropertyAPNGUnclampedDelayTime);
1496
                        if (! delay_num) {
1497
                            delay_num = (CFNumberRef)CFDictionaryGetValue(
1498
                                png_frame, kCGImagePropertyAPNGDelayTime);
1499
                        }
1500
                        if (delay_num) {
1501
                            CFNumberGetValue(delay_num,
1502
                                             kCFNumberDoubleType,
1503
                                             &delay_sec);
1504
                        }
1505
                    }
1506
                }
1507
#endif
1508
                CFRelease(frame_props);
129✔
1509
            }
129✔
1510
            if (delay_sec <= 0.0) {
129✔
1511
                delay_sec = 0.1;
83✔
1512
            }
83✔
1513
            frame->delay = (int)(delay_sec * 100.0 + 0.5);
129✔
1514
            if (frame->delay < 1) {
129!
1515
                frame->delay = 1;
1516
            }
1517

1518
            image = CGImageSourceCreateImageAtIndex(source, (CFIndex)i, NULL);
129✔
1519
            if (! image) {
129!
1520
                status = SIXEL_FALSE;
1521
                goto end;
1522
            }
1523

1524
            frame->width = (int)CGImageGetWidth(image);
129✔
1525
            frame->height = (int)CGImageGetHeight(image);
129✔
1526
            frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
129✔
1527
            stride = (size_t)frame->width * 4;
129✔
1528
            frame->pixels = sixel_allocator_malloc(
129✔
1529
                pchunk->allocator, (size_t)(frame->height * stride));
129✔
1530

1531
            if (frame->pixels == NULL) {
129!
1532
                sixel_helper_set_additional_message(
1533
                    "load_with_coregraphics: sixel_allocator_malloc() failed.");
1534
                status = SIXEL_BAD_ALLOCATION;
1535
                CGImageRelease(image);
1536
                goto end;
1537
            }
1538

1539
            ctx = CGBitmapContextCreate(frame->pixels,
258✔
1540
                                        frame->width,
129✔
1541
                                        frame->height,
129✔
1542
                                        8,
1543
                                        stride,
129✔
1544
                                        color_space,
129✔
1545
                                        kCGImageAlphaPremultipliedLast |
1546
                                            kCGBitmapByteOrder32Big);
1547
            if (!ctx) {
129!
1548
                CGImageRelease(image);
1549
                goto end;
1550
            }
1551

1552
            CGContextDrawImage(ctx,
258✔
1553
                               CGRectMake(0, 0, frame->width, frame->height),
129✔
1554
                               image);
129✔
1555
            CGContextRelease(ctx);
129✔
1556
            ctx = NULL;
129✔
1557

1558
            frame->multiframe = (frame_count > 1);
129✔
1559
            status = fn_load(frame, context);
129✔
1560
            sixel_allocator_free(pchunk->allocator, frame->pixels);
129✔
1561
            frame->pixels = NULL;
129✔
1562
            CGImageRelease(image);
129✔
1563
            image = NULL;
129✔
1564
            if (status != SIXEL_OK) {
129✔
1565
                goto end;
4✔
1566
            }
1567
            ++frame->frame_no;
125✔
1568
        }
125✔
1569

1570
        ++frame->loop_count;
85✔
1571

1572
        if (frame_count <= 1) {
85✔
1573
            break;
80✔
1574
        }
1575
        if (loop_control == SIXEL_LOOP_DISABLE) {
5✔
1576
            break;
2✔
1577
        }
1578
        if (loop_control == SIXEL_LOOP_AUTO) {
3!
1579
            if (anim_loop_count < 0) {
3!
1580
                break;
1581
            }
1582
            if (anim_loop_count > 0 && frame->loop_count >= anim_loop_count) {
3!
1583
                break;
1✔
1584
            }
1585
            continue;
2✔
1586
        }
1587
    }
1588

1589
    status = SIXEL_OK;
83✔
1590

1591
end:
1592
    if (ctx) {
141!
1593
        CGContextRelease(ctx);
1594
    }
1595
    if (color_space) {
141✔
1596
        CGColorSpaceRelease(color_space);
87✔
1597
    }
87✔
1598
    if (image) {
141!
1599
        CGImageRelease(image);
1600
    }
1601
    if (source) {
141!
1602
        CFRelease(source);
141✔
1603
    }
141✔
1604
    if (data) {
141!
1605
        CFRelease(data);
141✔
1606
    }
141✔
1607
    if (frame) {
141!
1608
        sixel_allocator_free(pchunk->allocator, frame->pixels);
141✔
1609
        sixel_allocator_free(pchunk->allocator, frame->palette);
141✔
1610
        sixel_allocator_free(pchunk->allocator, frame);
141✔
1611
    }
141✔
1612
    return status;
141✔
1613
}
1614
#endif  /* HAVE_COREGRAPHICS */
1615

1616
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
1617
static SIXELSTATUS
1618
load_with_quicklook(
58✔
1619
    sixel_chunk_t const       /* in */     *pchunk,
1620
    int                       /* in */     fstatic,
1621
    int                       /* in */     fuse_palette,
1622
    int                       /* in */     reqcolors,
1623
    unsigned char             /* in */     *bgcolor,
1624
    int                       /* in */     loop_control,
1625
    sixel_load_image_function /* in */     fn_load,
1626
    void                      /* in/out */ *context)
1627
{
1628
    SIXELSTATUS status = SIXEL_FALSE;
58✔
1629
    sixel_frame_t *frame = NULL;
58✔
1630
    CFStringRef path = NULL;
58✔
1631
    CFURLRef url = NULL;
58✔
1632
    CGImageRef image = NULL;
58✔
1633
    CGColorSpaceRef color_space = NULL;
58✔
1634
    CGContextRef ctx = NULL;
58✔
1635
    CGRect bounds;
1636
    size_t stride;
1637
    unsigned char fill_color[3];
1638
    CGFloat fill_r;
1639
    CGFloat fill_g;
1640
    CGFloat fill_b;
1641

1642
    (void)fstatic;
58✔
1643
    (void)fuse_palette;
58✔
1644
    (void)reqcolors;
58✔
1645
    (void)loop_control;
58✔
1646

1647
    if (pchunk == NULL || pchunk->source_path == NULL) {
58!
1648
        goto end;
42✔
1649
    }
1650

1651
    status = sixel_frame_new(&frame, pchunk->allocator);
16✔
1652
    if (SIXEL_FAILED(status)) {
16!
1653
        goto end;
1654
    }
1655

1656
    path = CFStringCreateWithCString(kCFAllocatorDefault,
32✔
1657
                                     pchunk->source_path,
16✔
1658
                                     kCFStringEncodingUTF8);
1659
    if (path == NULL) {
16!
1660
        status = SIXEL_RUNTIME_ERROR;
1661
        goto end;
1662
    }
1663

1664
    url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
32✔
1665
                                        path,
16✔
1666
                                        kCFURLPOSIXPathStyle,
1667
                                        false);
1668
    if (url == NULL) {
16!
1669
        status = SIXEL_RUNTIME_ERROR;
1670
        goto end;
1671
    }
1672

1673
    CGSize max_size;
1674
    max_size.width = 1000.0f;
16✔
1675
    max_size.height = 1000.0f;
16✔
1676
#if HAVE_QUICKLOOK_THUMBNAILING
1677
    image = sixel_quicklook_thumbnail_create(url, max_size);
16✔
1678
    if (image == NULL) {
16✔
1679
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1680
#  pragma clang diagnostic push
1681
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
1682
# endif
1683
        image = QLThumbnailImageCreate(kCFAllocatorDefault,
24✔
1684
                                       url,
12✔
1685
                                       max_size,
1686
                                       NULL);
1687
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1688
#  pragma clang diagnostic pop
1689
# endif
1690
    }
12✔
1691
#else
1692
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1693
#  pragma clang diagnostic push
1694
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
1695
# endif
1696
    image = QLThumbnailImageCreate(kCFAllocatorDefault,
1697
                                   url,
1698
                                   max_size,
1699
                                   NULL);
1700
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1701
#  pragma clang diagnostic pop
1702
# endif
1703
#endif
1704
    if (image == NULL) {
16✔
1705
        status = SIXEL_RUNTIME_ERROR;
12✔
1706
        sixel_helper_set_additional_message(
12✔
1707
            "load_with_quicklook: CQLThumbnailImageCreate() failed.");
1708
        goto end;
12✔
1709
    }
1710

1711
    color_space = CGColorSpaceCreateDeviceRGB();
4✔
1712
    if (color_space == NULL) {
4!
1713
        status = SIXEL_RUNTIME_ERROR;
1714
        sixel_helper_set_additional_message(
1715
            "load_with_quicklook: CGColorSpaceCreateDeviceRGB() failed.");
1716
        goto end;
1717
    }
1718

1719
    frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
4✔
1720
    frame->width = (int)CGImageGetWidth(image);
4✔
1721
    frame->height = (int)CGImageGetHeight(image);
4✔
1722
    if (frame->width <= 0 || frame->height <= 0) {
4!
1723
        status = SIXEL_RUNTIME_ERROR;
1724
        sixel_helper_set_additional_message(
1725
            "load_with_quicklook: invalid image size detected.");
1726
        goto end;
1727
    }
1728

1729
    stride = (size_t)frame->width * 4;
4✔
1730
    frame->pixels =
4✔
1731
        sixel_allocator_malloc(pchunk->allocator,
8✔
1732
                               (size_t)frame->height * stride);
4✔
1733
    if (frame->pixels == NULL) {
4!
1734
        sixel_helper_set_additional_message(
1735
            "load_with_quicklook: sixel_allocator_malloc() failed.");
1736
        status = SIXEL_BAD_ALLOCATION;
1737
        goto end;
1738
    }
1739

1740
    if (bgcolor != NULL) {
4!
1741
        fill_color[0] = bgcolor[0];
1742
        fill_color[1] = bgcolor[1];
1743
        fill_color[2] = bgcolor[2];
1744
    } else {
1745
        fill_color[0] = 255;
4✔
1746
        fill_color[1] = 255;
4✔
1747
        fill_color[2] = 255;
4✔
1748
    }
1749

1750
    ctx = CGBitmapContextCreate(frame->pixels,
8✔
1751
                                frame->width,
4✔
1752
                                frame->height,
4✔
1753
                                8,
1754
                                stride,
4✔
1755
                                color_space,
4✔
1756
                                kCGImageAlphaPremultipliedLast |
1757
                                    kCGBitmapByteOrder32Big);
1758
    if (ctx == NULL) {
4!
1759
        status = SIXEL_RUNTIME_ERROR;
1760
        sixel_helper_set_additional_message(
1761
            "load_with_quicklook: CGBitmapContextCreate() failed.");
1762
        goto end;
1763
    }
1764

1765
    bounds = CGRectMake(0,
4✔
1766
                        0,
1767
                        (CGFloat)frame->width,
4✔
1768
                        (CGFloat)frame->height);
4✔
1769
    fill_r = (CGFloat)fill_color[0] / 255.0f;
4✔
1770
    fill_g = (CGFloat)fill_color[1] / 255.0f;
4✔
1771
    fill_b = (CGFloat)fill_color[2] / 255.0f;
4✔
1772
    CGContextSetRGBFillColor(ctx, fill_r, fill_g, fill_b, 1.0f);
4✔
1773
    CGContextFillRect(ctx, bounds);
4✔
1774
    CGContextDrawImage(ctx, bounds, image);
4✔
1775
    CGContextFlush(ctx);
4✔
1776

1777
    /* Abort when Quick Look produced no visible pixels so other loaders run. */
1778
    {
1779
        size_t pixel_count;
1780
        size_t index;
1781
        unsigned char *pixel;
1782
        int has_content;
1783

1784
        pixel_count = (size_t)frame->width * (size_t)frame->height;
4✔
1785
        pixel = frame->pixels;
4✔
1786
        has_content = 0;
4✔
1787
        for (index = 0; index < pixel_count; ++index) {
4!
1788
            if (pixel[0] != fill_color[0] ||
4!
1789
                    pixel[1] != fill_color[1] ||
×
1790
                    pixel[2] != fill_color[2] ||
×
1791
                    pixel[3] != 0xff) {
1792
                has_content = 1;
4✔
1793
                break;
4✔
1794
            }
1795
            pixel += 4;
1796
        }
1797
        if (! has_content) {
4!
1798
            sixel_helper_set_additional_message(
1799
                "load_with_quicklook: thumbnail contained no visible pixels.");
1800
            status = SIXEL_BAD_INPUT;
1801
            CGContextRelease(ctx);
1802
            ctx = NULL;
1803
            goto end;
1804
        }
1805
    }
1806

1807
    CGContextRelease(ctx);
4✔
1808
    ctx = NULL;
4✔
1809

1810
    frame->delay = 0;
4✔
1811
    frame->frame_no = 0;
4✔
1812
    frame->loop_count = 1;
4✔
1813
    frame->multiframe = 0;
4✔
1814
    frame->transparent = (-1);
4✔
1815

1816
    status = sixel_frame_strip_alpha(frame, fill_color);
4✔
1817
    if (SIXEL_FAILED(status)) {
4!
1818
        goto end;
1819
    }
1820

1821
    status = fn_load(frame, context);
4✔
1822
    sixel_allocator_free(pchunk->allocator, frame->pixels);
4✔
1823
    frame->pixels = NULL;
4✔
1824
    if (status != SIXEL_OK) {
4✔
1825
        goto end;
1✔
1826
    }
1827

1828
    status = SIXEL_OK;
3✔
1829

1830
end:
1831
    if (ctx != NULL) {
58!
1832
        CGContextRelease(ctx);
1833
    }
1834
    if (color_space != NULL) {
58✔
1835
        CGColorSpaceRelease(color_space);
4✔
1836
    }
4✔
1837
    if (image != NULL) {
58✔
1838
        CGImageRelease(image);
4✔
1839
    }
4✔
1840
    if (url != NULL) {
58✔
1841
        CFRelease(url);
16✔
1842
    }
16✔
1843
    if (path != NULL) {
58✔
1844
        CFRelease(path);
16✔
1845
    }
16✔
1846
    if (frame != NULL) {
58✔
1847
        sixel_allocator_free(pchunk->allocator, frame->pixels);
16✔
1848
        sixel_allocator_free(pchunk->allocator, frame->palette);
16✔
1849
        sixel_allocator_free(pchunk->allocator, frame);
16✔
1850
    }
16✔
1851

1852
    return status;
58✔
1853
}
1854
#endif  /* HAVE_COREGRAPHICS && HAVE_QUICKLOOK */
1855

1856
#if HAVE_GD
1857
static int
1858
detect_file_format(int len, unsigned char *data)
1859
{
1860
    if (len > 18 && memcmp("TRUEVISION", data + len - 18, 10) == 0) {
1861
        return SIXEL_FORMAT_TGA;
1862
    }
1863

1864
    if (len > 3 && memcmp("GIF", data, 3) == 0) {
1865
        return SIXEL_FORMAT_GIF;
1866
    }
1867

1868
    if (len > 8 && memcmp("\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", data, 8) == 0) {
1869
        return SIXEL_FORMAT_PNG;
1870
    }
1871

1872
    if (len > 2 && memcmp("BM", data, 2) == 0) {
1873
        return SIXEL_FORMAT_BMP;
1874
    }
1875

1876
    if (len > 2 && memcmp("\xFF\xD8", data, 2) == 0) {
1877
        return SIXEL_FORMAT_JPG;
1878
    }
1879

1880
    if (len > 2 && memcmp("\x00\x00", data, 2) == 0) {
1881
        return SIXEL_FORMAT_WBMP;
1882
    }
1883

1884
    if (len > 2 && memcmp("\x4D\x4D", data, 2) == 0) {
1885
        return SIXEL_FORMAT_TIFF;
1886
    }
1887

1888
    if (len > 2 && memcmp("\x49\x49", data, 2) == 0) {
1889
        return SIXEL_FORMAT_TIFF;
1890
    }
1891

1892
    if (len > 2 && memcmp("\033P", data, 2) == 0) {
1893
        return SIXEL_FORMAT_SIXEL;
1894
    }
1895

1896
    if (len > 2 && data[0] == 0x90  && (data[len - 1] == 0x9C || data[len - 2] == 0x9C)) {
1897
        return SIXEL_FORMAT_SIXEL;
1898
    }
1899

1900
    if (len > 1 && data[0] == 'P' && data[1] >= '1' && data[1] <= '6') {
1901
        return SIXEL_FORMAT_PNM;
1902
    }
1903

1904
    if (len > 3 && memcmp("gd2", data, 3) == 0) {
1905
        return SIXEL_FORMAT_GD2;
1906
    }
1907

1908
    if (len > 4 && memcmp("8BPS", data, 4) == 0) {
1909
        return SIXEL_FORMAT_PSD;
1910
    }
1911

1912
    if (len > 11 && memcmp("#?RADIANCE\n", data, 11) == 0) {
1913
        return SIXEL_FORMAT_HDR;
1914
    }
1915

1916
    return (-1);
1917
}
1918
#endif /* HAVE_GD */
1919

1920
#if HAVE_GD
1921

1922
static SIXELSTATUS
1923
load_with_gd(
1924
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
1925
    int                       /* in */     fstatic,      /* static */
1926
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
1927
    int                       /* in */     reqcolors,    /* reqcolors */
1928
    unsigned char             /* in */     *bgcolor,     /* background color */
1929
    int                       /* in */     loop_control, /* one of enum loop_control */
1930
    sixel_load_image_function /* in */     fn_load,      /* callback */
1931
    void                      /* in/out */ *context      /* private data for callback */
1932
)
1933
{
1934
    SIXELSTATUS status = SIXEL_FALSE;
1935
    unsigned char *p;
1936
    gdImagePtr im = NULL;
1937
    int x, y;
1938
    int c;
1939
    sixel_frame_t *frame = NULL;
1940
    int format;
1941

1942
    (void) fstatic;
1943
    (void) fuse_palette;
1944
    (void) reqcolors;
1945
    (void) bgcolor;
1946
    (void) loop_control;
1947

1948
    format = detect_file_format(pchunk->size, pchunk->buffer);
1949

1950
    if (format == SIXEL_FORMAT_GIF) {
1951
#if HAVE_DECL_GDIMAGECREATEFROMGIFANIMPTR
1952
        gdImagePtr *ims = NULL;
1953
        int frames = 0;
1954
        int i;
1955
        int *delays = NULL;
1956

1957
        ims = gdImageCreateFromGifAnimPtr(pchunk->size, pchunk->buffer,
1958
                                          &frames, &delays);
1959
        if (ims == NULL) {
1960
            status = SIXEL_GD_ERROR;
1961
            goto end;
1962
        }
1963

1964
        for (i = 0; i < frames; i++) {
1965
            im = ims[i];
1966
            if (!gdImageTrueColor(im)) {
1967
# if HAVE_DECL_GDIMAGEPALETTETOTRUECOLOR
1968
                if (!gdImagePaletteToTrueColor(im)) {
1969
                    status = SIXEL_GD_ERROR;
1970
                    goto gif_end;
1971
                }
1972
# else
1973
                status = SIXEL_GD_ERROR;
1974
                goto gif_end;
1975
# endif
1976
            }
1977

1978
            status = sixel_frame_new(&frame, pchunk->allocator);
1979
            if (SIXEL_FAILED(status)) {
1980
                frame = NULL;
1981
                goto gif_end;
1982
            }
1983

1984
            frame->width = gdImageSX(im);
1985
            frame->height = gdImageSY(im);
1986
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
1987
            p = frame->pixels = sixel_allocator_malloc(
1988
                pchunk->allocator,
1989
                (size_t)(frame->width * frame->height * 3));
1990
            if (frame->pixels == NULL) {
1991
                sixel_helper_set_additional_message(
1992
                    "load_with_gd: sixel_allocator_malloc() failed.");
1993
                status = SIXEL_BAD_ALLOCATION;
1994
                sixel_frame_unref(frame);
1995
                frame = NULL;
1996
                goto gif_end;
1997
            }
1998
            for (y = 0; y < frame->height; y++) {
1999
                for (x = 0; x < frame->width; x++) {
2000
                    c = gdImageTrueColorPixel(im, x, y);
2001
                    *p++ = gdTrueColorGetRed(c);
2002
                    *p++ = gdTrueColorGetGreen(c);
2003
                    *p++ = gdTrueColorGetBlue(c);
2004
                }
2005
            }
2006

2007
            if (delays) {
2008
                frame->delay.tv_sec = delays[i] / 100;
2009
                frame->delay.tv_nsec = (delays[i] % 100) * 10000000L;
2010
            }
2011

2012
            status = fn_load(frame, context);
2013
            sixel_frame_unref(frame);
2014
            frame = NULL;
2015
            gdImageDestroy(im);
2016
            ims[i] = NULL;
2017
            if (SIXEL_FAILED(status)) {
2018
                goto gif_end;
2019
            }
2020
        }
2021

2022
        status = SIXEL_OK;
2023

2024
gif_end:
2025
        if (delays) {
2026
            gdFree(delays);
2027
        }
2028
        if (ims) {
2029
            for (i = 0; i < frames; i++) {
2030
                if (ims[i]) {
2031
                    gdImageDestroy(ims[i]);
2032
                }
2033
            }
2034
            gdFree(ims);
2035
        }
2036
        goto end;
2037
#else
2038
        status = SIXEL_GD_ERROR;
2039
        goto end;
2040
#endif
2041
    }
2042

2043
    switch (format) {
2044
#if HAVE_DECL_GDIMAGECREATEFROMPNGPTR
2045
        case SIXEL_FORMAT_PNG:
2046
            im = gdImageCreateFromPngPtr(pchunk->size, pchunk->buffer);
2047
            break;
2048
#endif  /* HAVE_DECL_GDIMAGECREATEFROMPNGPTR */
2049
#if HAVE_DECL_GDIMAGECREATEFROMBMPPTR
2050
        case SIXEL_FORMAT_BMP:
2051
            im = gdImageCreateFromBmpPtr(pchunk->size, pchunk->buffer);
2052
            break;
2053
#endif  /* HAVE_DECL_GDIMAGECREATEFROMBMPPTR */
2054
        case SIXEL_FORMAT_JPG:
2055
#if HAVE_DECL_GDIMAGECREATEFROMJPEGPTREX
2056
            im = gdImageCreateFromJpegPtrEx(pchunk->size, pchunk->buffer, 1);
2057
#elif HAVE_DECL_GDIMAGECREATEFROMJPEGPTR
2058
            im = gdImageCreateFromJpegPtr(pchunk->size, pchunk->buffer);
2059
#endif  /* HAVE_DECL_GDIMAGECREATEFROMJPEGPTREX */
2060
            break;
2061
#if HAVE_DECL_GDIMAGECREATEFROMTGAPTR
2062
        case SIXEL_FORMAT_TGA:
2063
            im = gdImageCreateFromTgaPtr(pchunk->size, pchunk->buffer);
2064
            break;
2065
#endif  /* HAVE_DECL_GDIMAGECREATEFROMTGAPTR */
2066
#if HAVE_DECL_GDIMAGECREATEFROMWBMPPTR
2067
        case SIXEL_FORMAT_WBMP:
2068
            im = gdImageCreateFromWBMPPtr(pchunk->size, pchunk->buffer);
2069
            break;
2070
#endif  /* HAVE_DECL_GDIMAGECREATEFROMWBMPPTR */
2071
#if HAVE_DECL_GDIMAGECREATEFROMTIFFPTR
2072
        case SIXEL_FORMAT_TIFF:
2073
            im = gdImageCreateFromTiffPtr(pchunk->size, pchunk->buffer);
2074
            break;
2075
#endif  /* HAVE_DECL_GDIMAGECREATEFROMTIFFPTR */
2076
#if HAVE_DECL_GDIMAGECREATEFROMGD2PTR
2077
        case SIXEL_FORMAT_GD2:
2078
            im = gdImageCreateFromGd2Ptr(pchunk->size, pchunk->buffer);
2079
            break;
2080
#endif  /* HAVE_DECL_GDIMAGECREATEFROMGD2PTR */
2081
        default:
2082
            status = SIXEL_GD_ERROR;
2083
            sixel_helper_set_additional_message(
2084
                "unexpected image format detected.");
2085
            goto end;
2086
    }
2087

2088
    if (im == NULL) {
2089
        status = SIXEL_GD_ERROR;
2090
        /* TODO: retrieve error detail */
2091
        goto end;
2092
    }
2093

2094
    if (!gdImageTrueColor(im)) {
2095
#if HAVE_DECL_GDIMAGEPALETTETOTRUECOLOR
2096
        if (!gdImagePaletteToTrueColor(im)) {
2097
            gdImageDestroy(im);
2098
            status = SIXEL_GD_ERROR;
2099
            /* TODO: retrieve error detail */
2100
            goto end;
2101
        }
2102
#else
2103
        status = SIXEL_GD_ERROR;
2104
        /* TODO: retrieve error detail */
2105
        goto end;
2106
#endif
2107
    }
2108

2109
    status = sixel_frame_new(&frame, pchunk->allocator);
2110
    if (SIXEL_FAILED(status)) {
2111
        goto end;
2112
    }
2113

2114
    frame->width = gdImageSX(im);
2115
    frame->height = gdImageSY(im);
2116
    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
2117
    p = frame->pixels = sixel_allocator_malloc(
2118
        pchunk->allocator, (size_t)(frame->width * frame->height * 3));
2119
    if (frame->pixels == NULL) {
2120
        sixel_helper_set_additional_message(
2121
            "load_with_gd: sixel_allocator_malloc() failed.");
2122
        status = SIXEL_BAD_ALLOCATION;
2123
        gdImageDestroy(im);
2124
        goto end;
2125
    }
2126
    for (y = 0; y < frame->height; y++) {
2127
        for (x = 0; x < frame->width; x++) {
2128
            c = gdImageTrueColorPixel(im, x, y);
2129
            *p++ = gdTrueColorGetRed(c);
2130
            *p++ = gdTrueColorGetGreen(c);
2131
            *p++ = gdTrueColorGetBlue(c);
2132
        }
2133
    }
2134
    gdImageDestroy(im);
2135

2136
    status = fn_load(frame, context);
2137
    if (SIXEL_FAILED(status)) {
2138
        goto end;
2139
    }
2140

2141
    sixel_frame_unref(frame);
2142

2143
    status = SIXEL_OK;
2144

2145
end:
2146
    return status;
2147
}
2148

2149
#endif  /* HAVE_GD */
2150

2151
#if HAVE_WIC
2152

2153
#include <windows.h>
2154
#include <wincodec.h>
2155

2156
SIXELSTATUS
2157
load_with_wic(
2158
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
2159
    int                       /* in */     fstatic,      /* static */
2160
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
2161
    int                       /* in */     reqcolors,    /* reqcolors */
2162
    unsigned char             /* in */     *bgcolor,     /* background color */
2163
    int                       /* in */     loop_control, /* one of enum loop_control */
2164
    sixel_load_image_function /* in */     fn_load,      /* callback */
2165
    void                      /* in/out */ *context      /* private data for callback */
2166
)
2167
{
2168
    HRESULT                 hr         = E_FAIL;
2169
    SIXELSTATUS             status     = SIXEL_FALSE;
2170
    IWICImagingFactory     *factory    = NULL;
2171
    IWICStream             *stream     = NULL;
2172
    IWICBitmapDecoder      *decoder    = NULL;
2173
    IWICBitmapFrameDecode  *wicframe   = NULL;
2174
    IWICFormatConverter    *conv       = NULL;
2175
    IWICBitmapSource       *src        = NULL;
2176
    IWICPalette            *wicpalette = NULL;
2177
    WICColor               *wiccolors  = NULL;
2178
    IWICMetadataQueryReader *qdecoder  = NULL;
2179
    IWICMetadataQueryReader *qframe    = NULL;
2180
    UINT                    ncolors    = 0;
2181
    sixel_frame_t          *frame      = NULL;
2182
    int                     comp       = 4;
2183
    UINT                    actual     = 0;
2184
    UINT                    i;
2185
    UINT                    frame_count = 0;
2186
    int                     anim_loop_count = (-1);
2187
    int                     is_gif;
2188
    WICColor                c;
2189

2190
    (void) reqcolors;
2191
    (void) bgcolor;
2192

2193
    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
2194
    if (FAILED(hr) && hr != RPC_E_CHANGED_MODE) {
2195
        return status;
2196
    }
2197

2198
    status = sixel_frame_new(&frame, pchunk->allocator);
2199
    if (SIXEL_FAILED(status)) {
2200
        goto end;
2201
    }
2202

2203
    hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
2204
                          &IID_IWICImagingFactory, (void**)&factory);
2205
    if (FAILED(hr)) {
2206
        goto end;
2207
    }
2208

2209
    hr = factory->lpVtbl->CreateStream(factory, &stream);
2210
    if (FAILED(hr)) {
2211
        goto end;
2212
    }
2213

2214
    hr = stream->lpVtbl->InitializeFromMemory(stream,
2215
                                              (BYTE*)pchunk->buffer,
2216
                                              (DWORD)pchunk->size);
2217
    if (FAILED(hr)) {
2218
        goto end;
2219
    }
2220

2221
    hr = factory->lpVtbl->CreateDecoderFromStream(factory,
2222
                                                  (IStream*)stream,
2223
                                                  NULL,
2224
                                                  WICDecodeMetadataCacheOnDemand,
2225
                                                  &decoder);
2226
    if (FAILED(hr)) {
2227
        goto end;
2228
    }
2229

2230
    is_gif = (memcmp("GIF", pchunk->buffer, 3) == 0);
2231

2232
    if (is_gif) {
2233
        hr = decoder->lpVtbl->GetFrameCount(decoder, &frame_count);
2234
        if (FAILED(hr)) {
2235
            goto end;
2236
        }
2237
        if (fstatic) {
2238
            frame_count = 1;
2239
        }
2240

2241
        hr = decoder->lpVtbl->GetMetadataQueryReader(decoder, &qdecoder);
2242
        if (SUCCEEDED(hr)) {
2243
            PROPVARIANT pv;
2244
            PropVariantInit(&pv);
2245
            hr = qdecoder->lpVtbl->GetMetadataByName(
2246
                qdecoder,
2247
                L"/appext/Application/NETSCAPE2.0/Loop",
2248
                &pv);
2249
            if (SUCCEEDED(hr) && pv.vt == VT_UI2) {
2250
                anim_loop_count = pv.uiVal;
2251
            }
2252
            PropVariantClear(&pv);
2253
            qdecoder->lpVtbl->Release(qdecoder);
2254
            qdecoder = NULL;
2255
        }
2256

2257
        frame->loop_count = 0;
2258
        for (;;) {
2259
            frame->frame_no = 0;
2260
            for (i = 0; i < frame_count; ++i) {
2261
                hr = decoder->lpVtbl->GetFrame(decoder, i, &wicframe);
2262
                if (FAILED(hr)) {
2263
                    goto end;
2264
                }
2265

2266
                hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
2267
                if (FAILED(hr)) {
2268
                    goto end;
2269
                }
2270
                hr = conv->lpVtbl->Initialize(conv,
2271
                                              (IWICBitmapSource*)wicframe,
2272
                                              &GUID_WICPixelFormat32bppRGBA,
2273
                                              WICBitmapDitherTypeNone,
2274
                                              NULL,
2275
                                              0.0,
2276
                                              WICBitmapPaletteTypeCustom);
2277
                if (FAILED(hr)) {
2278
                    goto end;
2279
                }
2280

2281
                src = (IWICBitmapSource*)conv;
2282
                comp = 4;
2283
                frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
2284

2285
                hr = src->lpVtbl->GetSize(
2286
                    src,
2287
                    (UINT *)&frame->width,
2288
                    (UINT *)&frame->height);
2289
                if (FAILED(hr)) {
2290
                    goto end;
2291
                }
2292

2293
                if (frame->width <= 0 || frame->height <= 0 ||
2294
                    frame->width > SIXEL_WIDTH_LIMIT ||
2295
                    frame->height > SIXEL_HEIGHT_LIMIT) {
2296
                    sixel_helper_set_additional_message(
2297
                        "load_with_wic: an invalid width or height parameter detected.");
2298
                    status = SIXEL_BAD_INPUT;
2299
                    goto end;
2300
                }
2301

2302
                frame->pixels = sixel_allocator_malloc(
2303
                    pchunk->allocator,
2304
                    (size_t)(frame->height * frame->width * comp));
2305
                if (frame->pixels == NULL) {
2306
                    hr = E_OUTOFMEMORY;
2307
                    goto end;
2308
                }
2309

2310
                {
2311
                    WICRect rc = { 0, 0, (INT)frame->width, (INT)frame->height };
2312
                    hr = src->lpVtbl->CopyPixels(
2313
                        src,
2314
                        &rc,
2315
                        frame->width * comp,
2316
                        (UINT)frame->width * frame->height * comp,
2317
                        frame->pixels);
2318
                    if (FAILED(hr)) {
2319
                        goto end;
2320
                    }
2321
                }
2322

2323
                frame->delay = 0;
2324
                hr = wicframe->lpVtbl->GetMetadataQueryReader(wicframe, &qframe);
2325
                if (SUCCEEDED(hr)) {
2326
                    PROPVARIANT pv;
2327
                    PropVariantInit(&pv);
2328
                    hr = qframe->lpVtbl->GetMetadataByName(
2329
                        qframe,
2330
                        L"/grctlext/Delay",
2331
                        &pv);
2332
                    if (SUCCEEDED(hr) && pv.vt == VT_UI2) {
2333
                        frame->delay = (int)(pv.uiVal) * 10;
2334
                    }
2335
                    PropVariantClear(&pv);
2336
                    qframe->lpVtbl->Release(qframe);
2337
                    qframe = NULL;
2338
                }
2339

2340
                frame->multiframe = 1;
2341
                status = fn_load(frame, context);
2342
                if (SIXEL_FAILED(status)) {
2343
                    goto end;
2344
                }
2345
                frame->pixels = NULL;
2346
                frame->palette = NULL;
2347

2348
                if (conv) {
2349
                    conv->lpVtbl->Release(conv);
2350
                    conv = NULL;
2351
                }
2352
                if (wicframe) {
2353
                    wicframe->lpVtbl->Release(wicframe);
2354
                    wicframe = NULL;
2355
                }
2356

2357
                frame->frame_no++;
2358
            }
2359

2360
            ++frame->loop_count;
2361

2362
            if (anim_loop_count < 0) {
2363
                break;
2364
            }
2365
            if (loop_control == SIXEL_LOOP_DISABLE || frame->frame_no == 1) {
2366
                break;
2367
            }
2368
            if (loop_control == SIXEL_LOOP_AUTO &&
2369
                frame->loop_count == anim_loop_count) {
2370
                break;
2371
            }
2372
        }
2373

2374
        status = SIXEL_OK;
2375
        goto end;
2376
    }
2377

2378
    hr = decoder->lpVtbl->GetFrame(decoder, 0, &wicframe);
2379
    if (FAILED(hr)) {
2380
        goto end;
2381
    }
2382

2383
    if (fuse_palette) {
2384
        hr = factory->lpVtbl->CreatePalette(factory, &wicpalette);
2385
        if (SUCCEEDED(hr)) {
2386
            hr = wicframe->lpVtbl->CopyPalette(wicframe, wicpalette);
2387
        }
2388
        if (SUCCEEDED(hr)) {
2389
            hr = wicpalette->lpVtbl->GetColorCount(wicpalette, &ncolors);
2390
        }
2391
        if (SUCCEEDED(hr) && ncolors > 0 && ncolors <= 256) {
2392
            hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
2393
            if (SUCCEEDED(hr)) {
2394
                hr = conv->lpVtbl->Initialize(conv,
2395
                                              (IWICBitmapSource*)wicframe,
2396
                                              &GUID_WICPixelFormat8bppIndexed,
2397
                                              WICBitmapDitherTypeNone,
2398
                                              wicpalette,
2399
                                              0.0,
2400
                                              WICBitmapPaletteTypeCustom);
2401
            }
2402
            if (SUCCEEDED(hr)) {
2403
                src = (IWICBitmapSource*)conv;
2404
                comp = 1;
2405
                frame->pixelformat = SIXEL_PIXELFORMAT_PAL8;
2406
                frame->palette = sixel_allocator_malloc(
2407
                    pchunk->allocator,
2408
                    (size_t)ncolors * 3);
2409
                if (frame->palette == NULL) {
2410
                    hr = E_OUTOFMEMORY;
2411
                } else {
2412
                    wiccolors = (WICColor *)sixel_allocator_malloc(
2413
                        pchunk->allocator,
2414
                        (size_t)ncolors * sizeof(WICColor));
2415
                    if (wiccolors == NULL) {
2416
                        hr = E_OUTOFMEMORY;
2417
                    } else {
2418
                        actual = 0;
2419
                        hr = wicpalette->lpVtbl->GetColors(
2420
                            wicpalette, ncolors, wiccolors, &actual);
2421
                        if (SUCCEEDED(hr) && actual == ncolors) {
2422
                            for (i = 0; i < ncolors; ++i) {
2423
                                c = wiccolors[i];
2424
                                frame->palette[i * 3 + 0] = (unsigned char)((c >> 16) & 0xFF);
2425
                                frame->palette[i * 3 + 1] = (unsigned char)((c >> 8) & 0xFF);
2426
                                frame->palette[i * 3 + 2] = (unsigned char)(c & 0xFF);
2427
                            }
2428
                            frame->ncolors = (int)ncolors;
2429
                        } else {
2430
                            hr = E_FAIL;
2431
                        }
2432
                    }
2433
                }
2434
            }
2435
            if (FAILED(hr)) {
2436
                if (conv) {
2437
                    conv->lpVtbl->Release(conv);
2438
                    conv = NULL;
2439
                }
2440
                sixel_allocator_free(pchunk->allocator, frame->palette);
2441
                frame->palette = NULL;
2442
                sixel_allocator_free(pchunk->allocator, wiccolors);
2443
                wiccolors = NULL;
2444
                src = NULL;
2445
            }
2446
        }
2447
    }
2448

2449
    if (src == NULL) {
2450
        hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
2451
        if (FAILED(hr)) {
2452
            goto end;
2453
        }
2454

2455
        hr = conv->lpVtbl->Initialize(conv, (IWICBitmapSource*)wicframe,
2456
                                      &GUID_WICPixelFormat32bppRGBA,
2457
                                      WICBitmapDitherTypeNone, NULL, 0.0,
2458
                                      WICBitmapPaletteTypeCustom);
2459
        if (FAILED(hr)) {
2460
            goto end;
2461
        }
2462

2463
        src = (IWICBitmapSource*)conv;
2464
        comp = 4;
2465
        frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
2466
    }
2467

2468
    hr = src->lpVtbl->GetSize(
2469
        src, (UINT *)&frame->width, (UINT *)&frame->height);
2470
    if (FAILED(hr)) {
2471
        goto end;
2472
    }
2473

2474
    /* check size */
2475
    if (frame->width <= 0) {
2476
        sixel_helper_set_additional_message(
2477
            "load_with_wic: an invalid width parameter detected.");
2478
        status = SIXEL_BAD_INPUT;
2479
        goto end;
2480
    }
2481
    if (frame->height <= 0) {
2482
        sixel_helper_set_additional_message(
2483
            "load_with_wic: an invalid width parameter detected.");
2484
        status = SIXEL_BAD_INPUT;
2485
        goto end;
2486
    }
2487
    if (frame->width > SIXEL_WIDTH_LIMIT) {
2488
        sixel_helper_set_additional_message(
2489
            "load_with_wic: given width parameter is too huge.");
2490
        status = SIXEL_BAD_INPUT;
2491
        goto end;
2492
    }
2493
    if (frame->height > SIXEL_HEIGHT_LIMIT) {
2494
        sixel_helper_set_additional_message(
2495
            "load_with_wic: given height parameter is too huge.");
2496
        status = SIXEL_BAD_INPUT;
2497
        goto end;
2498
    }
2499

2500
    frame->pixels = sixel_allocator_malloc(
2501
        pchunk->allocator,
2502
        (size_t)(frame->height * frame->width * comp));
2503

2504
    {
2505
        WICRect rc = { 0, 0, (INT)frame->width, (INT)frame->height };
2506
        hr = src->lpVtbl->CopyPixels(
2507
            src,
2508
            &rc,                                        /* prc */
2509
            frame->width * comp,                        /* cbStride */
2510
            (UINT)frame->width * frame->height * comp,  /* cbBufferSize */
2511
            frame->pixels);                             /* pbBuffer */
2512
        if (FAILED(hr)) {
2513
            goto end;
2514
        }
2515
    }
2516

2517
    status = fn_load(frame, context);
2518
    if (SIXEL_FAILED(status)) {
2519
        goto end;
2520
    }
2521

2522
end:
2523
    if (conv) {
2524
         conv->lpVtbl->Release(conv);
2525
    }
2526
    if (wicpalette) {
2527
         wicpalette->lpVtbl->Release(wicpalette);
2528
    }
2529
    if (wiccolors) {
2530
         sixel_allocator_free(pchunk->allocator, wiccolors);
2531
    }
2532
    if (wicframe) {
2533
         wicframe->lpVtbl->Release(wicframe);
2534
    }
2535
    if (qdecoder) {
2536
         qdecoder->lpVtbl->Release(qdecoder);
2537
    }
2538
    if (qframe) {
2539
         qframe->lpVtbl->Release(qframe);
2540
    }
2541
    if (stream) {
2542
         stream->lpVtbl->Release(stream);
2543
    }
2544
    if (factory) {
2545
         factory->lpVtbl->Release(factory);
2546
    }
2547
    sixel_frame_unref(frame);
2548

2549
    CoUninitialize();
2550

2551
    if (FAILED(hr)) {
2552
        return SIXEL_FALSE;
2553
    }
2554

2555
    return SIXEL_OK;
2556
}
2557

2558
#endif /* HAVE_WIC */
2559

2560
/* load image from file */
2561

2562
SIXELAPI SIXELSTATUS
2563
sixel_helper_load_image_file(
443✔
2564
    char const                /* in */     *filename,     /* source file name */
2565
    int                       /* in */     fstatic,       /* whether to extract static image from animated gif */
2566
    int                       /* in */     fuse_palette,  /* whether to use paletted image, set non-zero value to try to get paletted image */
2567
    int                       /* in */     reqcolors,     /* requested number of colors, should be equal or less than SIXEL_PALETTE_MAX */
2568
    unsigned char             /* in */     *bgcolor,      /* background color, may be NULL */
2569
    int                       /* in */     loop_control,  /* one of enum loopControl */
2570
    sixel_load_image_function /* in */     fn_load,       /* callback */
2571
    int                       /* in */     finsecure,     /* true if do not verify SSL */
2572
    int const                 /* in */     *cancel_flag,  /* cancel flag, may be NULL */
2573
    void                      /* in/out */ *context,      /* private data which is passed to callback function as an argument, may be NULL */
2574
    sixel_allocator_t         /* in */     *allocator     /* allocator object, may be NULL */
2575
)
2576
{
2577
    SIXELSTATUS status = SIXEL_FALSE;
443✔
2578
    sixel_chunk_t *pchunk = NULL;
443✔
2579

2580
    /* normalize reqested colors */
2581
    if (reqcolors > SIXEL_PALETTE_MAX) {
443!
2582
        reqcolors = SIXEL_PALETTE_MAX;
×
2583
    }
2584

2585
    /* create new chunk object from file */
2586
    status = sixel_chunk_new(&pchunk, filename, finsecure, cancel_flag, allocator);
443✔
2587
    if (status != SIXEL_OK) {
443✔
2588
        goto end;
17✔
2589
    }
2590

2591
    /* if input date is empty or 1 byte LF, ignore it and return successfully */
2592
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
426!
2593
        status = SIXEL_OK;
3✔
2594
        goto end;
3✔
2595
    }
2596

2597
    /* assertion */
2598
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
423!
2599
        status = SIXEL_LOGIC_ERROR;
×
2600
        goto end;
×
2601
    }
2602

2603
    status = SIXEL_FALSE;
423✔
2604
#ifdef HAVE_WIC
2605
    if (SIXEL_FAILED(status) && !chunk_is_gif(pchunk)) {
2606
        loader_trace_try("wic");
2607
        status = load_with_wic(pchunk,
2608
                               fstatic,
2609
                               fuse_palette,
2610
                               reqcolors,
2611
                               bgcolor,
2612
                               loop_control,
2613
                               fn_load,
2614
                               context);
2615
        loader_trace_result("wic", status);
2616
    }
2617
#endif  /* HAVE_WIC */
2618
#ifdef HAVE_COREGRAPHICS
2619
    if (SIXEL_FAILED(status)) {
141!
2620
        loader_trace_try("coregraphics");
141✔
2621
        status = load_with_coregraphics(pchunk,
282✔
2622
                                        fstatic,
141✔
2623
                                        fuse_palette,
141✔
2624
                                        reqcolors,
141✔
2625
                                        bgcolor,
141✔
2626
                                        loop_control,
141✔
2627
                                        fn_load,
141✔
2628
                                        context);
141✔
2629
        loader_trace_result("coregraphics", status);
141✔
2630
    }
141✔
2631
#endif  /* HAVE_COREGRAPHICS */
2632
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
2633
    if (SIXEL_FAILED(status)) {
141✔
2634
        loader_trace_try("quicklook");
58✔
2635
        status = load_with_quicklook(pchunk,
116✔
2636
                                     fstatic,
58✔
2637
                                     fuse_palette,
58✔
2638
                                     reqcolors,
58✔
2639
                                     bgcolor,
58✔
2640
                                     loop_control,
58✔
2641
                                     fn_load,
58✔
2642
                                     context);
58✔
2643
        loader_trace_result("quicklook", status);
58✔
2644
    }
58✔
2645
#endif  /* HAVE_COREGRAPHICS && HAVE_QUICKLOOK */
2646
#ifdef HAVE_GDK_PIXBUF2
2647
    if (SIXEL_FAILED(status)) {
2648
        loader_trace_try("gdk-pixbuf2");
2649
        status = load_with_gdkpixbuf(pchunk,
2650
                                     fstatic,
2651
                                     fuse_palette,
2652
                                     reqcolors,
2653
                                     bgcolor,
2654
                                     loop_control,
2655
                                     fn_load,
2656
                                     context);
2657
        loader_trace_result("gdk-pixbuf2", status);
2658
    }
2659
#endif  /* HAVE_GDK_PIXBUF2 */
2660
#if HAVE_GD
2661
    if (SIXEL_FAILED(status)) {
2662
        loader_trace_try("gd");
2663
        status = load_with_gd(pchunk,
2664
                              fstatic,
2665
                              fuse_palette,
2666
                              reqcolors,
2667
                              bgcolor,
2668
                              loop_control,
2669
                              fn_load,
2670
                              context);
2671
        loader_trace_result("gd", status);
2672
    }
2673
#endif  /* HAVE_GD */
2674
    if (SIXEL_FAILED(status)) {
423!
2675
        loader_trace_try("builtin");
337✔
2676
        status = load_with_builtin(pchunk,
392✔
2677
                                   fstatic,
55✔
2678
                                   fuse_palette,
55✔
2679
                                   reqcolors,
55✔
2680
                                   bgcolor,
55✔
2681
                                   loop_control,
55✔
2682
                                   fn_load,
55✔
2683
                                   context);
55✔
2684
        loader_trace_result("builtin", status);
337✔
2685
    }
55✔
2686
    if (SIXEL_FAILED(status)) {
423✔
2687
        goto end;
12✔
2688
    }
2689

2690
end:
274✔
2691
    sixel_chunk_destroy(pchunk);
443✔
2692

2693
    return status;
443✔
2694
}
2695

2696

2697
#if HAVE_TESTS
2698
static int
2699
test1(void)
×
2700
{
2701
    int nret = EXIT_FAILURE;
×
2702
    unsigned char *ptr = malloc(16);
×
2703

2704
    nret = EXIT_SUCCESS;
×
2705
    goto error;
×
2706

2707
    nret = EXIT_SUCCESS;
2708

2709
error:
2710
    free(ptr);
×
2711
    return nret;
×
2712
}
2713

2714

2715
SIXELAPI int
2716
sixel_loader_tests_main(void)
×
2717
{
2718
    int nret = EXIT_FAILURE;
×
2719
    size_t i;
2720
    typedef int (* testcase)(void);
2721

2722
    static testcase const testcases[] = {
2723
        test1,
2724
    };
2725

2726
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
2727
        nret = testcases[i]();
×
2728
        if (nret != EXIT_SUCCESS) {
×
2729
            goto error;
×
2730
        }
2731
    }
2732

2733
    nret = EXIT_SUCCESS;
×
2734

2735
error:
2736
    return nret;
×
2737
}
2738
#endif  /* HAVE_TESTS */
2739

2740
/* emacs Local Variables:      */
2741
/* emacs mode: c               */
2742
/* emacs tab-width: 4          */
2743
/* emacs indent-tabs-mode: nil */
2744
/* emacs c-basic-offset: 4     */
2745
/* emacs End:                  */
2746
/* vim: set expandtab ts=4 sts=4 sw=4 : */
2747
/* 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