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

saitoha / libsixel / 25410478234

06 May 2026 12:39AM UTC coverage: 85.548% (-0.02%) from 85.564%
25410478234

push

github

saitoha
fix: avoid tty fd unused in no-isatty builds

125661 of 267416 branches covered (46.99%)

151048 of 176566 relevant lines covered (85.55%)

8930818.64 hits per line

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

78.25
/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-view.h"
56
#include "frame-private.h"
57
#include "frame-factory.h"
58
#include "loader-common.h"
59
#include "loader-coregraphics.h"
60
#include "loader.h"
61
#include "timeline-logger.h"
62
#include "compat_stub.h"
63

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

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

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

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

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

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

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

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

213
static unsigned char
214
coregraphics_unpremultiply_channel(unsigned int value, unsigned int alpha)
96✔
215
{
216
    unsigned int unpremultiplied;
60✔
217

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

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

232
static double
233
coregraphics_clamp_unit(double value)
2,306,016✔
234
{
235
    if (value < 0.0) {
2,306,016!
236
        return 0.0;
×
237
    }
238
    if (value > 1.0) {
2,306,016!
239
        return 1.0;
×
240
    }
241
    return value;
2,306,016✔
242
}
2,306,016✔
243

244
static double
245
coregraphics_decode_srgb_unit(double value)
79,944✔
246
{
247
    value = coregraphics_clamp_unit(value);
79,944✔
248
    if (value <= 0.04045) {
79,944✔
249
        return value / 12.92;
3,432✔
250
    }
251
#if HAVE_MATH_H
252
    return pow((value + 0.055) / 1.055, 2.4);
76,512✔
253
#else
254
    return value;
255
#endif
256
}
79,944✔
257

258
static double
259
coregraphics_encode_srgb_unit(double value)
1,278,264✔
260
{
261
    value = coregraphics_clamp_unit(value);
1,278,264✔
262
    if (value <= 0.0031308) {
1,278,264✔
263
        return value * 12.92;
4,056✔
264
    }
265
#if HAVE_MATH_H
266
    return 1.055 * pow(value, 1.0 / 2.4) - 0.055;
1,274,208✔
267
#else
268
    return value;
269
#endif
270
}
1,278,264✔
271

272
static void
273
coregraphics_build_srgb_decode_u8_lut(double lut[256])
312✔
274
{
275
    int index;
195✔
276
    double unit;
195✔
277

278
    index = 0;
312✔
279
    unit = 0.0;
312✔
280
    if (lut == NULL) {
312✔
281
        return;
×
282
    }
283

284
    for (index = 0; index < 256; ++index) {
80,184✔
285
        unit = (double)index / 255.0;
79,872✔
286
        lut[index] = coregraphics_decode_srgb_unit(unit);
79,872✔
287
    }
79,872✔
288
}
312!
289

290
static void
291
coregraphics_build_srgb_encode_u8_lut(unsigned char *lut, size_t lut_size)
312✔
292
{
293
    size_t index;
195✔
294
    double unit;
195✔
295

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

302
    for (index = 0u; index < lut_size; ++index) {
1,278,576✔
303
        if (lut_size > 1u) {
1,278,264!
304
            unit = (double)index / (double)(lut_size - 1u);
1,278,264✔
305
        } else {
1,278,264✔
306
            unit = 0.0;
×
307
        }
308
        lut[index] = (unsigned char)(coregraphics_encode_srgb_unit(unit) *
2,556,528✔
309
                                     255.0 + 0.5);
1,278,264✔
310
    }
1,278,264✔
311
}
312!
312

313
static unsigned char
314
coregraphics_encode_linear_to_srgb_u8(double value,
947,808✔
315
                                      unsigned char const *lut,
316
                                      size_t lut_size)
317
{
318
    size_t index;
592,380✔
319

320
    index = 0u;
947,808✔
321
    if (lut == NULL || lut_size == 0u) {
947,808!
322
        return 0u;
×
323
    }
324

325
    value = coregraphics_clamp_unit(value);
947,808✔
326
    if (lut_size <= 1u) {
947,808!
327
        return lut[0];
×
328
    }
329

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

337
static int
338
coregraphics_property_to_bool(CFTypeRef value, int *out_value)
2,952✔
339
{
340
    CFTypeID type_id;
1,846✔
341
    int numeric_value;
1,846✔
342
    Boolean ok;
1,846✔
343

344
    type_id = 0;
2,952✔
345
    numeric_value = 0;
2,952✔
346
    ok = false;
2,952✔
347
    if (value == NULL || out_value == NULL) {
2,952!
348
        return 0;
2,544✔
349
    }
350

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

366
    return 0;
×
367
}
2,952✔
368

369
static int
370
coregraphics_dictionary_get_bool(CFDictionaryRef dict,
2,952✔
371
                                 CFStringRef key,
372
                                 int default_value)
373
{
374
    CFTypeRef value;
1,846✔
375
    int parsed;
1,846✔
376
    int result;
1,846✔
377

378
    value = NULL;
2,952✔
379
    parsed = 0;
2,952✔
380
    result = default_value;
2,952✔
381
    if (dict == NULL || key == NULL) {
2,952!
382
        return default_value;
×
383
    }
384

385
    value = CFDictionaryGetValue(dict, key);
2,952✔
386
    parsed = coregraphics_property_to_bool(value, &result);
2,952✔
387
    if (!parsed) {
2,952✔
388
        result = default_value;
2,544✔
389
    }
2,544✔
390
    return result;
2,952✔
391
}
2,952✔
392

393
static int
394
coregraphics_dictionary_get_int(CFDictionaryRef dict,
5,220✔
395
                                CFStringRef key,
396
                                int default_value)
397
{
398
    CFTypeRef value;
3,265✔
399
    int result;
3,265✔
400
    Boolean ok;
3,265✔
401

402
    value = NULL;
5,220✔
403
    result = default_value;
5,220✔
404
    ok = false;
5,220✔
405
    if (dict == NULL || key == NULL) {
5,220!
406
        return default_value;
×
407
    }
408

409
    value = CFDictionaryGetValue(dict, key);
5,220✔
410
    if (value == NULL || CFGetTypeID(value) != CFNumberGetTypeID()) {
5,220!
411
        return default_value;
3,196✔
412
    }
413

414
    ok = CFNumberGetValue((CFNumberRef)value, kCFNumberIntType, &result);
2,024✔
415
    if (!ok) {
2,024!
416
        result = default_value;
×
417
    }
×
418
    return result;
2,024✔
419
}
5,220✔
420

421
static int
422
coregraphics_dictionary_get_double(CFDictionaryRef dict,
732✔
423
                                   CFStringRef key,
424
                                   double *out_value)
425
{
426
    CFTypeRef value;
458✔
427
    Boolean ok;
458✔
428
    double parsed;
458✔
429

430
    value = NULL;
732✔
431
    ok = false;
732✔
432
    parsed = 0.0;
732✔
433
    if (dict == NULL || key == NULL || out_value == NULL) {
732!
434
        return 0;
×
435
    }
436

437
    value = CFDictionaryGetValue(dict, key);
732✔
438
    if (value == NULL || CFGetTypeID(value) != CFNumberGetTypeID()) {
732!
439
        return 0;
×
440
    }
441

442
    ok = CFNumberGetValue((CFNumberRef)value, kCFNumberDoubleType, &parsed);
732✔
443
    if (!ok) {
732✔
444
        return 0;
×
445
    }
446

447
    *out_value = parsed;
732✔
448
    return 1;
732✔
449
}
732✔
450

451
static int
452
coregraphics_sanitize_exif_orientation(int orientation)
5,248✔
453
{
454
    if (orientation < 1 || orientation > 8) {
5,248!
455
        return 1;
×
456
    }
457
    return orientation;
5,248✔
458
}
5,248✔
459

460
static int
461
coregraphics_resolve_exif_orientation(CFDictionaryRef props,
2,624✔
462
                                      int fallback_orientation)
463
{
464
    int resolved_orientation;
1,641✔
465

466
    resolved_orientation = coregraphics_sanitize_exif_orientation(
2,624✔
467
        fallback_orientation);
2,624✔
468
    if (props == NULL) {
2,624✔
469
        return resolved_orientation;
×
470
    }
471

472
    resolved_orientation = coregraphics_dictionary_get_int(
2,624✔
473
        props,
2,624✔
474
        kCGImagePropertyOrientation,
475
        resolved_orientation);
2,624✔
476
    return coregraphics_sanitize_exif_orientation(resolved_orientation);
2,624✔
477
}
2,624✔
478

479
static int
480
coregraphics_get_animation_keys(CFDictionaryRef props,
2,624✔
481
                                size_t frame_count,
482
                                CFDictionaryRef *out_dict,
483
                                CFStringRef *out_loop_key,
484
                                CFStringRef *out_delay_key,
485
                                CFStringRef *out_unclamped_delay_key)
486
{
487
    CFDictionaryRef dict;
1,641✔
488

489
    dict = NULL;
2,624✔
490
    if (out_dict == NULL) {
2,624✔
491
        return 0;
×
492
    }
493

494
    *out_dict = NULL;
2,624✔
495
    if (out_loop_key != NULL) {
2,624!
496
        *out_loop_key = NULL;
2,624✔
497
    }
2,624✔
498
    if (out_delay_key != NULL) {
2,766✔
499
        *out_delay_key = NULL;
1,484✔
500
    }
1,484✔
501
    if (out_unclamped_delay_key != NULL) {
2,482✔
502
        *out_unclamped_delay_key = NULL;
1,484✔
503
    }
1,484✔
504
    if (props == NULL) {
2,624✔
505
        return 0;
×
506
    }
507

508
    dict = (CFDictionaryRef)CFDictionaryGetValue(props,
5,248✔
509
                                                 kCGImagePropertyGIFDictionary);
2,624✔
510
    if (dict != NULL && CFGetTypeID(dict) == CFDictionaryGetTypeID()) {
2,624!
511
        *out_dict = dict;
512✔
512
        if (out_loop_key != NULL) {
512!
513
            *out_loop_key = kCGImagePropertyGIFLoopCount;
512✔
514
        }
512✔
515
        if (out_delay_key != NULL) {
531✔
516
            *out_delay_key = kCGImagePropertyGIFDelayTime;
356✔
517
        }
356✔
518
        if (out_unclamped_delay_key != NULL) {
493✔
519
            *out_unclamped_delay_key = kCGImagePropertyGIFUnclampedDelayTime;
356✔
520
        }
356✔
521
        return 1;
512✔
522
    }
523

524
    dict = (CFDictionaryRef)CFDictionaryGetValue(props,
3,912✔
525
                                                 kCGImagePropertyPNGDictionary);
1,956✔
526
    if (dict != NULL &&
2,820✔
527
        CFGetTypeID(dict) == CFDictionaryGetTypeID() &&
864!
528
        frame_count > 1u) {
864✔
529
        *out_dict = dict;
432✔
530
        if (out_loop_key != NULL) {
432!
531
            *out_loop_key = kCGImagePropertyAPNGLoopCount;
432✔
532
        }
432✔
533
        if (out_delay_key != NULL) {
453✔
534
            *out_delay_key = kCGImagePropertyAPNGDelayTime;
264✔
535
        }
264✔
536
        if (out_unclamped_delay_key != NULL) {
411✔
537
            *out_unclamped_delay_key = kCGImagePropertyAPNGUnclampedDelayTime;
264✔
538
        }
264✔
539
        return 1;
432✔
540
    }
541

542
    dict = (CFDictionaryRef)CFDictionaryGetValue(
1,492✔
543
        props,
1,492✔
544
        kCGImagePropertyWebPDictionary);
545
    if (dict != NULL &&
1,668!
546
        CFGetTypeID(dict) == CFDictionaryGetTypeID() &&
176!
547
        frame_count > 1u) {
176✔
548
        *out_dict = dict;
176✔
549
        if (out_loop_key != NULL) {
176!
550
            *out_loop_key = kCGImagePropertyWebPLoopCount;
176✔
551
        }
176✔
552
        if (out_delay_key != NULL) {
184✔
553
            *out_delay_key = kCGImagePropertyWebPDelayTime;
112✔
554
        }
112✔
555
        if (out_unclamped_delay_key != NULL) {
168✔
556
            *out_unclamped_delay_key = kCGImagePropertyWebPUnclampedDelayTime;
112✔
557
        }
112✔
558
        return 1;
176✔
559
    }
560

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

580
    return 0;
1,504✔
581
}
2,624✔
582

583
static int
584
coregraphics_resolve_animation_delay_cs(CFDictionaryRef dict,
732✔
585
                                        CFStringRef unclamped_delay_key,
586
                                        CFStringRef delay_key,
587
                                        int *delay_cs)
588
{
589
    double delay_value;
458✔
590
    double scaled_delay;
458✔
591
    double max_delay_seconds;
458✔
592

593
    delay_value = 0.0;
732✔
594
    scaled_delay = 0.0;
732✔
595
    max_delay_seconds = (double)INT_MAX / 100.0;
732✔
596
    if (dict == NULL || delay_cs == NULL) {
732!
597
        return 0;
×
598
    }
599

600
    if (!coregraphics_dictionary_get_double(dict,
1,464!
601
                                            unclamped_delay_key,
732✔
602
                                            &delay_value) &&
732!
603
        !coregraphics_dictionary_get_double(dict,
×
604
                                            delay_key,
×
605
                                            &delay_value)) {
606
        return 0;
×
607
    }
608

609
    if (!(delay_value >= 0.0)) {
641!
610
        delay_value = 0.0;
×
611
    }
×
612
    if (delay_value > max_delay_seconds) {
732!
613
        *delay_cs = INT_MAX;
×
614
        return 1;
×
615
    }
616

617
    scaled_delay = delay_value * 100.0;
732✔
618
    if (scaled_delay >= (double)INT_MAX) {
732!
619
        *delay_cs = INT_MAX;
×
620
        return 1;
×
621
    }
622

623
    *delay_cs = (int)scaled_delay;
732✔
624
    if (*delay_cs == 0 && delay_value > 0.0) {
732✔
625
        *delay_cs = 1;
224✔
626
    }
224✔
627
    return 1;
732✔
628
}
732✔
629

630
static int
631
coregraphics_image_is_indexed(CGImageRef image)
1,476✔
632
{
633
    CGColorSpaceRef color_space;
923✔
634

635
    color_space = NULL;
1,476✔
636
    if (image == NULL) {
1,476✔
637
        return 0;
×
638
    }
639

640
    color_space = CGImageGetColorSpace(image);
1,476✔
641
    if (color_space == NULL) {
1,476✔
642
        return 0;
×
643
    }
644
    return CGColorSpaceGetModel(color_space) == kCGColorSpaceModelIndexed;
1,476✔
645
}
1,476✔
646

647
static int
648
coregraphics_image_has_alpha(CGImageRef image, CFDictionaryRef frame_props)
1,476✔
649
{
650
    CGImageAlphaInfo alpha_info;
923✔
651
    int metadata_has_alpha;
923✔
652

653
    alpha_info = kCGImageAlphaNone;
1,476✔
654
    metadata_has_alpha = 0;
1,476✔
655
    if (image == NULL) {
1,476✔
656
        return 0;
×
657
    }
658

659
    metadata_has_alpha = coregraphics_dictionary_get_bool(
1,476✔
660
        frame_props,
1,476✔
661
        kCGImagePropertyHasAlpha,
1,476✔
662
        0);
663
    alpha_info = CGImageGetAlphaInfo(image);
1,476✔
664
    switch (alpha_info) {
1,476✔
665
    case kCGImageAlphaNone:
666
    case kCGImageAlphaNoneSkipLast:
667
    case kCGImageAlphaNoneSkipFirst:
668
        return metadata_has_alpha;
1,068✔
669
    default:
670
        return 1;
408✔
671
    }
672
}
1,476✔
673

674
static int
675
coregraphics_should_promote_float32(CGImageRef image, CFDictionaryRef props)
1,476✔
676
{
677
    int depth_bits;
923✔
678
    int is_float;
923✔
679
    size_t image_depth_bits;
923✔
680

681
    depth_bits = 0;
1,476✔
682
    is_float = 0;
1,476✔
683
    image_depth_bits = 0u;
1,476✔
684
    if (image == NULL) {
1,476✔
685
        return 0;
×
686
    }
687

688
    image_depth_bits = CGImageGetBitsPerComponent(image);
1,476✔
689
    depth_bits = (int)image_depth_bits;
1,476✔
690
    depth_bits = coregraphics_dictionary_get_int(props,
2,952✔
691
                                                 kCGImagePropertyDepth,
1,476✔
692
                                                 depth_bits);
1,476✔
693
    is_float = coregraphics_dictionary_get_bool(props,
2,952✔
694
                                                kCGImagePropertyIsFloat,
1,476✔
695
                                                0);
696
    if (is_float != 0 || depth_bits > 8) {
1,476!
697
        return 1;
24✔
698
    }
699

700
    return 0;
1,452✔
701
}
1,476✔
702

703
static void
704
coregraphics_reset_frame_storage(sixel_frame_t *frame)
1,516✔
705
{
706
    if (frame == NULL || frame->allocator == NULL) {
1,516!
707
        return;
×
708
    }
709

710
    if (frame->pixels.u8ptr != NULL) {
1,516✔
711
        sixel_allocator_free(frame->allocator, frame->pixels.u8ptr);
32✔
712
        frame->pixels.u8ptr = NULL;
32✔
713
    }
32✔
714
    if (frame->palette != NULL) {
1,335!
715
        sixel_allocator_free(frame->allocator, frame->palette);
×
716
        frame->palette = NULL;
×
717
    }
×
718
    if (frame->transparent_mask != NULL) {
1,331✔
719
        sixel_allocator_free(frame->allocator, frame->transparent_mask);
32✔
720
        frame->transparent_mask = NULL;
32✔
721
    }
32✔
722
    frame->transparent_mask_size = 0u;
1,516✔
723
    frame->ncolors = -1;
1,516✔
724
    frame->transparent = -1;
1,516✔
725
    frame->alpha_zero_is_transparent = 0;
1,516✔
726
}
1,516✔
727

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

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

757
    memset(zero_alpha_map, 0, (size_t)ncolors);
176✔
758
    *zero_alpha_count = 0;
176✔
759
    *has_partial_alpha = 0;
176✔
760

761
    if (frame_props == NULL) {
176✔
762
        return;
×
763
    }
764

765
    png_dict = (CFDictionaryRef)CFDictionaryGetValue(
176✔
766
        frame_props,
176✔
767
        kCGImagePropertyPNGDictionary);
176✔
768
    if (png_dict == NULL || CFGetTypeID(png_dict) != CFDictionaryGetTypeID()) {
176!
769
        return;
80✔
770
    }
771

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

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

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

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

802
static unsigned int
803
coregraphics_read_u32be(unsigned char const *bytes)
648✔
804
{
805
    unsigned int value;
405✔
806

807
    value = 0u;
648✔
808
    if (bytes == NULL) {
648✔
809
        return 0u;
×
810
    }
811

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

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

827
    cache->initialized = 0;
1,236✔
828
    cache->available = 0;
1,236✔
829
    cache->alpha_count = 0;
1,236✔
830
    memset(cache->alpha_entries, 0, sizeof(cache->alpha_entries));
1,236✔
831
}
1,236✔
832

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

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

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

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

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

897
    offset = 0u;
176✔
898
    chunk_size = 0u;
176✔
899
    chunk_length = 0u;
176✔
900
    bytes = NULL;
176✔
901
    alpha_count = 0;
176✔
902
    use_cache = 0;
176✔
903
    if (chunk == NULL ||
352!
904
        sixel_chunk_get_buffer(chunk) == NULL ||
176!
905
        sixel_chunk_get_size(chunk) < sizeof(png_signature) ||
176!
906
        ncolors <= 0 ||
176!
907
        zero_alpha_map == NULL ||
176!
908
        zero_alpha_count == NULL ||
176!
909
        has_partial_alpha == NULL) {
176✔
910
        return;
×
911
    }
912

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

934
    if (memcmp(sixel_chunk_get_buffer(chunk), png_signature, sizeof(png_signature)) != 0) {
176✔
935
        return;
80✔
936
    }
937

938
    offset = sizeof(png_signature);
96✔
939
    while (offset + 8u <= sixel_chunk_get_size(chunk)) {
744✔
940
        chunk_length = (size_t)coregraphics_read_u32be(sixel_chunk_get_buffer(chunk) + offset);
648✔
941
        offset += 4u;
648✔
942
        if (offset + 4u > sixel_chunk_get_size(chunk)) {
648!
943
            break;
×
944
        }
945

946
        bytes = sixel_chunk_get_buffer(chunk) + offset;
648✔
947
        if (offset + 4u + chunk_length + 4u > sixel_chunk_get_size(chunk)) {
648!
948
            break;
×
949
        }
950

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

985
        offset += 4u + chunk_length + 4u;
648✔
986
    }
987
}
176!
988

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

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

1021
    *ppalette = NULL;
176✔
1022
    *pncolors = 0;
176✔
1023

1024
    color_space = CGImageGetColorSpace(image);
176✔
1025
    if (color_space == NULL ||
176!
1026
        CGColorSpaceGetModel(color_space) != kCGColorSpaceModelIndexed) {
176✔
1027
        return SIXEL_FALSE;
×
1028
    }
1029

1030
    color_count = CGColorSpaceGetColorTableCount(color_space);
176✔
1031
    if (color_count == 0u || color_count > SIXEL_PALETTE_MAX) {
176!
1032
        return SIXEL_FALSE;
×
1033
    }
1034

1035
    base_space = CGColorSpaceGetBaseColorSpace(color_space);
176✔
1036
    if (base_space == NULL) {
176✔
1037
        return SIXEL_FALSE;
×
1038
    }
1039

1040
    component_count = CGColorSpaceGetNumberOfComponents(base_space);
176✔
1041
    if (component_count == 0u || component_count > 4u) {
176!
1042
        return SIXEL_FALSE;
×
1043
    }
1044

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

1056
    CGColorSpaceGetColorTable(color_space, table);
176✔
1057

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

1071
    for (index = 0u; index < color_count; ++index) {
30,816✔
1072
        if (component_count >= 3u) {
30,640!
1073
            palette[index * 3u + 0u] = table[index * component_count + 0u];
30,640✔
1074
            palette[index * 3u + 1u] = table[index * component_count + 1u];
30,640✔
1075
            palette[index * 3u + 2u] = table[index * component_count + 2u];
30,640✔
1076
        } else {
30,640✔
1077
            palette[index * 3u + 0u] = table[index * component_count + 0u];
×
1078
            palette[index * 3u + 1u] = table[index * component_count + 0u];
×
1079
            palette[index * 3u + 2u] = table[index * component_count + 0u];
×
1080
        }
1081
    }
30,640✔
1082

1083
    *ppalette = palette;
176✔
1084
    *pncolors = (int)color_count;
176✔
1085
    palette = NULL;
176✔
1086
    status = SIXEL_OK;
176✔
1087

1088
cleanup:
1089
    sixel_allocator_free(allocator, palette);
176✔
1090
    sixel_allocator_free(allocator, table);
176✔
1091
    return status;
176✔
1092
}
176✔
1093

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

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

