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

saitoha / libsixel / 24145663485

08 Apr 2026 04:06PM UTC coverage: 84.655% (+0.06%) from 84.598%
24145663485

push

github

saitoha
tests/gd: use decodable PNG in static start-frame no-op cases

86919 of 182155 branches covered (47.72%)

106012 of 125228 relevant lines covered (84.66%)

5289894.8 hits per line

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

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

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

34
#if HAVE_COREGRAPHICS
35

36
#include <stdio.h>
37
#include <limits.h>
38
#include <stdlib.h>
39
#if HAVE_ERRNO_H
40
# include <errno.h>
41
#endif
42

43
#if HAVE_STRING_H
44
# include <string.h>
45
#endif
46
#if HAVE_MATH_H
47
# include <math.h>
48
#endif
49

50
#include <ApplicationServices/ApplicationServices.h>
51
#include <ImageIO/ImageIO.h>
52

53
#include <sixel.h>
54

55
#include "chunk.h"
56
#include "frame.h"
57
#include "loader-common.h"
58
#include "loader-coregraphics.h"
59
#include "loader.h"
60
#include "logger.h"
61
#include "compat_stub.h"
62

63
/*
64
 * Keep animation metadata keys available even on SDKs that do not expose the
65
 * newer typed constants yet. ImageIO dictionaries use these stable key names.
66
 */
67
#ifndef kCGImagePropertyAPNGLoopCount
68
#define kCGImagePropertyAPNGLoopCount CFSTR("LoopCount")
69
#endif
70
#ifndef kCGImagePropertyAPNGDelayTime
71
#define kCGImagePropertyAPNGDelayTime CFSTR("DelayTime")
72
#endif
73
#ifndef kCGImagePropertyAPNGUnclampedDelayTime
74
#define kCGImagePropertyAPNGUnclampedDelayTime CFSTR("UnclampedDelayTime")
75
#endif
76
#ifndef kCGImagePropertyWebPDictionary
77
#define kCGImagePropertyWebPDictionary CFSTR("{WebP}")
78
#endif
79
#ifndef kCGImagePropertyWebPLoopCount
80
#define kCGImagePropertyWebPLoopCount CFSTR("LoopCount")
81
#endif
82
#ifndef kCGImagePropertyWebPDelayTime
83
#define kCGImagePropertyWebPDelayTime CFSTR("DelayTime")
84
#endif
85
#ifndef kCGImagePropertyWebPUnclampedDelayTime
86
#define kCGImagePropertyWebPUnclampedDelayTime CFSTR("UnclampedDelayTime")
87
#endif
88
#ifndef kCGImagePropertyHEICSDictionary
89
#define kCGImagePropertyHEICSDictionary CFSTR("{HEICS}")
90
#endif
91
#ifndef kCGImagePropertyHEICSLoopCount
92
#define kCGImagePropertyHEICSLoopCount CFSTR("LoopCount")
93
#endif
94
#ifndef kCGImagePropertyHEICSDelayTime
95
#define kCGImagePropertyHEICSDelayTime CFSTR("DelayTime")
96
#endif
97
#ifndef kCGImagePropertyHEICSUnclampedDelayTime
98
#define kCGImagePropertyHEICSUnclampedDelayTime CFSTR("UnclampedDelayTime")
99
#endif
100
#ifndef kCGImagePropertyOrientation
101
#define kCGImagePropertyOrientation CFSTR("Orientation")
102
#endif
103

104
#define COREGRAPHICS_SRGB_ENCODE_LUT_SIZE 4096u
105
#define COREGRAPHICS_FRAME_CACHE_MAX_BYTES_DEFAULT \
106
    (64u * 1024u * 1024u)
107

108
typedef struct sixel_loader_coregraphics_component {
109
    sixel_loader_component_t base;
110
    sixel_allocator_t *allocator;
111
    unsigned int ref;
112
    int fstatic;
113
    int fuse_palette;
114
    int reqcolors;
115
    unsigned char bgcolor[3];
116
    int has_bgcolor;
117
    int loop_control;
118
    int has_start_frame_no;
119
    int start_frame_no;
120
    int enable_orientation;
121
} sixel_loader_coregraphics_component_t;
122

123
typedef struct coregraphics_srgb_lut_cache {
124
    int prepared;
125
    double decode_lut[256];
126
    unsigned char encode_lut[COREGRAPHICS_SRGB_ENCODE_LUT_SIZE + 1u];
127
} coregraphics_srgb_lut_cache_t;
128

129
typedef struct coregraphics_png_trns_chunk_cache {
130
    int initialized;
131
    int available;
132
    int alpha_count;
133
    unsigned char alpha_entries[SIXEL_PALETTE_MAX];
134
} coregraphics_png_trns_chunk_cache_t;
135

136
typedef struct coregraphics_frame_meta_slot {
137
    int delay;
138
    int orientation;
139
    int has_alpha;
140
    int promote_float32;
141
    int is_indexed;
142
    unsigned char props_ready;
143
    unsigned char decode_hint_ready;
144
} coregraphics_frame_meta_slot_t;
145

146
typedef struct coregraphics_cache_slot {
147
    unsigned char decided;
148
    sixel_frame_t *frame;
149
} coregraphics_cache_slot_t;
150

151
typedef struct coregraphics_loader_state {
152
    SIXELSTATUS status;
153
    sixel_chunk_t const *chunk;
154
    int fstatic;
155
    int fuse_palette;
156
    int reqcolors;
157
    int enable_orientation;
158
    unsigned char *bgcolor;
159
    int loop_control;
160
    int start_frame_no_set;
161
    int start_frame_no_override;
162
    sixel_load_image_function fn_load;
163
    void *context;
164
    sixel_frame_t *frame;
165
    sixel_frame_t *emit_frame;
166
    sixel_frame_t *decode_frame;
167
    sixel_frame_t *cached_frame_tmp;
168
    CFDataRef data;
169
    CGImageSourceRef source;
170
    CGImageRef image;
171
    CFDictionaryRef props;
172
    CFDictionaryRef frame_props;
173
    size_t frame_count;
174
    int total_frames;
175
    int anim_loop_count;
176
    int is_animation_container;
177
    int source_orientation;
178
    int start_frame_no;
179
    int resolved_start_frame_no;
180
    int frame_index;
181
    int loop_no;
182
    int frames_in_loop;
183
    int stop_loop;
184
    size_t metadata_slots;
185
    coregraphics_frame_meta_slot_t single_meta_slot;
186
    coregraphics_frame_meta_slot_t *frame_meta_slots;
187
    coregraphics_frame_meta_slot_t *active_meta_slots;
188
    coregraphics_cache_slot_t *frame_cache_slots;
189
    int frame_cache_enabled;
190
    size_t frame_cache_max_bytes;
191
    size_t frame_cache_used_bytes;
192
    size_t frame_cache_frame_bytes;
193
    size_t image_width;
194
    size_t image_height;
195
    int frame_cache_keep;
196
    int frame_cache_decision_pending;
197
    int release_emit_frame;
198
    int cache_hit;
199
    int indexed_handled;
200
    int force_alpha_from_indexed;
201
    int has_alpha_like;
202
    int promote_float32;
203
    int frame_orientation;
204
    size_t frame_meta_slot;
205
    coregraphics_png_trns_chunk_cache_t png_trns_chunk_cache;
206
    coregraphics_srgb_lut_cache_t rgba8_lut_cache;
207
    CFIndex cf_data_length;
208
    size_t prefetch_index;
209
} coregraphics_loader_state_t;
210

211
static unsigned char
212
coregraphics_unpremultiply_channel(unsigned int value, unsigned int alpha)
132✔
213
{
214
    unsigned int unpremultiplied;
84✔
215

216
    if (alpha == 0u) {
132✔
217
        return 0u;
×
218
    }
219
    if (alpha >= 255u) {
132!
220
        return (unsigned char)value;
×
221
    }
222

223
    unpremultiplied = (value * 255u + alpha / 2u) / alpha;
132✔
224
    if (unpremultiplied > 255u) {
132!
225
        unpremultiplied = 255u;
×
226
    }
×
227
    return (unsigned char)unpremultiplied;
132✔
228
}
132✔
229

230
static double
231
coregraphics_clamp_unit(double value)
3,170,772✔
232
{
233
    if (value < 0.0) {
3,170,772!
234
        return 0.0;
×
235
    }
236
    if (value > 1.0) {
3,170,772!
237
        return 1.0;
×
238
    }
239
    return value;
3,170,772✔
240
}
3,170,772✔
241

242
static double
243
coregraphics_decode_srgb_unit(double value)
109,923✔
244
{
245
    value = coregraphics_clamp_unit(value);
109,923✔
246
    if (value <= 0.04045) {
109,923✔
247
        return value / 12.92;
4,719✔
248
    }
249
#if HAVE_MATH_H
250
    return pow((value + 0.055) / 1.055, 2.4);
105,204✔
251
#else
252
    return value;
253
#endif
254
}
109,923✔
255

256
static double
257
coregraphics_encode_srgb_unit(double value)
1,757,613✔
258
{
259
    value = coregraphics_clamp_unit(value);
1,757,613✔
260
    if (value <= 0.0031308) {
1,757,613✔
261
        return value * 12.92;
5,577✔
262
    }
263
#if HAVE_MATH_H
264
    return 1.055 * pow(value, 1.0 / 2.4) - 0.055;
1,752,036✔
265
#else
266
    return value;
267
#endif
268
}
1,757,613✔
269

270
static void
271
coregraphics_build_srgb_decode_u8_lut(double lut[256])
429✔
272
{
273
    int index;
273✔
274
    double unit;
273✔
275

276
    index = 0;
429✔
277
    unit = 0.0;
429✔
278
    if (lut == NULL) {
429✔
279
        return;
×
280
    }
281

282
    for (index = 0; index < 256; ++index) {
110,253✔
283
        unit = (double)index / 255.0;
109,824✔
284
        lut[index] = coregraphics_decode_srgb_unit(unit);
109,824✔
285
    }
109,824✔
286
}
429!
287

288
static void
289
coregraphics_build_srgb_encode_u8_lut(unsigned char *lut, size_t lut_size)
429✔
290
{
291
    size_t index;
273✔
292
    double unit;
273✔
293

294
    index = 0u;
429✔
295
    unit = 0.0;
429✔
296
    if (lut == NULL || lut_size == 0u) {
429!
297
        return;
×
298
    }
299

300
    for (index = 0u; index < lut_size; ++index) {
1,758,042✔
301
        if (lut_size > 1u) {
1,757,613!
302
            unit = (double)index / (double)(lut_size - 1u);
1,757,613✔
303
        } else {
1,757,613✔
304
            unit = 0.0;
×
305
        }
306
        lut[index] = (unsigned char)(coregraphics_encode_srgb_unit(unit) *
3,515,226✔
307
                                     255.0 + 0.5);
1,757,613✔
308
    }
1,757,613✔
309
}
429!
310

311
static unsigned char
312
coregraphics_encode_linear_to_srgb_u8(double value,
1,303,236✔
313
                                      unsigned char const *lut,
314
                                      size_t lut_size)
315
{
316
    size_t index;
829,332✔
317

318
    index = 0u;
1,303,236✔
319
    if (lut == NULL || lut_size == 0u) {
1,303,236!
320
        return 0u;
×
321
    }
322

323
    value = coregraphics_clamp_unit(value);
1,303,236✔
324
    if (lut_size <= 1u) {
1,303,236!
325
        return lut[0];
×
326
    }
327

328
    index = (size_t)(value * (double)(lut_size - 1u) + 0.5);
1,303,236✔
329
    if (index >= lut_size) {
1,303,236!
330
        index = lut_size - 1u;
×
331
    }
×
332
    return lut[index];
1,303,236✔
333
}
1,303,236✔
334

335
static int
336
coregraphics_property_to_bool(CFTypeRef value, int *out_value)
4,004✔
337
{
338
    CFTypeID type_id;
2,548✔
339
    int numeric_value;
2,548✔
340
    Boolean ok;
2,548✔
341

342
    type_id = 0;
4,004✔
343
    numeric_value = 0;
4,004✔
344
    ok = false;
4,004✔
345
    if (value == NULL || out_value == NULL) {
4,004!
346
        return 0;
3,443✔
347
    }
348

349
    type_id = CFGetTypeID(value);
561✔
350
    if (type_id == CFBooleanGetTypeID()) {
561!
351
        *out_value = CFBooleanGetValue((CFBooleanRef)value) ? 1 : 0;
561✔
352
        return 1;
561✔
353
    }
354
    if (type_id == CFNumberGetTypeID()) {
×
355
        ok = CFNumberGetValue((CFNumberRef)value,
×
356
                              kCFNumberIntType,
357
                              &numeric_value);
358
        if (ok) {
×
359
            *out_value = numeric_value != 0 ? 1 : 0;
×
360
            return 1;
×
361
        }
362
    }
×
363

364
    return 0;
×
365
}
4,004✔
366

367
static int
368
coregraphics_dictionary_get_bool(CFDictionaryRef dict,
4,004✔
369
                                 CFStringRef key,
370
                                 int default_value)
371
{
372
    CFTypeRef value;
2,548✔
373
    int parsed;
2,548✔
374
    int result;
2,548✔
375

376
    value = NULL;
4,004✔
377
    parsed = 0;
4,004✔
378
    result = default_value;
4,004✔
379
    if (dict == NULL || key == NULL) {
4,004!
380
        return default_value;
×
381
    }
382

383
    value = CFDictionaryGetValue(dict, key);
4,004✔
384
    parsed = coregraphics_property_to_bool(value, &result);
4,004✔
385
    if (!parsed) {
4,004✔
386
        result = default_value;
3,443✔
387
    }
3,443✔
388
    return result;
4,004✔
389
}
4,004✔
390

391
static int
392
coregraphics_dictionary_get_int(CFDictionaryRef dict,
7,095✔
393
                                CFStringRef key,
394
                                int default_value)
395
{
396
    CFTypeRef value;
4,515✔
397
    int result;
4,515✔
398
    Boolean ok;
4,515✔
399

400
    value = NULL;
7,095✔
401
    result = default_value;
7,095✔
402
    ok = false;
7,095✔
403
    if (dict == NULL || key == NULL) {
7,095!
404
        return default_value;
×
405
    }
406

407
    value = CFDictionaryGetValue(dict, key);
7,095✔
408
    if (value == NULL || CFGetTypeID(value) != CFNumberGetTypeID()) {
7,095!
409
        return default_value;
4,345✔
410
    }
411

412
    ok = CFNumberGetValue((CFNumberRef)value, kCFNumberIntType, &result);
2,750✔
413
    if (!ok) {
2,750!
414
        result = default_value;
×
415
    }
×
416
    return result;
2,750✔
417
}
7,095✔
418

419
static int
420
coregraphics_dictionary_get_double(CFDictionaryRef dict,
1,001✔
421
                                   CFStringRef key,
422
                                   double *out_value)
423
{
424
    CFTypeRef value;
637✔
425
    Boolean ok;
637✔
426
    double parsed;
637✔
427

428
    value = NULL;
1,001✔
429
    ok = false;
1,001✔
430
    parsed = 0.0;
1,001✔
431
    if (dict == NULL || key == NULL || out_value == NULL) {
1,001!
432
        return 0;
×
433
    }
434

435
    value = CFDictionaryGetValue(dict, key);
1,001✔
436
    if (value == NULL || CFGetTypeID(value) != CFNumberGetTypeID()) {
1,001!
437
        return 0;
×
438
    }
439

440
    ok = CFNumberGetValue((CFNumberRef)value, kCFNumberDoubleType, &parsed);
1,001✔
441
    if (!ok) {
1,001✔
442
        return 0;
×
443
    }
444

445
    *out_value = parsed;
1,001✔
446
    return 1;
1,001✔
447
}
1,001✔
448

449
static int
450
coregraphics_sanitize_exif_orientation(int orientation)
7,106✔
451
{
452
    if (orientation < 1 || orientation > 8) {
7,106!
453
        return 1;
×
454
    }
455
    return orientation;
7,106✔
456
}
7,106✔
457

458
static int
459
coregraphics_resolve_exif_orientation(CFDictionaryRef props,
3,553✔
460
                                      int fallback_orientation)
461
{
462
    int resolved_orientation;
2,261✔
463

464
    resolved_orientation = coregraphics_sanitize_exif_orientation(
3,553✔
465
        fallback_orientation);
3,553✔
466
    if (props == NULL) {
3,553✔
467
        return resolved_orientation;
×
468
    }
469

470
    resolved_orientation = coregraphics_dictionary_get_int(
3,553✔
471
        props,
3,553✔
472
        kCGImagePropertyOrientation,
473
        resolved_orientation);
3,553✔
474
    return coregraphics_sanitize_exif_orientation(resolved_orientation);
3,553✔
475
}
3,553✔
476

477
static int
478
coregraphics_get_animation_keys(CFDictionaryRef props,
3,553✔
479
                                size_t frame_count,
480
                                CFDictionaryRef *out_dict,
481
                                CFStringRef *out_loop_key,
482
                                CFStringRef *out_delay_key,
483
                                CFStringRef *out_unclamped_delay_key)
