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

saitoha / libsixel / 21384086795

27 Jan 2026 04:04AM UTC coverage: 76.473% (-0.04%) from 76.514%
21384086795

push

github

saitoha
compat: normalize libc path handling

21162 of 45579 branches covered (46.43%)

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

1273 existing lines in 3 files now uncovered.

37315 of 48795 relevant lines covered (76.47%)

18108891.8 hits per line

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

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

24
#if defined(HAVE_CONFIG_H)
25
#include "config.h"
26
#endif
27

28
/* STDC_HEADERS */
29
#include <stdio.h>
30
#include <stdlib.h>
31
#include <string.h>
32

33
#if HAVE_MATH_H
34
# include <math.h>
35
#endif  /* HAVE_MATH_H */
36
#if HAVE_LIMITS_H
37
# include <limits.h>
38
#endif  /* HAVE_LIMITS_H */
39
#if HAVE_UNISTD_H
40
# include <unistd.h>
41
#elif HAVE_SYS_UNISTD_H
42
# include <sys/unistd.h>
43
#endif  /* HAVE_UNISTD_H */
44
#if HAVE_FCNTL_H
45
# include <fcntl.h>
46
#endif  /* HAVE_FCNTL_H */
47
#if HAVE_SYS_STAT_H
48
# include <sys/stat.h>
49
#endif  /* HAVE_SYS_STAT_H */
50
#if HAVE_ERRNO_H
51
# include <errno.h>
52
#endif  /* HAVE_ERRNO_H */
53
#if HAVE_IO_H
54
#include <io.h>
55
#endif  /* HAVE_IO_H */
56

57
#include "decoder.h"
58
#include "decoder-parallel.h"
59
#include "clipboard.h"
60
#include "compat_stub.h"
61
#include "path.h"
62
#include "options.h"
63
#include "sixel_atomic.h"
64

65
static void
66
decoder_clipboard_select_format(char *dest,
30✔
67
                                size_t dest_size,
68
                                char const *format,
69
                                char const *fallback)
70
{
71
    char const *source;
16✔
72
    size_t limit;
16✔
73

74
    if (dest == NULL || dest_size == 0u) {
30!
75
        return;
76
    }
77

78
    source = fallback;
30✔
79
    if (format != NULL && format[0] != '\0') {
30!
80
        source = format;
10✔
81
    }
82

83
    limit = dest_size - 1u;
30✔
84
    if (limit == 0u) {
30!
85
        dest[0] = '\0';
×
UNCOV
86
        return;
×
87
    }
88

89
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
30✔
90
}
14!
91

92

93
static char *
94
decoder_create_temp_template_with_prefix(sixel_allocator_t *allocator,
16✔
95
                                         char const *prefix,
96
                                         size_t *capacity_out)
97
{
98
    char const *tmpdir;
9✔
99
    size_t tmpdir_len;
9✔
100
    size_t prefix_len;
9✔
101
    size_t suffix_len;
9✔
102
    size_t template_len;
9✔
103
    char *template_path;
9✔
104
    int needs_separator;
9✔
105
    size_t maximum_tmpdir_len;
9✔
106

107
    tmpdir = sixel_compat_getenv("TMPDIR");
16✔
108
#if defined(_WIN32)
109
    if (tmpdir == NULL || tmpdir[0] == '\0') {
8✔
110
        tmpdir = sixel_compat_getenv("TEMP");
8✔
111
    }
1✔
112
    if (tmpdir == NULL || tmpdir[0] == '\0') {
8✔
113
        tmpdir = sixel_compat_getenv("TMP");
114
    }
115
#endif
116
    if (tmpdir == NULL || tmpdir[0] == '\0') {
16!
117
#if defined(_WIN32)
118
        tmpdir = ".";
119
#else
120
        tmpdir = "/tmp";
2✔
121
#endif
122
    }
123

124
    tmpdir_len = strlen(tmpdir);
16✔
125
    prefix_len = strlen(prefix);
16✔
126
    suffix_len = prefix_len + strlen("-XXXXXX");
16✔
127
    maximum_tmpdir_len = (size_t)INT_MAX;
16✔
128

129
    if (maximum_tmpdir_len <= suffix_len + 2) {
16!
130
        return NULL;
131
    }
132
    if (tmpdir_len > maximum_tmpdir_len - (suffix_len + 2)) {
16!
133
        return NULL;
134
    }
135

136
    needs_separator = 1;
16✔
137
    if (tmpdir_len > 0) {
16!
138
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
16!
139
            needs_separator = 0;
12✔
140
        }
6✔
141
    }
7✔
142

143
    template_len = tmpdir_len + suffix_len + 2;
16✔
144
    template_path = (char *)sixel_allocator_malloc(allocator, template_len);
16✔
145
    if (template_path == NULL) {
16!
146
        return NULL;
147
    }
148

149
    if (needs_separator) {
16!
150
#if defined(_WIN32)
151
        (void)snprintf(template_path, template_len,
9✔
152
                       "%s\\%s-XXXXXX", tmpdir, prefix);
1✔
153
#else
154
        (void)snprintf(template_path, template_len,
2✔
155
                       "%s/%s-XXXXXX", tmpdir, prefix);
156
#endif
157
    } else {
1✔
158
        (void)snprintf(template_path, template_len,
6✔
159
                       "%s%s-XXXXXX", tmpdir, prefix);
160
    }
161

162
    if (capacity_out != NULL) {
16!
163
        *capacity_out = template_len;
16✔
164
    }
7✔
165

166
    return template_path;
10✔
167
}
7✔
168

169

170
static SIXELSTATUS
171
decoder_clipboard_create_spool(sixel_allocator_t *allocator,
16✔
172
                               char const *prefix,
173
                               char **path_out)
174
{
175
    SIXELSTATUS status;
9✔
176
    char *template_path;
9✔
177
    size_t template_capacity;
9✔
178
    int open_flags;
9✔
179
    int open_mode;
9✔
180
    int fd;
9✔
181
    char *tmpname_result;
9✔
182

183
    status = SIXEL_FALSE;
16✔
184
    template_path = NULL;
16✔
185
    template_capacity = 0u;
16✔
186
    open_flags = 0;
16✔
187
    open_mode = 0;
16✔
188
    fd = (-1);
16✔
189
    tmpname_result = NULL;
16✔
190

191
    template_path = decoder_create_temp_template_with_prefix(allocator,
23✔
192
                                                             prefix,
7✔
193
                                                             &template_capacity);
194
    if (template_path == NULL) {
16!
UNCOV
195
        sixel_helper_set_additional_message(
×
196
            "clipboard: failed to allocate spool template.");
197
        status = SIXEL_BAD_ALLOCATION;
×
UNCOV
198
        goto end;
×
199
    }
200

201
    if (sixel_compat_mktemp(template_path, template_capacity) != 0) {
16!
UNCOV
202
        tmpname_result = sixel_compat_tmpnam(template_path,
×
203
                                             template_capacity);
204
        if (tmpname_result == NULL) {
×
UNCOV
205
            sixel_helper_set_additional_message(
×
206
                "clipboard: failed to reserve spool template.");
207
            status = SIXEL_LIBC_ERROR;
×
UNCOV
208
            goto end;
×
209
        }
210
        template_capacity = strlen(template_path) + 1u;
211
    }
212

213
    open_flags = O_RDWR | O_CREAT | O_TRUNC;
16✔
214
#if defined(O_EXCL)
215
    open_flags |= O_EXCL;
16✔
216
#endif
217
    open_mode = S_IRUSR | S_IWUSR;
16✔
218
    fd = sixel_compat_open(template_path, open_flags, open_mode);
16✔
219
    if (fd < 0) {
16!
UNCOV
220
        sixel_helper_set_additional_message(
×
221
            "clipboard: failed to open spool file.");
222
        status = SIXEL_LIBC_ERROR;
×
UNCOV
223
        goto end;
×
224
    }
225

226
    *path_out = template_path;
16✔
227
    if (fd >= 0) {
16!
228
        (void)sixel_compat_close(fd);
16✔
229
        fd = (-1);
16✔
230
    }
7✔
231

232
    template_path = NULL;
16✔
233
    status = SIXEL_OK;
16✔
234

235
end:
3✔
236
    if (fd >= 0) {
16!
237
        (void)sixel_compat_close(fd);
238
    }
239
    if (template_path != NULL) {
16!
UNCOV
240
        sixel_allocator_free(allocator, template_path);
×
241
    }
242

243
    return status;
19✔
244
}
3✔
245

246

247
static SIXELSTATUS
248
decoder_clipboard_read_file(char const *path,
16✔
249
                            unsigned char **data,
250
                            size_t *size)
