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

saitoha / libsixel / 19990586813

06 Dec 2025 02:51PM UTC coverage: 43.588% (-0.2%) from 43.77%
19990586813

push

github

saitoha
fix: handle null background color in quicklook loader

10726 of 38713 branches covered (27.71%)

0 of 16 new or added lines in 1 file covered. (0.0%)

57 existing lines in 1 file now uncovered.

14720 of 33771 relevant lines covered (43.59%)

2905607.42 hits per line

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

25.97
/src/loader-quicklook.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
 * QuickLook thumbnail loader carved out of loader.c to keep macOS-specific
26
 * headers contained. The helper mirrors the previous flow: probe QuickLook,
27
 * request a thumbnail, and blit it into a sixel frame.
28
 */
29

30
#include "config.h"
31

32
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
33

34
#include <stdio.h>
35

36
#if HAVE_STRING_H
37
# include <string.h>
38
#endif
39

40
#include <CoreServices/CoreServices.h>
41
#include <QuickLook/QuickLook.h>
42

43
#include <sixel.h>
44

45
#include "chunk.h"
46
#include "frame.h"
47
#include "loader-common.h"
48
#include "loader-quicklook.h"
49

50
#if HAVE_QUICKLOOK_THUMBNAILING
51
CGImageRef
52
sixel_quicklook_thumbnail_create(CFURLRef url, CGSize max_size);
53
#endif
54

55
int
56
loader_quicklook_can_decode(sixel_chunk_t const *pchunk,
8✔
57
                            char const *filename)
58
{
59
    char const *path;
8✔
60
    CFStringRef path_ref;
8✔
61
    CFURLRef url;
8✔
62
    CGFloat max_dimension;
8✔
63
    CGSize max_size;
8✔
64
    CGImageRef image;
8✔
65
    int result;
8✔
66
    int hint;
8✔
67

68
    path = NULL;
8✔
69
    path_ref = NULL;
8✔
70
    url = NULL;
8✔
71
    image = NULL;
8✔
72
    result = 0;
8✔
73

74
    loader_thumbnailer_initialize_size_hint();
8✔
75

76
    if (pchunk != NULL && pchunk->source_path != NULL) {
8!
77
        path = pchunk->source_path;
6✔
78
    } else if (filename != NULL) {
8!
79
        path = filename;
×
80
    }
×
81

82
    if (path == NULL || strcmp(path, "-") == 0 ||
8!
83
            strstr(path, "://") != NULL) {
6✔
84
        return 0;
2✔
85
    }
86

87
    path_ref = CFStringCreateWithCString(kCFAllocatorDefault,
12✔
88
                                         path,
6✔
89
                                         kCFStringEncodingUTF8);
90
    if (path_ref == NULL) {
6!
91
        return 0;
×
92
    }
93

94
    url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
12✔
95
                                        path_ref,
6✔
96
                                        kCFURLPOSIXPathStyle,
97
                                        false);
98
    CFRelease(path_ref);
6✔
99
    path_ref = NULL;
6✔
100
    if (url == NULL) {
6!
101
        return 0;
×
102
    }
103

104
    hint = loader_thumbnailer_get_size_hint();
6✔
105
    if (hint > 0) {
6!
106
        max_dimension = (CGFloat)hint;
6✔
107
    } else {
6✔
108
        max_dimension = (CGFloat)loader_thumbnailer_get_default_size_hint();
×
109
    }
110
    max_size.width = max_dimension;
6✔
111
    max_size.height = max_dimension;
6✔
112

113
#if HAVE_QUICKLOOK_THUMBNAILING
114
    image = sixel_quicklook_thumbnail_create(url, max_size);
6✔
115
    if (image == NULL) {
6!
116
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
117
#  pragma clang diagnostic push
118
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
119
# endif
120
        image = QLThumbnailImageCreate(kCFAllocatorDefault,
12✔
121
                                       url,
6✔
122
                                       max_size,
123
                                       NULL);
124
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
125
#  pragma clang diagnostic pop
126
# endif
127
    }
6✔
128
#else
129
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
130
#  pragma clang diagnostic push
131
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
132
# endif
133
    image = QLThumbnailImageCreate(kCFAllocatorDefault,
134
                                   url,
135
                                   max_size,
136
                                   NULL);
137
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
138
#  pragma clang diagnostic pop
139
# endif
140
#endif
141