484
{
485
    CFDictionaryRef dict;
2,261✔
486

487
    dict = NULL;
3,553✔
488
    if (out_dict == NULL) {
3,553✔
489
        return 0;
×
490
    }
491

492
    *out_dict = NULL;
3,553✔
493
    if (out_loop_key != NULL) {
3,553!
494
        *out_loop_key = NULL;
3,553✔
495
    }
3,553✔
496
    if (out_delay_key != NULL) {
3,833✔
497
        *out_delay_key = NULL;
2,013✔
498
    }
2,013✔
499
    if (out_unclamped_delay_key != NULL) {
3,273✔
500
        *out_unclamped_delay_key = NULL;
2,013✔
501
    }
2,013✔
502
    if (props == NULL) {
3,553✔
503
        return 0;
×
504
    }
505

506
    dict = (CFDictionaryRef)CFDictionaryGetValue(props,
7,106✔
507
                                                 kCGImagePropertyGIFDictionary);
3,553✔
508
    if (dict != NULL && CFGetTypeID(dict) == CFDictionaryGetTypeID()) {
3,553!
509
        *out_dict = dict;
704✔
510
        if (out_loop_key != NULL) {
704!
511
            *out_loop_key = kCGImagePropertyGIFLoopCount;
704✔
512
        }
704✔
513
        if (out_delay_key != NULL) {
744✔
514
            *out_delay_key = kCGImagePropertyGIFDelayTime;
484✔
515
        }
484✔
516
        if (out_unclamped_delay_key != NULL) {
664✔
517
            *out_unclamped_delay_key = kCGImagePropertyGIFUnclampedDelayTime;
484✔
518
        }
484✔
519
        return 1;
704✔
520
    }
521

522
    dict = (CFDictionaryRef)CFDictionaryGetValue(props,
5,094✔
523
                                                 kCGImagePropertyPNGDictionary);
2,547✔
524
    if (dict != NULL &&
3,735✔
525
        CFGetTypeID(dict) == CFDictionaryGetTypeID() &&
1,188!
526
        frame_count > 1u) {
1,188✔
527
        *out_dict = dict;
594✔
528
        if (out_loop_key != NULL) {
594!
529
            *out_loop_key = kCGImagePropertyAPNGLoopCount;
594✔
530
        }
594✔
531
        if (out_delay_key != NULL) {
636✔
532
            *out_delay_key = kCGImagePropertyAPNGDelayTime;
363✔
533
        }
363✔
534
        if (out_unclamped_delay_key != NULL) {
552✔
535
            *out_unclamped_delay_key = kCGImagePropertyAPNGUnclampedDelayTime;
363✔
536
        }
363✔
537
        return 1;
594✔
538
    }
539

540
    dict = (CFDictionaryRef)CFDictionaryGetValue(
1,889✔
541
        props,
1,889✔
542
        kCGImagePropertyWebPDictionary);
543
    if (dict != NULL &&
2,131!
544
        CFGetTypeID(dict) == CFDictionaryGetTypeID() &&
242!
545
        frame_count > 1u) {
242✔
546
        *out_dict = dict;
242✔
547
        if (out_loop_key != NULL) {
242!
548
            *out_loop_key = kCGImagePropertyWebPLoopCount;
242✔
549
        }
242✔
550
        if (out_delay_key != NULL) {
258✔
551
            *out_delay_key = kCGImagePropertyWebPDelayTime;
154✔
552
        }
154✔
553
        if (out_unclamped_delay_key != NULL) {
226✔
554
            *out_unclamped_delay_key = kCGImagePropertyWebPUnclampedDelayTime;
154✔
555
        }
154✔
556
        return 1;
242✔
557
    }
558

559
    dict = (CFDictionaryRef)CFDictionaryGetValue(
1,647✔
560
        props,
1,647✔
561
        kCGImagePropertyHEICSDictionary);
562
    if (dict != NULL &&
1,647!
563
        CFGetTypeID(dict) == CFDictionaryGetTypeID() &&
×
564
        frame_count > 1u) {
×
565
        *out_dict = dict;
×
566
        if (out_loop_key != NULL) {
×
567
            *out_loop_key = kCGImagePropertyHEICSLoopCount;
×
568
        }
×
569
        if (out_delay_key != NULL) {
×
570
            *out_delay_key = kCGImagePropertyHEICSDelayTime;
×
571
        }
×
572
        if (out_unclamped_delay_key != NULL) {
×
573
            *out_unclamped_delay_key = kCGImagePropertyHEICSUnclampedDelayTime;
×
574
        }
×
575
        return 1;
×
576
    }
577

578
    return 0;
2,013✔
579
}
3,553✔
580

581
static int
582
coregraphics_resolve_animation_delay_cs(CFDictionaryRef dict,
1,001✔
583
                                        CFStringRef unclamped_delay_key,
584
                                        CFStringRef delay_key,
585
                                        int *delay_cs)
586
{
587
    double delay_value;
637✔
588
    double scaled_delay;
637✔
589
    double max_delay_seconds;
637✔
590

591
    delay_value = 0.0;
1,001✔
592
    scaled_delay = 0.0;
1,001✔
593
    max_delay_seconds = (double)INT_MAX / 100.0;
1,001✔
594
    if (dict == NULL || delay_cs == NULL) {
1,001!
595
        return 0;
×
596
    }
597

598
    if (!coregraphics_dictionary_get_double(dict,
2,002!
599
                                            unclamped_delay_key,
1,001✔
600
                                            &delay_value) &&
1,001!
601
        !coregraphics_dictionary_get_double(dict,
×
602
                                            delay_key,
×
603
                                            &delay_value)) {
604
        return 0;
×
605
    }
606

607
    if (!(delay_value >= 0.0)) {
819!
608
        delay_value = 0.0;
×
609
    }
×
610
    if (delay_value > max_delay_seconds) {
1,001!
611
        *delay_cs = INT_MAX;
×
612
        return 1;
×
613
    }
614

615
    scaled_delay = delay_value * 100.0;
1,001✔
616
    if (scaled_delay >= (double)INT_MAX) {
1,001!
617
        *delay_cs = INT_MAX;
×
618
        return 1;
×
619
    }
620

621
    *delay_cs = (int)scaled_delay;
1,001✔
622
    if (*delay_cs == 0 && delay_value > 0.0) {
1,001✔
623
        *delay_cs = 1;
308✔
624
    }
308✔
625
    return 1;
1,001✔
626
}
1,001✔
627

628
static int
629
coregraphics_image_is_indexed(CGImageRef image)
2,002✔
630
{
631
    CGColorSpaceRef color_space;
1,274✔
632

633
    color_space = NULL;
2,002✔
634
    if (image == NULL) {
2,002✔
635
        return 0;
×
636
    }
637

638
    color_space = CGImageGetColorSpace(image);
2,002✔
639
    if (color_space == NULL) {
2,002✔
640
        return 0;
×
641
    }
642
    return CGColorSpaceGetModel(color_space) == kCGColorSpaceModelIndexed;
2,002✔
643
}
2,002✔
644

645
static int
646
coregraphics_image_has_alpha(CGImageRef image, CFDictionaryRef frame_props)
2,002✔
647
{
648
    CGImageAlphaInfo alpha_info;
1,274✔
649
    int metadata_has_alpha;
1,274✔
650

651
    alpha_info = kCGImageAlphaNone;
2,002✔
652
    metadata_has_alpha = 0;
2,002✔
653
    if (image == NULL) {
2,002✔
654
        return 0;
×
655
    }
656

657
    metadata_has_alpha = coregraphics_dictionary_get_bool(
2,002✔
658
        frame_props,
2,002✔
659
        kCGImagePropertyHasAlpha,
2,002✔
660
        0);
661
    alpha_info = CGImageGetAlphaInfo(image);
2,002✔
662
    switch (alpha_info) {
2,002✔
663
    case kCGImageAlphaNone:
664
    case kCGImageAlphaNoneSkipLast:
665
    case kCGImageAlphaNoneSkipFirst:
666
        return metadata_has_alpha;
1,441✔
667
    default:
668
        return 1;
561✔
669
    }
670
}
2,002✔
671

672
static int
673
coregraphics_should_promote_float32(CGImageRef image, CFDictionaryRef props)
2,002✔
674
{
675
    int depth_bits;
1,274✔
676
    int is_float;
1,274✔
677
    size_t image_depth_bits;
1,274✔
678

679
    depth_bits = 0;
2,002✔
680
    is_float = 0;
2,002✔
681
    image_depth_bits = 0u;
2,002✔
682
    if (image == NULL) {
2,002✔
683
        return 0;
×
684
    }
685

686
    image_depth_bits = CGImageGetBitsPerComponent(image);
2,002✔
687
    depth_bits = (int)image_depth_bits;
2,002✔
688
    depth_bits = coregraphics_dictionary_get_int(props,
4,004✔
689
                                                 kCGImagePropertyDepth,
2,002✔
690
                                                 depth_bits);
2,002✔
691
    is_float = coregraphics_dictionary_get_bool(props,
4,004✔
692
                                                kCGImagePropertyIsFloat,
2,002✔
693
                                                0);
694
    if (is_float != 0 || depth_bits > 8) {
2,002!
695
        return 1;
33✔
696
    }
697

698
    return 0;
1,969✔
699
}
2,002✔
700

701
static void
702
coregraphics_reset_frame_storage(sixel_frame_t *frame)
2,057✔
703
{
704
    if (frame == NULL || frame->allocator == NULL) {
2,057!
705
        return;
×
706
    }
707

708
    if (frame->pixels.u8ptr != NULL) {
2,057✔
709
        sixel_allocator_free(frame->allocator, frame->pixels.u8ptr);
44✔
710
        frame->pixels.u8ptr = NULL;
44✔
711
    }
44✔
712
    if (frame->palette != NULL) {
1,699!
713
        sixel_allocator_free(frame->allocator, frame->palette);
×
714
        frame->palette = NULL;
×
715
    }
×
716
    if (frame->transparent_mask != NULL) {
1,691✔
717
        sixel_allocator_free(frame->allocator, frame->transparent_mask);
44✔
718
        frame->transparent_mask = NULL;
44✔
719
    }
44✔
720
    frame->transparent_mask_size = 0u;
2,057✔
721
    frame->ncolors = -1;
2,057✔
722
    frame->transparent = -1;
2,057✔
723
    frame->alpha_zero_is_transparent = 0;
2,057✔
724
}
2,057✔
725

726
static void
727
coregraphics_parse_png_transparency(CFDictionaryRef frame_props,
220✔
728
                                    int ncolors,
729
                                    unsigned char *zero_alpha_map,
730
                                    int *zero_alpha_count,
731
                                    int *has_partial_alpha)
732
{
733
    CFDictionaryRef png_dict;
140✔
734
    CFDataRef transparency_data;
140✔
735
    unsigned char const *alpha_bytes;
140✔
736
    CFIndex alpha_length;
140✔
737
    int alpha_count;
140✔
738
    int index;
140✔
739
    unsigned char alpha;
140✔
740

741
    png_dict = NULL;
220✔
742
    transparency_data = NULL;
220✔
743
    alpha_bytes = NULL;
220✔
744
    alpha_length = 0;
220✔
745
    alpha_count = 0;
220✔
746
    index = 0;
220✔
747
    alpha = 0u;
220✔
748
    if (zero_alpha_map == NULL ||
440!
749
        zero_alpha_count == NULL ||
220!
750
        has_partial_alpha == NULL ||
220!
751
        ncolors <= 0) {
220✔
752
        return;
×
753
    }
754

755
    memset(zero_alpha_map, 0, (size_t)ncolors);
220✔
756
    *zero_alpha_count = 0;
220✔
757
    *has_partial_alpha = 0;
220✔
758

759
    if (frame_props == NULL) {
220✔
760
        return;
×
761
    }
762

763
    png_dict = (CFDictionaryRef)CFDictionaryGetValue(
220✔
764
        frame_props,
220✔
765
        kCGImagePropertyPNGDictionary);
220✔
766
    if (png_dict == NULL || CFGetTypeID(png_dict) != CFDictionaryGetTypeID()) {
220!
767
        return;
88✔
768
    }
769

770
    transparency_data = (CFDataRef)CFDictionaryGetValue(
132✔
771
        png_dict,
132✔
772
        kCGImagePropertyPNGTransparency);
132✔
773
    if (transparency_data == NULL ||
132!
774
        CFGetTypeID(transparency_data) != CFDataGetTypeID()) {
×
775
        return;
132✔
776
    }
777

778
    alpha_bytes = CFDataGetBytePtr(transparency_data);
×
779
    alpha_length = CFDataGetLength(transparency_data);
×
780
    if (alpha_bytes == NULL || alpha_length <= 0) {
×
781
        return;
×
782
    }
783

784
    alpha_count = (int)alpha_length;
×
785
    if (alpha_count > ncolors) {
×
786
        alpha_count = ncolors;
×
787
    }
×
788

789
    for (index = 0; index < alpha_count; ++index) {
×
790
        alpha = alpha_bytes[index];
×
791
        if (alpha == 0u) {
×
792
            zero_alpha_map[index] = 1u;
×
793
            *zero_alpha_count += 1;
×
794
        } else if (alpha != 255u) {
×
795
            *has_partial_alpha = 1;
×
796
        }
×
797
    }
×
798
}
220!
799

800
static unsigned int
801
coregraphics_read_u32be(unsigned char const *bytes)
891✔
802
{
803
    unsigned int value;
567✔
804

805
    value = 0u;
891✔
806
    if (bytes == NULL) {
891✔
807
        return 0u;
×
808
    }
809

810
    value = (unsigned int)bytes[0] << 24;
891✔
811
    value |= (unsigned int)bytes[1] << 16;
891✔
812
    value |= (unsigned int)bytes[2] << 8;
891✔
813
    value |= (unsigned int)bytes[3];
891✔
814
    return value;
891✔
815
}
891✔
816

817
static void
818
coregraphics_png_trns_chunk_cache_init(coregraphics_png_trns_chunk_cache_t
1,716✔
819
                                       *cache)
820
{
821
    if (cache == NULL) {
1,716✔
822
        return;
×
823
    }
824

825
    cache->initialized = 0;
1,716✔
826
    cache->available = 0;
1,716✔
827
    cache->alpha_count = 0;
1,716✔
828
    memset(cache->alpha_entries, 0, sizeof(cache->alpha_entries));
1,716✔
829
}
1,716✔
830

831
static void
832
coregraphics_merge_png_transparency_entries(unsigned char const *alpha_entries,
×
833
                                            int alpha_count,
834
                                            int ncolors,
835
                                            unsigned char *zero_alpha_map,
836
                                            int *zero_alpha_count,
837
                                            int *has_partial_alpha)
838
{
839
    int index;
840
    int merge_count;
841
    unsigned char alpha;
842

843
    index = 0;
×
844
    merge_count = 0;
×
845
    alpha = 0u;
×
846
    if (alpha_entries == NULL ||
×
847
        alpha_count <= 0 ||
×
848
        ncolors <= 0 ||
×
849
        zero_alpha_map == NULL ||
×
850
        zero_alpha_count == NULL ||
×
851
        has_partial_alpha == NULL) {
×
852
        return;
×
853
    }
854

855
    merge_count = alpha_count;
×
856
    if (merge_count > ncolors) {
×
857
        merge_count = ncolors;
×
858
    }
×
859
    if (merge_count > SIXEL_PALETTE_MAX) {
×
860
        merge_count = SIXEL_PALETTE_MAX;
×
861
    }
×
862

863
    for (index = 0; index < merge_count; ++index) {
×
864
        alpha = alpha_entries[index];
×
865
        if (alpha == 0u) {
×
866
            if (zero_alpha_map[index] == 0u) {
×
867
                zero_alpha_map[index] = 1u;
×
868
                *zero_alpha_count += 1;
×
869
            }
×
870
        } else if (alpha != 255u) {
×
871
            *has_partial_alpha = 1;
×
872
        }
×
873
    }
×
874
}
×
875

876
static void
877
coregraphics_parse_png_transparency_chunk(
220✔
878
    sixel_chunk_t const *chunk,
879
    coregraphics_png_trns_chunk_cache_t *trns_cache,
880
    int ncolors,
881
    unsigned char *zero_alpha_map,
882
    int *zero_alpha_count,
883
    int *has_partial_alpha)
