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

saitoha / libsixel / 21840761027

09 Feb 2026 09:08PM UTC coverage: 81.265% (-0.04%) from 81.301%
21840761027

push

github

saitoha
tests: isolate HOME in TryExec absolute missing case

25737 of 51701 branches covered (49.78%)

41272 of 50787 relevant lines covered (81.26%)

3736998.54 hits per line

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

76.2
/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,
33✔
67
                                size_t dest_size,
68
                                char const *format,
69
                                char const *fallback)
70
{
71
    char const *source;
19✔
72
    size_t limit;
19✔
73

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

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

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

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

92

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

107
    tmpdir = sixel_compat_getenv("TMPDIR");
18✔
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') {
18!
117
#if defined(_WIN32)
118
        tmpdir = ".";
119
#else
120
        tmpdir = "/tmp";
3✔
121
#endif
122
    }
123

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

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

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

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

149
    if (needs_separator) {
18!
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,
3✔
155
                       "%s/%s-XXXXXX", tmpdir, prefix);
156
#endif
157
    } else {
1✔
158
        (void)snprintf(template_path, template_len,
7✔
159
                       "%s%s-XXXXXX", tmpdir, prefix);
160
    }
161

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

166
    return template_path;
11✔
167
}
8✔
168

169

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

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

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

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

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

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

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

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

243
    return status;
22✔
244
}
4✔
245

246

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

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

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

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

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

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

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

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

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

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

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

337
    return SIXEL_OK;
18✔
338
}
8✔
339

340

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

350
    p = (char *)sixel_allocator_malloc(allocator, (size_t)(strlen(s) + 1));
1,482✔
351
    if (p) {
1,482!
352
        (void)sixel_compat_strcpy(p, strlen(s) + 1, s);
1,482✔
353
    }
664✔
354
    return p;
1,814✔
355
}
332✔
356

357

358
/* create decoder object */
359
SIXELAPI SIXELSTATUS
360
sixel_decoder_new(
573✔
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;
573✔
366

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

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

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

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

409
    status = SIXEL_OK;
352✔
410

411
end:
317✔
412
    return status;
701✔
413
}
128✔
414

415

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

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

428
end:
429
    return decoder;
×
430
}
431

432

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

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

448

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

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

460

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

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

471
    previous = sixel_atomic_fetch_sub_u32(&decoder->ref, 1U);
1,500✔
472
    if (previous == 1U) {
1,500✔
473
        sixel_decoder_destroy(decoder);
555✔
474
    }
248✔
475
}
672!
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,
18✔
488
                      const unsigned char *palette,
489
                      int ncolors,
490
                      int bias,
491
                      sixel_allocator_t *allocator)
492
{
493
    size_t cache_size;
11✔
494
    int i;
11✔
495

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

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

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

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

524
    return SIXEL_OK;
11✔
525
}
8✔
526

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

537
static inline unsigned int
538
sixel_similarity_diff(const unsigned char *a, const unsigned char *b)
882✔
539
{
540
    int dr = (int)a[0] - (int)b[0];
882✔
541
    int dg = (int)a[1] - (int)b[1];
882✔
542
    int db = (int)a[2] - (int)b[2];
882✔
543
    return (unsigned int)(dr * dr + dg * dg + db * db);
1,078✔
544
}
196✔
545

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

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

575
    if (index1 < 0 || index1 >= similarity->ncolors ||
244,450!
576
        index2 < 0 || index2 >= similarity->ncolors) {
176,004!
577
        return 0;
578
    }
579

580
    if (index1 <= index2) {
176,004✔
581
        min_index = index1;
104,478✔
582
        max_index = index2;
104,478✔
583
    } else {
75,984✔
584
        min_index = index2;
5,040✔
585
        max_index = index1;
5,040✔
586
    }
587

588
    cache_pos = (size_t)min_index * (size_t)similarity->stride
254,228✔
589
              + (size_t)max_index;
176,004✔
590
    cached = similarity->cache[cache_pos];
176,004✔
591
    if (cached >= 0) {
176,004✔
592
        return (unsigned int)cached;
175,878✔
593
    }
594

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

599
#if 1
600
   /*    original: n = (p1 + p2) / 2
601
    */
602
    avg_color[0] = (unsigned char)(((unsigned int)p1[0]
182✔
603
                                    + (unsigned int)p2[0]) >> 1);
126✔
604
    avg_color[1] = (unsigned char)(((unsigned int)p1[1]
182✔
605
                                    + (unsigned int)p2[1]) >> 1);
126✔
606
    avg_color[2] = (unsigned char)(((unsigned int)p1[2]
182✔
607
                                    + (unsigned int)p2[2]) >> 1);