1142
    *ppixels = NULL;
168✔
1143
    bits_per_pixel = CGImageGetBitsPerPixel(image);
168✔
1144
    bytes_per_row = CGImageGetBytesPerRow(image);
168✔
1145
    if (bits_per_pixel != 1u &&
336!
1146
        bits_per_pixel != 2u &&
168!
1147
        bits_per_pixel != 4u &&
168!
1148
        bits_per_pixel != 8u) {
168✔
1149
        return SIXEL_FALSE;
8✔
1150
    }
1151

1152
    packed_row_bytes = ((size_t)width * bits_per_pixel + 7u) / 8u;
160✔
1153
    if (bytes_per_row < packed_row_bytes) {
160!
1154
        return SIXEL_FALSE;
×
1155
    }
1156
    if ((size_t)height > SIZE_MAX / bytes_per_row) {
160!
1157
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1158
    }
1159
    needed_bytes = (size_t)height * bytes_per_row;
160✔
1160
    if (needed_bytes > (size_t)SIXEL_ALLOCATE_BYTES_MAX) {
160✔
1161
        sixel_helper_set_additional_message(
16✔
1162
            "load_with_coregraphics: indexed source row payload is too "
1163
            "large.");
1164
        return SIXEL_BAD_INPUT;
16✔
1165
    }
1166

1167
    if ((size_t)width > SIZE_MAX / (size_t)height) {
144!
1168
        status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1169
        goto cleanup;
×
1170
    }
1171
    pixel_count = (size_t)width * (size_t)height;
144✔
1172
    if (pixel_count > (size_t)SIXEL_ALLOCATE_BYTES_MAX) {
144!
1173
        sixel_helper_set_additional_message(
×
1174
            "load_with_coregraphics: indexed image is too large.");
1175
        status = SIXEL_BAD_INPUT;
×
1176
        goto cleanup;
×
1177
    }
1178

1179
    provider = CGImageGetDataProvider(image);
144✔
1180
    if (provider == NULL) {
144✔
1181
        status = SIXEL_FALSE;
×
1182
        goto cleanup;
×
1183
    }
1184
    provider_data = CGDataProviderCopyData(provider);
144✔
1185
    if (provider_data == NULL) {
144✔
1186
        status = SIXEL_FALSE;
×
1187
        goto cleanup;
×
1188
    }
1189
    src = CFDataGetBytePtr(provider_data);
144✔
1190
    data_size = (size_t)CFDataGetLength(provider_data);
144✔
1191
    if (src == NULL || data_size < needed_bytes) {
144!
1192
        status = SIXEL_FALSE;
×
1193
        goto cleanup;
×
1194
    }
1195

1196
    pixels = (unsigned char *)sixel_allocator_malloc(allocator, pixel_count);
144✔
1197
    if (pixels == NULL) {
144✔
1198
        sixel_helper_set_additional_message(
×
1199
            "load_with_coregraphics: sixel_allocator_malloc() failed.");
1200
        status = SIXEL_BAD_ALLOCATION;
×
1201
        goto cleanup;
×
1202
    }
1203

1204
    for (y = 0u; y < (size_t)height; ++y) {
7,120✔
1205
        row = src + y * bytes_per_row;
6,976✔
1206
        if (bits_per_pixel == 8u) {
6,976!
1207
            memcpy(pixels + y * (size_t)width, row, (size_t)width);
6,976✔
1208
            continue;
6,976✔
1209
        }
1210
        for (x = 0; x < width; ++x) {
×
1211
            packed = row[((size_t)x * bits_per_pixel) / 8u];
×
1212
            shift = (int)(8u - bits_per_pixel
×
1213
                          - ((size_t)x * bits_per_pixel) % 8u);
×
1214
            pixels[y * (size_t)width + (size_t)x] =
×
1215
                (unsigned char)((packed >> shift)
×
1216
                                & ((1u << bits_per_pixel) - 1u));
×
1217
        }
×
1218
    }
×
1219

1220
    *ppixels = pixels;
144✔
1221
    pixels = NULL;
144✔
1222
    status = SIXEL_OK;
144✔
1223

1224
cleanup:
1225
    if (provider_data != NULL) {
144!
1226
        CFRelease(provider_data);
144✔
1227
    }
144✔
1228
    sixel_allocator_free(allocator, pixels);
144✔
1229
    return status;
144✔
1230
}
168✔
1231

1232
static SIXELSTATUS
1233
coregraphics_try_handle_indexed_frame(sixel_chunk_t const *chunk,
1,516✔
1234
                                      sixel_frame_t *frame,
1235
                                      CGImageRef image,
1236
                                      CFDictionaryRef frame_props,
1237
                                      coregraphics_png_trns_chunk_cache_t
1238
                                      *png_trns_chunk_cache,
1239
                                      int fuse_palette,
1240
                                      int reqcolors,
1241
                                      unsigned char const *bgcolor,
1242
                                      int *handled,
1243
                                      int *force_alpha)