884
{
885
    static unsigned char const png_signature[8] = {
886
        0x89u, 0x50u, 0x4eu, 0x47u, 0x0du, 0x0au, 0x1au, 0x0au
887
    };
888
    size_t offset;
140✔
889
    size_t chunk_size;
140✔
890
    size_t chunk_length;
140✔
891
    unsigned char const *bytes;
140✔
892
    int alpha_count;
140✔
893
    int use_cache;
140✔
894

895
    offset = 0u;
220✔
896
    chunk_size = 0u;
220✔
897
    chunk_length = 0u;
220✔
898
    bytes = NULL;
220✔
899
    alpha_count = 0;
220✔
900
    use_cache = 0;
220✔
901
    if (chunk == NULL ||
440!
902
        chunk->buffer == NULL ||
220!
903
        chunk->size < sizeof(png_signature) ||
220!
904
        ncolors <= 0 ||
220!
905
        zero_alpha_map == NULL ||
220!
906
        zero_alpha_count == NULL ||
220!
907
        has_partial_alpha == NULL) {
220✔
908
        return;
×
909
    }
910

911
    if (trns_cache != NULL) {
220!
912
        use_cache = 1;
220✔
913
        if (trns_cache->initialized != 0) {
220!
914
            if (trns_cache->available == 0) {
×
915
                return;
×
916
            }
917
            coregraphics_merge_png_transparency_entries(
×
918
                trns_cache->alpha_entries,
×
919
                trns_cache->alpha_count,
×
920
                ncolors,
×
921
                zero_alpha_map,
×
922
                zero_alpha_count,
×
923
                has_partial_alpha);
×
924
            return;
×
925
        }
926
        trns_cache->initialized = 1;
220✔
927
        trns_cache->available = 0;
220✔
928
        trns_cache->alpha_count = 0;
220✔
929
        memset(trns_cache->alpha_entries, 0, sizeof(trns_cache->alpha_entries));
220✔
930
    }
220✔
931

932
    if (memcmp(chunk->buffer, png_signature, sizeof(png_signature)) != 0) {
220✔
933
        return;
88✔
934
    }
935

936
    offset = sizeof(png_signature);
132✔
937
    while (offset + 8u <= chunk->size) {
1,023✔
938
        chunk_length = (size_t)coregraphics_read_u32be(chunk->buffer + offset);
891✔
939
        offset += 4u;
891✔
940
        if (offset + 4u > chunk->size) {
891!
941
            break;
×
942
        }
943

944
        bytes = chunk->buffer + offset;
891✔
945
        if (offset + 4u + chunk_length + 4u > chunk->size) {
891!
946
            break;
×
947
        }
948

949
        if (memcmp(bytes, "tRNS", 4u) == 0) {
891✔
950
            offset += 4u;
×
951
            chunk_size = chunk_length;
×
952
            if (chunk_size > (size_t)SIXEL_PALETTE_MAX) {
×
953
                chunk_size = (size_t)SIXEL_PALETTE_MAX;
×
954
            }
×
955
            alpha_count = (int)chunk_size;
×
956
            if (use_cache != 0) {
×
957
                if (alpha_count > 0) {
×
958
                    memcpy(trns_cache->alpha_entries,
×
959
                           chunk->buffer + offset,
960
                           (size_t)alpha_count);
961
                    trns_cache->alpha_count = alpha_count;
×
962
                    trns_cache->available = 1;
×
963
                    coregraphics_merge_png_transparency_entries(
×
964
                        trns_cache->alpha_entries,
×
965
                        trns_cache->alpha_count,
×
966
                        ncolors,
×
967
                        zero_alpha_map,
×
968
                        zero_alpha_count,
×
969
                        has_partial_alpha);
×
970
                }
×
971
            } else {
×
972
                coregraphics_merge_png_transparency_entries(
×
973
                    chunk->buffer + offset,
×
974
                    alpha_count,
×
975
                    ncolors,
×
976
                    zero_alpha_map,
×
977
                    zero_alpha_count,
×
978
                    has_partial_alpha);
×
979
            }
980
            break;
×
981
        }
982

983
        offset += 4u + chunk_length + 4u;
891✔
984
    }
985
}
220!
986

987
static SIXELSTATUS
988
coregraphics_copy_indexed_palette(CGImageRef image,
220✔
989
                                  sixel_allocator_t *allocator,
990
                                  unsigned char **ppalette,
991
                                  int *pncolors)
992
{
993
    SIXELSTATUS status;
140✔
994
    CGColorSpaceRef color_space;
140✔
995
    CGColorSpaceRef base_space;
140✔
996
    size_t color_count;
140✔
997
    size_t component_count;
140✔
998
    size_t table_size;
140✔
999
    unsigned char *palette;
140✔
1000
    unsigned char *table;
140✔
1001
    size_t index;
140✔
1002

1003
    status = SIXEL_FALSE;
220✔
1004
    color_space = NULL;
220✔
1005
    base_space = NULL;
220✔
1006
    color_count = 0u;
220✔
1007
    component_count = 0u;
220✔
1008
    table_size = 0u;
220✔
1009
    palette = NULL;
220✔
1010
    table = NULL;
220✔
1011
    index = 0u;
220✔
1012
    if (image == NULL ||
440!
1013
        allocator == NULL ||
220!
1014
        ppalette == NULL ||
220!
1015
        pncolors == NULL) {
220✔
1016
        return SIXEL_BAD_ARGUMENT;
×
1017
    }
1018

1019
    *ppalette = NULL;
220✔
1020
    *pncolors = 0;
220✔
1021

1022
    color_space = CGImageGetColorSpace(image);
220✔
1023
    if (color_space == NULL ||
220!
1024
        CGColorSpaceGetModel(color_space) != kCGColorSpaceModelIndexed) {
220✔
1025
        return SIXEL_FALSE;
×
1026
    }
1027

1028
    color_count = CGColorSpaceGetColorTableCount(color_space);
220✔
1029
    if (color_count == 0u || color_count > SIXEL_PALETTE_MAX) {
220!
1030
        return SIXEL_FALSE;
×
1031
    }
1032

1033
    base_space = CGColorSpaceGetBaseColorSpace(color_space);
220✔
1034
    if (base_space == NULL) {
220✔
1035
        return SIXEL_FALSE;
×
1036
    }
1037

1038
    component_count = CGColorSpaceGetNumberOfComponents(base_space);
220✔
1039
    if (component_count == 0u || component_count > 4u) {
220!
1040
        return SIXEL_FALSE;
×
1041
    }
1042

1043
    if (color_count > SIZE_MAX / component_count) {
220!
1044
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1045
    }
1046
    table_size = color_count * component_count;
220✔
1047
    table = (unsigned char *)sixel_allocator_malloc(allocator, table_size);
220✔
1048
    if (table == NULL) {
220✔
1049
        sixel_helper_set_additional_message(
×
1050
            "load_with_coregraphics: sixel_allocator_malloc() failed.");
1051
        return SIXEL_BAD_ALLOCATION;
×
1052
    }
1053

1054
    CGColorSpaceGetColorTable(color_space, table);
220✔
1055

1056
    if (color_count > SIZE_MAX / 3u) {
220!
1057
        status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1058
        goto cleanup;
×
1059
    }
1060
    palette = (unsigned char *)sixel_allocator_malloc(allocator,
440✔
1061
                                                      color_count * 3u);
220✔
1062
    if (palette == NULL) {
220✔
1063
        sixel_helper_set_additional_message(
×
1064
            "load_with_coregraphics: sixel_allocator_malloc() failed.");
1065
        status = SIXEL_BAD_ALLOCATION;
×
1066
        goto cleanup;
×
1067
    }
1068

1069
    for (index = 0u; index < color_count; ++index) {
42,306✔
1070
        if (component_count >= 3u) {
42,086!
1071
            palette[index * 3u + 0u] = table[index * component_count + 0u];
42,086✔
1072
            palette[index * 3u + 1u] = table[index * component_count + 1u];
42,086✔
1073
            palette[index * 3u + 2u] = table[index * component_count + 2u];
42,086✔
1074
        } else {
42,086✔
1075
            palette[index * 3u + 0u] = table[index * component_count + 0u];
×
1076
            palette[index * 3u + 1u] = table[index * component_count + 0u];
×
1077
            palette[index * 3u + 2u] = table[index * component_count + 0u];
×
1078
        }
1079
    }
42,086✔
1080

1081
    *ppalette = palette;
220✔
1082
    *pncolors = (int)color_count;
220✔
1083
    palette = NULL;
220✔
1084
    status = SIXEL_OK;
220✔
1085

1086
cleanup:
1087
    sixel_allocator_free(allocator, palette);
220✔
1088
    sixel_allocator_free(allocator, table);
220✔
1089
    return status;
220✔
1090
}
220✔
1091

1092
static SIXELSTATUS
1093
coregraphics_copy_indexed_pixels(CGImageRef image,
209✔
1094
                                 sixel_allocator_t *allocator,
1095
                                 int width,
1096
                                 int height,
1097
                                 unsigned char **ppixels)
1098
{
1099
    SIXELSTATUS status;
133✔
1100
    CGDataProviderRef provider;
133✔
1101
    CFDataRef provider_data;
133✔
1102
    unsigned char const *src;
133✔
1103
    unsigned char const *row;
133✔
1104
    unsigned char *pixels;
133✔
1105
    size_t bits_per_pixel;
133✔
1106
    size_t bytes_per_row;
133✔
1107
    size_t packed_row_bytes;
133✔
1108
    size_t pixel_count;
133✔
1109
    size_t needed_bytes;
133✔
1110
    size_t data_size;
133✔
1111
    size_t y;
133✔
1112
    int x;
133✔
1113
    unsigned char packed;
133✔
1114
    int shift;
133✔
1115

1116
    status = SIXEL_FALSE;
209✔
1117
    provider = NULL;
209✔
1118
    provider_data = NULL;
209✔
1119
    src = NULL;
209✔
1120
    row = NULL;
209✔
1121
    pixels = NULL;
209✔
1122
    bits_per_pixel = 0u;
209✔
1123
    bytes_per_row = 0u;
209✔
1124
    packed_row_bytes = 0u;
209✔
1125
    pixel_count = 0u;
209✔
1126
    needed_bytes = 0u;
209✔
1127
    data_size = 0u;
209✔
1128
    y = 0u;
209✔
1129
    x = 0;
209✔
1130
    packed = 0u;
209✔
1131
    shift = 0;
209✔
1132
    if (image == NULL ||
418!
1133
        allocator == NULL ||
209!
1134
        ppixels == NULL ||
209!
1135
        width <= 0 ||
209!
1136
        height <= 0) {
209✔
1137
        return SIXEL_BAD_ARGUMENT;
×
1138
    }
1139

1140
    *ppixels = NULL;
209✔
1141
    bits_per_pixel = CGImageGetBitsPerPixel(image);
209✔
1142
    bytes_per_row = CGImageGetBytesPerRow(image);
209✔
1143
    if (bits_per_pixel != 1u &&
418!
1144
        bits_per_pixel != 2u &&
209!
1145
        bits_per_pixel != 4u &&
209!
1146
        bits_per_pixel != 8u) {
209✔
1147
        return SIXEL_FALSE;
11✔
1148
    }
1149

1150
    packed_row_bytes = ((size_t)width * bits_per_pixel + 7u) / 8u;
198✔
1151
    if (bytes_per_row < packed_row_bytes) {
198!
1152
        return SIXEL_FALSE;
×
1153
    }
1154
    if ((size_t)height > SIZE_MAX / bytes_per_row) {
198!
1155
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1156
    }
1157
    needed_bytes = (size_t)height * bytes_per_row;
198✔
1158

1159
    provider = CGImageGetDataProvider(image);
198✔
1160
    if (provider == NULL) {
198✔
1161
        return SIXEL_FALSE;
×
1162
    }
1163
    provider_data = CGDataProviderCopyData(provider);
198✔
1164
    if (provider_data == NULL) {
198✔
1165
        return SIXEL_FALSE;
×
1166
    }
1167
    src = CFDataGetBytePtr(provider_data);
198✔
1168
    data_size = (size_t)CFDataGetLength(provider_data);
198✔
1169
    if (src == NULL || data_size < needed_bytes) {
198!
1170
        status = SIXEL_FALSE;
×
1171
        goto cleanup;
×
1172
    }
1173

1174
    if ((size_t)width > SIZE_MAX / (size_t)height) {
198!
1175
        status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1176
        goto cleanup;
×
1177
    }
1178
    pixel_count = (size_t)width * (size_t)height;
198✔
1179
    pixels = (unsigned char *)sixel_allocator_malloc(allocator, pixel_count);
198✔
1180
    if (pixels == NULL) {
198✔
1181
        sixel_helper_set_additional_message(
×
1182
            "load_with_coregraphics: sixel_allocator_malloc() failed.");
1183
        status = SIXEL_BAD_ALLOCATION;
×
1184
        goto cleanup;
×
1185
    }
1186

1187
    for (y = 0u; y < (size_t)height; ++y) {
9,790✔
1188
        row = src + y * bytes_per_row;
9,592✔
1189
        if (bits_per_pixel == 8u) {
9,592!
1190
            memcpy(pixels + y * (size_t)width, row, (size_t)width);
9,592✔
1191
            continue;
9,592✔
1192
        }
1193
        for (x = 0; x < width; ++x) {
×
1194
            packed = row[((size_t)x * bits_per_pixel) / 8u];
×
1195
            shift = (int)(8u - bits_per_pixel
×
1196
                          - ((size_t)x * bits_per_pixel) % 8u);
×
1197
            pixels[y * (size_t)width + (size_t)x] =
×
1198
                (unsigned char)((packed >> shift)
×
1199
                                & ((1u << bits_per_pixel) - 1u));
×
1200
        }
×
1201
    }
×
1202

1203
    *ppixels = pixels;
198✔
1204
    pixels = NULL;
198✔
1205
    status = SIXEL_OK;
198✔
1206

1207
cleanup:
1208
    if (provider_data != NULL) {
198!
1209
        CFRelease(provider_data);
198✔
1210
    }
198✔
1211
    sixel_allocator_free(allocator, pixels);
198✔
1212
    return status;
198✔
1213
}
209✔
1214

1215
static SIXELSTATUS
1216
coregraphics_try_handle_indexed_frame(sixel_chunk_t const *chunk,
2,057✔
1217
                                      sixel_frame_t *frame,
1218
                                      CGImageRef image,
1219
                                      CFDictionaryRef frame_props,
1220
                                      coregraphics_png_trns_chunk_cache_t
1221
                                      *png_trns_chunk_cache,
1222
                                      int fuse_palette,
1223
                                      int reqcolors,
1224
                                      unsigned char const *bgcolor,
1225
                                      int *handled,
1226
                                      int *force_alpha)