126✔
608
    (void) numerator;
105✔
609
    (void) denominator;
105✔
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);
126✔
633
    bias = similarity->bias;
126✔
634
    if (bias < 1) {
126!
635
        bias = 1;
636
    }
637
    scaled_distance = (unsigned long long)distance
182✔
638
                    * (unsigned long long)bias
126✔
639
                    + 50ULL;
56✔
640
    base_distance = (unsigned int)(scaled_distance / 100ULL);
126✔
641
    if (base_distance == 0U) {
126!
642
        base_distance = 1U;
643
    }
644

645
    for (i = 0; i < similarity->ncolors; ++i) {
1,134✔
646
        if (i == index1 || i == index2) {
1,008✔
647
            continue;
252✔
648
        }
649
        pk = palette + i * 3;
756✔
650
        diff = sixel_similarity_diff(avg_color, pk);
756✔
651
        if (diff < min_diff) {
756✔
652
            min_diff = diff;
590✔
653
        }
144✔
654
    }
336✔
655

656
    if (min_diff == UINT_MAX) {
126!
657
        min_diff = base_distance * 2U;
×
658
    }
659

660
    if (min_diff >= base_distance * 2U) {
126✔
661
        result = 5U;
22✔
662
    } else if (min_diff >= base_distance) {
106✔
663
        result = 8U;
11✔
664
    } else if ((unsigned long long)min_diff * 6ULL
108!
665
               >= (unsigned long long)base_distance * 5ULL) {
72!
666
        result = 7U;
667
    } else if ((unsigned long long)min_diff * 4ULL
100!
668
               >= (unsigned long long)base_distance * 3ULL) {
72!
669
        result = 7U;
670
    } else if ((unsigned long long)min_diff * 3ULL
100✔
671
               >= (unsigned long long)base_distance * 2ULL) {
72✔
672
        result = 5U;
11✔
673
    } else if ((unsigned long long)min_diff * 5ULL
83✔
674
               >= (unsigned long long)base_distance * 3ULL) {
33✔
675
        result = 7U;
11✔
676
    } else if ((unsigned long long)min_diff * 2ULL
58✔
677
               >= (unsigned long long)base_distance * 1ULL) {
22✔
678
        result = 4U;
11✔
679
    } else if ((unsigned long long)min_diff * 3ULL
33!
680
               >= (unsigned long long)base_distance * 1ULL) {
11✔
681
        result = 2U;
11✔
682
    } else {
8✔
683
        result = 0U;
×
684
    }
685

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

688
    return result;
126✔
689
}
78,224✔
690

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

703
static inline int
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);
×
708
    return gray[cy * width + cx];
×
709
}
710

711
static unsigned short
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 +
×
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;
×
732
    if (magnitude > 65535ULL) {
×
733
        magnitude = 65535ULL;
734
    }
735
    return (unsigned short)magnitude;
×
736
}
737

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

744
    if (percent <= 0) {
36!
745
        percent = 1;
22✔
746
    }
16✔
747

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

758
    return (unsigned short)scaled;
44✔
759
}
8✔
760