1244
{
1245
    /*
1246
     * Indexed-path policy:
1247
     *   - emit PAL8 only for opaque indexed frames when palette output is
1248
     *     enabled,
1249
     *   - keep binary keycolor transparency in PAL8 when reqcolors allows it,
1250
     *     otherwise preserve it as RGB+mask (or background composite),
1251
     *   - defer non-binary alpha to the generic RGBA decode path.
1252
     */
1253
    SIXELSTATUS status;
948✔
1254
    CGColorSpaceRef color_space;
948✔
1255
    unsigned char *palette;
948✔
1256
    unsigned char *indexed_pixels;
948✔
1257
    unsigned char *rgb_pixels;
948✔
1258
    unsigned char *mask;
948✔
1259
    unsigned char zero_alpha_map[SIXEL_PALETTE_MAX];
948✔
1260
    size_t pixel_count;
948✔
1261
    size_t index;
948✔
1262
    int ncolors;
948✔
1263
    int allow_pal8;
948✔
1264
    int reqcolors_clamped;
948✔
1265
    int zero_alpha_count;
948✔
1266
    int has_partial_alpha;
948✔
1267
    int has_keycolor_alpha;
948✔
1268
    int key_index;
948✔
1269
    unsigned char palette_index;
948✔
1270

1271
    status = SIXEL_FALSE;
1,516✔
1272
    color_space = NULL;
1,516✔
1273
    palette = NULL;
1,516✔
1274
    indexed_pixels = NULL;
1,516✔
1275
    rgb_pixels = NULL;
1,516✔
1276
    mask = NULL;
1,516✔
1277
    pixel_count = 0u;
1,516✔
1278
    index = 0u;
1,516✔
1279
    ncolors = 0;
1,516✔
1280
    allow_pal8 = 0;
1,516✔
1281
    reqcolors_clamped = 0;
1,516✔
1282
    zero_alpha_count = 0;
1,516✔
1283
    has_partial_alpha = 0;
1,516✔
1284
    has_keycolor_alpha = 0;
1,516✔
1285
    key_index = -1;
1,516✔
1286
    palette_index = 0u;
1,516✔
1287
    if (chunk == NULL ||
3,032!
1288
        frame == NULL ||
1,516!
1289
        image == NULL ||
1,516!
1290
        handled == NULL ||
1,516!
1291
        force_alpha == NULL) {
1,516✔
1292
        return SIXEL_BAD_ARGUMENT;
×
1293
    }
1294

1295
    *handled = 0;
1,516✔
1296
    *force_alpha = 0;
1,516✔
1297

1298
    color_space = CGImageGetColorSpace(image);
1,516✔
1299
    if (color_space == NULL ||
1,516!
1300
        CGColorSpaceGetModel(color_space) != kCGColorSpaceModelIndexed) {
1,516✔
1301
        return SIXEL_OK;
1,340✔
1302
    }
1303

1304
    status = coregraphics_copy_indexed_palette(image,
352✔
1305
                                               frame->allocator,
176✔
1306
                                               &palette,
1307
                                               &ncolors);
1308
    if (status == SIXEL_FALSE) {
176!
1309
        return SIXEL_OK;
×
1310
    }
1311
    if (SIXEL_FAILED(status)) {
176!
1312
        return status;
×
1313
    }
1314

1315
    memset(zero_alpha_map, 0, sizeof(zero_alpha_map));
176✔
1316
    coregraphics_parse_png_transparency(frame_props,
352✔
1317
                                        ncolors,
176✔
1318
                                        zero_alpha_map,
176✔
1319
                                        &zero_alpha_count,
1320
                                        &has_partial_alpha);
1321
    coregraphics_parse_png_transparency_chunk(chunk,
352✔
1322
                                              png_trns_chunk_cache,
176✔
1323
                                              ncolors,
176✔
1324
                                              zero_alpha_map,
176✔
1325
                                              &zero_alpha_count,
1326
                                              &has_partial_alpha);
1327
    if (has_partial_alpha != 0) {
176!
1328
        *force_alpha = 1;
×
1329
        status = SIXEL_OK;
×
1330
        goto cleanup;
×
1331
    }
1332

1333
    reqcolors_clamped = reqcolors;
176✔
1334
    if (reqcolors_clamped <= 0 || reqcolors_clamped > SIXEL_PALETTE_MAX) {
176!
1335
        reqcolors_clamped = SIXEL_PALETTE_MAX;
×
1336
    }
×
1337
    has_keycolor_alpha = zero_alpha_count > 0 ? 1 : 0;
176✔
1338
    allow_pal8 = fuse_palette != 0 &&
352!
1339
        ncolors > 0 &&
374!
1340
        ncolors <= reqcolors_clamped;
176✔
1341
    if (has_keycolor_alpha != 0 && bgcolor != NULL) {
198!
1342
        allow_pal8 = 0;
×
1343
    }
×
1344
    if (has_keycolor_alpha != 0) {
154!
1345
        for (index = 0u; index < (size_t)ncolors; ++index) {
×
1346
            if (zero_alpha_map[index] != 0u) {
×
1347
                key_index = (int)index;
×
1348
                break;
×
1349
            }
1350
        }
×
1351
    }
×
1352

1353
    if (allow_pal8 == 0 && has_keycolor_alpha == 0) {
155!
1354
        status = SIXEL_OK;
8✔
1355
        goto cleanup;
8✔
1356
    }
1357
    if (has_keycolor_alpha == 0) {
168✔
1358
        key_index = -1;
168✔
1359
    } else if (key_index < 0) {
168!
1360
        status = SIXEL_OK;
×
1361
        goto cleanup;
×
1362
    }
1363

1364
    if ((size_t)frame->width > SIZE_MAX / (size_t)frame->height) {
168!
1365
        status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1366
        goto cleanup;
×
1367
    }
1368
    pixel_count = (size_t)frame->width * (size_t)frame->height;
168✔
1369
    status = coregraphics_copy_indexed_pixels(image,
336✔
1370
                                              frame->allocator,
168✔
1371
                                              frame->width,
168✔
1372
                                              frame->height,
168✔
1373
                                              &indexed_pixels);
1374
    if (status == SIXEL_FALSE) {
168✔
1375
        status = SIXEL_OK;
8✔
1376
        goto cleanup;
8✔
1377
    }
1378
    if (SIXEL_FAILED(status)) {
160✔
1379
        goto cleanup;
16✔
1380
    }
1381

1382
    if (allow_pal8 != 0) {
144!
1383
        if (has_keycolor_alpha != 0 &&
144!
1384
            zero_alpha_count > 1 &&
×
1385
            key_index >= 0) {
×
1386
            for (index = 0u; index < pixel_count; ++index) {
×
1387
                palette_index = indexed_pixels[index];
×
1388
                if ((int)palette_index < ncolors &&
×
1389
                    zero_alpha_map[palette_index] != 0u &&
×
1390
                    (int)palette_index != key_index) {
×
1391
                    indexed_pixels[index] = (unsigned char)key_index;
×
1392
                }
×
1393
            }
×
1394
        }
×
1395
        frame->pixels.u8ptr = indexed_pixels;
144✔
1396
        indexed_pixels = NULL;
144✔
1397
        frame->palette = palette;
144✔
1398
        palette = NULL;
144✔
1399
        frame->ncolors = ncolors;
144✔
1400
        frame->pixelformat = SIXEL_PIXELFORMAT_PAL8;
144✔
1401
        frame->colorspace = SIXEL_COLORSPACE_GAMMA;
144✔
1402
        frame->transparent = has_keycolor_alpha ? key_index : -1;
144!
1403
        frame->alpha_zero_is_transparent = 0;
144✔
1404
        *handled = 1;
144✔
1405
        status = SIXEL_OK;
144✔
1406
        goto cleanup;
144✔
1407
    }
1408

1409
    if (has_keycolor_alpha == 0) {
×
1410
        status = SIXEL_OK;
×
1411
        goto cleanup;
×
1412
    }
1413

1414
    if (pixel_count > SIZE_MAX / 3u) {
×
1415
        status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1416
        goto cleanup;
×
1417
    }
1418
    rgb_pixels = (unsigned char *)sixel_allocator_malloc(frame->allocator,
×
1419
                                                         pixel_count * 3u);
×
1420
    if (rgb_pixels == NULL) {
×
1421
        sixel_helper_set_additional_message(
×
1422
            "load_with_coregraphics: sixel_allocator_malloc() failed.");
1423
        status = SIXEL_BAD_ALLOCATION;
×
1424
        goto cleanup;
×
1425
    }
1426

1427
    if (bgcolor == NULL) {
×
1428
        mask = (unsigned char *)sixel_allocator_malloc(frame->allocator,
×
1429
                                                       pixel_count);
×
1430
        if (mask == NULL) {
×
1431
            sixel_helper_set_additional_message(
×
1432
                "load_with_coregraphics: sixel_allocator_malloc() failed.");
1433
            status = SIXEL_BAD_ALLOCATION;
×
1434
            goto cleanup;
×
1435
        }
1436
    }
×
1437

1438
    for (index = 0u; index < pixel_count; ++index) {
×
1439
        palette_index = indexed_pixels[index];
×
1440
        if ((int)palette_index >= ncolors) {
×
1441
            palette_index = 0u;
×
1442
        }
×
1443
        if (zero_alpha_map[palette_index] != 0u) {
×
1444
            if (bgcolor != NULL) {
×
1445
                rgb_pixels[index * 3u + 0u] = bgcolor[0];
×
1446
                rgb_pixels[index * 3u + 1u] = bgcolor[1];
×
1447
                rgb_pixels[index * 3u + 2u] = bgcolor[2];
×
1448
            } else {
×
1449
                rgb_pixels[index * 3u + 0u] = 0u;
×
1450
                rgb_pixels[index * 3u + 1u] = 0u;
×
1451
                rgb_pixels[index * 3u + 2u] = 0u;
×
1452
                mask[index] = 1u;
×
1453
            }
1454
        } else {
×
1455
            rgb_pixels[index * 3u + 0u] =
×
1456
                palette[(size_t)palette_index * 3u + 0u];
×
1457
            rgb_pixels[index * 3u + 1u] =
×
1458
                palette[(size_t)palette_index * 3u + 1u];
×
1459
            rgb_pixels[index * 3u + 2u] =
×
1460
                palette[(size_t)palette_index * 3u + 2u];
×
1461
            if (mask != NULL) {
×
1462
                mask[index] = 0u;
×
1463
            }
×
1464
        }
1465
    }
×
1466

1467
    frame->pixels.u8ptr = rgb_pixels;
×
1468
    rgb_pixels = NULL;
×
1469
    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1470
    frame->colorspace = SIXEL_COLORSPACE_GAMMA;
×
1471
    frame->transparent = -1;
×
1472
    frame->ncolors = -1;
×
1473
    frame->alpha_zero_is_transparent = 0;
×
1474
    if (mask != NULL) {
×
1475
        frame->transparent_mask = mask;
×
1476
        frame->transparent_mask_size = pixel_count;
×
1477
        frame->alpha_zero_is_transparent = 1;
×
1478
        mask = NULL;
×
1479
    }
×
1480
    *handled = 1;
×
1481
    status = SIXEL_OK;
×
1482

1483
cleanup:
1484
    sixel_allocator_free(frame->allocator, palette);
176✔
1485
    sixel_allocator_free(frame->allocator, indexed_pixels);
176✔
1486
    sixel_allocator_free(frame->allocator, rgb_pixels);
176✔
1487
    sixel_allocator_free(frame->allocator, mask);
176✔
1488
    return status;
176✔
1489
}
1,516✔
1490

1491
static CGColorSpaceRef
1492
coregraphics_create_linear_colorspace(void)
24✔
1493
{
1494
    CGColorSpaceRef color_space;
15✔
1495

1496
    color_space = NULL;
24✔
1497
    color_space = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearSRGB);
24✔
1498
    if (color_space == NULL) {
24!
1499
        color_space = CGColorSpaceCreateWithName(kCGColorSpaceLinearSRGB);
×
1500
    }
×
1501
    if (color_space == NULL) {
21!
1502
        color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
×
1503
    }
×
1504

1505
    return color_space;
39✔
1506
}
15✔
1507

1508
static SIXELSTATUS
1509
coregraphics_decode_rgba8_frame(sixel_frame_t *frame,
1,332✔
1510
                                CGImageRef image,
1511
                                unsigned char const *bgcolor,
1512
                                int has_alpha_like,
1513
                                coregraphics_srgb_lut_cache_t *srgb_lut_cache)
1514
{
1515
    /*
1516
     * Draw into premultiplied RGBA and emit three-component output.
1517
     * When alpha-like transparency is present and no bgcolor is given,
1518
     * retain per-pixel zero-alpha semantics through transparent_mask.
1519
     */
1520
    SIXELSTATUS status;
833✔
1521
    CGColorSpaceRef color_space;
833✔
1522
    CGContextRef context;
833✔
1523
    unsigned char *pixels;
833✔
1524
    unsigned char *mask;
833✔
1525
    size_t stride;
833✔
1526
    size_t pixel_count;
833✔
1527
    size_t index;
833✔
1528
    unsigned int alpha;
833✔
1529
    unsigned char r8;
833✔
1530
    unsigned char g8;
833✔
1531
    unsigned char b8;
833✔
1532
    unsigned char out_r;
833✔
1533
    unsigned char out_g;
833✔
1534
    unsigned char out_b;
833✔
1535
    double alpha_unit;
833✔
1536
    double bg_linear[3];
833✔
1537
    double src_linear_r;
833✔
1538
    double src_linear_g;
833✔
1539
    double src_linear_b;
833✔
1540
    double out_linear_r;
833✔
1541
    double out_linear_g;
833✔
1542
    double out_linear_b;
833✔
1543
    double const *decode_lut;
833✔
1544
    unsigned char const *encode_lut;
833✔
1545
    size_t encode_lut_size;
833✔
1546

1547
    status = SIXEL_FALSE;
1,332✔
1548
    color_space = NULL;
1,332✔
1549
    context = NULL;
1,332✔
1550
    pixels = NULL;
1,332✔
1551
    mask = NULL;
1,332✔
1552
    stride = 0u;
1,332✔
1553
    pixel_count = 0u;
1,332✔
1554
    index = 0u;
1,332✔
1555
    alpha = 0u;
1,332✔
1556
    r8 = 0u;
1,332✔
1557
    g8 = 0u;
1,332✔
1558
    b8 = 0u;
1,332✔
1559
    out_r = 0u;
1,332✔
1560
    out_g = 0u;
1,332✔
1561
    out_b = 0u;
1,332✔
1562
    alpha_unit = 0.0;
1,332✔
1563
    bg_linear[0] = 0.0;
1,332✔
1564
    bg_linear[1] = 0.0;
1,332✔
1565
    bg_linear[2] = 0.0;
1,332✔
1566
    src_linear_r = 0.0;
1,332✔
1567
    src_linear_g = 0.0;
1,332✔
1568
    src_linear_b = 0.0;
1,332✔
1569
    out_linear_r = 0.0;
1,332✔
1570
    out_linear_g = 0.0;
1,332✔
1571
    out_linear_b = 0.0;
1,332✔
1572
    decode_lut = NULL;
1,332✔
1573
    encode_lut = NULL;
1,332✔
1574
    encode_lut_size = 0u;
1,332✔
1575
    if (frame == NULL || image == NULL || frame->allocator == NULL) {
1,332!
1576
        return SIXEL_BAD_ARGUMENT;
×
1577
    }
1578

1579
    if ((size_t)frame->width > SIZE_MAX / (size_t)frame->height) {
1,332!
1580
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1581
    }
1582
    pixel_count = (size_t)frame->width * (size_t)frame->height;
1,332✔
1583
    if (frame->width > 0 && (size_t)frame->width > SIZE_MAX / 4u) {
1,332!
1584
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1585
    }
1586
    stride = (size_t)frame->width * 4u;
1,332✔
1587
    if (pixel_count > 0u && (size_t)frame->height > SIZE_MAX / stride) {
1,332!
1588
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1589
    }
1590

1591
    pixels = (unsigned char *)sixel_allocator_malloc(frame->allocator,
2,664✔
1592
                                                     (size_t)frame->height *
2,664✔
1593
                                                     stride);
1,332✔
1594
    if (pixels == NULL) {
1,332✔
1595
        sixel_helper_set_additional_message(
×
1596
            "load_with_coregraphics: sixel_allocator_malloc() failed.");
1597
        return SIXEL_BAD_ALLOCATION;
×
1598
    }
1599

1600
    color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
1,332✔
1601
    if (color_space == NULL) {
1,332✔
1602
        sixel_helper_set_additional_message(
×
1603
            "load_with_coregraphics: CGColorSpaceCreateWithName failed.");
1604
        status = SIXEL_RUNTIME_ERROR;
×
1605
        goto cleanup;
×
1606
    }
1607

1608
    context = CGBitmapContextCreate(pixels,
2,664✔
1609
                                    (size_t)frame->width,
1,332✔
1610
                                    (size_t)frame->height,
1,332✔
1611
                                    8,
1612
                                    stride,
1,332✔
1613
                                    color_space,
1,332✔
1614
                                    kCGImageAlphaPremultipliedLast |
1615
                                        kCGBitmapByteOrder32Big);
1616
    if (context == NULL) {
1,332✔
1617
        sixel_helper_set_additional_message(
×
1618
            "load_with_coregraphics: CGBitmapContextCreate failed.");
1619
        status = SIXEL_RUNTIME_ERROR;
×
1620
        goto cleanup;
×
1621
    }
1622

1623
    CGContextSetBlendMode(context, kCGBlendModeCopy);
1,332✔
1624
    CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 0.0);
1,332✔
1625
    CGContextFillRect(context,
2,664✔
1626
                      CGRectMake(0,
1,332✔
1627
                                 0,
1628
                                 (CGFloat)frame->width,
1,332✔
1629
                                 (CGFloat)frame->height));
1,332✔
1630
    CGContextSetBlendMode(context, kCGBlendModeNormal);