1227
{
1228
    /*
1229
     * Indexed-path policy:
1230
     *   - emit PAL8 only for opaque indexed frames when palette output is
1231
     *     enabled,
1232
     *   - keep binary keycolor transparency in PAL8 when reqcolors allows it,
1233
     *     otherwise preserve it as RGB+mask (or background composite),
1234
     *   - defer non-binary alpha to the generic RGBA decode path.
1235
     */
1236
    SIXELSTATUS status;
1,309✔
1237
    CGColorSpaceRef color_space;
1,309✔
1238
    unsigned char *palette;
1,309✔
1239
    unsigned char *indexed_pixels;
1,309✔
1240
    unsigned char *rgb_pixels;
1,309✔
1241
    unsigned char *mask;
1,309✔
1242
    unsigned char zero_alpha_map[SIXEL_PALETTE_MAX];
1,309✔
1243
    size_t pixel_count;
1,309✔
1244
    size_t index;
1,309✔
1245
    int ncolors;
1,309✔
1246
    int allow_pal8;
1,309✔
1247
    int reqcolors_clamped;
1,309✔
1248
    int zero_alpha_count;
1,309✔
1249
    int has_partial_alpha;
1,309✔
1250
    int has_keycolor_alpha;
1,309✔
1251
    int key_index;
1,309✔
1252
    unsigned char palette_index;
1,309✔
1253

1254
    status = SIXEL_FALSE;
2,057✔
1255
    color_space = NULL;
2,057✔
1256
    palette = NULL;
2,057✔
1257
    indexed_pixels = NULL;
2,057✔
1258
    rgb_pixels = NULL;
2,057✔
1259
    mask = NULL;
2,057✔
1260
    pixel_count = 0u;
2,057✔
1261
    index = 0u;
2,057✔
1262
    ncolors = 0;
2,057✔
1263
    allow_pal8 = 0;
2,057✔
1264
    reqcolors_clamped = 0;
2,057✔
1265
    zero_alpha_count = 0;
2,057✔
1266
    has_partial_alpha = 0;
2,057✔
1267
    has_keycolor_alpha = 0;
2,057✔
1268
    key_index = -1;
2,057✔
1269
    palette_index = 0u;
2,057✔
1270
    if (chunk == NULL ||
4,114!
1271
        frame == NULL ||
2,057!
1272
        image == NULL ||
2,057!
1273
        handled == NULL ||
2,057!
1274
        force_alpha == NULL) {
2,057✔
1275
        return SIXEL_BAD_ARGUMENT;
×
1276
    }
1277

1278
    *handled = 0;
2,057✔
1279
    *force_alpha = 0;
2,057✔
1280

1281
    color_space = CGImageGetColorSpace(image);
2,057✔
1282
    if (color_space == NULL ||
2,057!
1283
        CGColorSpaceGetModel(color_space) != kCGColorSpaceModelIndexed) {
2,057✔
1284
        return SIXEL_OK;
1,837✔
1285
    }
1286

1287
    status = coregraphics_copy_indexed_palette(image,
440✔
1288
                                               frame->allocator,
220✔
1289
                                               &palette,
1290
                                               &ncolors);
1291
    if (status == SIXEL_FALSE) {
220!
1292
        return SIXEL_OK;
×
1293
    }
1294
    if (SIXEL_FAILED(status)) {
220!
1295
        return status;
×
1296
    }
1297

1298
    memset(zero_alpha_map, 0, sizeof(zero_alpha_map));
220✔
1299
    coregraphics_parse_png_transparency(frame_props,
440✔
1300
                                        ncolors,
220✔
1301
                                        zero_alpha_map,
220✔
1302
                                        &zero_alpha_count,
1303
                                        &has_partial_alpha);
1304
    coregraphics_parse_png_transparency_chunk(chunk,
440✔
1305
                                              png_trns_chunk_cache,
220✔
1306
                                              ncolors,
220✔
1307
                                              zero_alpha_map,
220✔
1308
                                              &zero_alpha_count,
1309
                                              &has_partial_alpha);
1310
    if (has_partial_alpha != 0) {
220!
1311
        *force_alpha = 1;
×
1312
        status = SIXEL_OK;
×
1313
        goto cleanup;
×
1314
    }
1315

1316
    reqcolors_clamped = reqcolors;
220✔
1317
    if (reqcolors_clamped <= 0 || reqcolors_clamped > SIXEL_PALETTE_MAX) {
220!
1318
        reqcolors_clamped = SIXEL_PALETTE_MAX;
×
1319
    }
×
1320
    has_keycolor_alpha = zero_alpha_count > 0 ? 1 : 0;
220✔
1321
    allow_pal8 = fuse_palette != 0 &&
440!
1322
        ncolors > 0 &&
480!
1323
        ncolors <= reqcolors_clamped;
220✔
1324
    if (has_keycolor_alpha != 0 && bgcolor != NULL) {
260!
1325
        allow_pal8 = 0;
×
1326
    }
×
1327
    if (has_keycolor_alpha != 0) {
180!
1328
        for (index = 0u; index < (size_t)ncolors; ++index) {
×
1329
            if (zero_alpha_map[index] != 0u) {
×
1330
                key_index = (int)index;
×
1331
                break;
×
1332
            }
1333
        }
×
1334
    }
×
1335

1336
    if (allow_pal8 == 0 && has_keycolor_alpha == 0) {
182!
1337
        status = SIXEL_OK;
11✔
1338
        goto cleanup;
11✔
1339
    }
1340
    if (has_keycolor_alpha == 0) {
209✔
1341
        key_index = -1;
209✔
1342
    } else if (key_index < 0) {
209!
1343
        status = SIXEL_OK;
×
1344
        goto cleanup;
×
1345
    }
1346

1347
    if ((size_t)frame->width > SIZE_MAX / (size_t)frame->height) {
209!
1348
        status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1349
        goto cleanup;
×
1350
    }
1351
    pixel_count = (size_t)frame->width * (size_t)frame->height;
209✔
1352
    status = coregraphics_copy_indexed_pixels(image,
418✔
1353
                                              frame->allocator,
209✔
1354
                                              frame->width,
209✔
1355
                                              frame->height,
209✔
1356
                                              &indexed_pixels);
1357
    if (status == SIXEL_FALSE) {
209✔
1358
        status = SIXEL_OK;
11✔
1359
        goto cleanup;
11✔
1360
    }
1361
    if (SIXEL_FAILED(status)) {
198!
1362
        goto cleanup;
×
1363
    }
1364

1365
    if (allow_pal8 != 0) {
198!
1366
        if (has_keycolor_alpha != 0 &&
198!
1367
            zero_alpha_count > 1 &&
×
1368
            key_index >= 0) {
×
1369
            for (index = 0u; index < pixel_count; ++index) {
×
1370
                palette_index = indexed_pixels[index];
×
1371
                if ((int)palette_index < ncolors &&
×
1372
                    zero_alpha_map[palette_index] != 0u &&
×
1373
                    (int)palette_index != key_index) {
×
1374
                    indexed_pixels[index] = (unsigned char)key_index;
×
1375
                }
×
1376
            }
×
1377
        }
×
1378
        frame->pixels.u8ptr = indexed_pixels;
198✔
1379
        indexed_pixels = NULL;
198✔
1380
        frame->palette = palette;
198✔
1381
        palette = NULL;
198✔
1382
        frame->ncolors = ncolors;
198✔
1383
        frame->pixelformat = SIXEL_PIXELFORMAT_PAL8;
198✔
1384
        frame->colorspace = SIXEL_COLORSPACE_GAMMA;
198✔
1385
        frame->transparent = has_keycolor_alpha ? key_index : -1;
198!
1386
        frame->alpha_zero_is_transparent = 0;
198✔
1387
        *handled = 1;
198✔
1388
        status = SIXEL_OK;
198✔
1389
        goto cleanup;
198✔
1390
    }
1391

1392
    if (has_keycolor_alpha == 0) {
×
1393
        status = SIXEL_OK;
×
1394
        goto cleanup;
×
1395
    }
1396

1397
    if (pixel_count > SIZE_MAX / 3u) {
×
1398
        status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1399
        goto cleanup;
×
1400
    }
1401
    rgb_pixels = (unsigned char *)sixel_allocator_malloc(frame->allocator,
×
1402
                                                         pixel_count * 3u);
×
1403
    if (rgb_pixels == NULL) {
×
1404
        sixel_helper_set_additional_message(
×
1405
            "load_with_coregraphics: sixel_allocator_malloc() failed.");
1406
        status = SIXEL_BAD_ALLOCATION;
×
1407
        goto cleanup;
×
1408
    }
1409

1410
    if (bgcolor == NULL) {
×
1411
        mask = (unsigned char *)sixel_allocator_malloc(frame->allocator,
×
1412
                                                       pixel_count);
×
1413
        if (mask == NULL) {
×
1414
            sixel_helper_set_additional_message(
×
1415
                "load_with_coregraphics: sixel_allocator_malloc() failed.");
1416
            status = SIXEL_BAD_ALLOCATION;
×
1417
            goto cleanup;
×
1418
        }
1419
    }
×
1420

1421
    for (index = 0u; index < pixel_count; ++index) {
×
1422
        palette_index = indexed_pixels[index];
×
1423
        if ((int)palette_index >= ncolors) {
×
1424
            palette_index = 0u;
×
1425
        }
×
1426
        if (zero_alpha_map[palette_index] != 0u) {
×
1427
            if (bgcolor != NULL) {
×
1428
                rgb_pixels[index * 3u + 0u] = bgcolor[0];
×
1429
                rgb_pixels[index * 3u + 1u] = bgcolor[1];
×
1430
                rgb_pixels[index * 3u + 2u] = bgcolor[2];
×
1431
            } else {
×
1432
                rgb_pixels[index * 3u + 0u] = 0u;
×
1433
                rgb_pixels[index * 3u + 1u] = 0u;
×
1434
                rgb_pixels[index * 3u + 2u] = 0u;
×
1435
                mask[index] = 1u;
×
1436
            }
1437
        } else {
×
1438
            rgb_pixels[index * 3u + 0u] =
×
1439
                palette[(size_t)palette_index * 3u + 0u];
×
1440
            rgb_pixels[index * 3u + 1u] =
×
1441
                palette[(size_t)palette_index * 3u + 1u];
×
1442
            rgb_pixels[index * 3u + 2u] =
×
1443
                palette[(size_t)palette_index * 3u + 2u];
×
1444
            if (mask != NULL) {
×
1445
                mask[index] = 0u;
×
1446
            }
×
1447
        }
1448
    }
×
1449

1450
    frame->pixels.u8ptr = rgb_pixels;
×
1451
    rgb_pixels = NULL;
×
1452
    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1453
    frame->colorspace = SIXEL_COLORSPACE_GAMMA;
×
1454
    frame->transparent = -1;
×
1455
    frame->ncolors = -1;
×
1456
    frame->alpha_zero_is_transparent = 0;
×
1457
    if (mask != NULL) {
×
1458
        frame->transparent_mask = mask;
×
1459
        frame->transparent_mask_size = pixel_count;
×
1460
        frame->alpha_zero_is_transparent = 1;
×
1461
        mask = NULL;
×
1462
    }
×
1463
    *handled = 1;
×
1464
    status = SIXEL_OK;
×
1465

1466
cleanup:
1467
    sixel_allocator_free(frame->allocator, palette);
220✔
1468
    sixel_allocator_free(frame->allocator, indexed_pixels);
220✔
1469
    sixel_allocator_free(frame->allocator, rgb_pixels);
220✔
1470
    sixel_allocator_free(frame->allocator, mask);
220✔
1471
    return status;
220✔
1472
}
2,057✔
1473

1474
static CGColorSpaceRef
1475
coregraphics_create_linear_colorspace(void)
33✔
1476
{
1477
    CGColorSpaceRef color_space;
21✔
1478

1479
    color_space = NULL;
33✔
1480
    color_space = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearSRGB);
33✔
1481
    if (color_space == NULL) {
33!
1482
        color_space = CGColorSpaceCreateWithName(kCGColorSpaceLinearSRGB);
×
1483
    }
×
1484
    if (color_space == NULL) {
27!
1485
        color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
×
1486
    }
×
1487

1488
    return color_space;
54✔
1489
}
21✔
1490

1491
static SIXELSTATUS
1492
coregraphics_decode_rgba8_frame(sixel_frame_t *frame,
1,826✔
1493
                                CGImageRef image,
1494
                                unsigned char const *bgcolor,
1495
                                int has_alpha_like,
1496
                                coregraphics_srgb_lut_cache_t *srgb_lut_cache)
1497
{
1498
    /*
1499
     * Draw into premultiplied RGBA and emit three-component output.
1500
     * When alpha-like transparency is present and no bgcolor is given,
1501
     * retain per-pixel zero-alpha semantics through transparent_mask.
1502
     */
1503
    SIXELSTATUS status;
1,162✔
1504
    CGColorSpaceRef color_space;
1,162✔
1505
    CGContextRef context;
1,162✔
1506
    unsigned char *pixels;
1,162✔
1507
    unsigned char *mask;
1,162✔
1508
    size_t stride;
1,162✔
1509
    size_t pixel_count;
1,162✔
1510
    size_t index;
1,162✔
1511
    unsigned int alpha;
1,162✔
1512
    unsigned char r8;
1,162✔
1513
    unsigned char g8;
1,162✔
1514
    unsigned char b8;
1,162✔
1515
    unsigned char out_r;
1,162✔
1516
    unsigned char out_g;
1,162✔
1517
    unsigned char out_b;
1,162✔
1518
    double alpha_unit;
1,162✔
1519
    double bg_linear[3];
1,162✔
1520
    double src_linear_r;
1,162✔
1521
    double src_linear_g;
1,162✔
1522
    double src_linear_b;
1,162✔
1523
    double out_linear_r;
1,162✔
1524
    double out_linear_g;
1,162✔
1525
    double out_linear_b;
1,162✔
1526
    double const *decode_lut;
1,162✔
1527
    unsigned char const *encode_lut;
1,162✔
1528
    size_t encode_lut_size;
1,162✔
1529

1530
    status = SIXEL_FALSE;
1,826✔
1531
    color_space = NULL;
1,826✔
1532
    context = NULL;
1,826✔
1533
    pixels = NULL;
1,826✔
1534
    mask = NULL;
1,826✔
1535
    stride = 0u;
1,826✔
1536
    pixel_count = 0u;
1,826✔
1537
    index = 0u;
1,826✔
1538
    alpha = 0u;
1,826✔
1539
    r8 = 0u;
1,826✔
1540
    g8 = 0u;
1,826✔
1541
    b8 = 0u;
1,826✔
1542
    out_r = 0u;
1,826✔
1543
    out_g = 0u;
1,826✔
1544
    out_b = 0u;
1,826✔
1545
    alpha_unit = 0.0;
1,826✔
1546
    bg_linear[0] = 0.0;
1,826✔
1547
    bg_linear[1] = 0.0;
1,826✔
1548
    bg_linear[2] = 0.0;
1,826✔
1549
    src_linear_r = 0.0;
1,826✔
1550
    src_linear_g = 0.0;
1,826✔
1551
    src_linear_b = 0.0;
1,826✔
1552
    out_linear_r = 0.0;
1,826✔
1553
    out_linear_g = 0.0;
1,826✔
1554
    out_linear_b = 0.0;
1,826✔
1555
    decode_lut = NULL;
1,826✔
1556
    encode_lut = NULL;
1,826✔
1557
    encode_lut_size = 0u;
1,826✔
1558
    if (frame == NULL || image == NULL || frame->allocator == NULL) {
1,826!
1559
        return SIXEL_BAD_ARGUMENT;
×
1560
    }
1561

1562
    if ((size_t)frame->width > SIZE_MAX / (size_t)frame->height) {
1,826!
1563
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1564
    }
1565
    pixel_count = (size_t)frame->width * (size_t)frame->height;
1,826✔
1566
    if (frame->width > 0 && (size_t)frame->width > SIZE_MAX / 4u) {
1,826!
1567
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1568
    }
1569
    stride = (size_t)frame->width * 4u;
1,826✔
1570
    if (pixel_count > 0u && (size_t)frame->height > SIZE_MAX / stride) {
1,826!
1571
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1572
    }
1573

1574
    pixels = (unsigned char *)sixel_allocator_malloc(frame->allocator,
3,652✔
1575
                                                     (size_t)frame->height *
3,652✔
1576
                                                     stride);
1,826✔
1577
    if (pixels == NULL) {
1,826✔
1578
        sixel_helper_set_additional_message(
×
1579
            "load_with_coregraphics: sixel_allocator_malloc() failed.");
1580
        return SIXEL_BAD_ALLOCATION;
×
1581
    }
1582

1583
    color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
1,826✔
1584
    if (color_space == NULL) {
1,826✔
1585
        sixel_helper_set_additional_message(
×
1586
            "load_with_coregraphics: CGColorSpaceCreateWithName failed.");
1587
        status = SIXEL_RUNTIME_ERROR;
×
1588
        goto cleanup;
×
1589
    }
1590

1591
    context = CGBitmapContextCreate(pixels,
3,652✔
1592
                                    (size_t)frame->width,
1,826✔
1593
                                    (size_t)frame->height,
1,826✔
1594
                                    8,
1595
                                    stride,
1,826✔
1596
                                    color_space,
1,826✔
1597
                                    kCGImageAlphaPremultipliedLast |
1598
                                        kCGBitmapByteOrder32Big);
1599
    if (context == NULL) {
1,826✔
1600
        sixel_helper_set_additional_message(
×
1601
            "load_with_coregraphics: CGBitmapContextCreate failed.");
1602
        status = SIXEL_RUNTIME_ERROR;
×
1603
        goto cleanup;
×
1604
    }
1605

1606
    CGContextSetBlendMode(context, kCGBlendModeCopy);
1,826✔
1607
    CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 0.0);
1,826✔
1608
    CGContextFillRect(context,
3,652✔
1609
                      CGRectMake(0,
1,826✔
1610
                                 0,
1611
                                 (CGFloat)frame->width,
1,826✔
1612
                                 (CGFloat)frame->height));
1,826✔
1613
    CGContextSetBlendMode(context, kCGBlendModeNormal);
1,826✔
1614
    CGContextDrawImage(context,
3,652✔
1615
                       CGRectMake(0,
1,826✔
1616
                                  0,
1617
                                  (CGFloat)frame->width,
1,826✔
1618
                                  (CGFloat)frame->height),
1,826✔
1619
                       image);
1,826✔
1620

1621
    if (bgcolor != NULL) {
1,826✔
1622
        bg_linear[0] = coregraphics_decode_srgb_unit((double)bgcolor[0] /
33✔
1623
                                                     255.0);
1624
        bg_linear[1] = coregraphics_decode_srgb_unit((double)bgcolor[1] /
33✔
1625
                                                     255.0);
1626
        bg_linear[2] = coregraphics_decode_srgb_unit((double)bgcolor[2] /
33✔
1627
                                                     255.0);
1628
    }
33✔
1629

1630
    if (has_alpha_like != 0 && bgcolor == NULL) {
1,606✔
1631
        mask = (unsigned char *)sixel_allocator_malloc(frame->allocator,
1,166✔
1632
                                                       pixel_count);
583✔
1633
        if (mask == NULL) {
583✔
1634
            sixel_helper_set_additional_message(
×
1635
                "load_with_coregraphics: sixel_allocator_malloc() failed.");
1636
            status = SIXEL_BAD_ALLOCATION;
×
1637
            goto cleanup;
×
1638
        }
1639
    }
583✔
1640
    if (has_alpha_like != 0) {
1,606✔
1641
        if (srgb_lut_cache == NULL) {
616✔
1642
            status = SIXEL_BAD_ARGUMENT;
×
1643
            goto cleanup;
×
1644
        }
1645
        if (srgb_lut_cache->prepared == 0) {
616✔
1646
            coregraphics_build_srgb_decode_u8_lut(srgb_lut_cache->decode_lut);
429✔
1647
            coregraphics_build_srgb_encode_u8_lut(
429✔
1648
                srgb_lut_cache->encode_lut,
429✔
1649
                sizeof(srgb_lut_cache->encode_lut));
1650
            srgb_lut_cache->prepared = 1;
429✔
1651
        }
429✔
1652
        decode_lut = srgb_lut_cache->decode_lut;
616✔
1653
        encode_lut = srgb_lut_cache->encode_lut;
616✔
1654
        encode_lut_size = sizeof(srgb_lut_cache->encode_lut);
616✔
1655
    }
616✔
1656