251
{
252
    FILE *stream;
9✔
253
    long seek_result;
9✔
254
    long file_size;
9✔
255
    unsigned char *buffer;
9✔
256
    size_t read_size;
9✔
257

258
    if (data == NULL || size == NULL) {
16!
UNCOV
259
        sixel_helper_set_additional_message(
×
260
            "clipboard: read buffer pointers are null.");
UNCOV
261
        return SIXEL_BAD_ARGUMENT;
×
262
    }
263

264
    *data = NULL;
16✔
265
    *size = 0u;
16✔
266

267
    if (path == NULL) {
16!
UNCOV
268
        sixel_helper_set_additional_message(
×
269
            "clipboard: spool path is null.");
UNCOV
270
        return SIXEL_BAD_ARGUMENT;
×
271
    }
272

273
    stream = sixel_compat_fopen(path, "rb");
16✔
274
    if (stream == NULL) {
16!
UNCOV
275
        sixel_helper_set_additional_message(
×
276
            "clipboard: failed to open spool file for read.");
UNCOV
277
        return SIXEL_LIBC_ERROR;
×
278
    }
279

280
    seek_result = fseek(stream, 0L, SEEK_END);
16✔
281
    if (seek_result != 0) {
16!
282
        (void)fclose(stream);
×
UNCOV
283
        sixel_helper_set_additional_message(
×
284
            "clipboard: failed to seek spool file.");
UNCOV
285
        return SIXEL_LIBC_ERROR;
×
286
    }
287

288
    file_size = ftell(stream);
16✔
289
    if (file_size < 0) {
16!
290
        (void)fclose(stream);
×
UNCOV
291
        sixel_helper_set_additional_message(
×
292
            "clipboard: failed to determine spool size.");
UNCOV
293
        return SIXEL_LIBC_ERROR;
×
294
    }
295

296
    seek_result = fseek(stream, 0L, SEEK_SET);
16✔
297
    if (seek_result != 0) {
16!
298
        (void)fclose(stream);
×
UNCOV
299
        sixel_helper_set_additional_message(
×
300
            "clipboard: failed to rewind spool file.");
UNCOV
301
        return SIXEL_LIBC_ERROR;
×
302
    }
303

304
    if (file_size == 0) {
16!
305
        buffer = NULL;
306
        read_size = 0u;
307
    } else {
308
        buffer = (unsigned char *)malloc((size_t)file_size);
16✔
309
        if (buffer == NULL) {
16!
310
            (void)fclose(stream);
×
UNCOV
311
            sixel_helper_set_additional_message(
×
312
                "clipboard: malloc() failed for spool payload.");
UNCOV
313
            return SIXEL_BAD_ALLOCATION;
×
314
        }
315
        read_size = fread(buffer, 1u, (size_t)file_size, stream);
16!
316
        if (read_size != (size_t)file_size) {
16!
317
            free(buffer);
×
318
            (void)fclose(stream);
×
UNCOV
319
            sixel_helper_set_additional_message(
×
320
                "clipboard: failed to read spool payload.");
UNCOV
321
            return SIXEL_LIBC_ERROR;
×
322
        }
323
    }
324

325
    if (fclose(stream) != 0) {
16!
326
        if (buffer != NULL) {
×
UNCOV
327
            free(buffer);
×
328
        }
UNCOV
329
        sixel_helper_set_additional_message(
×
330
            "clipboard: failed to close spool file after read.");
UNCOV
331
        return SIXEL_LIBC_ERROR;
×
332
    }
333

334
    *data = buffer;
16✔
335
    *size = read_size;
16✔
336

337
    return SIXEL_OK;
16✔
338
}
7✔
339

340