1,332✔
1631
    CGContextDrawImage(context,
2,664✔
1632
                       CGRectMake(0,
1,332✔
1633
                                  0,
1634
                                  (CGFloat)frame->width,
1,332✔
1635
                                  (CGFloat)frame->height),
1,332✔
1636
                       image);
1,332✔
1637

1638
    if (bgcolor != NULL) {
1,332✔
1639
        bg_linear[0] = coregraphics_decode_srgb_unit((double)bgcolor[0] /
24✔
1640
                                                     255.0);
1641
        bg_linear[1] = coregraphics_decode_srgb_unit((double)bgcolor[1] /
24✔
1642
                                                     255.0);
1643
        bg_linear[2] = coregraphics_decode_srgb_unit((double)bgcolor[2] /
24✔
1644
                                                     255.0);
1645
    }
24✔
1646

1647
    if (has_alpha_like != 0 && bgcolor == NULL) {
1,222✔
1648
        mask = (unsigned char *)sixel_allocator_malloc(frame->allocator,
848✔
1649
                                                       pixel_count);
424✔
1650
        if (mask == NULL) {
424✔
1651
            sixel_helper_set_additional_message(
×
1652
                "load_with_coregraphics: sixel_allocator_malloc() failed.");
1653
            status = SIXEL_BAD_ALLOCATION;
×
1654
            goto cleanup;
×
1655
        }
1656
    }
424✔
1657
    if (has_alpha_like != 0) {
1,222✔
1658
        if (srgb_lut_cache == NULL) {
448✔
1659
            status = SIXEL_BAD_ARGUMENT;
×
1660
            goto cleanup;
×
1661
        }
1662
        if (srgb_lut_cache->prepared == 0) {
448✔
1663
            coregraphics_build_srgb_decode_u8_lut(srgb_lut_cache->decode_lut);
312✔
1664
            coregraphics_build_srgb_encode_u8_lut(
312✔
1665
                srgb_lut_cache->encode_lut,
312✔
1666
                sizeof(srgb_lut_cache->encode_lut));
1667
            srgb_lut_cache->prepared = 1;
312✔
1668
        }
312✔
1669
        decode_lut = srgb_lut_cache->decode_lut;
448✔
1670
        encode_lut = srgb_lut_cache->encode_lut;
448✔
1671
        encode_lut_size = sizeof(srgb_lut_cache->encode_lut);
448✔
1672
    }
448✔
1673

1674
    if (has_alpha_like == 0) {
1,332✔
1675
        for (index = 0u; index < pixel_count; ++index) {
2,163,580✔
1676
            pixels[index * 3u + 0u] = pixels[index * 4u + 0u];
2,162,696✔
1677
            pixels[index * 3u + 1u] = pixels[index * 4u + 1u];
2,162,696✔
1678
            pixels[index * 3u + 2u] = pixels[index * 4u + 2u];
2,162,696✔
1679
        }
2,162,696✔
1680
        frame->pixels.u8ptr = pixels;
884✔
1681
        pixels = NULL;
884✔
1682
        frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
884✔
1683
        frame->colorspace = SIXEL_COLORSPACE_GAMMA;
884✔
1684
        frame->transparent = -1;
884✔
1685
        frame->ncolors = -1;
884✔
1686
        frame->alpha_zero_is_transparent = 0;
884✔
1687
        status = SIXEL_OK;
884✔
1688
        goto cleanup;
884✔
1689
    }
1690

1691
    for (index = 0u; index < pixel_count; ++index) {
316,384✔
1692
        alpha = pixels[index * 4u + 3u];
315,936✔
1693
        if (alpha == 0u) {
315,936✔
1694
            r8 = 0u;
104✔
1695
            g8 = 0u;
104✔
1696
            b8 = 0u;
104✔
1697
            alpha_unit = 0.0;
104✔
1698
            if (mask != NULL) {
104✔
1699
                mask[index] = 1u;
64✔
1700
            }
64✔
1701
        } else if (alpha >= 255u) {
315,936✔
1702
            r8 = pixels[index * 4u + 0u];
315,800✔
1703
            g8 = pixels[index * 4u + 1u];
315,800✔
1704
            b8 = pixels[index * 4u + 2u];
315,800✔
1705
            alpha_unit = 1.0;
315,800✔
1706
            if (mask != NULL) {
315,800✔
1707
                mask[index] = 0u;
315,760✔
1708
            }
315,760✔
1709
        } else {
315,800✔
1710
            r8 = coregraphics_unpremultiply_channel(pixels[index * 4u + 0u],
64✔
1711
                                                    alpha);
32✔
1712
            g8 = coregraphics_unpremultiply_channel(pixels[index * 4u + 1u],
64✔
1713
                                                    alpha);
32✔
1714
            b8 = coregraphics_unpremultiply_channel(pixels[index * 4u + 2u],
64✔
1715
                                                    alpha);
32✔
1716
            alpha_unit = (double)alpha / 255.0;
32✔
1717
            if (mask != NULL) {
32✔
1718
                mask[index] = 0u;
16✔
1719
            }
16✔
1720
        }
1721

1722
        src_linear_r = decode_lut[r8];
315,936✔
1723
        src_linear_g = decode_lut[g8];
315,936✔
1724
        src_linear_b = decode_lut[b8];
315,936✔
1725

1726
        if (bgcolor != NULL) {
315,936✔
1727
            out_linear_r = src_linear_r * alpha_unit
192✔
1728
                + bg_linear[0] * (1.0 - alpha_unit);
96✔
1729
            out_linear_g = src_linear_g * alpha_unit
192✔
1730
                + bg_linear[1] * (1.0 - alpha_unit);
96✔
1731
            out_linear_b = src_linear_b * alpha_unit
192✔
1732
                + bg_linear[2] * (1.0 - alpha_unit);
96✔
1733
        } else {
96✔
1734
            out_linear_r = src_linear_r * alpha_unit;
315,840✔
1735
            out_linear_g = src_linear_g * alpha_unit;
315,840✔
1736
            out_linear_b = src_linear_b * alpha_unit;
315,840✔
1737
        }
1738

1739
        out_r = coregraphics_encode_linear_to_srgb_u8(out_linear_r,
631,872✔
1740
                                                      encode_lut,
315,936✔
1741
                                                      encode_lut_size);
315,936✔
1742
        out_g = coregraphics_encode_linear_to_srgb_u8(out_linear_g,
631,872✔
1743
                                                      encode_lut,
315,936✔
1744
                                                      encode_lut_size);
315,936✔
1745
        out_b = coregraphics_encode_linear_to_srgb_u8(out_linear_b,
631,872✔
1746
                                                      encode_lut,
315,936✔
1747
                                                      encode_lut_size);
315,936✔
1748
        pixels[index * 3u + 0u] = out_r;
315,936✔
1749
        pixels[index * 3u + 1u] = out_g;
315,936✔
1750
        pixels[index * 3u + 2u] = out_b;
315,936✔
1751
    }
315,936✔
1752

1753
    frame->pixels.u8ptr = pixels;
448✔
1754
    pixels = NULL;
448✔
1755
    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
448✔
1756
    frame->colorspace = SIXEL_COLORSPACE_GAMMA;
448✔
1757
    frame->transparent = -1;
448✔
1758
    frame->ncolors = -1;
448✔
1759
    frame->alpha_zero_is_transparent = 0;
448✔
1760
    if (mask != NULL) {
448✔
1761
        frame->transparent_mask = mask;
424✔
1762
        frame->transparent_mask_size = pixel_count;
424✔
1763
        frame->alpha_zero_is_transparent = 1;
424✔
1764
        mask = NULL;
424✔
1765
    }
424✔
1766
    status = SIXEL_OK;
448✔
1767

1768
cleanup:
1769
    if (context != NULL) {
1,332!
1770
        CGContextRelease(context);
1,332✔
1771
    }
1,332✔
1772
    if (color_space != NULL) {
1,332!
1773
        CGColorSpaceRelease(color_space);
1,332✔
1774
    }
1,332✔
1775
    sixel_allocator_free(frame->allocator, pixels);
1,332✔
1776
    sixel_allocator_free(frame->allocator, mask);
1,332✔
1777
    return status;
1,332✔
1778
}
1,332✔
1779

1780
static SIXELSTATUS
1781
coregraphics_decode_float32_frame(sixel_frame_t *frame,
24✔
1782
                                  CGImageRef image,
1783
                                  unsigned char const *bgcolor,
1784
                                  int has_alpha_like)
1785
{
1786
    /*
1787
     * High-depth sources are decoded through a float32 linear context so
1788
     * background composition and alpha handling stay in linear light.
1789
     */
1790
    SIXELSTATUS status;
15✔
1791
    CGColorSpaceRef color_space;
15✔
1792
    CGContextRef context;
15✔
1793
    float *pixels;
15✔
1794
    unsigned char *mask;
15✔
1795
    size_t stride;
15✔
1796
    size_t pixel_count;
15✔
1797
    size_t index;
15✔
1798
    float alpha;
15✔
1799
    float out_r;
15✔
1800
    float out_g;
15✔
1801
    float out_b;
15✔
1802
    float bg_linear[3];
15✔
1803

1804
    status = SIXEL_FALSE;
24✔
1805
    color_space = NULL;
24✔
1806
    context = NULL;
24✔
1807
    pixels = NULL;
24✔
1808
    mask = NULL;
24✔
1809
    stride = 0u;
24✔
1810
    pixel_count = 0u;
24✔
1811
    index = 0u;
24✔
1812
    alpha = 0.0f;
24✔
1813
    out_r = 0.0f;
24✔
1814
    out_g = 0.0f;
24✔
1815
    out_b = 0.0f;
24✔
1816
    bg_linear[0] = 0.0f;
24✔
1817
    bg_linear[1] = 0.0f;
24✔
1818
    bg_linear[2] = 0.0f;
24✔
1819
    if (frame == NULL || image == NULL || frame->allocator == NULL) {
24!
1820
        return SIXEL_BAD_ARGUMENT;
×
1821
    }
1822

1823
    if ((size_t)frame->width > SIZE_MAX / (size_t)frame->height) {
24!
1824
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1825
    }
1826
    pixel_count = (size_t)frame->width * (size_t)frame->height;
24✔
1827
    if (frame->width > 0 &&
24!
1828
        (size_t)frame->width > SIZE_MAX / (4u * sizeof(float))) {
24✔
1829
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1830
    }
1831
    stride = (size_t)frame->width * 4u * sizeof(float);
24✔
1832
    if (pixel_count > 0u && (size_t)frame->height > SIZE_MAX / stride) {
24!
1833
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
1834
    }
1835

1836
    pixels = (float *)sixel_allocator_malloc(frame->allocator,
48✔
1837
                                             (size_t)frame->height * stride);
24✔
1838
    if (pixels == NULL) {
24✔
1839
        sixel_helper_set_additional_message(
×
1840
            "load_with_coregraphics: sixel_allocator_malloc() failed.");
1841
        return SIXEL_BAD_ALLOCATION;
×
1842
    }
1843

1844
    color_space = coregraphics_create_linear_colorspace();
24✔
1845
    if (color_space == NULL) {
24✔
1846
        sixel_helper_set_additional_message(
×
1847
            "load_with_coregraphics: CGColorSpaceCreateWithName failed.");
1848
        status = SIXEL_RUNTIME_ERROR;
×
1849
        goto cleanup;
×
1850
    }
1851

1852
    context = CGBitmapContextCreate(pixels,
48✔
1853
                                    (size_t)frame->width,
24✔
1854
                                    (size_t)frame->height,
24✔
1855
                                    32,
1856
                                    stride,
24✔
1857
                                    color_space,
24✔
1858
                                    kCGImageAlphaPremultipliedLast |
1859
                                        kCGBitmapByteOrder32Host |
1860
                                        kCGBitmapFloatComponents);
1861
    if (context == NULL) {
24✔
1862
        sixel_helper_set_additional_message(
×
1863
            "load_with_coregraphics: CGBitmapContextCreate failed.");
1864
        status = SIXEL_RUNTIME_ERROR;
×
1865
        goto cleanup;
×
1866
    }
1867

1868
    CGContextSetBlendMode(context, kCGBlendModeCopy);
24✔
1869
    CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 0.0);
24✔
1870
    CGContextFillRect(context,
48✔
1871
                      CGRectMake(0,
24✔
1872
                                 0,
1873
                                 (CGFloat)frame->width,
24✔
1874
                                 (CGFloat)frame->height));
24✔
1875
    CGContextSetBlendMode(context, kCGBlendModeNormal);
24✔
1876
    CGContextDrawImage(context,
48✔
1877
                       CGRectMake(0,
24✔
1878
                                  0,
1879
                                  (CGFloat)frame->width,
24✔
1880
                                  (CGFloat)frame->height),
24✔
1881
                       image);
24✔
1882

1883
    if (bgcolor != NULL) {
24!
1884
        bg_linear[0] = (float)coregraphics_decode_srgb_unit(
×
1885
            (double)bgcolor[0] / 255.0);
×
1886
        bg_linear[1] = (float)coregraphics_decode_srgb_unit(
×
1887
            (double)bgcolor[1] / 255.0);
×
1888
        bg_linear[2] = (float)coregraphics_decode_srgb_unit(
×
1889
            (double)bgcolor[2] / 255.0);
×
1890
    }
×
1891

1892
    if (has_alpha_like != 0 && bgcolor == NULL) {
21!
1893
        mask = (unsigned char *)sixel_allocator_malloc(frame->allocator,
×
1894
                                                       pixel_count);
×
1895
        if (mask == NULL) {
×
1896
            sixel_helper_set_additional_message(
×
1897
                "load_with_coregraphics: sixel_allocator_malloc() failed.");
1898
            status = SIXEL_BAD_ALLOCATION;
×
1899
            goto cleanup;
×
1900
        }
1901
    }
×
1902

1903
    if (has_alpha_like == 0) {
24✔
1904
        for (index = 0u; index < pixel_count; ++index) {
98,328✔
1905
            pixels[index * 3u + 0u] = pixels[index * 4u + 0u];
98,304✔
1906
            pixels[index * 3u + 1u] = pixels[index * 4u + 1u];
98,304✔
1907
            pixels[index * 3u + 2u] = pixels[index * 4u + 2u];
98,304✔
1908
        }
98,304✔
1909
    } else {
24✔
1910
        for (index = 0u; index < pixel_count; ++index) {
×
1911
            alpha = pixels[index * 4u + 3u];
×
1912
            if (alpha < 0.0f) {
×
1913
                alpha = 0.0f;
×
1914
            } else if (alpha > 1.0f) {
×
1915
                alpha = 1.0f;
×
1916
            }
×
1917

1918
            out_r = pixels[index * 4u + 0u];
×
1919
            out_g = pixels[index * 4u + 1u];
×
1920
            out_b = pixels[index * 4u + 2u];
×
1921
            if (bgcolor != NULL && alpha < 1.0f) {
×
1922
                out_r += bg_linear[0] * (1.0f - alpha);
×
1923
                out_g += bg_linear[1] * (1.0f - alpha);
×
1924
                out_b += bg_linear[2] * (1.0f - alpha);
×
1925
            } else if (mask != NULL) {
×
1926
                if (alpha <= 0.0f) {
×
1927
                    mask[index] = 1u;
×
1928
                } else {
×
1929
                    mask[index] = 0u;
×
1930
                }
1931
            }
×
1932

1933
            pixels[index * 3u + 0u] = out_r;
×
1934
            pixels[index * 3u + 1u] = out_g;
×
1935
            pixels[index * 3u + 2u] = out_b;
×
1936
        }
×
1937
    }