1657
    if (has_alpha_like == 0) {
1,826✔
1658
        for (index = 0u; index < pixel_count; ++index) {
2,953,082✔
1659
            pixels[index * 3u + 0u] = pixels[index * 4u + 0u];
2,951,872✔
1660
            pixels[index * 3u + 1u] = pixels[index * 4u + 1u];
2,951,872✔
1661
            pixels[index * 3u + 2u] = pixels[index * 4u + 2u];
2,951,872✔
1662
        }
2,951,872✔
1663
        frame->pixels.u8ptr = pixels;
1,210✔
1664
        pixels = NULL;
1,210✔
1665
        frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
1,210✔
1666
        frame->colorspace = SIXEL_COLORSPACE_GAMMA;
1,210✔
1667
        frame->transparent = -1;
1,210✔
1668
        frame->ncolors = -1;
1,210✔
1669
        frame->alpha_zero_is_transparent = 0;
1,210✔
1670
        status = SIXEL_OK;
1,210✔
1671
        goto cleanup;
1,210✔
1672
    }
1673

1674
    for (index = 0u; index < pixel_count; ++index) {
435,028✔
1675
        alpha = pixels[index * 4u + 3u];
434,412✔
1676
        if (alpha == 0u) {
434,412✔
1677
            r8 = 0u;
143✔
1678
            g8 = 0u;
143✔
1679
            b8 = 0u;
143✔
1680
            alpha_unit = 0.0;
143✔
1681
            if (mask != NULL) {
143✔
1682
                mask[index] = 1u;
88✔
1683
            }
88✔
1684
        } else if (alpha >= 255u) {
434,412✔
1685
            r8 = pixels[index * 4u + 0u];
434,225✔
1686
            g8 = pixels[index * 4u + 1u];
434,225✔
1687
            b8 = pixels[index * 4u + 2u];
434,225✔
1688
            alpha_unit = 1.0;
434,225✔
1689
            if (mask != NULL) {
434,225✔
1690
                mask[index] = 0u;
434,170✔
1691
            }
434,170✔
1692
        } else {
434,225✔
1693
            r8 = coregraphics_unpremultiply_channel(pixels[index * 4u + 0u],
88✔
1694
                                                    alpha);
44✔
1695
            g8 = coregraphics_unpremultiply_channel(pixels[index * 4u + 1u],
88✔
1696
                                                    alpha);
44✔
1697
            b8 = coregraphics_unpremultiply_channel(pixels[index * 4u + 2u],
88✔
1698
                                                    alpha);
44✔
1699
            alpha_unit = (double)alpha / 255.0;
44✔
1700
            if (mask != NULL) {
44✔
1701
                mask[index] = 0u;
22✔
1702
            }
22✔
1703
        }
1704

1705
        src_linear_r = decode_lut[r8];
434,412✔
1706
        src_linear_g = decode_lut[g8];
434,412✔
1707
        src_linear_b = decode_lut[b8];
434,412✔
1708

1709
        if (bgcolor != NULL) {
434,412✔
1710
            out_linear_r = src_linear_r * alpha_unit
264✔
1711
                + bg_linear[0] * (1.0 - alpha_unit);
132✔
1712
            out_linear_g = src_linear_g * alpha_unit
264✔
1713
                + bg_linear[1] * (1.0 - alpha_unit);
132✔
1714
            out_linear_b = src_linear_b * alpha_unit
264✔
1715
                + bg_linear[2] * (1.0 - alpha_unit);
132✔
1716
        } else {
132✔
1717
            out_linear_r = src_linear_r * alpha_unit;
434,280✔
1718
            out_linear_g = src_linear_g * alpha_unit;
434,280✔
1719
            out_linear_b = src_linear_b * alpha_unit;
434,280✔
1720
        }
1721

1722
        out_r = coregraphics_encode_linear_to_srgb_u8(out_linear_r,
868,824✔
1723
                                                      encode_lut,
434,412✔
1724
                                                      encode_lut_size);
434,412✔
1725
        out_g = coregraphics_encode_linear_to_srgb_u8(out_linear_g,
868,824✔
1726
                                                      encode_lut,
434,412✔
1727
                                                      encode_lut_size);
434,412✔
1728
        out_b = coregraphics_encode_linear_to_srgb_u8(out_linear_b,
868,824✔
1729
                                                      encode_lut,
434,412✔
1730
                                                      encode_lut_size);
434,412✔
1731
        pixels[index * 3u + 0u] = out_r;
434,412✔
1732
        pixels[index * 3u + 1u] = out_g;
434,412✔
1733
        pixels[index * 3u + 2u] = out_b;
434,412✔
1734
    }
434,412✔
1735

1736
    frame->pixels.u8ptr = pixels;
616✔
1737
    pixels = NULL;
616✔
1738
    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
616✔
1739
    frame->colorspace = SIXEL_COLORSPACE_GAMMA;
616✔
1740
    frame->transparent = -1;
616✔
1741
    frame->ncolors = -1;
616✔
1742
    frame->alpha_zero_is_transparent = 0;
616✔
1743
    if (mask != NULL) {
616✔
1744
        frame->transparent_mask = mask;
583✔
1745
        frame->transparent_mask_size = pixel_count;
583✔
1746
        frame->alpha_zero_is_transparent = 1;
583✔
1747
        mask = NULL;
583✔
1748
    }
583✔
1749
    status = SIXEL_OK;
616✔
1750

1751
cleanup:
1752
    if (context != NULL) {
1,826!
1753
        CGContextRelease(context);
1,826✔
1754
    }
1,826✔
1755
    if (color_space != NULL) {
1,826!
1756
        CGColorSpaceRelease(color_space);
1,826✔
1757
    }
1,826✔
1758
    sixel_allocator_free(frame->allocator, pixels);
1,826✔
1759
    sixel_allocator_free(frame->allocator, mask);
1,826✔
1760
    return status;
1,826✔
1761
}
1,826✔
1762

1763
static SIXELSTATUS
1764
coregraphics_decode_float32_frame(sixel_frame_t *frame,
33✔
1765
                                  CGImageRef image,
1766
                                  unsigned char const *bgcolor,
1767
                                  int has_alpha_like)
1768
{
1769
    /*
1770
     * High-depth sources are decoded through a float32 linear context so
1771
     * background composition and alpha handling stay in linear light.
1772
     */
1773
    SIXELSTATUS status;
21✔
1774
    CGColorSpaceRef color_space;
21✔
1775
    CGContextRef context;
21✔
1776
    float *pixels;
21✔
1777
    unsigned char *mask;
21✔
1778
    size_t stride;
21✔
1779
    size_t pixel_count;
21✔
1780
    size_t index;
21✔
1781
    float alpha;
21✔
1782
    float out_r;
21✔
1783
    float out_g;
21✔
1784
    float out_b;
21✔
1785
    float bg_linear[3];
21✔
1786

1787
    status = SIXEL_FALSE;
33✔
1788
    color_space = NULL;
33✔
1789
    context = NULL;
33✔
1790
    pixels = NULL;
33✔
1791
    mask = NULL;
33✔
1792
    stride = 0u;
33✔
1793
    pixel_count = 0u;
33✔
1794
    index = 0u;
33✔
1795
    alpha = 0.0f;
33✔
1796
    out_r = 0.0f;
33✔
1797
    out_g = 0.0f;
33✔
1798
    out_b = 0.0f;
33✔
1799
    bg_linear[0] = 0.0f;
33✔
1800
    bg_linear[1] = 0.0f;
33✔
1801
    bg_linear[2] = 0.0f;
33✔
1802
    if (frame == NULL || image == NULL || frame->allocator == NULL) {
33!
1803
        return SIXEL_BAD_ARGUMENT;
×
1804
    }
1805

1806
    if ((size_t)frame->width > SIZE_MAX / (size_t)frame->height) {
33!
1807
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1808
    }
1809
    pixel_count = (size_t)frame->width * (size_t)frame->height;
33✔
1810
    if (frame->width > 0 &&
33!
1811
        (size_t)frame->width > SIZE_MAX / (4u * sizeof(float))) {
33✔
1812
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1813
    }
1814
    stride = (size_t)frame->width * 4u * sizeof(float);
33✔
1815
    if (pixel_count > 0u && (size_t)frame->height > SIZE_MAX / stride) {
33!
1816
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1817
    }
1818

1819
    pixels = (float *)sixel_allocator_malloc(frame->allocator,
66✔
1820
                                             (size_t)frame->height * stride);
33✔
1821
    if (pixels == NULL) {
33✔
1822
        sixel_helper_set_additional_message(
×
1823
            "load_with_coregraphics: sixel_allocator_malloc() failed.");
1824
        return SIXEL_BAD_ALLOCATION;
×
1825
    }
1826

1827
    color_space = coregraphics_create_linear_colorspace();
33✔
1828
    if (color_space == NULL) {
33✔
1829
        sixel_helper_set_additional_message(
×
1830
            "load_with_coregraphics: CGColorSpaceCreateWithName failed.");
1831
        status = SIXEL_RUNTIME_ERROR;
×
1832
        goto cleanup;
×
1833
    }
1834

1835
    context = CGBitmapContextCreate(pixels,
66✔
1836
                                    (size_t)frame->width,
33✔
1837
                                    (size_t)frame->height,
33✔
1838
                                    32,
1839
                                    stride,
33✔
1840
                                    color_space,
33✔
1841
                                    kCGImageAlphaPremultipliedLast |
1842
                                        kCGBitmapByteOrder32Host |
1843
                                        kCGBitmapFloatComponents);
1844
    if (context == NULL) {
33✔
1845
        sixel_helper_set_additional_message(
×
1846
            "load_with_coregraphics: CGBitmapContextCreate failed.");
1847
        status = SIXEL_RUNTIME_ERROR;
×
1848
        goto cleanup;
×
1849
    }
1850

1851
    CGContextSetBlendMode(context, kCGBlendModeCopy);
33✔
1852
    CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 0.0);
33✔
1853
    CGContextFillRect(context,
66✔
1854
                      CGRectMake(0,
33✔
1855
                                 0,
1856
                                 (CGFloat)frame->width,
33✔
1857
                                 (CGFloat)frame->height));
33✔
1858
    CGContextSetBlendMode(context, kCGBlendModeNormal);
33✔
1859
    CGContextDrawImage(context,
66✔
1860
                       CGRectMake(0,
33✔
1861
                                  0,
1862
                                  (CGFloat)frame->width,
33✔
1863
                                  (CGFloat)frame->height),
33✔
1864
                       image);
33✔
1865

1866
    if (bgcolor != NULL) {
33!
1867
        bg_linear[0] = (float)coregraphics_decode_srgb_unit(
×
1868
            (double)bgcolor[0] / 255.0);
×
1869
        bg_linear[1] = (float)coregraphics_decode_srgb_unit(
×
1870
            (double)bgcolor[1] / 255.0);
×
1871
        bg_linear[2] = (float)coregraphics_decode_srgb_unit(
×
1872
            (double)bgcolor[2] / 255.0);
×
1873
    }
×
1874

1875
    if (has_alpha_like != 0 && bgcolor == NULL) {
27!
1876
        mask = (unsigned char *)sixel_allocator_malloc(frame->allocator,
×
1877
                                                       pixel_count);
×
1878
        if (mask == NULL) {
×
1879
            sixel_helper_set_additional_message(
×
1880
                "load_with_coregraphics: sixel_allocator_malloc() failed.");
1881
            status = SIXEL_BAD_ALLOCATION;
×
1882
            goto cleanup;
×
1883
        }
1884
    }
×
1885

1886
    if (has_alpha_like == 0) {
33✔
1887
        for (index = 0u; index < pixel_count; ++index) {
135,201✔
1888
            pixels[index * 3u + 0u] = pixels[index * 4u + 0u];
135,168✔
1889
            pixels[index * 3u + 1u] = pixels[index * 4u + 1u];
135,168✔
1890
            pixels[index * 3u + 2u] = pixels[index * 4u + 2u];
135,168✔
1891
        }
135,168✔
1892
    } else {
33✔
1893
        for (index = 0u; index < pixel_count; ++index) {
×
1894
            alpha = pixels[index * 4u + 3u];
×
1895
            if (alpha < 0.0f) {
×
1896
                alpha = 0.0f;
×
1897
            } else if (alpha > 1.0f) {
×
1898
                alpha = 1.0f;
×
1899
            }
×
1900

1901
            out_r = pixels[index * 4u + 0u];
×
1902
            out_g = pixels[index * 4u + 1u];
×
1903
            out_b = pixels[index * 4u + 2u];
×
1904
            if (bgcolor != NULL && alpha < 1.0f) {
×
1905
                out_r += bg_linear[0] * (1.0f - alpha);
×
1906
                out_g += bg_linear[1] * (1.0f - alpha);
×
1907
                out_b += bg_linear[2] * (1.0f - alpha);
×
1908
            } else if (mask != NULL) {
×
1909
                if (alpha <= 0.0f) {
×
1910
                    mask[index] = 1u;
×
1911
                } else {
×
1912
                    mask[index] = 0u;
×
1913
                }
1914
            }
×
1915

1916
            pixels[index * 3u + 0u] = out_r;
×
1917
            pixels[index * 3u + 1u] = out_g;
×
1918
            pixels[index * 3u + 2u] = out_b;
×
1919
        }
×
1920
    }
1921

1922
    frame->pixels.f32ptr = pixels;
33✔
1923
    pixels = NULL;
33✔
1924
    frame->pixelformat = SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
33✔
1925
    frame->colorspace = SIXEL_COLORSPACE_LINEAR;
33✔
1926
    frame->transparent = -1;
33✔
1927
    frame->ncolors = -1;
33✔
1928
    frame->alpha_zero_is_transparent = 0;
33✔
1929
    if (mask != NULL) {
33!
1930
        frame->transparent_mask = mask;
×
1931
        frame->transparent_mask_size = pixel_count;
×
1932
        frame->alpha_zero_is_transparent = 1;
×
1933
        mask = NULL;
×
1934
    }
×
1935
    status = SIXEL_OK;
33✔
1936

1937
cleanup:
1938
    if (context != NULL) {
33!
1939
        CGContextRelease(context);
33✔
1940
    }
33✔
1941
    if (color_space != NULL) {
33!
1942
        CGColorSpaceRelease(color_space);
33✔
1943
    }
33✔
1944
    sixel_allocator_free(frame->allocator, pixels);
33✔
1945
    sixel_allocator_free(frame->allocator, mask);
33✔
1946
    return status;
33✔
1947
}
33✔
1948

1949

1950
static SIXELSTATUS
1951
coregraphics_parse_animation_start_frame_no(int *start_frame_no)
308✔
1952
{
1953
    SIXELSTATUS status;
196✔
1954
    char const *env_value;
196✔
1955
    char *endptr;
196✔
1956
    long parsed;
196✔
1957

1958
    status = SIXEL_OK;
308✔
1959
    env_value = NULL;
308✔
1960
    endptr = NULL;
308✔
1961
    parsed = 0;
308✔
1962

1963
    *start_frame_no = INT_MIN;
308✔
1964
    env_value = sixel_compat_getenv("SIXEL_LOADER_ANIMATION_START_FRAME_NO");
308✔
1965
    if (env_value == NULL || env_value[0] == '\0') {
308!
1966
        goto end;
308✔
1967
    }
1968

1969
    parsed = strtol(env_value, &endptr, 10);
×
1970
    if (endptr == env_value || *endptr != '\0') {
×
1971
        sixel_helper_set_additional_message(
×
1972
            "SIXEL_LOADER_ANIMATION_START_FRAME_NO must be an integer.");
1973
        status = SIXEL_BAD_INPUT;
×
1974
        goto end;
×
1975
    }
1976
    if (parsed < (long)INT_MIN || parsed > (long)INT_MAX) {
×
1977
        sixel_helper_set_additional_message(
×
1978
            "SIXEL_LOADER_ANIMATION_START_FRAME_NO is out of range.");
1979
        status = SIXEL_BAD_INPUT;
×
1980
        goto end;
×
1981
    }
1982

1983
    *start_frame_no = (int)parsed;
×
1984

1985
end:
1986
    return status;
504✔
1987
}
196✔
1988

1989
static SIXELSTATUS
1990
coregraphics_resolve_animation_start_frame_no(int start_frame_no,
176✔
1991
                                              int frame_count,
1992
                                              int *resolved)
1993
{
1994
    SIXELSTATUS status;
112✔
1995
    int index;
112✔
1996

1997
    status = SIXEL_OK;
176✔
1998
    index = 0;
176✔
1999

2000
    if (frame_count <= 0) {
176!
2001
        sixel_helper_set_additional_message(
×
2002
            "Animation frame count must be positive.");
2003
        status = SIXEL_BAD_INPUT;
×
2004
        goto end;
×
2005
    }
2006

2007
    if (start_frame_no >= 0) {
176✔
2008
        index = start_frame_no;
132✔
2009
    } else {
132✔
2010
        index = frame_count + start_frame_no;
44✔
2011
    }
2012

2013
    if (index < 0 || index >= frame_count) {
176✔
2014
        sixel_helper_set_additional_message(
22✔
2015
            "SIXEL_LOADER_ANIMATION_START_FRAME_NO is outside"
2016
            " the animation frame range.");
2017
        status = SIXEL_BAD_INPUT;
22✔
2018
        goto end;
22✔
2019
    }
2020

2021
    *resolved = index;
154✔
2022

2023
end:
2024
    return status;
288✔
2025
}
112✔
2026

2027
static SIXELSTATUS
2028
coregraphics_parse_frame_cache_max_bytes(size_t *max_bytes,
1,716✔
2029
                                         int *cache_enabled)