761
static SIXELSTATUS
762
sixel_dequantize_k_undither(unsigned char *indexed_pixels,
18✔
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;
18✔
773
    unsigned char *rgb = NULL;
18✔
774
    int *gray = NULL;
18✔
775
    unsigned short *prewitt = NULL;
18✔
776
    sixel_similarity_t similarity;
11✔
777
    size_t num_pixels;
11✔
778
    int x;
11✔
779
    int y;
11✔
780
    unsigned short strong_threshold;
11✔
781
    unsigned short detail_threshold;
11✔
782
    static const int neighbor_offsets[8][4] = {
7✔
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;
11✔
788
    size_t out_index;
11✔
789
    int palette_index;
11✔
790
    unsigned int center_weight;
11✔
791
    unsigned int total_weight = 0;
18✔
792
    unsigned int accum_r;
11✔
793
    unsigned int accum_g;
11✔
794
    unsigned int accum_b;
11✔
795
    unsigned short gradient;
11✔
796
    int neighbor;
11✔
797
    int nx;
11✔
798
    int ny;
11✔
799
    int numerator;
11✔
800
    int denominator;
11✔
801
    unsigned int weight;
11✔
802
    const unsigned char *neighbor_color;
11✔
803
    int neighbor_index;
11✔
804

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

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

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

813
    strong_threshold = sixel_scale_threshold(256U, edge_strength);
18!
814
    detail_threshold = sixel_scale_threshold(160U, edge_strength);
18!
815
    if (strong_threshold < detail_threshold) {
18!
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(
18✔
824
        allocator,
8✔
825
        num_pixels * 3);
8✔
826
    if (rgb == NULL) {
18!
827
        sixel_helper_set_additional_message(
×
828
            "sixel_dequantize_k_undither: "
829
            "sixel_allocator_malloc() failed.");
830
        status = SIXEL_BAD_ALLOCATION;
×
831
        goto end;
×
832
    }
833

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

845
    prewitt = (unsigned short *)sixel_allocator_malloc(
22✔
846
        allocator,
8✔
847
        num_pixels * sizeof(unsigned short));
15✔
848
    if (prewitt == NULL) {
18!
849
        sixel_helper_set_additional_message(
×
850
            "sixel_dequantize_k_undither: "
851
            "sixel_allocator_malloc() failed.");
852
        status = SIXEL_BAD_ALLOCATION;
×
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(
18✔
861
        &similarity,
862
        palette,
8✔
863
        ncolors,
8✔
864
        similarity_bias,
8✔
865
        allocator);
8✔
866
    if (SIXEL_FAILED(status)) {
18!
867
        goto end;
×
868
    }
869

870
    for (y = 0; y < height; ++y) {
270✔
871
        for (x = 0; x < width; ++x) {
23,688✔
872
            palette_index = indexed_pixels[y * width + x];
23,436✔
873
            if (palette_index < 0 || palette_index >= ncolors) {
23,436!
874
                palette_index = 0;
×
875
            }
876

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

883
            if (edge_strength > 0) {
23,436!
884
                gray[y * width + x] = (int)color[0]
×
885
                                    + (int)color[1] * 2
×
886
                                    + (int)color[2];
×
887
                /*
888
                 * Edge detection keeps high-frequency content intact while we
889
                 * smooth dithering noise in flatter regions.
890
                 */
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) {
×
900
                    continue;
×
901
                }
902

903
                if (gradient > detail_threshold) {
×
904
                    center_weight = 24U;
905
                } else {
906
                    center_weight = 8U;
9,114✔
907
                }
908
            } else {
909
                center_weight = 8U;
14,322✔
910
            }
911

912
            out_index = (size_t)(y * width + x) * 3;
23,436✔
913
            accum_r = (unsigned int)rgb[out_index + 0] * center_weight;
23,436✔
914
            accum_g = (unsigned int)rgb[out_index + 1] * center_weight;
23,436✔
915
            accum_b = (unsigned int)rgb[out_index + 2] * center_weight;
23,436✔
916
            total_weight = center_weight;
23,436✔
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) {
210,924✔
924
                nx = x + neighbor_offsets[neighbor][0];
187,488✔
925
                ny = y + neighbor_offsets[neighbor][1];
187,488✔
926
                numerator = neighbor_offsets[neighbor][2];
187,488✔
927
                denominator = neighbor_offsets[neighbor][3];
187,488✔
928

929
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
187,488✔
930
                    continue;
11,484✔
931
                }
932

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

938
                if (numerator) {
176,004!
939
                    weight = sixel_similarity_compare(
176,004✔
940
                        &similarity,
941
                        palette_index,
78,224✔
942
                        neighbor_index,
78,224✔
943
                        numerator,
78,224✔
944
                        denominator);
78,224✔
945
                    if (weight == 0) {
176,004!
946
                        continue;
×
947
                    }
948

949
                    neighbor_color = palette + neighbor_index * 3;
176,004✔
950
                    accum_r += (unsigned int)neighbor_color[0] * weight;
176,004✔
951
                    accum_g += (unsigned int)neighbor_color[1] * weight;
176,004✔
952
                    accum_b += (unsigned int)neighbor_color[2] * weight;
176,004✔
953
                    total_weight += weight;
176,004✔
954
                }
78,224✔
955
            }
78,224✔
956

957
            if (total_weight > 0U) {
23,436!
958
                rgb[out_index + 0] = (unsigned char)(accum_r / total_weight);
23,436✔
959
                rgb[out_index + 1] = (unsigned char)(accum_g / total_weight);
23,436✔
960
                rgb[out_index + 2] = (unsigned char)(accum_b / total_weight);
23,436✔
961
            }
10,416✔
962
        }
10,416✔
963
    }
112✔
964

965

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

970
end:
10✔
971
    sixel_similarity_destroy(&similarity, allocator);
18!
972
    sixel_allocator_free(allocator, rgb);
18✔
973
    sixel_allocator_free(allocator, gray);
18✔
974
    sixel_allocator_free(allocator, prewitt);
18✔
975
    return status;
18✔
976
}
8✔
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(
552✔
995
    sixel_decoder_t /* in */ *decoder,
996
    int             /* in */ arg,
997
    char const      /* in */ *value
998
)
999
{
1000
    SIXELSTATUS status = SIXEL_FALSE;
552✔
1001
    unsigned int path_flags;
335✔
1002
    int path_check;
335✔
1003
    char const *payload = NULL;
552✔
1004
    sixel_clipboard_spec_t clipboard_spec;
335✔
1005
    int match_index;
335✔
1006
    sixel_option_choice_result_t match_result;
335✔
1007
    char match_detail[128];
335✔
1008
    char match_message[256];
335✔
1009
    char const *filename = NULL;
552✔
1010
    size_t libc_buffer_size;
335✔
1011
    char *libc_buffer;
335✔
1012
    char const *libc_path;
335✔
1013
    long bias;
335✔
1014
    long parsed_value;
335✔
1015
    char *endptr;
335✔
1016

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

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

1067
        payload = value;
195✔
1068
        if (strncmp(value, "png:", 4) == 0) {
195✔
1069
            payload = value + 4;
72✔
1070
            if (payload[0] == '\0') {
72✔
1071
                sixel_helper_set_additional_message(
18✔
1072
                    "missing target after the \"png:\" prefix.");
1073
                return SIXEL_BAD_ARGUMENT;
18✔
1074
            }
1075
            libc_buffer_size = sixel_path_to_libc_buffer_size(payload);
54✔
1076
            if (libc_buffer_size > 0u) {
54!
1077
                libc_buffer = (char *)malloc(libc_buffer_size);
4✔
1078
                if (libc_buffer == NULL) {
4!
1079
                    sixel_helper_set_additional_message(
×
1080
                        "sixel_decoder_setopt: malloc() failed for png path "
1081
                        "buffer.");
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!
1088
                    sixel_helper_set_additional_message(
×
1089
                        "sixel_decoder_setopt: invalid png output path.");
1090
                    free(libc_buffer);
×
1091
                    return SIXEL_BAD_ARGUMENT;
×
1092
                }
1093
                filename = libc_path;
1094
            } else {
1095
                filename = payload;
33✔
1096
            }
1097
        } else {
24✔
1098
            filename = value;
77✔
1099
        }
1100

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

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

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

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

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

1183
    case SIXEL_OPTFLAG_SIZE:  /* s */
1184
        parsed_value = 0L;
×
1185
        endptr = NULL;
×
1186
        errno = 0;
×
1187
        parsed_value = strtol(value, &endptr, 10);
×
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;
×
1196
        break;
×
1197

1198
    case SIXEL_OPTFLAG_EDGE:  /* e */
1199
        parsed_value = 0L;
×
1200
        endptr = NULL;
×
1201
        errno = 0;
×
1202
        parsed_value = strtol(value, &endptr, 10);
×
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;
×
1211
        break;
×
1212

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

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

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

1230
    status = SIXEL_OK;
297✔
1231

1232
end:
294✔
1233
    sixel_decoder_unref(decoder);
534✔
1234

1235
    return status;
534✔
1236
}
248✔
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(
393✔
1243
    sixel_decoder_t /* in */ *decoder)