1938

1939
    frame->pixels.f32ptr = pixels;
24✔
1940
    pixels = NULL;
24✔
1941
    frame->pixelformat = SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
24✔
1942
    frame->colorspace = SIXEL_COLORSPACE_LINEAR;
24✔
1943
    frame->transparent = -1;
24✔
1944
    frame->ncolors = -1;
24✔
1945
    frame->alpha_zero_is_transparent = 0;
24✔
1946
    if (mask != NULL) {
24!
1947
        frame->transparent_mask = mask;
×
1948
        frame->transparent_mask_size = pixel_count;
×
1949
        frame->alpha_zero_is_transparent = 1;
×
1950
        mask = NULL;
×
1951
    }
×
1952
    status = SIXEL_OK;
24✔
1953

1954
cleanup:
1955
    if (context != NULL) {
24!
1956
        CGContextRelease(context);
24✔
1957
    }
24✔
1958
    if (color_space != NULL) {
24!
1959
        CGColorSpaceRelease(color_space);
24✔
1960
    }
24✔
1961
    sixel_allocator_free(frame->allocator, pixels);
24✔
1962
    sixel_allocator_free(frame->allocator, mask);
24✔
1963
    return status;
24✔
1964
}
24✔
1965

1966

1967
static SIXELSTATUS
1968
coregraphics_parse_animation_start_frame_no(int *start_frame_no)
224✔
1969
{
1970
    SIXELSTATUS status;
140✔
1971
    char const *env_value;
140✔
1972
    char *endptr;
140✔
1973
    long parsed;
140✔
1974

1975
    status = SIXEL_OK;
224✔
1976
    env_value = NULL;
224✔
1977
    endptr = NULL;
224✔
1978
    parsed = 0;
224✔
1979

1980
    *start_frame_no = INT_MIN;
224✔
1981
    env_value = sixel_compat_getenv("SIXEL_LOADER_ANIMATION_START_FRAME_NO");
224✔
1982
    if (env_value == NULL || env_value[0] == '\0') {
224!
1983
        goto end;
224✔
1984
    }
1985

1986
    parsed = strtol(env_value, &endptr, 10);
×
1987
    if (endptr == env_value || *endptr != '\0') {
×
1988
        sixel_helper_set_additional_message(
×
1989
            "SIXEL_LOADER_ANIMATION_START_FRAME_NO must be an integer.");
1990
        status = SIXEL_BAD_INPUT;
×
1991
        goto end;
×
1992
    }
1993
    if (parsed < (long)INT_MIN || parsed > (long)INT_MAX) {
×
1994
        sixel_helper_set_additional_message(
×
1995
            "SIXEL_LOADER_ANIMATION_START_FRAME_NO is out of range.");
1996
        status = SIXEL_BAD_INPUT;
×
1997
        goto end;
×
1998
    }
1999

2000
    *start_frame_no = (int)parsed;
×
2001

2002
end:
2003
    return status;
364✔
2004
}
140✔
2005

2006
static SIXELSTATUS
2007
coregraphics_resolve_animation_start_frame_no(int start_frame_no,
128✔
2008
                                              int frame_count,
2009
                                              int *resolved)
2010
{
2011
    SIXELSTATUS status;
80✔
2012
    int index;
80✔
2013

2014
    status = SIXEL_OK;
128✔
2015
    index = 0;
128✔
2016

2017
    if (frame_count <= 0) {
128!
2018
        sixel_helper_set_additional_message(
×
2019
            "Animation frame count must be positive.");
2020
        status = SIXEL_BAD_INPUT;
×
2021
        goto end;
×
2022
    }
2023

2024
    if (start_frame_no >= 0) {
128✔
2025
        index = start_frame_no;
96✔
2026
    } else {
96✔
2027
        index = frame_count + start_frame_no;
32✔
2028
    }
2029

2030
    if (index < 0 || index >= frame_count) {
128✔
2031
        sixel_helper_set_additional_message(
16✔
2032
            "SIXEL_LOADER_ANIMATION_START_FRAME_NO is outside"
2033
            " the animation frame range.");
2034
        status = SIXEL_BAD_INPUT;
16✔
2035
        goto end;
16✔
2036
    }
2037

2038
    *resolved = index;
112✔
2039

2040
end:
2041
    return status;
208✔
2042
}
80✔
2043

2044
static SIXELSTATUS
2045
coregraphics_parse_frame_cache_max_bytes(size_t *max_bytes,
1,236✔
2046
                                         int *cache_enabled)
2047
{
2048
    SIXELSTATUS status;
773✔
2049
    char const *env_value;
773✔
2050
    char *endptr;
773✔
2051
    unsigned long long parsed;
773✔
2052

2053
    status = SIXEL_OK;
1,236✔
2054
    env_value = NULL;
1,236✔
2055
    endptr = NULL;
1,236✔
2056
    parsed = 0ull;
1,236✔
2057
    if (max_bytes == NULL || cache_enabled == NULL) {
1,236!
2058
        return SIXEL_BAD_ARGUMENT;
×
2059
    }
2060

2061
    *max_bytes = COREGRAPHICS_FRAME_CACHE_MAX_BYTES_DEFAULT;
1,236✔
2062
    *cache_enabled = 1;
1,236✔
2063
    env_value = sixel_compat_getenv(
1,236✔
2064
        "SIXEL_LOADER_COREGRAPHICS_CACHE_MAX_BYTES");
2065
    if (env_value == NULL || env_value[0] == '\0') {
1,236!
2066
        goto end;
1,188✔
2067
    }
2068

2069
    errno = 0;
48✔
2070
    parsed = strtoull(env_value, &endptr, 10);
48✔
2071
    if (errno == ERANGE || endptr == env_value || *endptr != '\0') {
48!
2072
        sixel_helper_set_additional_message(
8✔
2073
            "SIXEL_LOADER_COREGRAPHICS_CACHE_MAX_BYTES "
2074
            "must be a non-negative integer.");
2075
        status = SIXEL_BAD_INPUT;
8✔
2076
        goto end;
8✔
2077
    }
2078
    if (parsed == 0ull) {
40✔
2079
        *max_bytes = 0u;
8✔
2080
        *cache_enabled = 0;
8✔
2081
        goto end;
8✔
2082
    }
2083
    if (parsed > (unsigned long long)SIZE_MAX) {
32!
2084
        sixel_helper_set_additional_message(
×
2085
            "SIXEL_LOADER_COREGRAPHICS_CACHE_MAX_BYTES is out of range.");
2086
        status = SIXEL_BAD_INPUT;
×
2087
        goto end;
×
2088
    }
2089

2090
    *max_bytes = (size_t)parsed;
32✔
2091

2092
end:
2093
    return status;
1,236✔
2094
}
1,236✔
2095

2096
static SIXELSTATUS
2097
coregraphics_frame_measure_storage(sixel_frame_t const *frame,
600✔
2098
                                   size_t *storage_bytes)
2099
{
2100
    sixel_frame_interface_t *frame_if;
375✔
2101

2102
    frame_if = NULL;
600✔
2103
    if (frame == NULL || storage_bytes == NULL) {
600!
2104
        return SIXEL_BAD_ARGUMENT;
×
2105
    }
2106

2107
    frame_if = sixel_frame_as_interface(frame);
600✔
2108
    if (frame_if->vtbl == NULL ||
600!
2109
        frame_if->vtbl->measure_storage == NULL) {
600✔
2110
        return SIXEL_BAD_ARGUMENT;
×
2111
    }
2112

2113
    return frame_if->vtbl->measure_storage(frame_if, storage_bytes);
600✔
2114
}
600✔
2115

2116
static SIXELSTATUS
2117
coregraphics_frame_set_handoff_shareable(sixel_frame_t *frame,
792✔
2118
                                         int shareable)
2119
{
2120
    SIXELSTATUS status;
495✔
2121
    sixel_frame_interface_t *frame_if;
495✔
2122
    sixel_frame_timeline_t timeline;
495✔
2123

2124
    status = SIXEL_FALSE;
792✔
2125
    frame_if = NULL;
792✔
2126
    memset(&timeline, 0, sizeof(timeline));
792✔
2127
    if (frame == NULL) {
792✔
2128
        return SIXEL_BAD_ARGUMENT;
×
2129
    }
2130

2131
    frame_if = sixel_frame_as_interface(frame);
792✔
2132
    if (frame_if->vtbl == NULL ||
1,584!
2133
        frame_if->vtbl->get_timeline == NULL ||
792!
2134
        frame_if->vtbl->set_timeline == NULL) {
792✔
2135
        return SIXEL_BAD_ARGUMENT;
×
2136
    }
2137

2138
    status = frame_if->vtbl->get_timeline(frame_if, &timeline);
792✔
2139
    if (SIXEL_FAILED(status)) {
792!
2140
        return status;
×
2141
    }
2142

2143
    timeline.handoff_shareable = shareable != 0 ? 1 : 0;
792✔
2144
    return frame_if->vtbl->set_timeline(frame_if, &timeline);
792✔
2145
}
792✔
2146

2147
static SIXELSTATUS
2148
coregraphics_frame_clone(sixel_frame_t const *frame,
168✔
2149
                         sixel_allocator_t *allocator,
2150
                         sixel_frame_t **frame_out)
2151
{
2152
    SIXELSTATUS status;
105✔
2153
    sixel_frame_interface_t *frame_if;
105✔
2154
    sixel_frame_interface_t *clone_if;
105✔
2155

2156
    status = SIXEL_BAD_ARGUMENT;
168✔
2157
    frame_if = NULL;
168✔
2158
    clone_if = NULL;
168✔
2159
    if (frame == NULL || frame_out == NULL) {
168!
2160
        return status;
×
2161
    }
2162
    *frame_out = NULL;
168✔
2163

2164
    frame_if = sixel_frame_as_interface(frame);
168✔
2165
    if (frame_if->vtbl == NULL || frame_if->vtbl->clone == NULL) {
168!
2166
        return status;
×
2167
    }
2168

2169
    status = frame_if->vtbl->clone(frame_if, allocator, &clone_if);
168✔
2170
    if (SIXEL_FAILED(status)) {
168!
2171
        return status;
×
2172
    }
2173
    if (clone_if == NULL) {
168✔
2174
        return SIXEL_BAD_ALLOCATION;
×
2175
    }
2176

2177
    *frame_out = (sixel_frame_t *)clone_if;
168✔
2178
    return SIXEL_OK;
168✔
2179
}
168✔
2180

2181
static void
2182
coregraphics_loader_state_init(
1,236✔
2183
    coregraphics_loader_state_t *state,
2184
    sixel_chunk_t const *chunk,
2185
    sixel_allocator_t *allocator,
2186
    int fstatic,
2187
    int fuse_palette,
2188
    int reqcolors,
2189
    int enable_orientation,
2190
    unsigned char *bgcolor,
2191
    int loop_control,
2192
    int start_frame_no_set,
2193
    int start_frame_no_override,
2194
    sixel_load_image_function fn_load,
2195
    void *context)
2196
{
2197
    if (state == NULL) {
1,236✔
2198
        return;
×
2199
    }
2200

2201
    state->status = SIXEL_FALSE;
1,236✔
2202
    state->chunk = chunk;
1,236✔
2203
    state->allocator = allocator;
1,236✔
2204
    state->fstatic = fstatic;
1,236✔
2205
    state->fuse_palette = fuse_palette;
1,236✔
2206
    state->reqcolors = reqcolors;
1,236✔
2207
    state->enable_orientation = enable_orientation;
1,236✔
2208
    state->bgcolor = bgcolor;
1,236✔
2209
    state->loop_control = loop_control;
1,236✔
2210
    state->start_frame_no_set = start_frame_no_set;
1,236✔
2211
    state->start_frame_no_override = start_frame_no_override;
1,236✔
2212
    state->fn_load = fn_load;
1,236✔
2213
    state->context = context;
1,236✔
2214
    state->frame = NULL;
1,236✔
2215
    state->emit_frame = NULL;
1,236✔
2216
    state->decode_frame = NULL;
1,236✔
2217
    state->cached_frame_tmp = NULL;
1,236✔
2218
    state->data = NULL;
1,236✔
2219
    state->source = NULL;
1,236✔
2220
    state->image = NULL;
1,236✔
2221
    state->props = NULL;
1,236✔
2222
    state->frame_props = NULL;
1,236✔
2223
    state->frame_count = 0u;
1,236✔
2224
    state->total_frames = 0;
1,236✔
2225
    state->anim_loop_count = -1;
1,236✔
2226
    state->is_animation_container = 0;
1,236✔
2227
    state->source_orientation = 1;
1,236✔
2228
    state->start_frame_no = INT_MIN;
1,236✔
2229
    state->resolved_start_frame_no = INT_MIN;
1,236✔
2230
    state->frame_index = 0;
1,236✔
2231
    state->loop_no = 0;
1,236✔
2232
    state->frames_in_loop = 0;
1,236✔
2233
    state->stop_loop = 0;
1,236✔
2234
    state->metadata_slots = 0u;
1,236✔
2235
    state->single_meta_slot.delay = 0;
1,236✔
2236
    state->single_meta_slot.orientation = 1;
1,236✔
2237
    state->single_meta_slot.has_alpha = 0;
1,236✔
2238
    state->single_meta_slot.promote_float32 = 0;
1,236✔
2239
    state->single_meta_slot.is_indexed = 0;
1,236✔
2240
    state->single_meta_slot.props_ready = 0u;
1,236✔
2241
    state->single_meta_slot.decode_hint_ready = 0u;
1,236✔
2242
    state->frame_meta_slots = NULL;
1,236✔
2243
    state->active_meta_slots = NULL;
1,236✔
2244
    state->frame_cache_slots = NULL;
1,236✔
2245
    state->frame_cache_enabled = 0;
1,236✔
2246
    state->frame_cache_max_bytes = 0u;
1,236✔
2247
    state->frame_cache_used_bytes = 0u;
1,236✔
2248
    state->frame_cache_frame_bytes = 0u;
1,236✔
2249
    state->image_width = 0u;
1,236✔
2250
    state->image_height = 0u;
1,236✔
2251
    state->frame_cache_keep = 0;
1,236✔
2252
    state->frame_cache_decision_pending = 0;
1,236✔
2253
    state->release_emit_frame = 0;
1,236✔
2254
    state->cache_hit = 0;
1,236✔
2255
    state->indexed_handled = 0;
1,236✔
2256
    state->force_alpha_from_indexed = 0;
1,236✔
2257
    state->has_alpha_like = 0;
1,236✔
2258
    state->promote_float32 = 0;
1,236✔
2259
    state->frame_orientation = 1;
1,236✔
2260
    state->frame_meta_slot = 0u;
1,236✔
2261
    coregraphics_png_trns_chunk_cache_init(&state->png_trns_chunk_cache);
1,236✔
2262
    state->rgba8_lut_cache.prepared = 0;
1,236✔
2263
    state->cf_data_length = 0;
1,236✔
2264
    state->prefetch_index = 0u;
1,236✔
2265
}
1,236✔
2266

2267
static SIXELSTATUS
2268
coregraphics_check_cancel(void *context)
8,696✔
2269
{
2270
    if (sixel_loader_callback_is_canceled(context)) {
8,696!
2271
        return SIXEL_INTERRUPTED;
×
2272
    }
2273
    return SIXEL_OK;
8,696✔
2274
}
8,696✔
2275