2030
{
2031
    SIXELSTATUS status;
1,092✔
2032
    char const *env_value;
1,092✔
2033
    char *endptr;
1,092✔
2034
    unsigned long long parsed;
1,092✔
2035

2036
    status = SIXEL_OK;
1,716✔
2037
    env_value = NULL;
1,716✔
2038
    endptr = NULL;
1,716✔
2039
    parsed = 0ull;
1,716✔
2040
    if (max_bytes == NULL || cache_enabled == NULL) {
1,716!
2041
        return SIXEL_BAD_ARGUMENT;
×
2042
    }
2043

2044
    *max_bytes = COREGRAPHICS_FRAME_CACHE_MAX_BYTES_DEFAULT;
1,716✔
2045
    *cache_enabled = 1;
1,716✔
2046
    env_value = sixel_compat_getenv(
1,716✔
2047
        "SIXEL_LOADER_COREGRAPHICS_CACHE_MAX_BYTES");
2048
    if (env_value == NULL || env_value[0] == '\0') {
1,716!
2049
        goto end;
1,650✔
2050
    }
2051

2052
    errno = 0;
66✔
2053
    parsed = strtoull(env_value, &endptr, 10);
66✔
2054
    if (errno == ERANGE || endptr == env_value || *endptr != '\0') {
66!
2055
        sixel_helper_set_additional_message(
11✔
2056
            "SIXEL_LOADER_COREGRAPHICS_CACHE_MAX_BYTES "
2057
            "must be a non-negative integer.");
2058
        status = SIXEL_BAD_INPUT;
11✔
2059
        goto end;
11✔
2060
    }
2061
    if (parsed == 0ull) {
55✔
2062
        *max_bytes = 0u;
11✔
2063
        *cache_enabled = 0;
11✔
2064
        goto end;
11✔
2065
    }
2066
    if (parsed > (unsigned long long)SIZE_MAX) {
44!
2067
        sixel_helper_set_additional_message(
×
2068
            "SIXEL_LOADER_COREGRAPHICS_CACHE_MAX_BYTES is out of range.");
2069
        status = SIXEL_BAD_INPUT;
×
2070
        goto end;
×
2071
    }
2072

2073
    *max_bytes = (size_t)parsed;
44✔
2074

2075
end:
2076
    return status;
1,716✔
2077
}
1,716✔
2078

2079
static int
2080
coregraphics_measure_frame_cache_bytes(sixel_frame_t const *frame,
825✔
2081
                                       size_t *total_bytes)
2082
{
2083
    int depth;
525✔
2084
    size_t pixel_count;
525✔
2085
    size_t pixels_bytes;
525✔
2086
    size_t palette_bytes;
525✔
2087
    size_t mask_bytes;
525✔
2088

2089
    depth = 0;
825✔
2090
    pixel_count = 0u;
825✔
2091
    pixels_bytes = 0u;
825✔
2092
    palette_bytes = 0u;
825✔
2093
    mask_bytes = 0u;
825✔
2094
    if (frame == NULL || total_bytes == NULL) {
825!
2095
        return 0;
×
2096
    }
2097
    if (frame->width <= 0 || frame->height <= 0) {
825!
2098
        return 0;
×
2099
    }
2100
    if ((size_t)frame->width > SIZE_MAX / (size_t)frame->height) {
825!
2101
        return 0;
×
2102
    }
2103
    depth = sixel_helper_compute_depth(frame->pixelformat);
825✔
2104
    if (depth <= 0) {
825!
2105
        return 0;
×
2106
    }
2107
    pixel_count = (size_t)frame->width * (size_t)frame->height;
825✔
2108
    if (pixel_count > SIZE_MAX / (size_t)depth) {
825!
2109
        return 0;
×
2110
    }
2111
    pixels_bytes = pixel_count * (size_t)depth;
825✔
2112
    *total_bytes = pixels_bytes;
825✔
2113

2114
    /*
2115
     * Include palette and transparency mask storage so cache limiting follows
2116
     * the decoded frame footprint rather than a fixed bytes-per-pixel guess.
2117
     */
2118
    if (frame->palette != NULL && frame->ncolors > 0) {
825!
2119
        if ((size_t)frame->ncolors > SIZE_MAX / 3u) {
×
2120
            return 0;
×
2121
        }
2122
        palette_bytes = (size_t)frame->ncolors * 3u;
×
2123
        if (*total_bytes > SIZE_MAX - palette_bytes) {
×
2124
            return 0;
×
2125
        }
2126
        *total_bytes += palette_bytes;
×
2127
    }
×
2128
    if (frame->transparent_mask != NULL && frame->transparent_mask_size > 0u) {
725!
2129
        mask_bytes = frame->transparent_mask_size;
275✔
2130
        if (*total_bytes > SIZE_MAX - mask_bytes) {
275!
2131
            return 0;
×
2132
        }
2133
        *total_bytes += mask_bytes;
275✔
2134
    }
275✔
2135
    return 1;
825✔
2136
}
825✔
2137

2138
static void
2139
coregraphics_loader_state_init(
1,716✔
2140
    coregraphics_loader_state_t *state,
2141
    sixel_chunk_t const *chunk,
2142
    int fstatic,
2143
    int fuse_palette,
2144
    int reqcolors,
2145
    int enable_orientation,
2146
    unsigned char *bgcolor,
2147
    int loop_control,
2148
    int start_frame_no_set,
2149
    int start_frame_no_override,
2150
    sixel_load_image_function fn_load,
2151
    void *context)
2152
{
2153
    if (state == NULL) {
1,716✔
2154
        return;
×
2155
    }
2156

2157
    state->status = SIXEL_FALSE;
1,716✔
2158
    state->chunk = chunk;
1,716✔
2159
    state->fstatic = fstatic;
1,716✔
2160
    state->fuse_palette = fuse_palette;
1,716✔
2161
    state->reqcolors = reqcolors;
1,716✔
2162
    state->enable_orientation = enable_orientation;
1,716✔
2163
    state->bgcolor = bgcolor;
1,716✔
2164
    state->loop_control = loop_control;
1,716✔
2165
    state->start_frame_no_set = start_frame_no_set;
1,716✔
2166
    state->start_frame_no_override = start_frame_no_override;
1,716✔
2167
    state->fn_load = fn_load;
1,716✔
2168
    state->context = context;
1,716✔
2169
    state->frame = NULL;
1,716✔
2170
    state->emit_frame = NULL;
1,716✔
2171
    state->decode_frame = NULL;
1,716✔
2172
    state->cached_frame_tmp = NULL;
1,716✔
2173
    state->data = NULL;
1,716✔
2174
    state->source = NULL;
1,716✔
2175
    state->image = NULL;
1,716✔
2176
    state->props = NULL;
1,716✔
2177
    state->frame_props = NULL;
1,716✔
2178
    state->frame_count = 0u;
1,716✔
2179
    state->total_frames = 0;
1,716✔
2180
    state->anim_loop_count = -1;
1,716✔
2181
    state->is_animation_container = 0;
1,716✔
2182
    state->source_orientation = 1;
1,716✔
2183
    state->start_frame_no = INT_MIN;
1,716✔
2184
    state->resolved_start_frame_no = INT_MIN;
1,716✔
2185
    state->frame_index = 0;
1,716✔
2186
    state->loop_no = 0;
1,716✔
2187
    state->frames_in_loop = 0;
1,716✔
2188
    state->stop_loop = 0;
1,716✔
2189
    state->metadata_slots = 0u;
1,716✔
2190
    state->single_meta_slot.delay = 0;
1,716✔
2191
    state->single_meta_slot.orientation = 1;
1,716✔
2192
    state->single_meta_slot.has_alpha = 0;
1,716✔
2193
    state->single_meta_slot.promote_float32 = 0;
1,716✔
2194
    state->single_meta_slot.is_indexed = 0;
1,716✔
2195
    state->single_meta_slot.props_ready = 0u;
1,716✔
2196
    state->single_meta_slot.decode_hint_ready = 0u;
1,716✔
2197
    state->frame_meta_slots = NULL;
1,716✔
2198
    state->active_meta_slots = NULL;
1,716✔
2199
    state->frame_cache_slots = NULL;
1,716✔
2200
    state->frame_cache_enabled = 0;
1,716✔
2201
    state->frame_cache_max_bytes = 0u;
1,716✔
2202
    state->frame_cache_used_bytes = 0u;
1,716✔
2203
    state->frame_cache_frame_bytes = 0u;
1,716✔
2204
    state->image_width = 0u;
1,716✔
2205
    state->image_height = 0u;
1,716✔
2206
    state->frame_cache_keep = 0;
1,716✔
2207
    state->frame_cache_decision_pending = 0;
1,716✔
2208
    state->release_emit_frame = 0;
1,716✔
2209
    state->cache_hit = 0;
1,716✔
2210
    state->indexed_handled = 0;
1,716✔
2211
    state->force_alpha_from_indexed = 0;
1,716✔
2212
    state->has_alpha_like = 0;
1,716✔
2213
    state->promote_float32 = 0;
1,716✔
2214
    state->frame_orientation = 1;
1,716✔
2215
    state->frame_meta_slot = 0u;
1,716✔
2216
    coregraphics_png_trns_chunk_cache_init(&state->png_trns_chunk_cache);
1,716✔
2217
    state->rgba8_lut_cache.prepared = 0;
1,716✔
2218
    state->cf_data_length = 0;
1,716✔
2219
    state->prefetch_index = 0u;
1,716✔
2220
}
1,716✔
2221

2222
static SIXELSTATUS
2223
coregraphics_check_cancel(void *context)
11,836✔
2224
{
2225
    if (sixel_loader_callback_is_canceled(context)) {
11,836!
2226
        return SIXEL_INTERRUPTED;
×
2227
    }
2228
    return SIXEL_OK;
11,836✔
2229
}
11,836✔
2230

2231
static SIXELSTATUS
2232
coregraphics_prepare_source_and_root_metadata(
1,716✔
2233
    coregraphics_loader_state_t *state)
2234
{
2235
    SIXELSTATUS status;
1,092✔
2236
    CFDictionaryRef anim_dict;
1,092✔
2237
    CFStringRef anim_loop_key;
1,092✔
2238

2239
    status = SIXEL_OK;
1,716✔
2240
    anim_dict = NULL;
1,716✔
2241
    anim_loop_key = NULL;
1,716✔
2242
    if (state == NULL || state->chunk == NULL || state->fn_load == NULL) {
1,716!
2243
        return SIXEL_BAD_ARGUMENT;
×
2244
    }
2245

2246
    status = coregraphics_parse_frame_cache_max_bytes(
1,716✔
2247
        &state->frame_cache_max_bytes,
1,716✔
2248
        &state->frame_cache_enabled);
1,716✔
2249
    if (status != SIXEL_OK) {
1,716✔
2250
        return status;
11✔
2251
    }
2252

2253
    status = sixel_frame_new(&state->frame, state->chunk->allocator);
1,705✔
2254
    if (SIXEL_FAILED(status)) {
1,705!
2255
        return status;
×
2256
    }
2257

2258
    if (state->chunk->size > (size_t)LONG_MAX) {
1,705✔
2259
        sixel_helper_set_additional_message(
11✔
2260
            "load_with_coregraphics: input chunk size is too large.");
2261
        return SIXEL_BAD_INTEGER_OVERFLOW;
11✔
2262
    }
2263
    state->cf_data_length = (CFIndex)state->chunk->size;
1,694✔
2264
    state->data = CFDataCreate(kCFAllocatorDefault,
3,388✔
2265
                               state->chunk->buffer,
1,694✔
2266
                               state->cf_data_length);
1,694✔
2267
    if (state->data == NULL) {
1,694✔
2268
        sixel_helper_set_additional_message(
×
2269
            "load_with_coregraphics: CFDataCreate failed.");
2270
        return SIXEL_FALSE;
×
2271
    }
2272

2273
    state->source = CGImageSourceCreateWithData(state->data, NULL);
1,694✔
2274
    if (state->source == NULL) {
1,694✔
2275
        sixel_helper_set_additional_message(
×
2276
            "load_with_coregraphics: CGImageSourceCreateWithData failed.");
2277
        return SIXEL_FALSE;
×
2278
    }
2279

2280
    state->frame_count = CGImageSourceGetCount(state->source);
1,694✔
2281
    if (state->frame_count == 0u) {
1,694✔
2282
        sixel_helper_set_additional_message(
154✔
2283
            "load_with_coregraphics: input has no decodable frames.");
2284
        return SIXEL_FALSE;
154✔
2285
    }
2286
    if (state->frame_count > (size_t)INT_MAX) {
1,540!
2287
        sixel_helper_set_additional_message(
×
2288
            "load_with_coregraphics: frame count is too large.");
2289
        return SIXEL_BAD_INPUT;
×
2290
    }
2291
    state->total_frames = (int)state->frame_count;
1,540✔
2292

2293
    state->props = CGImageSourceCopyProperties(state->source, NULL);
1,540✔
2294
    if (state->props != NULL) {
1,540!
2295
        state->source_orientation = coregraphics_resolve_exif_orientation(
1,540✔
2296
            state->props,
1,540✔
2297
            state->source_orientation);
1,540✔
2298
        /*
2299
         * Treat multi-frame decoding as animation only when the source
2300
         * exposes known animation dictionaries. This keeps multi-size ICO
2301
         * decoding static while enabling APNG/WebP/HEICS animation.
2302
         */
2303
        if (coregraphics_get_animation_keys(state->props,
3,080✔
2304
                                            state->frame_count,
1,540✔
2305
                                            &anim_dict,
2306
                                            &anim_loop_key,
2307
                                            NULL,
2308
                                            NULL)) {
2309
            if (state->frame_count > 1u) {
539✔
2310
                state->is_animation_container = 1;
484✔
2311
            }
484✔
2312
            state->anim_loop_count = coregraphics_dictionary_get_int(
539✔
2313
                anim_dict,
539✔
2314
                anim_loop_key,
539✔
2315
                state->anim_loop_count);
539✔
2316
        }
539✔
2317
    }
1,540✔
2318

2319
    if (state->is_animation_container != 0) {
1,732✔
2320
        /*
2321
         * Keep start-frame controls animation-only so static decode paths do
2322
         * not reject malformed env values.
2323
         */
2324
        if (state->start_frame_no_set != 0) {
484✔
2325
            state->start_frame_no = state->start_frame_no_override;
176✔
2326
        } else {
176✔
2327
            status = coregraphics_parse_animation_start_frame_no(
308✔
2328
                &state->start_frame_no);
308✔
2329
            if (status != SIXEL_OK) {
308!
2330
                return status;
×
2331
            }
2332
        }
2333
        if (state->start_frame_no != INT_MIN) {
484✔
2334
            status = coregraphics_resolve_animation_start_frame_no(
176✔
2335
                state->start_frame_no,
176✔
2336
                state->total_frames,
176✔
2337
                &state->resolved_start_frame_no);
176✔
2338
            if (status != SIXEL_OK) {
176✔
2339
                return status;
22✔
2340
            }
2341
        }
154✔
2342
    }
462✔
2343

2344
    if (state->frame_cache_enabled != 0) {
1,516✔
2345
        if (state->fstatic != 0 || state->is_animation_container == 0) {
1,507✔
2346
            state->frame_cache_enabled = 0;
1,166✔
2347
        }
1,166✔
2348
    }
1,507✔
2349
    if (state->fstatic == 0 && state->is_animation_container != 0) {
1,558✔
2350
        state->metadata_slots = (size_t)state->total_frames;
352✔
2351
    } else {
352✔
2352
        /*
2353
         * Static and non-animation paths emit one selected frame, so keep
2354
         * metadata caches to a single slot instead of total frame count.
2355
         */
2356
        state->metadata_slots = 1u;
1,166✔
2357
    }
2358

2359
    return SIXEL_OK;
1,518✔
2360
}
1,716✔
2361

2362
static SIXELSTATUS
2363
coregraphics_prepare_metadata_slots(coregraphics_loader_state_t *state)
1,518✔
2364
{
2365
    if (state == NULL || state->frame == NULL) {
1,518!
2366
        return SIXEL_BAD_ARGUMENT;
×
2367
    }
2368
    if (state->metadata_slots == 0u) {
1,518✔
2369
        sixel_helper_set_additional_message(
×
2370
            "load_with_coregraphics: frame metadata is too large.");
2371
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
2372
    }
2373
    if (state->metadata_slots > 1u &&
1,518!
2374
        state->metadata_slots > SIZE_MAX / sizeof(*state->frame_meta_slots)) {
352✔
2375
        sixel_helper_set_additional_message(
×
2376
            "load_with_coregraphics: frame metadata is too large.");
2377
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
2378
    }
2379
    if (state->metadata_slots > 1u) {
1,518✔
2380
        state->frame_meta_slots = (coregraphics_frame_meta_slot_t *)
352✔
2381
            sixel_allocator_calloc(state->frame->allocator,
704✔
2382
                                   state->metadata_slots,
352✔
2383
                                   sizeof(*state->frame_meta_slots));
2384
        if (state->frame_meta_slots == NULL) {
352✔
2385
            sixel_helper_set_additional_message(
×
2386
                "load_with_coregraphics: sixel_allocator_calloc() failed.");
2387
            return SIXEL_BAD_ALLOCATION;
×
2388
        }
2389
        state->active_meta_slots = state->frame_meta_slots;
352✔
2390
    } else {
352✔
2391
        state->single_meta_slot.orientation = state->source_orientation;
1,166✔
2392
        state->active_meta_slots = &state->single_meta_slot;
1,166✔
2393
    }
2394
    if (state->frame_cache_enabled != 0) {
1,518✔
2395
        if ((size_t)state->total_frames > SIZE_MAX /
341!
2396
            sizeof(*state->frame_cache_slots)) {
2397
            sixel_helper_set_additional_message(
×
2398
                "load_with_coregraphics: frame metadata is too large.");
2399
            return SIXEL_BAD_INTEGER_OVERFLOW;
×
2400
        }
2401
        state->frame_cache_slots = (coregraphics_cache_slot_t *)
341✔
2402
            sixel_allocator_calloc(state->frame->allocator,
682✔
2403
                                   (size_t)state->total_frames,
341✔
2404
                                   sizeof(*state->frame_cache_slots));
2405
        if (state->frame_cache_slots == NULL) {
341✔
2406
            sixel_helper_set_additional_message(
×
2407
                "load_with_coregraphics: sixel_allocator_calloc() failed.");
2408
            return SIXEL_BAD_ALLOCATION;
×
2409
        }
2410
    }
341✔
2411
    return SIXEL_OK;
1,518✔
2412
}
1,518✔
2413