341
/* original version of strdup(3) with allocator object */
342
static char *
343
strdup_with_allocator(
1,421✔
344
    char const          /* in */ *s,          /* source buffer */
345
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
346
                                                 destination buffer */
347
{
348
    char *p;
823✔
349

350
    p = (char *)sixel_allocator_malloc(allocator, (size_t)(strlen(s) + 1));
1,421✔
351
    if (p) {
1,421!
352
        (void)sixel_compat_strcpy(p, strlen(s) + 1, s);
1,421✔
353
    }
643✔
354
    return p;
1,703✔
355
}
282✔
356

357

358
/* create decoder object */
359
SIXELAPI SIXELSTATUS
360
sixel_decoder_new(
527✔
361
    sixel_decoder_t    /* out */ **ppdecoder,  /* decoder object to be created */
362
    sixel_allocator_t  /* in */  *allocator)   /* allocator, null if you use
363
                                                  default allocator */
364
{
365
    SIXELSTATUS status = SIXEL_FALSE;
527✔
366

367
    if (allocator == NULL) {
527!
368
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
527✔
369
        if (SIXEL_FAILED(status)) {
527!
UNCOV
370
            goto end;
×
371
        }
372
    } else {
235✔
UNCOV
373
        sixel_allocator_ref(allocator);
×
374
    }
375

376
    *ppdecoder = sixel_allocator_malloc(allocator, sizeof(sixel_decoder_t));
527✔
377
    if (*ppdecoder == NULL) {
527!
378
        sixel_allocator_unref(allocator);
×
UNCOV
379
        sixel_helper_set_additional_message(
×
380
            "sixel_decoder_new: sixel_allocator_malloc() failed.");
381
        status = SIXEL_BAD_ALLOCATION;
×
UNCOV
382
        goto end;
×
383
    }
384

385
    (*ppdecoder)->ref          = 1U;
527✔
386
    (*ppdecoder)->output       = strdup_with_allocator("-", allocator);
527✔
387
    (*ppdecoder)->input        = strdup_with_allocator("-", allocator);
527✔
388
    (*ppdecoder)->allocator    = allocator;
527✔
389
    (*ppdecoder)->dequantize_method = SIXEL_DEQUANTIZE_NONE;
527✔
390
    (*ppdecoder)->dequantize_similarity_bias = 100;
527✔
391
    (*ppdecoder)->dequantize_edge_strength = 0;
527✔
392
    (*ppdecoder)->thumbnail_size = 0;
527✔
393
    (*ppdecoder)->direct_color = 0;
527✔
394
    (*ppdecoder)->clipboard_input_active = 0;
527✔
395
    (*ppdecoder)->clipboard_output_active = 0;
527✔
396
    (*ppdecoder)->clipboard_input_format[0] = '\0';
527✔
397
    (*ppdecoder)->clipboard_output_format[0] = '\0';
527✔
398

399
    if ((*ppdecoder)->output == NULL || (*ppdecoder)->input == NULL) {
527!
400
        sixel_decoder_unref(*ppdecoder);
×
401
        *ppdecoder = NULL;
×
UNCOV
402
        sixel_helper_set_additional_message(
×
403
            "sixel_decoder_new: strdup_with_allocator() failed.");
404
        status = SIXEL_BAD_ALLOCATION;
×
405
        sixel_allocator_unref(allocator);
×
UNCOV
406
        goto end;
×
407
    }
408

409
    status = SIXEL_OK;
328✔
410

411
end:
292✔
412
    return status;
629✔
413
}
102✔
414

415

416
/* deprecated version of sixel_decoder_new() */
417
SIXELAPI /* deprecated */ sixel_decoder_t *
UNCOV
418
sixel_decoder_create(void)
×
419
{
420
    SIXELSTATUS status = SIXEL_FALSE;
×
UNCOV
421
    sixel_decoder_t *decoder = NULL;
×
422

423
    status = sixel_decoder_new(&decoder, NULL);
×
UNCOV
424
    if (SIXEL_FAILED(status)) {
×
425
        goto end;
426
    }
427

428
end:
UNCOV
429
    return decoder;
×
430
}
431

432

433
/* destroy a decoder object */
434
static void
435
sixel_decoder_destroy(sixel_decoder_t *decoder)
511✔
436
{
437
    sixel_allocator_t *allocator;
292✔
438

439
    if (decoder) {
511!
440
        allocator = decoder->allocator;
511✔
441
        sixel_allocator_free(allocator, decoder->input);
511✔
442
        sixel_allocator_free(allocator, decoder->output);
511✔
443
        sixel_allocator_free(allocator, decoder);
511✔
444
        sixel_allocator_unref(allocator);
511✔
445
    }
228✔
446
}
511✔
447

448

449
/* increase reference count of decoder object (thread-safe) */
450
SIXELAPI void
451
sixel_decoder_ref(sixel_decoder_t *decoder)
926✔
452
{
453
    if (decoder == NULL) {
926!
454
        return;
455
    }
456

457
    (void)sixel_atomic_fetch_add_u32(&decoder->ref, 1U);
926✔
458
}
422✔
459

460

461
/* decrease reference count of decoder object (thread-safe) */
462
SIXELAPI void
463
sixel_decoder_unref(sixel_decoder_t *decoder)
1,437✔
464
{
465
    unsigned int previous;
832✔
466

467
    if (decoder == NULL) {
1,437!
468
        return;
469
    }
470

471
    previous = sixel_atomic_fetch_sub_u32(&decoder->ref, 1U);
1,437✔
472
    if (previous == 1U) {
1,437✔
473
        sixel_decoder_destroy(decoder);
511✔
474
    }
228✔
475
}
650!
476

477

478
typedef struct sixel_similarity {
479
    const unsigned char *palette;
480
    int ncolors;
481
    int stride;
482
    signed char *cache;
483
    int bias;
484
} sixel_similarity_t;
485

486
static SIXELSTATUS
487
sixel_similarity_init(sixel_similarity_t *similarity,
16✔
488
                      const unsigned char *palette,
489
                      int ncolors,
490
                      int bias,
491
                      sixel_allocator_t *allocator)
492
{
493
    size_t cache_size;
9✔
494
    int i;
9✔
495

496
    if (bias < 1) {
16!
497
        bias = 1;
498
    }
499

500
    similarity->palette = palette;
16✔
501
    similarity->ncolors = ncolors;
16✔
502
    similarity->stride = ncolors;
16✔
503
    similarity->bias = bias;
16✔
504

505
    cache_size = (size_t)ncolors * (size_t)ncolors;
16✔
506
    if (cache_size == 0) {
16!
507
        similarity->cache = NULL;
×
UNCOV
508
        return SIXEL_OK;
×
509
    }
510

511
    similarity->cache = (signed char *)sixel_allocator_malloc(
16✔
512
        allocator,
7✔
513
        cache_size);
7✔
514
    if (similarity->cache == NULL) {
16!
UNCOV
515
        sixel_helper_set_additional_message(
×
516
            "sixel_similarity_init: sixel_allocator_malloc() failed.");
UNCOV
517
        return SIXEL_BAD_ALLOCATION;
×
518
    }
519
    memset(similarity->cache, -1, cache_size);
16✔
520
    for (i = 0; i < ncolors; ++i) {
2,688✔
521
        similarity->cache[i * similarity->stride + i] = 7;
2,672✔
522
    }
1,169✔
523

524
    return SIXEL_OK;
10✔
525
}
7✔
526

527
static void
528
sixel_similarity_destroy(sixel_similarity_t *similarity,
16✔
529
                         sixel_allocator_t *allocator)
530
{
531
    if (similarity->cache != NULL) {
16!
532
        sixel_allocator_free(allocator, similarity->cache);
16✔
533
        similarity->cache = NULL;
16✔
534
    }
7✔
535
}
10✔
536

537
static inline unsigned int
538
sixel_similarity_diff(const unsigned char *a, const unsigned char *b)
8,570,912✔
539
{
540
    int dr = (int)a[0] - (int)b[0];
8,570,912✔
541
    int dg = (int)a[1] - (int)b[1];
8,570,912✔
542
    int db = (int)a[2] - (int)b[2];
8,570,912✔
543
    return (unsigned int)(dr * dr + dg * dg + db * db);
10,177,958✔
544
}
1,607,046✔
545

546
static unsigned int
547
sixel_similarity_compare(sixel_similarity_t *similarity,
34,459,264✔
548
                         int index1,
549
                         int index2,
550
                         int numerator,
551
                         int denominator)
552
{
553
    int min_index;
19,383,336✔
554
    int max_index;
19,383,336✔
555
    size_t cache_pos;
19,383,336✔
556
    signed char cached;
19,383,336✔
557
    const unsigned char *palette;
19,383,336✔
558
    const unsigned char *p1;
19,383,336✔
559
    const unsigned char *p2;
19,383,336✔
560
    unsigned char avg_color[3];
19,383,336✔
561
    unsigned int distance;
19,383,336✔
562
    unsigned int base_distance;
19,383,336✔
563
    unsigned long long scaled_distance;
19,383,336✔
564
    int bias;
19,383,336✔
565
    unsigned int min_diff = UINT_MAX;
34,459,264✔
566
    int i;
19,383,336✔
567
    unsigned int result;
19,383,336✔
568
    const unsigned char *pk;
19,383,336✔
569
    unsigned int diff;
19,383,336✔
570

571
    if (similarity->cache == NULL) {
34,459,264!
572
        return 0;
573
    }
574

575
    if (index1 < 0 || index1 >= similarity->ncolors ||
47,381,488!
576
        index2 < 0 || index2 >= similarity->ncolors) {
34,459,264!
577
        return 0;
578
    }
579

580
    if (index1 <= index2) {
34,459,264✔
581
        min_index = index1;
15,047,390✔
582
        max_index = index2;
15,047,390✔
583
    } else {
10,533,173✔
584
        min_index = index2;
10,383,440✔
585
        max_index = index1;
10,383,440✔
586
    }
587

588
    cache_pos = (size_t)min_index * (size_t)similarity->stride
49,535,192✔
589
              + (size_t)max_index;
34,459,264✔
590
    cached = similarity->cache[cache_pos];
34,459,264✔
591
    if (cached >= 0) {
34,459,264✔
592
        return (unsigned int)cached;
34,407,632✔
593
    }
594

595
    palette = similarity->palette;
51,632✔
596
    p1 = palette + index1 * 3;
51,632✔
597
    p2 = palette + index2 * 3;
51,632✔
598

599
#if 1
600
   /*    original: n = (p1 + p2) / 2
601
    */
602
    avg_color[0] = (unsigned char)(((unsigned int)p1[0]
74,221✔
603
                                    + (unsigned int)p2[0]) >> 1);
51,632✔
604
    avg_color[1] = (unsigned char)(((unsigned int)p1[1]
74,221✔
605
                                    + (unsigned int)p2[1]) >> 1);
51,632✔
606
    avg_color[2] = (unsigned char)(((unsigned int)p1[2]
74,221✔
607
                                    + (unsigned int)p2[2]) >> 1);
51,632✔
608
    (void) numerator;
41,951✔
609
    (void) denominator;
41,951✔
610
#else
611
   /*
612
    *    diffuse(pos_a, n1) -> p1
613
    *    diffuse(pos_b, n2) -> p2
614
    *
615
    *    when n1 == n2 == n:
616
    *
617
    *    p2 = n + (n - p1) * numerator / denominator
618
    * => p2 * denominator = n * denominator + (n - p1) * numerator
619
    * => p2 * denominator = n * denominator + n * numerator - p1 * numerator
620
    * => n * (denominator + numerator) = p1 * numerator + p2 * denominator
621
    * => n = (p1 * numerator + p2 * denominator) / (denominator + numerator)
622
    *
623
    */
624
    avg_color[0] = (p1[0] * numerator + p2[0] * denominator + (numerator + denominator + 0.5) / 2)
625
                 / (numerator + denominator);
626
    avg_color[1] = (p1[1] * numerator + p2[1] * denominator + (numerator + denominator + 0.5) / 2)
627
                 / (numerator + denominator);
628
    avg_color[2] = (p1[2] * numerator + p2[2] * denominator + (numerator + denominator + 0.5) / 2)
629
                 / (numerator + denominator);
630
#endif
631

632
    distance = sixel_similarity_diff(avg_color, p1);
51,632✔
633
    bias = similarity->bias;
51,632✔
634
    if (bias < 1) {
51,632!
635
        bias = 1;
636
    }
637
    scaled_distance = (unsigned long long)distance
74,221✔
638
                    * (unsigned long long)bias
51,632✔
639
                    + 50ULL;
22,589✔
640
    base_distance = (unsigned int)(scaled_distance / 100ULL);
51,632✔
641
    if (base_distance == 0U) {
51,632!
642
        base_distance = 1U;
643
    }
644

645
    for (i = 0; i < similarity->ncolors; ++i) {
8,674,176✔
646
        if (i == index1 || i == index2) {
8,622,544✔
647
            continue;
103,264✔
648
        }
649
        pk = palette + i * 3;
8,519,280✔
650
        diff = sixel_similarity_diff(avg_color, pk);
8,519,280✔
651
        if (diff < min_diff) {
8,519,280✔
652
            min_diff = diff;
3,484,364✔
653
        }
175,637✔
654
    }
3,727,185✔
655

656
    if (min_diff == UINT_MAX) {
51,632!
UNCOV
657
        min_diff = base_distance * 2U;
×
658
    }
659

660
    if (min_diff >= base_distance * 2U) {
51,632✔
661
        result = 5U;
1,720✔
662
    } else if (min_diff >= base_distance) {
50,084✔
663
        result = 8U;
2,070✔
664
    } else if ((unsigned long long)min_diff * 6ULL
64,105✔
665
               >= (unsigned long long)base_distance * 5ULL) {
45,568✔
666
        result = 7U;
740✔
667
    } else if ((unsigned long long)min_diff * 4ULL
61,546✔
668
               >= (unsigned long long)base_distance * 3ULL) {
44,384✔
669
        result = 7U;
500✔
670
    } else if ((unsigned long long)min_diff * 3ULL
60,278✔
671
               >= (unsigned long long)base_distance * 2ULL) {
43,584✔
672
        result = 5U;
640✔
673
    } else if ((unsigned long long)min_diff * 5ULL
58,968✔
674
               >= (unsigned long long)base_distance * 3ULL) {
26,600✔
675
        result = 7U;
630✔
676
    } else if ((unsigned long long)min_diff * 2ULL
57,575✔
677
               >= (unsigned long long)base_distance * 1ULL) {
25,970✔
678
        result = 4U;
1,180✔
679
    } else if ((unsigned long long)min_diff * 3ULL
55,364✔
680
               >= (unsigned long long)base_distance * 1ULL) {
24,790✔
681
        result = 2U;
3,250✔
682
    } else {
2,275✔
683
        result = 0U;
34,464✔
684
    }
685

686
    similarity->cache[cache_pos] = (signed char)result;
51,632✔
687

688
    return result;
51,632✔
689
}
15,075,928✔
690

691
static inline int
UNCOV
692
sixel_clamp(int value, int min_value, int max_value)
×
693
{
UNCOV
694
    if (value < min_value) {
×
695
        return min_value;
696
    }
UNCOV
697
    if (value > max_value) {
×
698
        return max_value;
699
    }
700
    return value;
701
}
702

703
static inline int
UNCOV
704
sixel_get_gray(const int *gray, int width, int height, int x, int y)
×
705
{
706
    int cx = sixel_clamp(x, 0, width - 1);
×
707
    int cy = sixel_clamp(y, 0, height - 1);
×
UNCOV
708
    return gray[cy * width + cx];
×
709
}
710

711
static unsigned short
UNCOV
712
sixel_prewitt_value(const int *gray, int width, int height, int x, int y)
×
713
{
714
    int top_prev = sixel_get_gray(gray, width, height, x - 1, y - 1);
×
715
    int top_curr = sixel_get_gray(gray, width, height, x, y - 1);
×
716
    int top_next = sixel_get_gray(gray, width, height, x + 1, y - 1);
×
717
    int mid_prev = sixel_get_gray(gray, width, height, x - 1, y);
×
718
    int mid_next = sixel_get_gray(gray, width, height, x + 1, y);
×
719
    int bot_prev = sixel_get_gray(gray, width, height, x - 1, y + 1);
×
720
    int bot_curr = sixel_get_gray(gray, width, height, x, y + 1);
×
721
    int bot_next = sixel_get_gray(gray, width, height, x + 1, y + 1);
×
722
    long gx = (long)top_next - (long)top_prev +
×
UNCOV
723
              (long)mid_next - (long)mid_prev +
×
724
              (long)bot_next - (long)bot_prev;
725
    long gy = (long)bot_prev + (long)bot_curr + (long)bot_next -
×
726
              (long)top_prev - (long)top_curr - (long)top_next;
×
727
    unsigned long long magnitude = (unsigned long long)gx
×
728
                                 * (unsigned long long)gx
×
729
                                 + (unsigned long long)gy
×
730
                                 * (unsigned long long)gy;
×
731
    magnitude /= 256ULL;
×
UNCOV
732
    if (magnitude > 65535ULL) {
×
733
        magnitude = 65535ULL;
734
    }
UNCOV
735
    return (unsigned short)magnitude;
×
736
}
737

738
static unsigned short
739
sixel_scale_threshold(unsigned short base, int percent)
32✔
740
{
741
    unsigned long long numerator;
18✔
742
    unsigned long long scaled;
18✔
743

744
    if (percent <= 0) {
32!
745
        percent = 1;
20✔
746
    }
14✔
747

748
    numerator = (unsigned long long)base * 100ULL
46✔
749
              + (unsigned long long)percent / 2ULL;
26✔
750
    scaled = numerator / (unsigned long long)percent;
32✔
751
    if (scaled == 0ULL) {
32!
752
        scaled = 1ULL;
753
    }
754
    if (scaled > USHRT_MAX) {
32!
755
        scaled = USHRT_MAX;
756
    }
757

758
    return (unsigned short)scaled;
38✔
759
}
6✔
760

761
static SIXELSTATUS
762
sixel_dequantize_k_undither(unsigned char *indexed_pixels,
16✔
763
                            int width,
764
                            int height,
765
                            unsigned char *palette,
766
                            int ncolors,
767
                            int similarity_bias,
768
                            int edge_strength,
769
                            sixel_allocator_t *allocator,
770
                            unsigned char **output)
771
{
772
    SIXELSTATUS status = SIXEL_FALSE;
16✔
773
    unsigned char *rgb = NULL;
16✔
774
    int *gray = NULL;
16✔
775
    unsigned short *prewitt = NULL;
16✔
776
    sixel_similarity_t similarity;
9✔
777
    size_t num_pixels;
9✔
778
    int x;
9✔
779
    int y;
9✔
780
    unsigned short strong_threshold;
9✔
781
    unsigned short detail_threshold;
9✔
782
    static const int neighbor_offsets[8][4] = {
6✔
783
        {-1, -1,  10, 16}, {0, -1, 16, 16}, {1, -1,   6, 16},
784
        {-1,  0,  11, 16},                  {1,  0,  11, 16},
785
        {-1,  1,   6, 16}, {0,  1, 16, 16}, {1,  1,  10, 16}
786
    };
787
    const unsigned char *color;
9✔
788
    size_t out_index;
9✔
789
    int palette_index;
9✔
790
    unsigned int center_weight;
9✔
791
    unsigned int total_weight = 0;
16✔
792
    unsigned int accum_r;
9✔
793
    unsigned int accum_g;
9✔
794
    unsigned int accum_b;
9✔
795
    unsigned short gradient;
9✔
796
    int neighbor;
9✔
797
    int nx;
9✔
798
    int ny;
9✔
799
    int numerator;
9✔
800
    int denominator;
9✔
801
    unsigned int weight;
9✔
802
    const unsigned char *neighbor_color;
9✔
803
    int neighbor_index;
9✔
804

805
    if (width <= 0 || height <= 0 || palette == NULL || ncolors <= 0) {
16!
806
        return SIXEL_BAD_INPUT;
807
    }
808

809
    num_pixels = (size_t)width * (size_t)height;
16✔
810

811
    memset(&similarity, 0, sizeof(sixel_similarity_t));
16!
812

813
    strong_threshold = sixel_scale_threshold(256U, edge_strength);
16!
814
    detail_threshold = sixel_scale_threshold(160U, edge_strength);
16!
815
    if (strong_threshold < detail_threshold) {
16!
816
        strong_threshold = detail_threshold;
817
    }
818

819
    /*
820
     * Build RGB and luminance buffers so we can reuse the similarity cache
821
     * and gradient analysis across the reconstructed image.
822
     */
823
    rgb = (unsigned char *)sixel_allocator_malloc(
16✔
824
        allocator,
7✔
825
        num_pixels * 3);
7✔
826
    if (rgb == NULL) {
16!
UNCOV
827
        sixel_helper_set_additional_message(
×
828
            "sixel_dequantize_k_undither: "
829
            "sixel_allocator_malloc() failed.");
830
        status = SIXEL_BAD_ALLOCATION;
×
UNCOV
831
        goto end;
×
832
    }
833

834
    gray = (int *)sixel_allocator_malloc(
20✔
835
        allocator,
7✔
836
        num_pixels * sizeof(int));
14✔
837
    if (gray == NULL) {
16!
UNCOV
838
        sixel_helper_set_additional_message(
×
839
            "sixel_dequantize_k_undither: "
840
            "sixel_allocator_malloc() failed.");
841
        status = SIXEL_BAD_ALLOCATION;
×
UNCOV
842
        goto end;
×
843
    }
844

845
    prewitt = (unsigned short *)sixel_allocator_malloc(
20✔
846
        allocator,
7✔
847
        num_pixels * sizeof(unsigned short));
14✔
848
    if (prewitt == NULL) {
16!
UNCOV
849
        sixel_helper_set_additional_message(
×
850
            "sixel_dequantize_k_undither: "
851
            "sixel_allocator_malloc() failed.");
852
        status = SIXEL_BAD_ALLOCATION;
×
UNCOV
853
        goto end;
×
854
    }
855

856
    /*
857
     * Pre-compute palette distance heuristics so each neighbour lookup reuses
858
     * the k_undither-style similarity table.
859
     */
860
    status = sixel_similarity_init(
16✔
861
        &similarity,
862
        palette,
7✔
863
        ncolors,
7✔
864
        similarity_bias,
7✔
865
        allocator);
7✔
866
    if (SIXEL_FAILED(status)) {
16!
UNCOV
867
        goto end;
×
868
    }
869

870
    for (y = 0; y < height; ++y) {
7,216✔
871
        for (x = 0; x < width; ++x) {
4,327,200✔
872
            palette_index = indexed_pixels[y * width + x];
4,320,000✔
873
            if (palette_index < 0 || palette_index >= ncolors) {
4,320,000!
UNCOV
874
                palette_index = 0;
×
875
            }
876

877
            color = palette + palette_index * 3;
4,320,000✔
878
            out_index = (size_t)(y * width + x) * 3;
4,320,000✔
879
            rgb[out_index + 0] = color[0];
4,320,000✔
880
            rgb[out_index + 1] = color[1];
4,320,000✔
881
            rgb[out_index + 2] = color[2];
4,320,000✔
882

883
            if (edge_strength > 0) {
4,320,000!
884
                gray[y * width + x] = (int)color[0]
×
885
                                    + (int)color[1] * 2
×
UNCOV
886
                                    + (int)color[2];
×
887
                /*
888
                 * Edge detection keeps high-frequency content intact while we
889
                 * smooth dithering noise in flatter regions.
890
                 */
UNCOV
891
                prewitt[y * width + x] = sixel_prewitt_value(
×
892
                    gray,
893
                    width,
894
                    height,
895
                    x,
896
                    y);
897

898
                gradient = prewitt[y * width + x];
×
899
                if (gradient > strong_threshold) {
×
UNCOV
900
                    continue;
×
901
                }
902

UNCOV
903
                if (gradient > detail_threshold) {
×
904
                    center_weight = 24U;
905
                } else {
906
                    center_weight = 8U;
1,620,000✔
907
                }
908
            } else {
909
                center_weight = 8U;
2,700,000✔
910
            }
911

912
            out_index = (size_t)(y * width + x) * 3;
4,320,000✔
913
            accum_r = (unsigned int)rgb[out_index + 0] * center_weight;
4,320,000✔
914
            accum_g = (unsigned int)rgb[out_index + 1] * center_weight;
4,320,000✔
915
            accum_b = (unsigned int)rgb[out_index + 2] * center_weight;
4,320,000✔
916
            total_weight = center_weight;
4,320,000✔
917

918
            /*
919
             * Blend neighbours that stay within the palette similarity
920
             * threshold so Floyd-Steinberg noise is averaged away without
921
             * bleeding across pronounced edges.
922
             */
923
            for (neighbor = 0; neighbor < 8; ++neighbor) {
38,880,000✔
924
                nx = x + neighbor_offsets[neighbor][0];
34,560,000✔
925
                ny = y + neighbor_offsets[neighbor][1];
34,560,000✔
926
                numerator = neighbor_offsets[neighbor][2];
34,560,000✔
927
                denominator = neighbor_offsets[neighbor][3];
34,560,000✔
928

929
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
34,560,000✔
930
                    continue;
100,736✔
931
                }
932

933
                neighbor_index = indexed_pixels[ny * width + nx];
34,459,264✔
934
                if (neighbor_index < 0 || neighbor_index >= ncolors) {
34,459,264!
UNCOV
935
                    continue;
×
936
                }
937

938
                if (numerator) {
34,459,264!
939
                    weight = sixel_similarity_compare(
34,459,264✔
940
                        &similarity,
941
                        palette_index,
15,075,928✔
942
                        neighbor_index,
15,075,928✔
943
                        numerator,
15,075,928✔
944
                        denominator);
15,075,928✔
945
                    if (weight == 0) {
34,459,264✔
946
                        continue;
1,872,800✔
947
                    }
948

949
                    neighbor_color = palette + neighbor_index * 3;
32,586,464✔
950
                    accum_r += (unsigned int)neighbor_color[0] * weight;
32,586,464✔
951
                    accum_g += (unsigned int)neighbor_color[1] * weight;
32,586,464✔
952
                    accum_b += (unsigned int)neighbor_color[2] * weight;
32,586,464✔
953
                    total_weight += weight;
32,586,464✔
954
                }
14,256,578✔
955
            }
14,256,578✔
956

957
            if (total_weight > 0U) {
4,320,000!
958
                rgb[out_index + 0] = (unsigned char)(accum_r / total_weight);
4,320,000✔
959
                rgb[out_index + 1] = (unsigned char)(accum_g / total_weight);
4,320,000✔
960
                rgb[out_index + 2] = (unsigned char)(accum_b / total_weight);
4,320,000✔
961
            }
1,890,000✔
962
        }
1,890,000✔
963
    }
3,150✔
964

965

966
    *output = rgb;
16✔
967
    rgb = NULL;
16✔
968
    status = SIXEL_OK;
16✔
969

970
end:
9✔
971
    sixel_similarity_destroy(&similarity, allocator);
16!
972
    sixel_allocator_free(allocator, rgb);
16✔
973
    sixel_allocator_free(allocator, gray);
16✔
974
    sixel_allocator_free(allocator, prewitt);
16✔
975
    return status;
16✔
976
}
7✔
977
/*
978
 * The dequantizer accepts a method supplied by the shared option helper. The
979
 * decoder keeps a parallel lookup table that translates the matched index
980
 * into the execution constant.
981
 */