2276
static SIXELSTATUS
2277
coregraphics_prepare_source_and_root_metadata(
1,236✔
2278
    coregraphics_loader_state_t *state)
2279
{
2280
    SIXELSTATUS status;
773✔
2281
    CFDictionaryRef anim_dict;
773✔
2282
    CFStringRef anim_loop_key;
773✔
2283

2284
    status = SIXEL_OK;
1,236✔
2285
    anim_dict = NULL;
1,236✔
2286
    anim_loop_key = NULL;
1,236✔
2287
    if (state == NULL || state->chunk == NULL ||
2,472!
2288
        state->allocator == NULL || state->fn_load == NULL) {
1,236!
2289
        return SIXEL_BAD_ARGUMENT;
×
2290
    }
2291

2292
    status = coregraphics_parse_frame_cache_max_bytes(
1,236✔
2293
        &state->frame_cache_max_bytes,
1,236✔
2294
        &state->frame_cache_enabled);
1,236✔
2295
    if (status != SIXEL_OK) {
1,236✔
2296
        return status;
8✔
2297
    }
2298

2299
    status = sixel_frame_create_from_factory(&state->frame,
2,456✔
2300
                                             state->allocator);
1,228✔
2301
    if (SIXEL_FAILED(status)) {
1,228!
2302
        return status;
×
2303
    }
2304

2305
    if (sixel_chunk_get_size(state->chunk) > (size_t)LONG_MAX) {
1,228✔
2306
        sixel_helper_set_additional_message(
8✔
2307
            "load_with_coregraphics: input chunk size is too large.");
2308
        return SIXEL_BAD_INTEGER_OVERFLOW;
8✔
2309
    }
2310
    state->cf_data_length = (CFIndex)sixel_chunk_get_size(state->chunk);
1,220✔
2311
    state->data = CFDataCreate(kCFAllocatorDefault,
2,440✔
2312
                               sixel_chunk_get_buffer(state->chunk),
1,220✔
2313
                               state->cf_data_length);
1,220✔
2314
    if (state->data == NULL) {
1,220✔
2315
        sixel_helper_set_additional_message(
×
2316
            "load_with_coregraphics: CFDataCreate failed.");
2317
        return SIXEL_FALSE;
×
2318
    }
2319

2320
    state->source = CGImageSourceCreateWithData(state->data, NULL);
1,220✔
2321
    if (state->source == NULL) {
1,220✔
2322
        sixel_helper_set_additional_message(
×
2323
            "load_with_coregraphics: CGImageSourceCreateWithData failed.");
2324
        return SIXEL_FALSE;
×
2325
    }
2326

2327
    state->frame_count = CGImageSourceGetCount(state->source);
1,220✔
2328
    if (state->frame_count == 0u) {
1,220✔
2329
        sixel_helper_set_additional_message(
80✔
2330
            "load_with_coregraphics: input has no decodable frames.");
2331
        return SIXEL_FALSE;
80✔
2332
    }
2333
    if (state->frame_count > (size_t)INT_MAX) {
1,140!
2334
        sixel_helper_set_additional_message(
×
2335
            "load_with_coregraphics: frame count is too large.");
2336
        return SIXEL_BAD_INPUT;
×
2337
    }
2338
    state->total_frames = (int)state->frame_count;
1,140✔
2339

2340
    state->props = CGImageSourceCopyProperties(state->source, NULL);
1,140✔
2341
    if (state->props != NULL) {
1,140!
2342
        state->source_orientation = coregraphics_resolve_exif_orientation(
1,140✔
2343
            state->props,
1,140✔
2344
            state->source_orientation);
1,140✔
2345
        /*
2346
         * Treat multi-frame decoding as animation only when the source
2347
         * exposes known animation dictionaries. This keeps multi-size ICO
2348
         * decoding static while enabling APNG/WebP/HEICS animation.
2349
         */
2350
        if (coregraphics_get_animation_keys(state->props,
2,280✔
2351
                                            state->frame_count,
1,140✔
2352
                                            &anim_dict,
2353
                                            &anim_loop_key,
2354
                                            NULL,
2355
                                            NULL)) {
2356
            if (state->frame_count > 1u) {
388✔
2357
                state->is_animation_container = 1;
352✔
2358
            }
352✔
2359
            state->anim_loop_count = coregraphics_dictionary_get_int(
388✔
2360
                anim_dict,
388✔
2361
                anim_loop_key,
388✔
2362
                state->anim_loop_count);
388✔
2363
        }
388✔
2364
    }
1,140✔
2365

2366
    if (state->is_animation_container != 0) {
1,238✔
2367
        /*
2368
         * Keep start-frame controls animation-only so static decode paths do
2369
         * not reject malformed env values.
2370
         */
2371
        if (state->start_frame_no_set != 0) {
352✔
2372
            state->start_frame_no = state->start_frame_no_override;
128✔
2373
        } else {
128✔
2374
            status = coregraphics_parse_animation_start_frame_no(
224✔
2375
                &state->start_frame_no);
224✔
2376
            if (status != SIXEL_OK) {
224!
2377
                return status;
×
2378
            }
2379
        }
2380
        if (state->start_frame_no != INT_MIN) {
352✔
2381
            status = coregraphics_resolve_animation_start_frame_no(
128✔
2382
                state->start_frame_no,
128✔
2383
                state->total_frames,
128✔
2384
                &state->resolved_start_frame_no);
128✔
2385
            if (status != SIXEL_OK) {
128✔
2386
                return status;
16✔
2387
            }
2388
        }
112✔
2389
    }
336✔
2390

2391
    if (state->frame_cache_enabled != 0) {
1,123✔
2392
        if (state->fstatic != 0 || state->is_animation_container == 0) {
1,116✔
2393
            state->frame_cache_enabled = 0;
868✔
2394
        }
868✔
2395
    }
1,116✔
2396
    if (state->fstatic == 0 && state->is_animation_container != 0) {
1,144✔
2397
        state->metadata_slots = (size_t)state->total_frames;
256✔
2398
    } else {
256✔
2399
        /*
2400
         * Static and non-animation paths emit one selected frame, so keep
2401
         * metadata caches to a single slot instead of total frame count.
2402
         */
2403
        state->metadata_slots = 1u;
868✔
2404
    }
2405

2406
    return SIXEL_OK;
1,124✔
2407
}
1,236✔
2408

2409
static SIXELSTATUS
2410
coregraphics_prepare_metadata_slots(coregraphics_loader_state_t *state)
1,124✔
2411
{
2412
    if (state == NULL || state->frame == NULL) {
1,124!
2413
        return SIXEL_BAD_ARGUMENT;
×
2414
    }
2415
    if (state->metadata_slots == 0u) {
1,124✔
2416
        sixel_helper_set_additional_message(
×
2417
            "load_with_coregraphics: frame metadata is too large.");
2418
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
2419
    }
2420
    if (state->metadata_slots > 1u &&
1,124!
2421
        state->metadata_slots > SIZE_MAX / sizeof(*state->frame_meta_slots)) {
256✔
2422
        sixel_helper_set_additional_message(
×
2423
            "load_with_coregraphics: frame metadata is too large.");
2424
        return SIXEL_BAD_INTEGER_OVERFLOW;
×
2425
    }
2426
    if (state->metadata_slots > 1u) {
1,124✔
2427
        state->frame_meta_slots = (coregraphics_frame_meta_slot_t *)
256✔
2428
            sixel_allocator_calloc(state->frame->allocator,
512✔
2429
                                   state->metadata_slots,
256✔
2430
                                   sizeof(*state->frame_meta_slots));
2431
        if (state->frame_meta_slots == NULL) {
256✔
2432
            sixel_helper_set_additional_message(
×
2433
                "load_with_coregraphics: sixel_allocator_calloc() failed.");
2434
            return SIXEL_BAD_ALLOCATION;
×
2435
        }
2436
        state->active_meta_slots = state->frame_meta_slots;
256✔
2437
    } else {
256✔
2438
        state->single_meta_slot.orientation = state->source_orientation;
868✔
2439
        state->active_meta_slots = &state->single_meta_slot;
868✔
2440
    }
2441
    if (state->frame_cache_enabled != 0) {
1,124✔
2442
        if ((size_t)state->total_frames > SIZE_MAX /
248!
2443
            sizeof(*state->frame_cache_slots)) {
2444
            sixel_helper_set_additional_message(
×
2445
                "load_with_coregraphics: frame metadata is too large.");
2446
            return SIXEL_BAD_INTEGER_OVERFLOW;
×
2447
        }
2448
        state->frame_cache_slots = (coregraphics_cache_slot_t *)
248✔
2449
            sixel_allocator_calloc(state->frame->allocator,
496✔
2450
                                   (size_t)state->total_frames,
248✔
2451
                                   sizeof(*state->frame_cache_slots));
2452
        if (state->frame_cache_slots == NULL) {
248✔
2453
            sixel_helper_set_additional_message(
×
2454
                "load_with_coregraphics: sixel_allocator_calloc() failed.");
2455
            return SIXEL_BAD_ALLOCATION;
×
2456
        }
2457
    }
248✔
2458
    return SIXEL_OK;
1,124✔
2459
}
1,124✔
2460

2461
static SIXELSTATUS
2462
coregraphics_prepare_frame_metadata(coregraphics_loader_state_t *state)
1,692✔
2463
{
2464
    SIXELSTATUS status;
1,058✔
2465
    coregraphics_frame_meta_slot_t *slot;
1,058✔
2466
    CFDictionaryRef frame_anim_dict;
1,058✔
2467
    CFStringRef frame_loop_key;
1,058✔
2468
    CFStringRef frame_delay_key;
1,058✔
2469
    CFStringRef frame_unclamped_delay_key;
1,058✔
2470

2471
    status = SIXEL_OK;
1,692✔
2472
    slot = NULL;
1,692✔
2473
    frame_anim_dict = NULL;
1,692✔
2474
    frame_loop_key = NULL;
1,692✔
2475
    frame_delay_key = NULL;
1,692✔
2476
    frame_unclamped_delay_key = NULL;
1,692✔
2477
    if (state == NULL || state->active_meta_slots == NULL) {
1,692!
2478
        return SIXEL_BAD_ARGUMENT;
×
2479
    }
2480

2481
    slot = &state->active_meta_slots[state->frame_meta_slot];
1,692✔
2482
    if (slot->props_ready == 0u) {
1,692✔
2483
        status = coregraphics_check_cancel(state->context);
1,484✔
2484
        if (status != SIXEL_OK) {
1,484!
2485
            return status;
×
2486
        }
2487
        state->frame_props = CGImageSourceCopyPropertiesAtIndex(
1,484✔
2488
            state->source,
1,484✔
2489
            (size_t)state->frame_index,
1,484✔
2490
            NULL);
2491
        slot->delay = 0;
1,484✔
2492
        if (state->frame_props != NULL) {
1,484!
2493
            if (coregraphics_get_animation_keys(state->frame_props,
2,968✔
2494
                                                state->frame_count,
1,484✔
2495
                                                &frame_anim_dict,
2496
                                                &frame_loop_key,
2497
                                                &frame_delay_key,
2498
                                                &frame_unclamped_delay_key)) {
2499
                state->anim_loop_count = coregraphics_dictionary_get_int(
732✔
2500
                    frame_anim_dict,
732✔
2501
                    frame_loop_key,
732✔
2502
                    state->anim_loop_count);
732✔
2503
                coregraphics_resolve_animation_delay_cs(
732✔
2504
                    frame_anim_dict,
732✔
2505
                    frame_unclamped_delay_key,
732✔
2506
                    frame_delay_key,
732✔
2507
                    &slot->delay);
732✔
2508
            }
732✔
2509
        }
1,484✔
2510
        slot->orientation = coregraphics_resolve_exif_orientation(
1,484✔
2511
            state->frame_props,
1,484✔
2512
            state->source_orientation);
1,484✔
2513
        slot->props_ready = 1u;
1,484✔
2514
    }
1,484✔
2515
    state->frame_orientation = slot->orientation;
1,692✔
2516
    return SIXEL_OK;
1,692✔
2517
}
1,692✔
2518