2414
static SIXELSTATUS
2415
coregraphics_prepare_frame_metadata(coregraphics_loader_state_t *state)
2,299✔
2416
{
2417
    SIXELSTATUS status;
1,463✔
2418
    coregraphics_frame_meta_slot_t *slot;
1,463✔
2419
    CFDictionaryRef frame_anim_dict;
1,463✔
2420
    CFStringRef frame_loop_key;
1,463✔
2421
    CFStringRef frame_delay_key;
1,463✔
2422
    CFStringRef frame_unclamped_delay_key;
1,463✔
2423

2424
    status = SIXEL_OK;
2,299✔
2425
    slot = NULL;
2,299✔
2426
    frame_anim_dict = NULL;
2,299✔
2427
    frame_loop_key = NULL;
2,299✔
2428
    frame_delay_key = NULL;
2,299✔
2429
    frame_unclamped_delay_key = NULL;
2,299✔
2430
    if (state == NULL || state->active_meta_slots == NULL) {
2,299!
2431
        return SIXEL_BAD_ARGUMENT;
×
2432
    }
2433

2434
    slot = &state->active_meta_slots[state->frame_meta_slot];
2,299✔
2435
    if (slot->props_ready == 0u) {
2,299✔
2436
        status = coregraphics_check_cancel(state->context);
2,013✔
2437
        if (status != SIXEL_OK) {
2,013!
2438
            return status;
×
2439
        }
2440
        state->frame_props = CGImageSourceCopyPropertiesAtIndex(
2,013✔
2441
            state->source,
2,013✔
2442
            (size_t)state->frame_index,
2,013✔
2443
            NULL);
2444
        slot->delay = 0;
2,013✔
2445
        if (state->frame_props != NULL) {
2,013!
2446
            if (coregraphics_get_animation_keys(state->frame_props,
4,026✔
2447
                                                state->frame_count,
2,013✔
2448
                                                &frame_anim_dict,
2449
                                                &frame_loop_key,
2450
                                                &frame_delay_key,
2451
                                                &frame_unclamped_delay_key)) {
2452
                state->anim_loop_count = coregraphics_dictionary_get_int(
1,001✔
2453
                    frame_anim_dict,
1,001✔
2454
                    frame_loop_key,
1,001✔
2455
                    state->anim_loop_count);
1,001✔
2456
                coregraphics_resolve_animation_delay_cs(
1,001✔
2457
                    frame_anim_dict,
1,001✔
2458
                    frame_unclamped_delay_key,
1,001✔
2459
                    frame_delay_key,
1,001✔
2460
                    &slot->delay);
1,001✔
2461
            }
1,001✔
2462
        }
2,013✔
2463
        slot->orientation = coregraphics_resolve_exif_orientation(
2,013✔
2464
            state->frame_props,
2,013✔
2465
            state->source_orientation);
2,013✔
2466
        slot->props_ready = 1u;
2,013✔
2467
    }
2,013✔
2468
    state->frame_orientation = slot->orientation;
2,299✔
2469
    return SIXEL_OK;
2,299✔
2470
}
2,299✔
2471

2472
static SIXELSTATUS
2473
coregraphics_process_single_frame(coregraphics_loader_state_t *state)
2,299✔
2474
{
2475
    SIXELSTATUS status;
1,463✔
2476
    coregraphics_frame_meta_slot_t *slot;
1,463✔
2477
    coregraphics_cache_slot_t *cache_slot;
1,463✔
2478

2479
    status = SIXEL_OK;
2,299✔
2480
    slot = NULL;
2,299✔
2481
    cache_slot = NULL;
2,299✔
2482
    if (state == NULL || state->active_meta_slots == NULL) {
2,299!
2483
        return SIXEL_BAD_ARGUMENT;
×
2484
    }
2485

2486
    slot = &state->active_meta_slots[state->frame_meta_slot];
2,299✔
2487
    cache_slot = NULL;
2,299✔
2488
    if (state->frame_cache_slots != NULL) {
2,299✔
2489
        cache_slot = &state->frame_cache_slots[(size_t)state->frame_index];
1,089✔
2490
    }
1,089✔
2491
    state->emit_frame = NULL;
2,079✔
2492
    state->decode_frame = NULL;
2,079✔
2493
    state->frame_cache_keep = 0;
2,079✔
2494
    state->frame_cache_decision_pending = 0;
2,079✔
2495
    state->release_emit_frame = 0;
2,079✔
2496
    state->cache_hit = 0;
2,079✔
2497
    if (cache_slot != NULL && cache_slot->frame != NULL) {
2,079✔
2498
        state->emit_frame = cache_slot->frame;
231✔
2499
        state->cache_hit = 1;
231✔
2500
    }
231✔
2501

2502
    status = coregraphics_prepare_frame_metadata(state);
2,299✔
2503
    if (status != SIXEL_OK) {
2,299!
2504
        return status;
×
2505
    }
2506

2507
    if (state->cache_hit == 0) {
2,299✔
2508
        status = coregraphics_check_cancel(state->context);
2,068✔
2509
        if (status != SIXEL_OK) {
2,068!
2510
            return status;
×
2511
        }
2512
        state->image = CGImageSourceCreateImageAtIndex(
2,068✔
2513
            state->source,
2,068✔
2514
            (size_t)state->frame_index,
2,068✔
2515
            NULL);
2516
        if (state->image == NULL) {
2,068✔
2517
            sixel_helper_set_additional_message(
11✔
2518
                "load_with_coregraphics: "
2519
                "CGImageSourceCreateImageAtIndex failed.");
2520
            return SIXEL_FALSE;
11✔
2521
        }
2522

2523
        if (cache_slot != NULL) {
2,057✔
2524
            if (cache_slot->decided == 0u &&
858!
2525
                state->frame_cache_used_bytes >=
1,650✔
2526
                state->frame_cache_max_bytes) {
825✔
2527
                /*
2528
                 * Once cache usage reaches the configured cap, every
2529
                 * remaining frame must bypass cache. Mark it decided early so
2530
                 * later loops skip temporary-frame probes.
2531
                 */
2532
                cache_slot->decided = 1u;
×
2533
            }
×
2534
            if (cache_slot->decided == 0u) {
858✔
2535
                status = sixel_frame_new(&state->cached_frame_tmp,
1,650✔
2536
                                         state->chunk->allocator);
825✔
2537
                if (SIXEL_FAILED(status)) {
825!
2538
                    return status;
×
2539
                }
2540
                state->decode_frame = state->cached_frame_tmp;
825✔
2541
                state->frame_cache_decision_pending = 1;
825✔
2542
            } else {
825✔
2543
                state->decode_frame = state->frame;
33✔
2544
            }
2545
        } else {
858✔
2546
            state->decode_frame = state->frame;
1,199✔
2547
        }
2548

2549
        if (slot->decode_hint_ready == 0u) {
2,057✔
2550
            slot->is_indexed = coregraphics_image_is_indexed(state->image);
2,002✔
2551
            slot->has_alpha = coregraphics_image_has_alpha(
2,002✔
2552
                state->image,
2,002✔
2553
                state->frame_props);
2,002✔
2554
            slot->promote_float32 = coregraphics_should_promote_float32(
2,002✔
2555
                state->image,
2,002✔
2556
                state->frame_props);
2,002✔
2557
            slot->decode_hint_ready = 1u;
2,002✔
2558
        }
2,002✔
2559

2560
        state->image_width = CGImageGetWidth(state->image);
2,057✔
2561
        state->image_height = CGImageGetHeight(state->image);
2,057✔
2562
        if (state->image_width > (size_t)INT_MAX) {
2,057!
2563
            sixel_helper_set_additional_message(
×
2564
                "load_with_coregraphics: given width parameter is too"
2565
                " huge.");
2566
            return SIXEL_BAD_INPUT;
×
2567
        }
2568
        if (state->image_height > (size_t)INT_MAX) {
2,057!
2569
            sixel_helper_set_additional_message(
×
2570
                "load_with_coregraphics: given height parameter is too"
2571
                " huge.");
2572
            return SIXEL_BAD_INPUT;
×
2573
        }
2574
        state->decode_frame->width = (int)state->image_width;
2,057✔
2575
        state->decode_frame->height = (int)state->image_height;
2,057✔
2576
        if (state->image_width > (size_t)SIXEL_WIDTH_LIMIT) {
2,057!
2577
            sixel_helper_set_additional_message(
×
2578
                "load_with_coregraphics: given width parameter is too"
2579
                " huge.");
2580
            return SIXEL_BAD_INPUT;
×
2581
        }
2582
        if (state->image_height > (size_t)SIXEL_HEIGHT_LIMIT) {
2,057!
2583
            sixel_helper_set_additional_message(
×
2584
                "load_with_coregraphics: given height parameter is too"
2585
                " huge.");
2586
            return SIXEL_BAD_INPUT;
×
2587
        }
2588
        if (state->decode_frame->width <= 0) {
2,057!
2589
            sixel_helper_set_additional_message(
×
2590
                "load_with_coregraphics: an invalid width parameter"
2591
                " detected.");
2592
            return SIXEL_BAD_INPUT;
×
2593
        }
2594
        if (state->decode_frame->height <= 0) {
2,057!
2595
            sixel_helper_set_additional_message(
×
2596
                "load_with_coregraphics: an invalid height parameter"
2597
                " detected.");
2598
            return SIXEL_BAD_INPUT;
×
2599
        }
2600
        if ((size_t)state->decode_frame->width >
4,114!
2601
            SIZE_MAX / (size_t)state->decode_frame->height) {
2,057✔
2602
            sixel_helper_set_additional_message(
×
2603
                "load_with_coregraphics: too large image.");
2604
            return SIXEL_RUNTIME_ERROR;
×
2605
        }
2606

2607
        coregraphics_reset_frame_storage(state->decode_frame);
2,057✔
2608
        status = coregraphics_try_handle_indexed_frame(
2,057✔
2609
            state->chunk,
2,057✔
2610
            state->decode_frame,
2,057✔
2611
            state->image,
2,057✔
2612
            state->frame_props,
2,057✔
2613
            &state->png_trns_chunk_cache,
2,057✔
2614
            state->fuse_palette,
2,057✔
2615
            state->reqcolors,
2,057✔
2616
            state->bgcolor,
2,057✔
2617
            &state->indexed_handled,
2,057✔
2618
            &state->force_alpha_from_indexed);
2,057✔
2619
        if (SIXEL_FAILED(status)) {
2,057!
2620
            return status;
×
2621
        }
2622
        if (state->indexed_handled == 0) {
2,057✔
2623
            status = coregraphics_check_cancel(state->context);
1,859✔
2624
            if (status != SIXEL_OK) {
1,859!
2625
                return status;
×
2626
            }
2627
            state->has_alpha_like = state->force_alpha_from_indexed != 0 ||
1,859!
2628
                slot->has_alpha != 0;
1,859✔
2629
            state->promote_float32 = slot->promote_float32;
1,859✔
2630
            if (state->promote_float32 != 0) {
1,859✔
2631
                status = coregraphics_decode_float32_frame(
33✔
2632
                    state->decode_frame,
33✔
2633
                    state->image,
33✔
2634
                    state->bgcolor,
33✔
2635
                    state->has_alpha_like);
33✔
2636
            } else {
33✔
2637
                status = coregraphics_decode_rgba8_frame(
1,826✔
2638
                    state->decode_frame,
1,826✔
2639
                    state->image,
1,826✔
2640
                    state->bgcolor,
1,826✔
2641
                    state->has_alpha_like,
1,826✔
2642
                    &state->rgba8_lut_cache);
1,826✔
2643
            }
2644
            if (SIXEL_FAILED(status)) {
1,859!
2645
                return status;
×
2646
            }
2647
        }
1,859✔
2648
        if (state->enable_orientation != 0 &&
2,093!
2649
            state->frame_orientation >= 2 &&
2,013✔
2650
            state->frame_orientation <= 8) {
44✔
2651
            status = loader_frame_apply_orientation(
44✔
2652
                state->decode_frame,
44✔
2653
                state->frame_orientation);
44✔
2654
            if (SIXEL_FAILED(status)) {
44!
2655
                return status;
×
2656
            }
2657
        }
44✔
2658

2659
        if (cache_slot != NULL) {
2,057✔
2660
            if (state->frame_cache_decision_pending != 0) {
858✔
2661
                state->frame_cache_frame_bytes = 0u;
825✔
2662
                if (!coregraphics_measure_frame_cache_bytes(
825✔
2663
                        state->decode_frame,
825✔
2664
                        &state->frame_cache_frame_bytes)) {
825✔
2665
                    sixel_helper_set_additional_message(
×
2666
                        "load_with_coregraphics: failed to estimate "
2667
                        "decoded frame size.");
2668
                    return SIXEL_BAD_INTEGER_OVERFLOW;
×
2669
                }
2670
                if (state->frame_cache_frame_bytes <=
1,650✔
2671
                    state->frame_cache_max_bytes &&
1,650✔
2672
                    state->frame_cache_used_bytes <=
1,606✔
2673
                    state->frame_cache_max_bytes -
1,606✔
2674
                    state->frame_cache_frame_bytes) {
803✔
2675
                    state->frame_cache_keep = 1;
792✔
2676
                }
792✔
2677
                cache_slot->decided = 1u;
825✔
2678
                if (state->frame_cache_keep != 0) {
825✔
2679
                    state->decode_frame->handoff_shareable = 1;
792✔
2680
                    cache_slot->frame = state->decode_frame;
792✔
2681
                    state->frame_cache_used_bytes +=
792✔
2682
                        state->frame_cache_frame_bytes;
792✔
2683
                    state->emit_frame = state->decode_frame;
792✔
2684
                    state->cached_frame_tmp = NULL;
792✔
2685
                } else {
792✔
2686
                    state->decode_frame->handoff_shareable = 0;
33✔
2687
                    state->emit_frame = state->decode_frame;
33✔
2688
                    state->release_emit_frame = 1;
33✔
2689
                }
2690
            } else {
825✔
2691
                state->decode_frame->handoff_shareable = 0;
33✔
2692
                state->emit_frame = state->decode_frame;
33✔
2693
            }
2694
        } else {
858✔
2695
            state->emit_frame = state->decode_frame;
1,199✔
2696
        }
2697
    }
2,057✔
2698

2699
    if (state->frame_props != NULL) {
2,256✔
2700
        CFRelease(state->frame_props);
2,002✔
2701
        state->frame_props = NULL;
2,002✔
2702
    }
2,002✔
2703
    if (state->image != NULL) {
2,246✔
2704
        CGImageRelease(state->image);
2,057✔
2705
        state->image = NULL;
2,057✔
2706
    }
2,057✔
2707
    if (state->emit_frame == NULL && cache_slot != NULL) {
2,620!
2708
        state->emit_frame = cache_slot->frame;
×
2709
    }
×
2710
    return SIXEL_OK;
2,288✔
2711
}
2,299✔
2712

2713
static SIXELSTATUS
2714
coregraphics_emit_frame(coregraphics_loader_state_t *state)
2,288✔
2715
{
2716
    SIXELSTATUS status;
1,456✔
2717
    coregraphics_frame_meta_slot_t *slot;
1,456✔
2718

2719
    status = SIXEL_OK;
2,288✔
2720
    slot = NULL;
2,288✔
2721
    if (state == NULL || state->active_meta_slots == NULL) {
2,288!
2722
        return SIXEL_BAD_ARGUMENT;
×
2723
    }
2724
    if (state->emit_frame == NULL) {
2,288✔
2725
        sixel_helper_set_additional_message(
×
2726
            "load_with_coregraphics: failed to select output frame.");
2727
        return SIXEL_FALSE;
×
2728
    }
2729

2730
    slot = &state->active_meta_slots[state->frame_meta_slot];
2,288✔
2731
    state->emit_frame->frame_no = state->frames_in_loop;
2,288✔
2732
    state->emit_frame->loop_count = state->loop_no;
2,288✔
2733
    state->emit_frame->delay = slot->delay;
2,288✔
2734
    state->emit_frame->multiframe = (state->fstatic == 0 &&
4,172✔
2735
                                     state->frame_count > 1u &&
3,605✔
2736
                                     state->is_animation_container != 0);
1,155✔
2737
    status = state->fn_load(state->emit_frame, state->context);
2,288✔
2738
    if (status != SIXEL_OK) {
2,288✔
2739
        return status;
363✔
2740
    }
2741

2742
    return coregraphics_check_cancel(state->context);
1,925✔
2743
}
2,288✔
2744