982
static int const g_decoder_dequant_methods[] = {
983
    SIXEL_DEQUANTIZE_NONE,
984
    SIXEL_DEQUANTIZE_K_UNDITHER
985
};
986

987
static sixel_option_choice_t const g_decoder_dequant_choices[] = {
988
    { "none", 0 },
989
    { "k_undither", 1 }
990
};
991

992
/* set an option flag to decoder object */
993
SIXELAPI SIXELSTATUS
994
sixel_decoder_setopt(
559✔
995
    sixel_decoder_t /* in */ *decoder,
996
    int             /* in */ arg,
997
    char const      /* in */ *value
998
)
999
{
1000
    SIXELSTATUS status = SIXEL_FALSE;
559✔
1001
    unsigned int path_flags;
329✔
1002
    int path_check;
329✔
1003
    char const *payload = NULL;
559✔
1004
    sixel_clipboard_spec_t clipboard_spec;
329✔
1005
    int match_index;
329✔
1006
    sixel_option_choice_result_t match_result;
329✔
1007
    char match_detail[128];
329✔
1008
    char match_message[256];
329✔
1009
    char const *filename = NULL;
559✔
1010
    size_t libc_buffer_size;
329✔
1011
    char *libc_buffer;
329✔
1012
    char const *libc_path;
329✔
1013
    long bias;
329✔
1014
    long parsed_value;
329✔
1015
    char *endptr;
329✔
1016

1017
    sixel_decoder_ref(decoder);
559✔
1018
    path_flags = 0u;
559✔
1019
    path_check = 0;
559✔
1020
    libc_buffer_size = 0u;
559✔
1021
    libc_buffer = NULL;
559✔
1022
    libc_path = NULL;
559✔
1023

1024
    switch(arg) {
559!
1025
    case SIXEL_OPTFLAG_INPUT:  /* i */
109✔
1026
        path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
208✔
1027
            SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
1028
            SIXEL_OPTION_PATH_ALLOW_REMOTE;
1029
        if (value != NULL) {
208!
1030
            path_check = sixel_option_validate_filesystem_path(
208✔
1031
                value,
99✔
1032
                value,
99✔
1033
                path_flags);
99✔
1034
            if (path_check != 0) {
208✔
1035
                status = SIXEL_BAD_ARGUMENT;
16✔
1036
                goto end;
16✔
1037
            }
1038
        }
92✔
1039
        decoder->clipboard_input_active = 0;
192✔
1040
        decoder->clipboard_input_format[0] = '\0';
192✔
1041
        if (value != NULL) {
192!
1042
            clipboard_spec.is_clipboard = 0;
192✔
1043
            clipboard_spec.format[0] = '\0';
192✔
1044
            if (sixel_clipboard_parse_spec(value, &clipboard_spec)
192!
1045
                    && clipboard_spec.is_clipboard) {
99!
1046
                decoder_clipboard_select_format(
14✔
1047
                    decoder->clipboard_input_format,
14✔
1048
                    sizeof(decoder->clipboard_input_format),
1049
                    clipboard_spec.format,
7✔
1050
                    "sixel");
1051
                decoder->clipboard_input_active = 1;
14✔
1052
            }
7✔
1053
        }
92✔
1054
        free(decoder->input);
192✔
1055
        decoder->input = strdup_with_allocator(value, decoder->allocator);
192✔
1056
        if (decoder->input == NULL) {
192!
UNCOV
1057
            sixel_helper_set_additional_message(
×
1058
                "sixel_decoder_setopt: strdup_with_allocator() failed.");
UNCOV
1059
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
1060
            goto end;
×
1061
        }
1062
        break;
116✔
1063
    case SIXEL_OPTFLAG_OUTPUT:  /* o */
103✔
1064
        decoder->clipboard_output_active = 0;
191✔
1065
        decoder->clipboard_output_format[0] = '\0';
191✔
1066

1067
        payload = value;
191✔
1068
        if (strncmp(value, "png:", 4) == 0) {
191✔
1069
            payload = value + 4;
64✔
1070
            if (payload[0] == '\0') {
64✔
1071
                sixel_helper_set_additional_message(
16✔
1072
                    "missing target after the \"png:\" prefix.");
1073
                return SIXEL_BAD_ARGUMENT;
16✔
1074
            }
1075
            libc_buffer_size = sixel_path_to_libc_buffer_size(payload);
48✔
1076
            if (libc_buffer_size > 0u) {
48!
1077
                libc_buffer = (char *)malloc(libc_buffer_size);
4✔
1078
                if (libc_buffer == NULL) {
4!
UNCOV
1079
                    sixel_helper_set_additional_message(
×
1080
                        "sixel_decoder_setopt: malloc() failed for png path "
1081
                        "buffer.");
UNCOV
1082
                    return SIXEL_BAD_ALLOCATION;
×
1083
                }
1084
                libc_path = sixel_path_to_libc(payload,
4✔
1085
                                               libc_buffer,
1086
                                               libc_buffer_size);
1087
                if (libc_path == NULL) {
4!
UNCOV
1088
                    sixel_helper_set_additional_message(
×
1089
                        "sixel_decoder_setopt: invalid png output path.");
UNCOV
1090
                    free(libc_buffer);
×
UNCOV
1091
                    return SIXEL_BAD_ARGUMENT;
×
1092
                }
1093
                filename = libc_path;
1094
            } else {
1095
                filename = payload;
30✔
1096
            }
1097
        } else {
21✔
1098
            filename = value;
78✔
1099
        }
1100

1101
        if (filename != NULL) {
108!
1102
            clipboard_spec.is_clipboard = 0;
175✔
1103
            clipboard_spec.format[0] = '\0';
175✔
1104
            if (sixel_clipboard_parse_spec(filename, &clipboard_spec)
175!
1105
                    && clipboard_spec.is_clipboard) {
90!
1106
                decoder_clipboard_select_format(
16✔
1107
                    decoder->clipboard_output_format,
16✔
1108
                    sizeof(decoder->clipboard_output_format),
1109
                    clipboard_spec.format,
7✔
1110
                    "png");
1111
                decoder->clipboard_output_active = 1;
16✔
1112
            }
7✔
1113
        }
81✔
1114
        free(decoder->output);
175✔
1115
        decoder->output = strdup_with_allocator(filename, decoder->allocator);
175✔
1116
        if (libc_buffer != NULL) {
175!
1117
            free(libc_buffer);
4✔
1118
            libc_buffer = NULL;
4✔
1119
        }
1120
        if (decoder->output == NULL) {
175!
UNCOV
1121
            sixel_helper_set_additional_message(
×
1122
                "sixel_decoder_setopt: strdup_with_allocator() failed.");
UNCOV
1123
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
1124
            goto end;
×
1125
        }
1126
        break;
108✔
1127
    case SIXEL_OPTFLAG_DEQUANTIZE:  /* d */
18✔
1128
        if (value == NULL) {
32!
1129
            sixel_helper_set_additional_message(
×
1130
                "sixel_decoder_setopt: -d/--dequantize requires an argument.");
UNCOV
1131
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
1132
            goto end;
×
1133
        }
1134

1135
        match_index = 0;
32✔
1136
        memset(match_detail, 0, sizeof(match_detail));
32✔
1137
        memset(match_message, 0, sizeof(match_message));
32✔
1138

1139
        match_result = sixel_option_match_choice(
32✔
1140
            value,
14✔
1141
            g_decoder_dequant_choices,
1142
            sizeof(g_decoder_dequant_choices) /
1143
            sizeof(g_decoder_dequant_choices[0]),
1144
            &match_index,
1145
            match_detail,
14✔
1146
            sizeof(match_detail));
1147
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
32!
1148
            decoder->dequantize_method =
32✔
1149
                g_decoder_dequant_methods[match_index];
32✔
1150
        } else {
14✔
UNCOV
1151
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
UNCOV
1152
                sixel_option_report_ambiguous_prefix(
×
1153
                    value,
1154
                    match_detail,
1155
                    match_message,
1156
                    sizeof(match_message));
1157
            } else {
UNCOV
1158
                sixel_option_report_invalid_choice(
×
1159
                    "unsupported dequantize method.",
1160
                    match_detail,
1161
                    match_message,
1162
                    sizeof(match_message));
1163
            }
UNCOV
1164
            status = SIXEL_BAD_ARGUMENT;
×
UNCOV
1165
            goto end;
×
1166
        }