1244
{
1245
    SIXELSTATUS status = SIXEL_FALSE;
393✔
1246
    unsigned char *raw_data = NULL;
393✔
1247
    int sx;
239✔
1248
    int sy;
239✔
1249
    int raw_len;
239✔
1250
    int max;
239✔
1251
    int n;
239✔
1252
    FILE *input_fp = NULL;
393✔
1253
    char message[2048];
239✔
1254
    unsigned char *indexed_pixels = NULL;
393✔
1255
    unsigned char *palette = NULL;
393✔
1256
    unsigned char *rgb_pixels = NULL;
393✔
1257
    unsigned char *direct_pixels = NULL;
393✔
1258
    unsigned char *output_pixels;
239✔
1259
    unsigned char *output_palette;
239✔
1260
    int output_pixelformat;
239✔
1261
    int ncolors;
239✔
1262
    sixel_frame_t *frame;
239✔
1263
    int new_width;
239✔
1264
    int new_height;
239✔
1265
    double scaled_width;
239✔
1266
    double scaled_height;
239✔
1267
    int max_dimension;
239✔
1268
    int thumbnail_size;
239✔
1269
    int frame_ncolors;
239✔
1270
    unsigned char *clipboard_blob;
239✔
1271
    size_t clipboard_blob_size;
239✔
1272
    SIXELSTATUS clipboard_status;
239✔
1273
    char *clipboard_output_path;
239✔
1274
    unsigned char *clipboard_output_data;
239✔
1275
    size_t clipboard_output_size;
239✔
1276
    SIXELSTATUS clipboard_output_status;
239✔
1277
    sixel_logger_t logger;
239✔
1278
    int logger_prepared;
239✔
1279

1280
    sixel_decoder_ref(decoder);
393✔
1281

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

1302
    raw_len = 0;
393✔
1303
    max = 0;
393✔
1304
    if (decoder->clipboard_input_active) {
393!
1305
        clipboard_status = sixel_clipboard_read(
19✔
1306
            decoder->clipboard_input_format,
15✔
1307
            &clipboard_blob,
1308
            &clipboard_blob_size,
1309
            decoder->allocator);
8✔
1310
        if (SIXEL_FAILED(clipboard_status)) {
15!
1311
            status = clipboard_status;
×
1312
            goto end;
×
1313
        }
1314
        max = (int)((clipboard_blob_size > 0u)
15!
1315
                    ? clipboard_blob_size
8✔
1316
                    : 1u);
1317
        raw_data = (unsigned char *)sixel_allocator_malloc(
15✔
1318
            decoder->allocator,
8✔
1319
            (size_t)max);
8✔
1320
        if (raw_data == NULL) {
15!
1321
            sixel_helper_set_additional_message(
×
1322
                "sixel_decoder_decode: sixel_allocator_malloc() failed.");
1323
            status = SIXEL_BAD_ALLOCATION;
×
1324
            goto end;
×
1325
        }
1326
        if (clipboard_blob_size > 0u && clipboard_blob != NULL) {
15!
1327
            memcpy(raw_data, clipboard_blob, clipboard_blob_size);
15✔
1328
        }
8✔
1329
        raw_len = (int)clipboard_blob_size;
15✔
1330
        if (clipboard_blob != NULL) {
15!
1331
            free(clipboard_blob);
15✔
1332
            clipboard_blob = NULL;
15✔
1333
        }
8✔
1334
    } else {
8✔
1335
        if (strcmp(decoder->input, "-") == 0) {
378✔
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;
252✔
1341
        } else {
112✔
1342
            input_fp = sixel_compat_fopen(decoder->input, "rb");
126✔
1343
            if (! input_fp) {
126!
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);
×
1350
                status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1351
                goto end;
×
1352
            }
1353
        }
1354

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

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

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

1388
        if (input_fp != NULL && input_fp != stdin) {
378!
1389
            fclose(input_fp);
126✔
1390
        }
56✔
1391
    }