2745
static void
2746
coregraphics_cleanup_state(coregraphics_loader_state_t *state)
1,716✔
2747
{
2748
    if (state == NULL) {
1,716!
2749
        return;
×
2750
    }
2751
    if (state->cached_frame_tmp != NULL) {
1,716!
2752
        sixel_frame_unref(state->cached_frame_tmp);
×
2753
        state->cached_frame_tmp = NULL;
×
2754
    }
×
2755
    if (state->frame_props != NULL) {
1,406✔
2756
        CFRelease(state->frame_props);
11✔
2757
        state->frame_props = NULL;
11✔
2758
    }
11✔
2759
    if (state->image != NULL) {
1,408!
2760
        CGImageRelease(state->image);
×
2761
        state->image = NULL;
×
2762
    }
×
2763
    if (state->source != NULL) {
1,712✔
2764
        CFRelease(state->source);
1,694✔
2765
        state->source = NULL;
1,694✔
2766
    }
1,694✔
2767
    if (state->props != NULL) {
1,740✔
2768
        CFRelease(state->props);
1,540✔
2769
        state->props = NULL;
1,540✔
2770
    }
1,540✔
2771
    if (state->data != NULL) {
1,712✔
2772
        CFRelease(state->data);
1,694✔
2773
        state->data = NULL;
1,694✔
2774
    }
1,694✔
2775
    if (state->frame != NULL) {
1,714✔
2776
        if (state->frame_cache_slots != NULL) {
1,705✔
2777
            for (state->prefetch_index = 0u;
1,320✔
2778
                 state->prefetch_index < (size_t)state->total_frames;
1,320✔
2779
                 ++state->prefetch_index) {
979✔
2780
                if (state->frame_cache_slots[state->prefetch_index].frame !=
979✔
2781
                    NULL) {
2782
                    sixel_frame_unref(
792✔
2783
                        state->frame_cache_slots[state->prefetch_index].frame);
792✔
2784
                    state->frame_cache_slots[state->prefetch_index].frame =
792✔
2785
                        NULL;
2786
                }
792✔
2787
            }
979✔
2788
        }
341✔
2789
        sixel_allocator_free(state->frame->allocator, state->frame_cache_slots);
1,705✔
2790
        sixel_allocator_free(state->frame->allocator, state->frame_meta_slots);
1,705✔
2791
        sixel_frame_unref(state->frame);
1,705✔
2792
        state->frame = NULL;
1,705✔
2793
    }
1,705✔
2794
}
1,716✔
2795

2796
static SIXELSTATUS
2797
load_with_coregraphics(
1,716✔
2798
    sixel_chunk_t const       /* in */     *pchunk,
2799
    int                       /* in */     fstatic,
2800
    int                       /* in */     fuse_palette,
2801
    int                       /* in */     reqcolors,
2802
    int                       /* in */     enable_orientation,
2803
    unsigned char             /* in */     *bgcolor,
2804
    int                       /* in */     loop_control,
2805
    int                       /* in */     start_frame_no_set,
2806
    int                       /* in */     start_frame_no_override,
2807
    sixel_load_image_function /* in */     fn_load,
2808
    void                      /* in/out */ *context)
2809
{
2810
    SIXELSTATUS status;
1,092✔
2811
    coregraphics_loader_state_t state;
1,092✔
2812

2813
    status = SIXEL_FALSE;
1,716✔
2814
    coregraphics_loader_state_init(&state,
1,716✔
2815
                                   pchunk,
1,716✔
2816
                                   fstatic,
1,716✔
2817
                                   fuse_palette,
1,716✔
2818
                                   reqcolors,
1,716✔
2819
                                   enable_orientation,
1,716✔
2820
                                   bgcolor,
1,716✔
2821
                                   loop_control,
1,716✔
2822
                                   start_frame_no_set,
1,716✔
2823
                                   start_frame_no_override,
1,716✔
2824
                                   fn_load,
1,716✔
2825
                                   context);
1,716✔
2826

2827
    status = coregraphics_prepare_source_and_root_metadata(&state);
1,716✔
2828
    if (SIXEL_FAILED(status)) {
1,716✔
2829
        goto end;
198✔
2830
    }
2831
    status = coregraphics_prepare_metadata_slots(&state);
1,518✔
2832
    if (SIXEL_FAILED(status)) {
1,518!
2833
        goto end;
×
2834
    }
2835

2836
    state.frame->multiframe = (state.fstatic == 0 &&
2,630✔
2837
                               state.frame_count > 1u &&
2,056✔
2838
                               state.is_animation_container != 0);
374✔
2839

2840
    for (;;) {
1,672✔
2841
        status = coregraphics_check_cancel(state.context);
1,672✔
2842
        if (status != SIXEL_OK) {
1,672✔
2843
            goto end;
×
2844
        }
2845

2846
        state.frame_index = 0;
1,672✔
2847
        if (state.loop_no == 0 && state.resolved_start_frame_no != INT_MIN) {
1,672✔
2848
            /*
2849
             * Apply start-frame override only on the first loop. Later loops
2850
             * always restart from frame 0 to preserve normal replay behavior.
2851
             */
2852
            state.frame_index = state.resolved_start_frame_no;
154✔
2853
        }
154✔
2854
        state.frames_in_loop = 0;
1,672✔
2855

2856
        while (state.frame_index < state.total_frames) {
2,772✔
2857
            status = coregraphics_check_cancel(state.context);
2,299✔
2858
            if (status != SIXEL_OK) {
2,299✔
2859
                goto end;
×
2860
            }
2861
            if (state.fstatic == 0 && state.is_animation_container != 0) {
2,299✔
2862
                state.frame_meta_slot = (size_t)state.frame_index;
1,133✔
2863
            } else {
1,133✔
2864
                state.frame_meta_slot = 0u;
1,166✔
2865
            }
2866

2867
            status = coregraphics_process_single_frame(&state);
2,299✔
2868
            if (status != SIXEL_OK) {
2,299✔
2869
                goto end;
11✔
2870
            }
2871
            status = coregraphics_emit_frame(&state);
2,288✔
2872
            if (status != SIXEL_OK) {
2,288✔
2873
                goto end;
363✔
2874
            }
2875

2876
            ++state.frame_index;
1,925✔
2877
            ++state.frames_in_loop;
1,925✔
2878
            if (state.release_emit_frame != 0) {
1,925✔
2879
                sixel_frame_unref(state.emit_frame);
33✔
2880
                state.emit_frame = NULL;
33✔
2881
                state.cached_frame_tmp = NULL;
33✔
2882
            }
33✔
2883
            if (state.fstatic != 0 || state.is_animation_container == 0) {
1,883✔
2884
                status = SIXEL_OK;
825✔
2885
                goto end;
825✔
2886
            }
2887
        }
2888

2889
        ++state.loop_no;
473✔
2890
        state.stop_loop = 0;
473✔
2891
        if (state.total_frames <= 1 ||
473!
2892
            state.loop_control == SIXEL_LOOP_DISABLE) {
473✔
2893
            state.stop_loop = 1;
209✔
2894
        } else if (state.loop_control == SIXEL_LOOP_AUTO) {
473✔
2895
            if (state.anim_loop_count < 0) {
220✔
2896
                state.stop_loop = 1;
×
2897
            } else if (state.anim_loop_count > 0 &&
220!
2898
                       state.loop_no >= state.anim_loop_count) {
220✔
2899
                state.stop_loop = 1;
110✔
2900
            }
110✔
2901
        }
220✔
2902
        if (state.stop_loop != 0) {
473✔
2903
            break;
319✔
2904
        }
2905
    }
2906

2907
    status = SIXEL_OK;
319✔
2908

2909
end:
2910
    coregraphics_cleanup_state(&state);
1,716✔
2911
    return status;
2,808✔
2912
}
1,092✔
2913

2914

2915
static void
2916
sixel_loader_coregraphics_ref(sixel_loader_component_t *component)
30,195✔
2917
{
2918
    sixel_loader_coregraphics_component_t *self;
19,347✔
2919

2920
    self = NULL;
30,195✔
2921
    if (component == NULL) {
30,195✔
2922
        return;
×
2923
    }
2924

2925
    self = (sixel_loader_coregraphics_component_t *)component;
30,195✔
2926
    ++self->ref;
30,195✔
2927
}
30,195!
2928

2929
static void
2930
sixel_loader_coregraphics_unref(sixel_loader_component_t *component)
60,775✔
2931
{
2932
    sixel_loader_coregraphics_component_t *self;
38,939✔
2933
    sixel_allocator_t *allocator;
38,939✔
2934

2935
    self = NULL;
60,775✔
2936
    allocator = NULL;
60,775✔
2937
    if (component == NULL) {
60,775✔
2938
        return;
×
2939
    }
2940

2941
    self = (sixel_loader_coregraphics_component_t *)component;
60,775✔
2942
    if (self->ref == 0u) {
60,775✔
2943
        return;
×
2944
    }
2945

2946
    --self->ref;
60,775✔
2947
    if (self->ref > 0u) {
60,775✔
2948
        return;
30,195✔
2949
    }
2950

2951
    allocator = self->allocator;
30,580✔
2952
    sixel_allocator_free(allocator, self);
30,580✔
2953
    sixel_allocator_unref(allocator);
30,580✔
2954
}
60,775!
2955

2956
static SIXELSTATUS
2957
sixel_loader_coregraphics_setopt(sixel_loader_component_t *component,
22,858✔
2958
                                 int option,
2959
                                 void const *value)
2960
{
2961
    sixel_loader_coregraphics_component_t *self;
14,546✔
2962
    int const *flag;
14,546✔
2963
    unsigned char const *color;
14,546✔
2964

2965
    self = NULL;
22,858✔
2966
    flag = NULL;
22,858✔
2967
    color = NULL;
22,858✔
2968
    if (component == NULL) {
22,858✔
2969
        return SIXEL_BAD_ARGUMENT;
×
2970
    }
2971

2972
    self = (sixel_loader_coregraphics_component_t *)component;
22,858✔
2973
    switch (option) {
22,858✔
2974
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
2975
        flag = (int const *)value;
1,716✔
2976
        self->fstatic = flag != NULL ? *flag : 0;
1,716✔
2977
        return SIXEL_OK;
1,716✔
2978
    case SIXEL_LOADER_OPTION_USE_PALETTE:
2979
        flag = (int const *)value;
1,716✔
2980
        self->fuse_palette = flag != NULL ? *flag : 0;
1,716✔
2981
        return SIXEL_OK;
1,716✔
2982
    case SIXEL_LOADER_OPTION_REQCOLORS:
2983
        flag = (int const *)value;
1,716✔
2984
        if (flag != NULL) {
1,716!
2985
            self->reqcolors = *flag;
1,716✔
2986
        }
1,716✔
2987
        return SIXEL_OK;
1,716✔
2988
    case SIXEL_LOADER_OPTION_BGCOLOR:
2989
        if (value == NULL) {
1,419✔
2990
            self->has_bgcolor = 0;
1,386✔
2991
            return SIXEL_OK;
1,386✔
2992
        }
2993
        color = (unsigned char const *)value;
33✔
2994
        self->bgcolor[0] = color[0];
33✔
2995
        self->bgcolor[1] = color[1];
33✔
2996
        self->bgcolor[2] = color[2];
33✔
2997
        self->has_bgcolor = 1;
33✔
2998
        return SIXEL_OK;
33✔
2999
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
3000
        flag = (int const *)value;
1,595✔
3001
        if (flag != NULL) {
1,595!
3002
            self->loop_control = *flag;
1,595✔
3003
        }
1,595✔
3004
        return SIXEL_OK;
1,595✔
3005
    case SIXEL_LOADER_OPTION_START_FRAME_NO:
3006
        if (value == NULL) {
1,386✔
3007
            self->has_start_frame_no = 0;
1,199✔
3008
            self->start_frame_no = INT_MIN;
1,199✔
3009
            return SIXEL_OK;
1,199✔
3010
        }
3011
        flag = (int const *)value;
187✔
3012
        self->start_frame_no = *flag;
187✔
3013
        self->has_start_frame_no = 1;
187✔
3014
        return SIXEL_OK;
187✔
3015
    case SIXEL_LOADER_COMPONENT_OPTION_COREGRAPHICS_ENABLE_ORIENTATION:
3016
        flag = (int const *)value;
1,331✔
3017
        self->enable_orientation = (flag == NULL || *flag != 0) ? 1 : 0;
1,331!
3018
        return SIXEL_OK;
1,331✔
3019
    default:
3020
        return SIXEL_OK;
11,979✔
3021
    }
3022
}
22,858✔
3023

3024
static SIXELSTATUS
3025
sixel_loader_coregraphics_load(sixel_loader_component_t *component,
1,716✔
3026
                               sixel_chunk_t const *chunk,
3027
                               sixel_load_image_function fn_load,
3028
                               void *context)
3029
{
3030
    sixel_loader_coregraphics_component_t *self;
1,092✔
3031
    unsigned char *bgcolor;
1,092✔
3032

3033
    self = NULL;
1,716✔
3034
    bgcolor = NULL;
1,716✔
3035
    if (component == NULL || chunk == NULL || fn_load == NULL) {
1,716!
3036
        return SIXEL_BAD_ARGUMENT;
×
3037
    }
3038

3039
    self = (sixel_loader_coregraphics_component_t *)component;
1,716✔
3040
    if (self->has_bgcolor) {
1,716✔
3041
        bgcolor = self->bgcolor;
33✔
3042
    }
33✔
3043

3044
    return load_with_coregraphics(chunk,
3,432✔
3045
                                  self->fstatic,
1,716✔
3046
                                  self->fuse_palette,
1,716✔
3047
                                  self->reqcolors,
1,716✔
3048
                                  self->enable_orientation,
1,716✔
3049
                                  bgcolor,
1,716✔
3050
                                  self->loop_control,
1,716✔
3051
                                  self->has_start_frame_no,
1,716✔
3052
                                  self->start_frame_no,
1,716✔
3053
                                  fn_load,
1,716✔
3054
                                  context);
1,716✔
3055
}
1,716✔
3056

3057
static char const *
3058
sixel_loader_coregraphics_name(sixel_loader_component_t const *component)
2,662✔
3059
{
3060
    (void)component;
2,662✔
3061
    return "coregraphics";
2,662✔
3062
}
3063

3064
static sixel_loader_component_vtbl_t const g_sixel_loader_coregraphics_vtbl = {
3065
    sixel_loader_coregraphics_ref,
3066
    sixel_loader_coregraphics_unref,
3067
    sixel_loader_coregraphics_setopt,
3068
    sixel_loader_coregraphics_load,
3069
    sixel_loader_coregraphics_name
3070
};
3071

3072
SIXELSTATUS
3073
sixel_loader_coregraphics_new(sixel_allocator_t *allocator,
30,580✔
3074
                              sixel_loader_component_t **ppcomponent)
3075
{
3076
    sixel_loader_coregraphics_component_t *self;
19,592✔
3077

3078
    self = NULL;
30,580✔
3079
    if (allocator == NULL || ppcomponent == NULL) {
30,580!
3080
        return SIXEL_BAD_ARGUMENT;
×
3081
    }
3082

3083
    *ppcomponent = NULL;
30,580✔
3084
    self = (sixel_loader_coregraphics_component_t *)
30,580✔
3085
        sixel_allocator_malloc(allocator, sizeof(*self));
30,580✔
3086
    if (self == NULL) {
30,580✔
3087
        return SIXEL_BAD_ALLOCATION;
×
3088
    }
3089

3090
    memset(self, 0, sizeof(*self));
30,580✔
3091
    self->base.vtbl = &g_sixel_loader_coregraphics_vtbl;
30,580✔
3092
    self->allocator = allocator;
30,580✔
3093
    self->ref = 1u;
30,580✔
3094
    self->reqcolors = SIXEL_PALETTE_MAX;
30,580✔
3095
    self->loop_control = SIXEL_LOOP_AUTO;
30,580✔
3096
    self->start_frame_no = INT_MIN;
30,580✔
3097
    self->enable_orientation = 1;
30,580✔
3098
    sixel_allocator_ref(allocator);
30,580✔
3099
    *ppcomponent = &self->base;
30,580✔
3100
    return SIXEL_OK;
30,580✔
3101
}
30,580✔
3102

3103
#endif  /* HAVE_COREGRAPHICS */
3104

3105
#if !HAVE_COREGRAPHICS
3106
/*
3107
 * Anchor a harmless symbol so the translation unit stays non-empty when
3108
 * CoreGraphics is unavailable.
3109
 */
3110
typedef int loader_coregraphics_disabled;
3111
#endif
3112

3113

3114
/* emacs Local Variables:      */
3115
/* emacs mode: c               */
3116
/* emacs tab-width: 4          */
3117
/* emacs indent-tabs-mode: nil */
3118
/* emacs c-basic-offset: 4     */
3119
/* emacs End:                  */
3120
/* vim: set expandtab ts=4 sts=4 sw=4 : */
3121
/* 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