2519
static SIXELSTATUS
2520
coregraphics_process_single_frame(coregraphics_loader_state_t *state)
1,692✔
2521
{
2522
    SIXELSTATUS status;
1,058✔
2523
    coregraphics_frame_meta_slot_t *slot;
1,058✔
2524
    coregraphics_cache_slot_t *cache_slot;
1,058✔
2525

2526
    status = SIXEL_OK;
1,692✔
2527
    slot = NULL;
1,692✔
2528
    cache_slot = NULL;
1,692✔
2529
    if (state == NULL || state->active_meta_slots == NULL) {
1,692!
2530
        return SIXEL_BAD_ARGUMENT;
×
2531
    }
2532

2533
    slot = &state->active_meta_slots[state->frame_meta_slot];
1,692✔
2534
    cache_slot = NULL;
1,692✔
2535
    if (state->frame_cache_slots != NULL) {
1,692✔
2536
        cache_slot = &state->frame_cache_slots[(size_t)state->frame_index];
792✔
2537
    }
792✔
2538
    state->emit_frame = NULL;
1,580✔
2539
    state->decode_frame = NULL;
1,580✔
2540
    state->frame_cache_keep = 0;
1,580✔
2541
    state->frame_cache_decision_pending = 0;
1,580✔
2542
    state->release_emit_frame = 0;
1,580✔
2543
    state->cache_hit = 0;
1,580✔
2544
    if (cache_slot != NULL && cache_slot->frame != NULL) {
1,580✔
2545
        state->emit_frame = cache_slot->frame;
168✔
2546
        state->cache_hit = 1;
168✔
2547
    }
168✔
2548

2549
    status = coregraphics_prepare_frame_metadata(state);
1,692✔
2550
    if (status != SIXEL_OK) {
1,692!
2551
        return status;
×
2552
    }
2553

2554
    if (state->cache_hit == 0) {
1,692✔
2555
        status = coregraphics_check_cancel(state->context);
1,524✔
2556
        if (status != SIXEL_OK) {
1,524!
2557
            return status;
×
2558
        }
2559
        state->image = CGImageSourceCreateImageAtIndex(
1,524✔
2560
            state->source,
1,524✔
2561
            (size_t)state->frame_index,
1,524✔
2562
            NULL);
2563
        if (state->image == NULL) {
1,524✔
2564
            sixel_helper_set_additional_message(
8✔
2565
                "load_with_coregraphics: "
2566
                "CGImageSourceCreateImageAtIndex failed.");
2567
            return SIXEL_FALSE;
8✔
2568
        }
2569

2570
        if (cache_slot != NULL) {
1,516✔
2571
            if (cache_slot->decided == 0u &&
624!
2572
                state->frame_cache_used_bytes >=
1,200✔
2573
                state->frame_cache_max_bytes) {
600✔
2574
                /*
2575
                 * Once cache usage reaches the configured cap, every
2576
                 * remaining frame must bypass cache. Mark it decided early so
2577
                 * later loops skip temporary-frame probes.
2578
                 */
2579
                cache_slot->decided = 1u;
×
2580
            }
×
2581
            if (cache_slot->decided == 0u) {
624✔
2582
                status = sixel_frame_create_from_factory(
600✔
2583
                    &state->cached_frame_tmp,
600✔
2584
                    state->allocator);
600✔
2585
                if (SIXEL_FAILED(status)) {
600!
2586
                    return status;
×
2587
                }
2588
                state->decode_frame = state->cached_frame_tmp;
600✔
2589
                state->frame_cache_decision_pending = 1;
600✔
2590
            } else {
600✔
2591
                state->decode_frame = state->frame;
24✔
2592
            }
2593
        } else {
624✔
2594
            state->decode_frame = state->frame;
892✔
2595
        }
2596

2597
        if (slot->decode_hint_ready == 0u) {
1,516✔
2598
            slot->is_indexed = coregraphics_image_is_indexed(state->image);
1,476✔
2599
            slot->has_alpha = coregraphics_image_has_alpha(
1,476✔
2600
                state->image,
1,476✔
2601
                state->frame_props);
1,476✔
2602
            slot->promote_float32 = coregraphics_should_promote_float32(
1,476✔
2603
                state->image,
1,476✔
2604
                state->frame_props);
1,476✔
2605
            slot->decode_hint_ready = 1u;
1,476✔
2606
        }
1,476✔
2607

2608
        state->image_width = CGImageGetWidth(state->image);
1,516✔
2609
        state->image_height = CGImageGetHeight(state->image);
1,516✔
2610
        if (state->image_width > (size_t)INT_MAX) {
1,516!
2611
            sixel_helper_set_additional_message(
×
2612
                "load_with_coregraphics: given width parameter is too"
2613
                " huge.");
2614
            return SIXEL_BAD_INPUT;
×
2615
        }
2616
        if (state->image_height > (size_t)INT_MAX) {
1,516!
2617
            sixel_helper_set_additional_message(
×
2618
                "load_with_coregraphics: given height parameter is too"
2619
                " huge.");
2620
            return SIXEL_BAD_INPUT;
×
2621
        }
2622
        state->decode_frame->width = (int)state->image_width;
1,516✔
2623
        state->decode_frame->height = (int)state->image_height;
1,516✔
2624
        if (state->image_width > (size_t)SIXEL_WIDTH_LIMIT) {
1,516!
2625
            sixel_helper_set_additional_message(
×
2626
                "load_with_coregraphics: given width parameter is too"
2627
                " huge.");
2628
            return SIXEL_BAD_INPUT;
×
2629
        }
2630
        if (state->image_height > (size_t)SIXEL_HEIGHT_LIMIT) {
1,516!
2631
            sixel_helper_set_additional_message(
×
2632
                "load_with_coregraphics: given height parameter is too"
2633
                " huge.");
2634
            return SIXEL_BAD_INPUT;
×
2635
        }
2636
        if (state->decode_frame->width <= 0) {
1,516!
2637
            sixel_helper_set_additional_message(
×
2638
                "load_with_coregraphics: an invalid width parameter"
2639
                " detected.");
2640
            return SIXEL_BAD_INPUT;
×
2641
        }
2642
        if (state->decode_frame->height <= 0) {
1,516!
2643
            sixel_helper_set_additional_message(
×
2644
                "load_with_coregraphics: an invalid height parameter"
2645
                " detected.");
2646
            return SIXEL_BAD_INPUT;
×
2647
        }
2648
        if ((size_t)state->decode_frame->width >
3,032!
2649
            SIZE_MAX / (size_t)state->decode_frame->height) {
1,516✔
2650
            sixel_helper_set_additional_message(
×
2651
                "load_with_coregraphics: too large image.");
2652
            return SIXEL_RUNTIME_ERROR;
×
2653
        }
2654

2655
        coregraphics_reset_frame_storage(state->decode_frame);
1,516✔
2656
        status = coregraphics_try_handle_indexed_frame(
1,516✔
2657
            state->chunk,
1,516✔
2658
            state->decode_frame,
1,516✔
2659
            state->image,
1,516✔
2660
            state->frame_props,
1,516✔
2661
            &state->png_trns_chunk_cache,
1,516✔
2662
            state->fuse_palette,
1,516✔
2663
            state->reqcolors,
1,516✔
2664
            state->bgcolor,
1,516✔
2665
            &state->indexed_handled,
1,516✔
2666
            &state->force_alpha_from_indexed);
1,516✔
2667
        if (SIXEL_FAILED(status)) {
1,516✔
2668
            return status;
16✔
2669
        }
2670
        if (state->indexed_handled == 0) {
1,500✔
2671
            status = coregraphics_check_cancel(state->context);
1,356✔
2672
            if (status != SIXEL_OK) {
1,356!
2673
                return status;
×
2674
            }
2675
            state->has_alpha_like = state->force_alpha_from_indexed != 0 ||
1,356!
2676
                slot->has_alpha != 0;
1,356✔
2677
            state->promote_float32 = slot->promote_float32;
1,356✔
2678
            if (state->promote_float32 != 0) {
1,356✔
2679
                status = coregraphics_decode_float32_frame(
24✔
2680
                    state->decode_frame,
24✔
2681
                    state->image,
24✔
2682
                    state->bgcolor,
24✔
2683
                    state->has_alpha_like);
24✔
2684
            } else {
24✔
2685
                status = coregraphics_decode_rgba8_frame(
1,332✔
2686
                    state->decode_frame,
1,332✔
2687
                    state->image,
1,332✔
2688
                    state->bgcolor,
1,332✔
2689
                    state->has_alpha_like,
1,332✔
2690
                    &state->rgba8_lut_cache);
1,332✔
2691
            }
2692
            if (SIXEL_FAILED(status)) {
1,356!
2693
                return status;
×
2694
            }
2695
        }
1,356✔
2696
        if (state->enable_orientation != 0 &&
1,528!
2697
            state->frame_orientation >= 2 &&
1,468✔
2698
            state->frame_orientation <= 8) {
32✔
2699
            status = loader_frame_apply_orientation(
32✔
2700
                state->decode_frame,
32✔
2701
                state->frame_orientation);
32✔
2702
            if (SIXEL_FAILED(status)) {
32!
2703
                return status;
×
2704
            }
2705
        }
32✔
2706

2707
        if (cache_slot != NULL) {
1,500✔
2708
            if (state->frame_cache_decision_pending != 0) {
624✔
2709
                state->frame_cache_frame_bytes = 0u;
600✔
2710
                if (SIXEL_FAILED(coregraphics_frame_measure_storage(
600!
2711
                        state->decode_frame,
2712
                        &state->frame_cache_frame_bytes))) {
2713
                    sixel_helper_set_additional_message(
×
2714
                        "load_with_coregraphics: failed to estimate "
2715
                        "decoded frame size.");
2716
                    return SIXEL_BAD_INTEGER_OVERFLOW;
×
2717
                }
2718
                if (state->frame_cache_frame_bytes <=
1,200✔
2719
                    state->frame_cache_max_bytes &&
1,200✔
2720
                    state->frame_cache_used_bytes <=
1,168✔
2721
                    state->frame_cache_max_bytes -
1,168✔
2722
                    state->frame_cache_frame_bytes) {
584✔
2723
                    state->frame_cache_keep = 1;
576✔
2724
                }
576✔
2725
                cache_slot->decided = 1u;
600✔
2726
                if (state->frame_cache_keep != 0) {
600✔
2727
                    status = coregraphics_frame_set_handoff_shareable(
576✔
2728
                        state->decode_frame, 1);
576✔
2729
                    if (SIXEL_FAILED(status)) {
576!
2730
                        return status;
×
2731
                    }
2732
                    cache_slot->frame = state->decode_frame;
576✔
2733
                    state->frame_cache_used_bytes +=
576✔
2734
                        state->frame_cache_frame_bytes;
576✔
2735
                    state->emit_frame = state->decode_frame;
576✔
2736
                    state->cached_frame_tmp = NULL;
576✔
2737
                } else {
576✔
2738
                    status = coregraphics_frame_set_handoff_shareable(
24✔
2739
                        state->decode_frame, 0);
24✔
2740
                    if (SIXEL_FAILED(status)) {
24!
2741
                        return status;
×
2742
                    }
2743
                    state->emit_frame = state->decode_frame;
24✔
2744
                    state->release_emit_frame = 1;
24✔
2745
                }
2746
            } else {
600✔
2747
                status = coregraphics_frame_set_handoff_shareable(
24✔
2748
                    state->decode_frame, 0);
24✔
2749
                if (SIXEL_FAILED(status)) {
24!
2750
                    return status;
×
2751
                }
2752
                state->emit_frame = state->decode_frame;
24✔
2753
            }
2754
        } else {
624✔
2755
            state->emit_frame = state->decode_frame;
876✔
2756
        }
2757
    }
1,500✔
2758

2759
    if (state->frame_props != NULL) {
1,652✔
2760
        CFRelease(state->frame_props);
1,460✔
2761
        state->frame_props = NULL;
1,460✔
2762
    }
1,460✔
2763
    if (state->image != NULL) {
1,647✔
2764
        CGImageRelease(state->image);
1,500✔
2765
        state->image = NULL;
1,500✔
2766
    }
1,500✔
2767
    if (state->emit_frame == NULL && cache_slot != NULL) {
1,834!
2768
        state->emit_frame = cache_slot->frame;
×
2769
    }
×
2770
    return SIXEL_OK;
1,668✔
2771
}
1,692✔
2772

2773
static SIXELSTATUS
2774
coregraphics_emit_frame(coregraphics_loader_state_t *state)
1,668✔
2775
{
2776
    SIXELSTATUS status;
1,043✔
2777
    sixel_frame_t *replay_frame;
1,043✔
2778
    coregraphics_frame_meta_slot_t *slot;
1,043✔
2779

2780
    status = SIXEL_OK;
1,668✔
2781
    replay_frame = NULL;
1,668✔
2782
    slot = NULL;
1,668✔
2783
    if (state == NULL || state->active_meta_slots == NULL) {
1,668!
2784
        return SIXEL_BAD_ARGUMENT;
×
2785
    }
2786
    if (state->emit_frame == NULL) {
1,668✔
2787
        sixel_helper_set_additional_message(
×
2788
            "load_with_coregraphics: failed to select output frame.");
2789
        return SIXEL_FALSE;
×
2790
    }
2791

2792
    slot = &state->active_meta_slots[state->frame_meta_slot];
1,668✔
2793
    if (state->cache_hit != 0) {
1,668✔
2794
        /*
2795
         * Replay-cache frames may still be read by the pipeline worker from an
2796
         * earlier loop.  Keep the cached storage immutable and stamp per-emit
2797
         * timeline metadata on a private clone before by-ref handoff.
2798
         */
2799
        status = coregraphics_frame_clone(
168✔
2800
            state->emit_frame,
168✔
2801
            state->allocator,
168✔
2802
            &replay_frame);
2803
        if (SIXEL_FAILED(status)) {
168!
2804
            return status;
×
2805
        }
2806
        status = coregraphics_frame_set_handoff_shareable(replay_frame, 1);
168✔
2807
        if (SIXEL_FAILED(status)) {
168!
2808
            sixel_frame_unref(replay_frame);
×
2809
            return status;
×
2810
        }
2811
        state->emit_frame = replay_frame;
168✔
2812
        state->release_emit_frame = 1;
168✔
2813
    }
168✔
2814
    sixel_frame_set_frame_no(state->emit_frame, state->frames_in_loop);
1,646✔
2815
    sixel_frame_set_loop_count(state->emit_frame, state->loop_no);
1,646✔
2816
    sixel_frame_set_delay(state->emit_frame, slot->delay);
1,646✔
2817
    sixel_frame_set_multiframe(
1,668✔
2818
        state->emit_frame,
1,646✔
2819
        state->fstatic == 0 &&
1,646✔
2820
        state->frame_count > 1u &&
2,589✔
2821
        state->is_animation_container != 0);
840✔
2822
    status = state->fn_load(state->emit_frame, state->context);
1,668✔
2823
    if (status != SIXEL_OK) {
1,668✔
2824
        return status;
264✔
2825
    }
2826

2827
    return coregraphics_check_cancel(state->context);
1,404✔
2828
}
1,668✔
2829

2830
static void
2831
coregraphics_cleanup_state(coregraphics_loader_state_t *state)
1,236✔
2832
{
2833
    if (state == NULL) {
1,236!
2834
        return;
×
2835
    }
2836
    if (state->release_emit_frame != 0 &&
1,252!
2837
        state->emit_frame != NULL &&
72✔
2838
        state->emit_frame != state->cached_frame_tmp) {
16✔
2839
        sixel_frame_unref(state->emit_frame);
16✔
2840
        state->emit_frame = NULL;
16✔
2841
    }
16✔
2842
    if (state->cached_frame_tmp != NULL) {
1,086!
2843
        sixel_frame_unref(state->cached_frame_tmp);
×
2844
        state->cached_frame_tmp = NULL;
×
2845
    }
×
2846
    if (state->frame_props != NULL) {
1,085✔
2847
        CFRelease(state->frame_props);
24✔
2848
        state->frame_props = NULL;
24✔
2849
    }
24✔
2850
    if (state->image != NULL) {
1,086✔
2851
        CGImageRelease(state->image);
16✔
2852
        state->image = NULL;
16✔
2853
    }
16✔
2854
    if (state->source != NULL) {
1,234✔
2855
        CFRelease(state->source);
1,220✔
2856
        state->source = NULL;
1,220✔
2857
    }
1,220✔
2858
    if (state->props != NULL) {
1,244✔
2859
        CFRelease(state->props);
1,140✔
2860
        state->props = NULL;
1,140✔
2861
    }
1,140✔
2862
    if (state->data != NULL) {
1,234✔
2863
        CFRelease(state->data);
1,220✔
2864
        state->data = NULL;
1,220✔
2865
    }
1,220✔
2866
    if (state->frame != NULL) {
1,235✔
2867
        if (state->frame_cache_slots != NULL) {
1,228✔
2868
            for (state->prefetch_index = 0u;
960✔
2869
                 state->prefetch_index < (size_t)state->total_frames;
960✔
2870
                 ++state->prefetch_index) {
712✔
2871
                if (state->frame_cache_slots[state->prefetch_index].frame !=
712✔
2872
                    NULL) {
2873
                    sixel_frame_unref(
576✔
2874
                        state->frame_cache_slots[state->prefetch_index].frame);
576✔
2875
                    state->frame_cache_slots[state->prefetch_index].frame =
576✔
2876
                        NULL;
2877
                }
576✔
2878
            }
712✔
2879
        }
248✔
2880
        sixel_allocator_free(state->frame->allocator, state->frame_cache_slots);
1,228✔
2881
        sixel_allocator_free(state->frame->allocator, state->frame_meta_slots);
1,228✔
2882
        sixel_frame_unref(state->frame);
1,228✔
2883
        state->frame = NULL;
1,228✔
2884
    }
1,228✔
2885
}
1,236✔
2886

2887
static SIXELSTATUS
2888
load_with_coregraphics(
1,236✔
2889
    sixel_chunk_t const       /* in */     *pchunk,
2890
    sixel_allocator_t         /* in */     *allocator,
2891
    int                       /* in */     fstatic,
2892
    int                       /* in */     fuse_palette,
2893
    int                       /* in */     reqcolors,
2894
    int                       /* in */     enable_orientation,
2895
    unsigned char             /* in */     *bgcolor,
2896
    int                       /* in */     loop_control,
2897
    int                       /* in */     start_frame_no_set,
2898
    int                       /* in */     start_frame_no_override,
2899
    sixel_load_image_function /* in */     fn_load,
2900
    void                      /* in/out */ *context)