1167
        break;
32✔
1168

1169
    case SIXEL_OPTFLAG_SIMILARITY:  /* S */
9✔
1170
        errno = 0;
16✔
1171
        bias = strtol(value, &endptr, 10);
16✔
1172
        if (endptr == value || endptr[0] != '\0' ||
16!
UNCOV
1173
            errno == ERANGE || bias < 0 || bias > 1000) {
×
1174
            sixel_helper_set_additional_message(
16✔
1175
                "similarity must be an integer between 0 and 1000.");
1176
            status = SIXEL_BAD_ARGUMENT;
16✔
1177
            goto end;
16✔
1178
        }
1179

UNCOV
1180
        decoder->dequantize_similarity_bias = (int)bias;
×
UNCOV
1181
        break;
×
1182

1183
    case SIXEL_OPTFLAG_SIZE:  /* s */
UNCOV
1184
        parsed_value = 0L;
×
1185
        endptr = NULL;
×
1186
        errno = 0;
×
UNCOV
1187
        parsed_value = strtol(value, &endptr, 10);
×
UNCOV
1188
        if (endptr == value || *endptr != '\0' || errno == ERANGE ||
×
1189
            parsed_value < 1L || parsed_value > (long)INT_MAX) {
×
1190
            sixel_helper_set_additional_message(
×
1191
                "size must be greater than zero.");
1192
            status = SIXEL_BAD_ARGUMENT;
×
1193
            goto end;
×
1194
        }