142
    if (image != NULL) {
6!
143
        result = 1;
×
144
        CGImageRelease(image);
×
145
        image = NULL;
×
146
    }
×
147

148
    CFRelease(url);
6✔
149
    url = NULL;
6✔
150

151
    return result;
6✔
152
}
8✔
153

154
int
155
loader_quicklook_can_decode_chunk(sixel_chunk_t const *pchunk)
5✔
156
{
157
    /*
158
     * Registry predicates receive only the chunk. This wrapper forwards to the
159
     * full probe while omitting any filename hint so the registry table keeps
160
     * type-safe pointers.
161
     */
162
    return loader_quicklook_can_decode(pchunk, NULL);
5✔
163
}
164

165
SIXELSTATUS
166
load_with_quicklook(
×
167
    sixel_chunk_t const       /* in */     *pchunk,
168
    int                       /* in */     fstatic,
169
    int                       /* in */     fuse_palette,
170
    int                       /* in */     reqcolors,
171
    unsigned char             /* in */     *bgcolor,
172
    int                       /* in */     loop_control,
173
    sixel_load_image_function /* in */     fn_load,
174
    void                      /* in/out */ *context)
175
{
176
    SIXELSTATUS status = SIXEL_FALSE;
×
177
    sixel_frame_t *frame = NULL;
×
178
    CFStringRef path = NULL;
×
179
    CFURLRef url = NULL;
×
180
    CGImageRef image = NULL;
×
181
    CGColorSpaceRef color_space = NULL;
×
182
    CGContextRef ctx = NULL;
×
183
    CGRect bounds;
×
184
    size_t stride;
×
185
    unsigned char fill_color[3];
×
NEW
186
    unsigned char default_bgcolor[3];
×
NEW
187
    unsigned char const *fill_source;
×
188
    CGFloat fill_r;
×
189
    CGFloat fill_g;
×
190
    CGFloat fill_b;
×
191
    CGFloat max_dimension;
×
192
    CGSize max_size;
×
193
    unsigned char *pixels;
×
194
    int hint;
×
195

196
    (void)fstatic;
×
197
    (void)fuse_palette;
×
198
    (void)reqcolors;
×
199
    (void)loop_control;
×
200

201
    if (pchunk == NULL || pchunk->source_path == NULL) {
×
202
        goto end;
×
203
    }
204

205
    loader_thumbnailer_initialize_size_hint();
×
206

207
    status = sixel_frame_new(&frame, pchunk->allocator);
×
208
    if (SIXEL_FAILED(status)) {
×
209
        goto end;
×
210
    }
211

212
    path = CFStringCreateWithCString(kCFAllocatorDefault,
×
213
                                     pchunk->source_path,
×
214
                                     kCFStringEncodingUTF8);
215
    if (path == NULL) {
×
216
        status = SIXEL_RUNTIME_ERROR;
×
217
        goto end;
×
218
    }
219

220
    url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
×
221
                                        path,
×
222
                                        kCFURLPOSIXPathStyle,
223
                                        false);
224
    if (url == NULL) {
×
225
        status = SIXEL_RUNTIME_ERROR;
×
226
        goto end;
×
227
    }
228

229
    hint = loader_thumbnailer_get_size_hint();
×
230
    if (hint > 0) {
×
231
        max_dimension = (CGFloat)hint;
×
232
    } else {
×
233
        max_dimension = (CGFloat)loader_thumbnailer_get_default_size_hint();
×
234
    }
235
    max_size.width = max_dimension;
×
236
    max_size.height = max_dimension;
×
237

238
#if HAVE_QUICKLOOK_THUMBNAILING
239
    image = sixel_quicklook_thumbnail_create(url, max_size);
×
240
    if (image == NULL) {
×
241
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
242
#  pragma clang diagnostic push
243
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
244
# endif
245
        image = QLThumbnailImageCreate(kCFAllocatorDefault,
×
246
                                       url,
×
247
                                       max_size,
248
                                       NULL);
249
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
250
#  pragma clang diagnostic pop
251
# endif
252
    }
×
253
#else
254
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
255
#  pragma clang diagnostic push
256
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
257
# endif
258
    image = QLThumbnailImageCreate(kCFAllocatorDefault,
259
                                   url,
260
                                   max_size,
261
                                   NULL);
262
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
263
#  pragma clang diagnostic pop
264
# endif
265
#endif
266