1392

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

1402
    ncolors = 0;
375✔
1403

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

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

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

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

1483
        if (output_pixelformat == SIXEL_PIXELFORMAT_PAL8) {
183✔
1484
            frame_ncolors = ncolors;
267✔
1485
        } else {
120✔
1486
            frame_ncolors = 0;
11✔
1487
        }
1488
    }
1489

1490
    if (thumbnail_size > 0) {
375!
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
         */
1497
        max_dimension = sx;
×
1498
        if (sy > max_dimension) {
×
1499
            max_dimension = sy;
1500
        }
1501
        if (max_dimension > 0) {
×
1502
            if (sx >= sy) {
×
1503
                new_width = thumbnail_size;
×
1504
                scaled_height = (double)sy * (double)thumbnail_size /
×
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
            }
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
                 */
1525
                status = sixel_frame_new(&frame, decoder->allocator);
×
1526
                if (SIXEL_FAILED(status)) {
×
1527
                    goto end;
×
1528
                }
1529
                status = sixel_frame_init(
×
1530
                    frame,
1531
                    output_pixels,
1532
                    sx,
1533
                    sy,
1534
                    output_pixelformat,
1535
                    output_palette,
1536
                    frame_ncolors);
1537
                if (SIXEL_FAILED(status)) {
×
1538
                    goto end;
×
1539
                }
1540
                if (output_pixels == indexed_pixels) {
×
1541
                    indexed_pixels = NULL;
×
1542
                }
1543
                if (output_pixels == rgb_pixels) {
×
1544
                    rgb_pixels = NULL;
×
1545
                }
1546
                if (output_palette == palette) {
×
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)) {
×
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
                 */
1562
                sx = sixel_frame_get_width(frame);
×
1563
                sy = sixel_frame_get_height(frame);
×
1564
                output_pixels = sixel_frame_get_pixels(frame);
×
1565
                output_palette = NULL;
×
1566
                output_pixelformat = sixel_frame_get_pixelformat(frame);
×
1567
            }
1568
        }
1569
    }
1570

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

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

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

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

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

1659
    return status;
481✔
1660
}
88✔
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