1195
        decoder->thumbnail_size = (int)parsed_value;
×
UNCOV
1196
        break;
×
1197

1198
    case SIXEL_OPTFLAG_EDGE:  /* e */
UNCOV
1199
        parsed_value = 0L;
×
1200
        endptr = NULL;
×
1201
        errno = 0;
×
UNCOV
1202
        parsed_value = strtol(value, &endptr, 10);
×
UNCOV
1203
        if (endptr == value || *endptr != '\0' || errno == ERANGE ||
×
1204
            parsed_value < 0L || parsed_value > 1000L) {
×
1205
            sixel_helper_set_additional_message(
×
1206
                "edge bias must be between 1 and 1000.");
1207
            status = SIXEL_BAD_ARGUMENT;
×
1208
            goto end;
×
1209
        }
1210
        decoder->dequantize_edge_strength = (int)parsed_value;
×
UNCOV
1211
        break;
×
1212

1213
    case SIXEL_OPTFLAG_DIRECT:  /* D */
54✔
1214
        decoder->direct_color = 1;
96✔
1215
        break;
96✔
1216

1217
    case SIXEL_OPTFLAG_THREADS:  /* = */
9✔
1218
        status = sixel_decoder_parallel_override_threads(value);
16✔
1219
        if (SIXEL_FAILED(status)) {
16!
1220
            goto end;
16✔
1221
        }
1222
        break;
1223

UNCOV
1224
    case '?':
×
1225
    default:
UNCOV
1226
        status = SIXEL_BAD_ARGUMENT;
×
UNCOV
1227
        goto end;
×
1228
    }
1229

1230
    status = SIXEL_OK;
304✔
1231

1232
end:
293✔
1233
    sixel_decoder_unref(decoder);
543✔
1234

1235
    return status;
543✔
1236
}
257✔
1237

1238

1239
/* load source data from stdin or the file specified with
1240
   SIXEL_OPTFLAG_INPUT flag, and decode it */
1241
SIXELAPI SIXELSTATUS
1242
sixel_decoder_decode(
367✔
1243
    sixel_decoder_t /* in */ *decoder)