267
    if (image == NULL) {
×
268
        status = SIXEL_RUNTIME_ERROR;
×
269
        goto end;
×
270
    }
271

272
    bounds = CGRectMake(0.0, 0.0,
×
273
                        (CGFloat)CGImageGetWidth(image),
×
274
                        (CGFloat)CGImageGetHeight(image));
×
275
    frame->width = (int)bounds.size.width;
×
276
    frame->height = (int)bounds.size.height;
×
NEW
277
    frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
×
278
    /*
279
     * QuickLook renders into a premultiplied RGBA buffer. Keep a four-byte
280
     * stride so the pixel format matches the bitmap context layout.
281
     */
282
    stride = (size_t)frame->width * 4;
×
283

284
    /*
285
     * Background colors are optional for most loaders. QuickLook renders
286
     * into a bitmap that needs an explicit clear color, so choose a safe
287
     * default when callers did not supply one.
288
     */
NEW
289
    fill_source = bgcolor;
×
NEW
290
    if (fill_source == NULL) {
×
NEW
291
        default_bgcolor[0] = 0;
×
NEW
292
        default_bgcolor[1] = 0;
×
NEW
293
        default_bgcolor[2] = 0;
×
NEW
294
        fill_source = default_bgcolor;
×
NEW
295
    }
×
NEW
296
    fill_color[0] = fill_source[0];
×
NEW
297
    fill_color[1] = fill_source[1];
×
NEW
298
    fill_color[2] = fill_source[2];
×
NEW
299
    fill_r = (CGFloat)fill_color[0] / 255.0;
×
NEW
300
    fill_g = (CGFloat)fill_color[1] / 255.0;
×
NEW
301
    fill_b = (CGFloat)fill_color[2] / 255.0;
×
302
    /* QuickLook renders into RGBA so no palette mapping is required. */
303
    sixel_frame_set_pixels(frame,
×
304
                           sixel_allocator_malloc(
×
305
                               pchunk->allocator,
×
306
                               (size_t)(frame->height * stride)));
×
307
    pixels = sixel_frame_get_pixels(frame);
×
308
    if (pixels == NULL) {
×
309
        status = SIXEL_BAD_ALLOCATION;
×
310
        goto end;
×
311
    }
312

313
    color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
×
314
    if (color_space == NULL) {
×
315
        status = SIXEL_RUNTIME_ERROR;
×
316
        goto end;
×
317
    }
318
    ctx = CGBitmapContextCreate(pixels,
×
319
                                (size_t)frame->width,
×
320
                                (size_t)frame->height,
×
321
                                8,
322
                                stride,
×
323
                                color_space,
×
324
                                kCGImageAlphaPremultipliedLast |
325
                                        kCGBitmapByteOrder32Big);
326
    if (ctx == NULL) {
×
327
        status = SIXEL_RUNTIME_ERROR;
×
328
        goto end;
×
329
    }
330

331
    CGContextSetRGBFillColor(ctx, fill_r, fill_g, fill_b, 1.0);
×
332
    CGContextFillRect(ctx, bounds);
×
333
    CGContextDrawImage(ctx, bounds, image);
×
334
    frame->multiframe = 0;
×
335
    frame->frame_no = 0;
×
336
    frame->delay = 0;
×
337
    status = fn_load(frame, context);
×
338
    if (status != SIXEL_OK) {
×
339
        goto end;
×
340
    }
341
    status = SIXEL_OK;
×
342

343
end:
344
    if (ctx) {
×
345
        CGContextRelease(ctx);
×
346
    }
×
347
    if (color_space) {
×
348
        CGColorSpaceRelease(color_space);
×
349
    }
×
350
    if (image) {
×
351
        CGImageRelease(image);
×
352
    }
×
353
    if (url) {
×
354
        CFRelease(url);
×
355
    }
×
356
    if (path) {
×
357
        CFRelease(path);
×
358
    }
×
359
    if (frame) {
×
360
        sixel_frame_unref(frame);
×
361
    }
×
362
    return status;
×
363
}
×
364

365
#endif  /* HAVE_COREGRAPHICS && HAVE_QUICKLOOK */
366

367
#if !(HAVE_COREGRAPHICS && HAVE_QUICKLOOK)
368
/*
369
 * Preserve a non-empty unit when QuickLook support is disabled to avoid
370
 * pedantic compiler warnings.
371
 */
372
typedef int loader_quicklook_disabled;
373
#endif
374

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