2901
{
2902
    SIXELSTATUS status;
773✔
2903
    coregraphics_loader_state_t state;
773✔
2904

2905
    status = SIXEL_FALSE;
1,236✔
2906
    coregraphics_loader_state_init(&state,
1,236✔
2907
                                   pchunk,
1,236✔
2908
                                   allocator,
1,236✔
2909
                                   fstatic,
1,236✔
2910
                                   fuse_palette,
1,236✔
2911
                                   reqcolors,
1,236✔
2912
                                   enable_orientation,
1,236✔
2913
                                   bgcolor,
1,236✔
2914
                                   loop_control,
1,236✔
2915
                                   start_frame_no_set,
1,236✔
2916
                                   start_frame_no_override,
1,236✔
2917
                                   fn_load,
1,236✔
2918
                                   context);
1,236✔
2919

2920
    status = coregraphics_prepare_source_and_root_metadata(&state);
1,236✔
2921
    if (SIXEL_FAILED(status)) {
1,236✔
2922
        goto end;
112✔
2923
    }
2924
    status = coregraphics_prepare_metadata_slots(&state);
1,124✔
2925
    if (SIXEL_FAILED(status)) {
1,124!
2926
        goto end;
×
2927
    }
2928

2929
    sixel_frame_set_multiframe(
1,124✔
2930
        state.frame,
1,124✔
2931
        state.fstatic == 0 &&
1,124✔
2932
        state.frame_count > 1u &&
1,480✔
2933
        state.is_animation_container != 0);
272✔
2934

2935
    for (;;) {
1,236✔
2936
        status = coregraphics_check_cancel(state.context);
1,236✔
2937
        if (status != SIXEL_OK) {
1,236✔
2938
            goto end;
×
2939
        }
2940

2941
        state.frame_index = 0;
1,236✔
2942
        if (state.loop_no == 0 && state.resolved_start_frame_no != INT_MIN) {
1,236✔
2943
            /*
2944
             * Apply start-frame override only on the first loop. Later loops
2945
             * always restart from frame 0 to preserve normal replay behavior.
2946
             */
2947
            state.frame_index = state.resolved_start_frame_no;
112✔
2948
        }
112✔
2949
        state.frames_in_loop = 0;
1,236✔
2950

2951
        while (state.frame_index < state.total_frames) {
2,036✔
2952
            status = coregraphics_check_cancel(state.context);
1,692✔
2953
            if (status != SIXEL_OK) {
1,692✔
2954
                goto end;
×
2955
            }
2956
            if (state.fstatic == 0 && state.is_animation_container != 0) {
1,692✔
2957
                state.frame_meta_slot = (size_t)state.frame_index;
824✔
2958
            } else {
824✔
2959
                state.frame_meta_slot = 0u;
868✔
2960
            }
2961

2962
            status = coregraphics_process_single_frame(&state);
1,692✔
2963
            if (status != SIXEL_OK) {
1,692✔
2964
                goto end;
24✔
2965
            }
2966
            status = coregraphics_emit_frame(&state);
1,668✔
2967
            if (status != SIXEL_OK) {
1,668✔
2968
                goto end;
264✔
2969
            }
2970

2971
            ++state.frame_index;
1,404✔
2972
            ++state.frames_in_loop;
1,404✔
2973
            if (state.release_emit_frame != 0) {
1,404✔
2974
                sixel_frame_unref(state.emit_frame);
176✔
2975
                state.emit_frame = NULL;
176✔
2976
                state.cached_frame_tmp = NULL;
176✔
2977
            }
176✔
2978
            if (state.fstatic != 0 || state.is_animation_container == 0) {
1,383✔
2979
                status = SIXEL_OK;
604✔
2980
                goto end;
604✔
2981
            }
2982
        }
2983

2984
        ++state.loop_no;
344✔
2985
        state.stop_loop = 0;
344✔
2986
        if (state.total_frames <= 1 ||
344!
2987
            state.loop_control == SIXEL_LOOP_DISABLE) {
344✔
2988
            state.stop_loop = 1;
152✔
2989
        } else if (state.loop_control == SIXEL_LOOP_AUTO) {
344✔
2990
            if (state.anim_loop_count < 0) {
160✔
2991
                state.stop_loop = 1;
×
2992
            } else if (state.anim_loop_count > 0 &&
160!
2993
                       state.loop_no >= state.anim_loop_count) {
160✔
2994
                state.stop_loop = 1;
80✔
2995
            }
80✔
2996
        }
160✔
2997
        if (state.stop_loop != 0) {
344✔
2998
            break;
232✔
2999
        }
3000
    }
3001

3002
    status = SIXEL_OK;
232✔
3003

3004
end:
3005
    coregraphics_cleanup_state(&state);
1,236✔
3006
    return status;
2,009✔
3007
}
773✔
3008

3009

3010
static void
3011
sixel_loader_coregraphics_ref(sixel_loader_component_t *component)
24,194✔
3012
{
3013
    sixel_loader_coregraphics_component_t *self;
15,255✔
3014

3015
    self = NULL;
24,194✔
3016
    if (component == NULL) {
24,194✔
3017
        return;
×
3018
    }
3019

3020
    self = (sixel_loader_coregraphics_component_t *)component;
24,194✔
3021
    ++self->ref;
24,194✔
3022
}
24,194!
3023

3024
static void
3025
sixel_loader_coregraphics_unref(sixel_loader_component_t *component)
48,676✔
3026
{
3027
    sixel_loader_coregraphics_component_t *self;
30,690✔
3028
    sixel_allocator_t *allocator;
30,690✔
3029

3030
    self = NULL;
48,676✔
3031
    allocator = NULL;
48,676✔
3032
    if (component == NULL) {
48,676✔
3033
        return;
×
3034
    }
3035

3036
    self = (sixel_loader_coregraphics_component_t *)component;
48,676✔
3037
    if (self->ref == 0u) {
48,676✔
3038
        return;
×
3039
    }
3040

3041
    --self->ref;
48,676✔
3042
    if (self->ref > 0u) {
48,676✔
3043
        return;
24,194✔
3044
    }
3045

3046
    allocator = self->allocator;
24,482✔
3047
    sixel_allocator_free(allocator, self);
24,482✔
3048
    sixel_allocator_unref(allocator);
24,482✔
3049
}
48,676!
3050

3051
static SIXELSTATUS
3052
sixel_loader_coregraphics_setopt(sixel_loader_component_t *component,
460,846✔
3053
                                 int option,
3054
                                 void const *value)
3055
{
3056
    sixel_loader_coregraphics_component_t *self;
290,570✔
3057
    int const *flag;
290,570✔
3058
    unsigned char const *color;
290,570✔
3059

3060
    self = NULL;
460,846✔
3061
    flag = NULL;
460,846✔
3062
    color = NULL;
460,846✔
3063
    if (component == NULL) {
460,846✔
3064
        return SIXEL_BAD_ARGUMENT;
×
3065
    }
3066

3067
    self = (sixel_loader_coregraphics_component_t *)component;
460,846✔
3068
    switch (option) {
460,846✔
3069
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
3070
        flag = (int const *)value;
24,482✔
3071
        self->fstatic = flag != NULL ? *flag : 0;
24,482✔
3072
        return SIXEL_OK;
24,482✔
3073
    case SIXEL_LOADER_OPTION_USE_PALETTE:
3074
        flag = (int const *)value;
24,482✔
3075
        self->fuse_palette = flag != NULL ? *flag : 0;
24,482✔
3076
        return SIXEL_OK;
24,482✔
3077
    case SIXEL_LOADER_OPTION_REQCOLORS:
3078
        flag = (int const *)value;
24,482✔
3079
        if (flag != NULL) {
24,482!
3080
            self->reqcolors = *flag;
24,482✔
3081
        }
24,482✔
3082
        return SIXEL_OK;
24,482✔
3083
    case SIXEL_LOADER_OPTION_BGCOLOR:
3084
        if (value == NULL) {
24,258✔
3085
            self->has_bgcolor = 0;
24,162✔
3086
            return SIXEL_OK;
24,162✔
3087
        }
3088
        color = (unsigned char const *)value;
96✔
3089
        self->bgcolor[0] = color[0];
96✔
3090
        self->bgcolor[1] = color[1];
96✔
3091
        self->bgcolor[2] = color[2];
96✔
3092
        self->has_bgcolor = 1;
96✔
3093
        return SIXEL_OK;
96✔
3094
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
3095
        flag = (int const *)value;
24,386✔
3096
        if (flag != NULL) {
24,386!
3097
            self->loop_control = *flag;
24,386✔
3098
        }
24,386✔
3099
        return SIXEL_OK;
24,386✔
3100
    case SIXEL_LOADER_OPTION_START_FRAME_NO:
3101
        if (value == NULL) {
24,234✔
3102
            self->has_start_frame_no = 0;
23,262✔
3103
            self->start_frame_no = INT_MIN;
23,262✔
3104
            return SIXEL_OK;
23,262✔
3105
        }
3106
        flag = (int const *)value;
972✔
3107
        self->start_frame_no = *flag;
972✔
3108
        self->has_start_frame_no = 1;
972✔
3109
        return SIXEL_OK;
972✔
3110
    case SIXEL_LOADER_COMPONENT_OPTION_COREGRAPHICS_ENABLE_ORIENTATION:
3111
        flag = (int const *)value;
24,194✔
3112
        self->enable_orientation = (flag == NULL || *flag != 0) ? 1 : 0;
24,194!
3113
        return SIXEL_OK;
24,194✔
3114
    default:
3115
        return SIXEL_OK;
290,328✔
3116
    }
3117
}
460,846✔
3118

3119
static SIXELSTATUS
3120
sixel_loader_coregraphics_load(sixel_loader_component_t *component,
1,236✔
3121
                               sixel_chunk_t const *chunk,
3122
                               sixel_load_image_function fn_load,
3123
                               void *context)
3124
{
3125
    sixel_loader_coregraphics_component_t *self;
773✔
3126
    unsigned char *bgcolor;
773✔
3127
    SIXELSTATUS status;
773✔
3128
    int header_job_id;
773✔
3129
    int decode_job_id;
773✔
3130
    sixel_loader_timeline_callback_state_t timeline_state;
773✔
3131

3132
    self = NULL;
1,236✔
3133
    bgcolor = NULL;
1,236✔
3134
    status = SIXEL_FALSE;
1,236✔
3135
    header_job_id = -1;
1,236✔
3136
    decode_job_id = -1;
1,236✔
3137
    if (component == NULL || chunk == NULL || fn_load == NULL) {
1,236!
3138
        return SIXEL_BAD_ARGUMENT;
×
3139
    }
3140

3141
    self = (sixel_loader_coregraphics_component_t *)component;
1,236✔
3142
    if (self->has_bgcolor) {
1,236✔
3143
        bgcolor = self->bgcolor;
24✔
3144
    }
24✔
3145

3146
    header_job_id = loader_timeline_phase_start("header/read");
1,236✔
3147
    decode_job_id = loader_timeline_phase_start("decode/pixels");
1,236✔
3148
    loader_timeline_callback_state_init(&timeline_state,
1,236✔
3149
                                        fn_load,
1,236✔
3150
                                        context,
1,236✔
3151
                                        header_job_id,
1,236✔
3152
                                        decode_job_id);
1,236✔
3153

3154
    status = load_with_coregraphics(chunk,
2,472✔
3155
                                    self->allocator,
1,236✔
3156
                                    self->fstatic,
1,236✔
3157
                                    self->fuse_palette,
1,236✔
3158
                                    self->reqcolors,
1,236✔
3159
                                    self->enable_orientation,
1,236✔
3160
                                    bgcolor,
1,236✔
3161
                                    self->loop_control,
1,236✔
3162
                                    self->has_start_frame_no,
1,236✔
3163
                                    self->start_frame_no,
1,236✔
3164
                                    loader_timeline_emit_frame_callback,
3165
                                    &timeline_state);
3166

3167
    loader_timeline_callback_close_header(&timeline_state, status);
1,236✔
3168
    loader_timeline_callback_close_decode(&timeline_state, status);
1,236✔
3169
    loader_timeline_optional_skip_if_unmarked("post/colorspace");
1,236✔
3170
    loader_timeline_optional_skip_if_unmarked("post/background");
1,236✔
3171
    loader_timeline_optional_skip_if_unmarked("post/icc");
1,236✔
3172

3173
    return status;
1,236✔
3174
}
1,236✔
3175

3176
static char const *
3177
sixel_loader_coregraphics_name(sixel_loader_component_t const *component)
25,142✔
3178
{
3179
    (void)component;
25,142✔
3180
    return "coregraphics";
25,142✔
3181
}
3182

3183
static int
3184
sixel_loader_coregraphics_predicate(sixel_loader_component_t *component,
292✔
3185
                                    sixel_chunk_t const *chunk)
3186
{
3187
    (void)component;
292✔
3188
    (void)chunk;
292✔
3189
    return 1;
292✔
3190
}
3191

3192
static sixel_loader_component_vtbl_t const g_sixel_loader_coregraphics_vtbl = {
3193
    sixel_loader_coregraphics_ref,
3194
    sixel_loader_coregraphics_unref,
3195
    sixel_loader_coregraphics_setopt,
3196
    sixel_loader_coregraphics_load,
3197
    sixel_loader_coregraphics_name,
3198
    sixel_loader_coregraphics_predicate
3199
};
3200

3201
SIXELSTATUS
3202
sixel_loader_coregraphics_new(sixel_allocator_t *allocator,
24,482✔
3203
                              void **ppcomponent)
3204
{
3205
    sixel_loader_coregraphics_component_t *self;
15,435✔
3206

3207
    self = NULL;
24,482✔
3208
    if (allocator == NULL || ppcomponent == NULL) {
24,482!
3209
        return SIXEL_BAD_ARGUMENT;
×
3210
    }
3211

3212
    *ppcomponent = NULL;
24,482✔
3213
    self = (sixel_loader_coregraphics_component_t *)
24,482✔
3214
        sixel_allocator_malloc(allocator, sizeof(*self));
24,482✔
3215
    if (self == NULL) {
24,482✔
3216
        return SIXEL_BAD_ALLOCATION;
×
3217
    }
3218

3219
    memset(self, 0, sizeof(*self));
24,482✔
3220
    self->base.vtbl = &g_sixel_loader_coregraphics_vtbl;
24,482✔
3221
    self->allocator = allocator;
24,482✔
3222
    self->ref = 1u;
24,482✔
3223
    self->reqcolors = SIXEL_PALETTE_MAX;
24,482✔
3224
    self->loop_control = SIXEL_LOOP_AUTO;
24,482✔
3225
    self->start_frame_no = INT_MIN;
24,482✔
3226
    self->enable_orientation = 1;
24,482✔
3227
    sixel_allocator_ref(allocator);
24,482✔
3228
    *ppcomponent = &self->base;
24,482✔
3229
    return SIXEL_OK;
24,482✔
3230
}
24,482✔
3231

3232
#endif  /* HAVE_COREGRAPHICS */
3233

3234
#if !HAVE_COREGRAPHICS
3235
/*
3236
 * Anchor a harmless symbol so the translation unit stays non-empty when
3237
 * CoreGraphics is unavailable.
3238
 */
3239
typedef int loader_coregraphics_disabled;
3240
#endif
3241

3242

3243
/* emacs Local Variables:      */
3244
/* emacs mode: c               */
3245
/* emacs tab-width: 4          */
3246
/* emacs indent-tabs-mode: nil */
3247
/* emacs c-basic-offset: 4     */
3248
/* emacs End:                  */
3249
/* vim: set expandtab ts=4 sts=4 sw=4 : */
3250
/* 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