1244
{
1245
    SIXELSTATUS status = SIXEL_FALSE;
367✔
1246
    unsigned char *raw_data = NULL;
367✔
1247
    int sx;
211✔
1248
    int sy;
211✔
1249
    int raw_len;
211✔
1250
    int max;
211✔
1251
    int n;
211✔
1252
    FILE *input_fp = NULL;
367✔
1253
    char message[2048];
211✔
1254
    unsigned char *indexed_pixels = NULL;
367✔
1255
    unsigned char *palette = NULL;
367✔
1256
    unsigned char *rgb_pixels = NULL;
367✔
1257
    unsigned char *direct_pixels = NULL;
367✔
1258
    unsigned char *output_pixels;
211✔
1259
    unsigned char *output_palette;
211✔
1260
    int output_pixelformat;
211✔
1261
    int ncolors;
211✔
1262
    sixel_frame_t *frame;
211✔
1263
    int new_width;
211✔
1264
    int new_height;
211✔
1265
    double scaled_width;
211✔
1266
    double scaled_height;
211✔
1267
    int max_dimension;
211✔
1268
    int thumbnail_size;
211✔
1269
    int frame_ncolors;
211✔
1270
    unsigned char *clipboard_blob;
211✔
1271
    size_t clipboard_blob_size;
211✔
1272
    SIXELSTATUS clipboard_status;
211✔
1273
    char *clipboard_output_path;
211✔
1274
    unsigned char *clipboard_output_data;
211✔
1275
    size_t clipboard_output_size;
211✔
1276
    SIXELSTATUS clipboard_output_status;
211✔
1277
    sixel_logger_t logger;
211✔
1278
    int logger_prepared;
211✔
1279

1280
    sixel_decoder_ref(decoder);
367✔
1281

1282
    frame = NULL;
367✔
1283
    new_width = 0;
367✔
1284
    new_height = 0;
367✔
1285
    scaled_width = 0.0;
367✔
1286
    scaled_height = 0.0;
367✔
1287
    max_dimension = 0;
367✔
1288
    thumbnail_size = decoder->thumbnail_size;
367✔
1289
    frame_ncolors = -1;
367✔
1290
    clipboard_blob = NULL;
367✔
1291
    clipboard_blob_size = 0u;
367✔
1292
    clipboard_status = SIXEL_OK;
367✔
1293
    clipboard_output_path = NULL;
367✔
1294
    clipboard_output_data = NULL;
367✔
1295
    clipboard_output_size = 0u;
367✔
1296
    clipboard_output_status = SIXEL_OK;
367✔
1297
    input_fp = NULL;
367✔
1298
    sixel_logger_init(&logger);
367✔
1299
    (void)sixel_logger_prepare_env(&logger);
367✔
1300
    logger_prepared = logger.active;
367✔
1301

1302
    raw_len = 0;
367✔
1303
    max = 0;
367✔
1304
    if (decoder->clipboard_input_active) {
367!
1305
        clipboard_status = sixel_clipboard_read(
18✔
1306
            decoder->clipboard_input_format,
14✔
1307
            &clipboard_blob,
1308
            &clipboard_blob_size,
1309
            decoder->allocator);
7✔
1310
        if (SIXEL_FAILED(clipboard_status)) {
14!
UNCOV
1311
            status = clipboard_status;
×
UNCOV
1312
            goto end;
×
1313
        }
1314
        max = (int)((clipboard_blob_size > 0u)
14!
1315
                    ? clipboard_blob_size
7✔
1316
                    : 1u);
1317
        raw_data = (unsigned char *)sixel_allocator_malloc(
14✔
1318
            decoder->allocator,
7✔
1319
            (size_t)max);
7✔
1320
        if (raw_data == NULL) {
14!
UNCOV
1321
            sixel_helper_set_additional_message(
×
1322
                "sixel_decoder_decode: sixel_allocator_malloc() failed.");
UNCOV
1323
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
1324
            goto end;
×
1325
        }
1326
        if (clipboard_blob_size > 0u && clipboard_blob != NULL) {
14!
1327
            memcpy(raw_data, clipboard_blob, clipboard_blob_size);
14✔
1328
        }
7✔
1329
        raw_len = (int)clipboard_blob_size;
14✔
1330
        if (clipboard_blob != NULL) {
14!
1331
            free(clipboard_blob);
14✔
1332
            clipboard_blob = NULL;
14✔
1333
        }
7✔
1334
    } else {
7✔
1335
        if (strcmp(decoder->input, "-") == 0) {
353✔
1336
            /* for windows */
1337
#if defined(O_BINARY)
1338
            (void)sixel_compat_set_binary(STDIN_FILENO);
112✔
1339
#endif  /* defined(O_BINARY) */
1340
            input_fp = stdin;
224✔
1341
        } else {
98✔
1342
            input_fp = sixel_compat_fopen(decoder->input, "rb");
129✔
1343
            if (! input_fp) {
129!
UNCOV
1344
                (void)snprintf(
×
1345
                    message,
1346
                    sizeof(message) - 1,
1347
                    "sixel_decoder_decode: failed to open input file: %s.",
1348
                    decoder->input);
1349
                sixel_helper_set_additional_message(message);
×
UNCOV
1350
                status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
UNCOV
1351
                goto end;
×
1352
            }
1353
        }
1354

1355
        raw_len = 0;
353✔
1356
        max = 64 * 1024;
353✔
1357

1358
        raw_data = (unsigned char *)sixel_allocator_malloc(
353✔
1359
            decoder->allocator,
158✔
1360
            (size_t)max);
158✔
1361
        if (raw_data == NULL) {
353!
UNCOV
1362
            sixel_helper_set_additional_message(
×
1363
                "sixel_decoder_decode: sixel_allocator_malloc() failed.");
UNCOV
1364
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
1365
            goto end;
×
1366
        }
1367

1368
        for (;;) {
20,207✔
1369
            if ((max - raw_len) < 4096) {
16,576✔
1370
                max *= 2;
620✔
1371
                raw_data = (unsigned char *)sixel_allocator_realloc(
620✔
1372
                    decoder->allocator,
276✔
1373
                    raw_data,
276✔
1374
                    (size_t)max);
276✔
1375
                if (raw_data == NULL) {
620!
UNCOV
1376
                    sixel_helper_set_additional_message(
×
1377
                        "sixel_decoder_decode: sixel_allocator_realloc() failed.");
UNCOV
1378
                    status = SIXEL_BAD_ALLOCATION;
×
UNCOV
1379
                    goto end;
×
1380
                }
1381
            }
276✔
1382
            if ((n = (int)fread(raw_data + raw_len, 1, 4096, input_fp)) <= 0) {
18,795!
1383
                break;
218✔
1384
            }
1385
            raw_len += n;
16,223✔
1386
        }
1387

1388
        if (input_fp != NULL && input_fp != stdin) {
353!
1389
            fclose(input_fp);
129✔
1390
        }
60✔
1391
    }
1392

1393
    if (decoder->direct_color != 0 &&
367✔
1394
            decoder->dequantize_method != SIXEL_DEQUANTIZE_NONE) {
96✔
1395
        sixel_helper_set_additional_message(
16✔
1396
            "sixel_decoder_decode: direct option "
1397
            "cannot be combined with dequantize option.");
1398
        status = SIXEL_BAD_ARGUMENT;
16✔
1399
        goto end;
16✔
1400
    }
1401

1402
    ncolors = 0;
351✔
1403

1404
    if (decoder->direct_color != 0) {
351✔
1405
        status = sixel_decode_direct(
80✔
1406
            raw_data,
35✔
1407
            raw_len,
35✔
1408
            &direct_pixels,
1409
            &sx,
1410
            &sy,
1411
            decoder->allocator);
35✔
1412
    } else {
35✔
1413
        status = sixel_decode_raw(
271✔
1414
            raw_data,
123✔
1415
            raw_len,
123✔
1416
            &indexed_pixels,
1417
            &sx,
1418
            &sy,
1419
            &palette,
1420
            &ncolors,
1421
            decoder->allocator);
123✔
1422
    }
1423
    if (SIXEL_FAILED(status)) {
351!
UNCOV
1424
        goto end;
×
1425
    }
1426

1427
    if (sx > SIXEL_WIDTH_LIMIT || sy > SIXEL_HEIGHT_LIMIT) {
351!
UNCOV
1428
        status = SIXEL_BAD_INPUT;
×
1429
        goto end;
×
1430
    }
1431

1432
    if (decoder->direct_color != 0) {
351✔
1433
        output_pixels = direct_pixels;
80✔
1434
        output_palette = NULL;
80✔
1435
        output_pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
80✔
1436
        frame_ncolors = 0;
80✔
1437
    } else {
35✔
1438
        output_pixels = indexed_pixels;
271✔
1439
        output_palette = palette;
271✔
1440
        output_pixelformat = SIXEL_PIXELFORMAT_PAL8;
271✔
1441

1442
        if (decoder->dequantize_method == SIXEL_DEQUANTIZE_K_UNDITHER) {
271✔
1443
            if (logger_prepared) {
16!
UNCOV
1444
                sixel_logger_logf(&logger,
×
1445
                                  "decoder",
1446
                                  "undither",
1447
                                  "start",
1448
                                  0);
1449
            }
1450
            status = sixel_dequantize_k_undither(
16✔
1451
                indexed_pixels,
7✔
1452
                sx,
7✔
1453
                sy,
7✔
1454
                palette,
7✔
1455
                ncolors,
7✔
1456
                decoder->dequantize_similarity_bias,
7✔
1457
                decoder->dequantize_edge_strength,
7✔
1458
                decoder->allocator,
7✔
1459
                &rgb_pixels);
1460
            if (SIXEL_FAILED(status)) {
16!
UNCOV
1461
                if (logger_prepared) {
×
UNCOV
1462
                    sixel_logger_logf(
×
1463
                        &logger,
1464
                        "decoder",
1465
                        "undither",
1466
                        "abort",
1467
                        0);
1468
                }
UNCOV
1469
                goto end;
×
1470
            }
1471
            if (logger_prepared) {
16!
UNCOV
1472
                sixel_logger_logf(&logger,
×
1473
                                  "decoder",
1474
                                  "undither",
1475
                                  "finish",
1476
                                  0);
1477
            }
1478
            output_pixels = rgb_pixels;
16✔
1479
            output_palette = NULL;
16✔
1480
            output_pixelformat = SIXEL_PIXELFORMAT_RGB888;
16✔
1481
        }
7✔
1482

1483
        if (output_pixelformat == SIXEL_PIXELFORMAT_PAL8) {
174✔
1484
            frame_ncolors = ncolors;
255✔
1485
        } else {
116✔
1486
            frame_ncolors = 0;
10✔
1487
        }
1488
    }
1489

1490
    if (thumbnail_size > 0) {
351!
1491
        /*
1492
         * When the caller requests a thumbnail, compute the new geometry
1493
         * while preserving the original aspect ratio. We only allocate a
1494
         * frame when the dimensions actually change, so the fast path for
1495
         * matching sizes still avoids any additional allocations.
1496
         */
UNCOV
1497
        max_dimension = sx;
×
UNCOV
1498
        if (sy > max_dimension) {
×
1499
            max_dimension = sy;
1500
        }
UNCOV
1501
        if (max_dimension > 0) {
×
1502
            if (sx >= sy) {
×
1503
                new_width = thumbnail_size;
×
UNCOV
1504
                scaled_height = (double)sy * (double)thumbnail_size /
×
UNCOV
1505
                    (double)sx;
×
1506
                new_height = (int)(scaled_height + 0.5);
×
1507
            } else {
1508
                new_height = thumbnail_size;
×
1509
                scaled_width = (double)sx * (double)thumbnail_size /
×
1510
                    (double)sy;
×
1511
                new_width = (int)(scaled_width + 0.5);
×
1512
            }
1513
            if (new_width < 1) {
×
1514
                new_width = 1;
1515
            }
1516
            if (new_height < 1) {
×
1517
                new_height = 1;
1518
            }
UNCOV
1519
            if (new_width != sx || new_height != sy) {
×
1520
                /*
1521
                 * Wrap the decoded pixels in a frame so we can reuse the
1522
                 * central scaling helper. Ownership transfers to the frame,
1523
                 * which keeps the lifetime rules identical on both paths.
1524
                 */
UNCOV
1525
                status = sixel_frame_new(&frame, decoder->allocator);
×
UNCOV
1526
                if (SIXEL_FAILED(status)) {
×
UNCOV
1527
                    goto end;
×
1528
                }
UNCOV
1529
                status = sixel_frame_init(
×
1530
                    frame,
1531
                    output_pixels,
1532
                    sx,
1533
                    sy,
1534
                    output_pixelformat,
1535
                    output_palette,
1536
                    frame_ncolors);
UNCOV
1537
                if (SIXEL_FAILED(status)) {
×
UNCOV
1538
                    goto end;
×
1539
                }
UNCOV
1540
                if (output_pixels == indexed_pixels) {
×
UNCOV
1541
                    indexed_pixels = NULL;
×
1542
                }
1543
                if (output_pixels == rgb_pixels) {
×
UNCOV
1544
                    rgb_pixels = NULL;
×
1545
                }
1546
                if (output_palette == palette) {
×
UNCOV
1547
                    palette = NULL;
×
1548
                }
1549
                status = sixel_frame_resize(
×
1550
                    frame,
1551
                    new_width,
1552
                    new_height,
1553
                    SIXEL_RES_BILINEAR);
1554
                if (SIXEL_FAILED(status)) {
×
UNCOV
1555
                    goto end;
×
1556
                }
1557
                /*
1558
                 * The resized frame already exposes a tightly packed RGB
1559
                 * buffer, so write the updated dimensions and references
1560
                 * back to the main encoder path.
1561
                 */
UNCOV
1562
                sx = sixel_frame_get_width(frame);
×
UNCOV
1563
                sy = sixel_frame_get_height(frame);
×
UNCOV
1564
                output_pixels = sixel_frame_get_pixels(frame);
×
UNCOV
1565
                output_palette = NULL;
×
UNCOV
1566
                output_pixelformat = sixel_frame_get_pixelformat(frame);
×
1567
            }
1568
        }
1569
    }
1570

1571
    if (decoder->clipboard_output_active) {
351✔
1572
        clipboard_output_status = decoder_clipboard_create_spool(
16✔
1573
            decoder->allocator,
7✔
1574
            "clipboard-out",
1575
            &clipboard_output_path);
1576
        if (SIXEL_FAILED(clipboard_output_status)) {
16!
UNCOV
1577
            status = clipboard_output_status;
×
UNCOV
1578
            goto end;
×
1579
        }
1580
    }
7✔
1581

1582
    if (logger_prepared) {
351!
1583
        sixel_logger_logf(&logger,
×
1584
                          "io",
1585
                          "png",
1586
                          "start",
1587
                          0);
1588
    }
1589
    status = sixel_helper_write_image_file(
351✔
1590
        output_pixels,
158✔
1591
        sx,
158✔
1592
        sy,
158✔
1593
        output_palette,
158✔
1594
        output_pixelformat,
158✔
1595
        decoder->clipboard_output_active
351✔
1596
            ? clipboard_output_path
7✔
1597
            : decoder->output,
151✔
1598
        SIXEL_FORMAT_PNG,
1599
        decoder->allocator);
158✔
1600
    if (SIXEL_FAILED(status)) {
351!
UNCOV
1601
        if (logger_prepared) {
×
UNCOV
1602
            sixel_logger_logf(&logger,
×
1603
                              "io",
1604
                              "png",
1605
                              "abort",
1606
                              0);
1607
        }
UNCOV
1608
        goto end;
×
1609
    }
1610
    if (logger_prepared) {
351!
UNCOV
1611
        sixel_logger_logf(&logger,
×
1612
                          "io",
1613
                          "png",
1614
                          "finish",
1615
                          0);
1616
    }
1617

1618
    if (decoder->clipboard_output_active) {
358✔
1619
        clipboard_output_status = decoder_clipboard_read_file(
16✔
1620
            clipboard_output_path,
7✔
1621
            &clipboard_output_data,
1622
            &clipboard_output_size);
1623
        if (SIXEL_SUCCEEDED(clipboard_output_status)) {
16!
1624
            clipboard_output_status = sixel_clipboard_write(
16✔
1625
                decoder->clipboard_output_format,
16✔
1626
                clipboard_output_data,
7✔
1627
                clipboard_output_size);
7✔
1628
        }
7✔
1629
        if (clipboard_output_data != NULL) {
16!
1630
            free(clipboard_output_data);
16✔
1631
            clipboard_output_data = NULL;
16✔
1632
        }
7✔
1633
        if (SIXEL_FAILED(clipboard_output_status)) {
16!
1634
            status = clipboard_output_status;
2✔
1635
            goto end;
2✔
1636
        }
1637
    }
7✔
1638

1639
end:
191✔
1640
    sixel_frame_unref(frame);
367✔
1641
    sixel_allocator_free(decoder->allocator, raw_data);
367✔
1642
    sixel_allocator_free(decoder->allocator, indexed_pixels);
367✔
1643
    sixel_allocator_free(decoder->allocator, palette);
367✔
1644
    sixel_allocator_free(decoder->allocator, direct_pixels);
367✔
1645
    sixel_allocator_free(decoder->allocator, rgb_pixels);
367✔
1646
    if (clipboard_blob != NULL) {
367!
UNCOV
1647
        free(clipboard_blob);
×
1648
    }
1649
    if (clipboard_output_path != NULL) {
367✔
1650
        (void)sixel_compat_unlink(clipboard_output_path);
16✔
1651
        sixel_allocator_free(decoder->allocator, clipboard_output_path);
16✔
1652
    }
7✔
1653

1654
    sixel_decoder_unref(decoder);
367✔
1655
    if (logger_prepared) {
367!
UNCOV
1656
        sixel_logger_close(&logger);
×
1657
    }
1658

1659
    return status;
439✔
1660
}
72✔
1661

1662

1663
/* Exercise legacy constructor and refcounting for the decoder. */
1664

1665
/* emacs Local Variables:      */
1666
/* emacs mode: c               */
1667
/* emacs tab-width: 4          */
1668
/* emacs indent-tabs-mode: nil */
1669
/* emacs c-basic-offset: 4     */
1670
/* emacs End:                  */
1671
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1672
/* 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