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

saitoha / libsixel / 21335896708

25 Jan 2026 04:33PM UTC coverage: 76.581% (-2.3%) from 78.904%
21335896708

push

github

saitoha
meson: set build type to plain

20012 of 44638 branches covered (44.83%)

36354 of 47471 relevant lines covered (76.58%)

13461842.27 hits per line

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

77.25
/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 "options.h"
62
#include "sixel_atomic.h"
63

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

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

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

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

88
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
22✔
89
}
6!
90

91

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

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

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

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

135
    needs_separator = 1;
12✔
136
    if (tmpdir_len > 0) {
12!
137
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
12!
138
            needs_separator = 0;
8✔
139
        }
2✔
140
    }
3✔
141

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

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

161
    if (capacity_out != NULL) {
12!
162
        *capacity_out = template_len;
12✔
163
    }
3✔
164

165
    return template_path;
6✔
166
}
3✔
167

168

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

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

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

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

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

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

231
    template_path = NULL;
12✔
232
    status = SIXEL_OK;
12✔
233

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

242
    return status;
13✔
243
}
1✔
244

245

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

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

263
    *data = NULL;
12✔
264
    *size = 0u;
12✔
265

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

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

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

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

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

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

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

333
    *data = buffer;
12✔
334
    *size = read_size;
12✔
335

336
    return SIXEL_OK;
12✔
337
}
3✔
338

339

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

349
    p = (char *)sixel_allocator_malloc(allocator, (size_t)(strlen(s) + 1));
3,157✔
350
    if (p) {
3,157!
351
        (void)sixel_compat_strcpy(p, strlen(s) + 1, s);
3,157✔
352
    }
795✔
353
    return p;
3,427✔
354
}
270✔
355

356

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

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

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

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

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

408
    status = SIXEL_OK;
456✔
409

410
end:
688✔
411
    return status;
997✔
412
}
78✔
413

414

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

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

427
end:
428
    return decoder;
×
429
}
430

431

432
/* destroy a decoder object */
433
static void
434
sixel_decoder_destroy(sixel_decoder_t *decoder)
907✔
435
{
436
    sixel_allocator_t *allocator;
534✔
437

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

447

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

456
    (void)sixel_atomic_fetch_add_u32(&decoder->ref, 1U);
2,262✔
457
}
570✔
458

459

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

466
    if (decoder == NULL) {
3,169!
467
        return;
468
    }
469

470
    previous = sixel_atomic_fetch_sub_u32(&decoder->ref, 1U);
3,169✔
471
    if (previous == 1U) {
3,169✔
472
        sixel_decoder_destroy(decoder);
907✔
473
    }
228✔
474
}
798!
475

476

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

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

495
    if (bias < 1) {
12!
496
        bias = 1;
497
    }
498

499
    similarity->palette = palette;
12✔
500
    similarity->ncolors = ncolors;
12✔
501
    similarity->stride = ncolors;
12✔
502
    similarity->bias = bias;
12✔
503

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

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

523
    return SIXEL_OK;
6✔
524
}
3✔
525

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

536
static inline unsigned int
537
sixel_similarity_diff(const unsigned char *a, const unsigned char *b)
6,428,184✔
538
{
539
    int dr = (int)a[0] - (int)b[0];
6,428,184✔
540
    int dg = (int)a[1] - (int)b[1];
6,428,184✔
541
    int db = (int)a[2] - (int)b[2];
6,428,184✔
542
    return (unsigned int)(dr * dr + dg * dg + db * db);
6,963,866✔
543
}
535,682✔
544

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

570
    if (similarity->cache == NULL) {
25,844,448!
571
        return 0;
572
    }
573

574
    if (index1 < 0 || index1 >= similarity->ncolors ||
30,151,856!
575
        index2 < 0 || index2 >= similarity->ncolors) {
25,844,448!
576
        return 0;
577
    }
578

579
    if (index1 <= index2) {
25,844,448✔
580
        min_index = index1;
9,028,434✔
581
        max_index = index2;
9,028,434✔
582
    } else {
4,514,217✔
583
        min_index = index2;
7,787,580✔
584
        max_index = index1;
7,787,580✔
585
    }
586

587
    cache_pos = (size_t)min_index * (size_t)similarity->stride
32,305,560✔
588
              + (size_t)max_index;
25,844,448✔
589
    cached = similarity->cache[cache_pos];
25,844,448✔
590
    if (cached >= 0) {
25,844,448✔
591
        return (unsigned int)cached;
25,805,724✔
592
    }
593

594
    palette = similarity->palette;
38,724✔
595
    p1 = palette + index1 * 3;
38,724✔
596
    p2 = palette + index2 * 3;
38,724✔
597

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

631
    distance = sixel_similarity_diff(avg_color, p1);
38,724✔
632
    bias = similarity->bias;
38,724✔
633
    if (bias < 1) {
38,724!
634
        bias = 1;
635
    }
636
    scaled_distance = (unsigned long long)distance
48,405✔
637
                    * (unsigned long long)bias
38,724✔
638
                    + 50ULL;
9,681✔
639
    base_distance = (unsigned int)(scaled_distance / 100ULL);
38,724✔
640
    if (base_distance == 0U) {
38,724!
641
        base_distance = 1U;
642
    }
643

644
    for (i = 0; i < similarity->ncolors; ++i) {
6,505,632✔
645
        if (i == index1 || i == index2) {
6,466,908✔
646
            continue;
77,448✔
647
        }
648
        pk = palette + i * 3;
6,389,460✔
649
        diff = sixel_similarity_diff(avg_color, pk);
6,389,460✔
650
        if (diff < min_diff) {
6,389,460✔
651
            min_diff = diff;
3,384,000✔
652
        }
75,273✔
653
    }
1,597,365✔
654

655
    if (min_diff == UINT_MAX) {
38,724!
656
        min_diff = base_distance * 2U;
×
657
    }
658

659
    if (min_diff >= base_distance * 2U) {
38,724✔
660
        result = 5U;
1,032✔
661
    } else if (min_diff >= base_distance) {
37,176✔
662
        result = 8U;
1,242✔
663
    } else if ((unsigned long long)min_diff * 6ULL
40,493✔
664
               >= (unsigned long long)base_distance * 5ULL) {
34,176✔
665
        result = 7U;
444✔
666
    } else if ((unsigned long long)min_diff * 4ULL
39,058✔
667
               >= (unsigned long long)base_distance * 3ULL) {
33,288✔
668
        result = 7U;
300✔
669
    } else if ((unsigned long long)min_diff * 3ULL
38,286✔
670
               >= (unsigned long long)base_distance * 2ULL) {
32,688✔
671
        result = 5U;
384✔
672
    } else if ((unsigned long long)min_diff * 5ULL
37,432✔
673
               >= (unsigned long long)base_distance * 3ULL) {
15,960✔
674
        result = 7U;
378✔
675
    } else if ((unsigned long long)min_diff * 2ULL
36,547✔
676
               >= (unsigned long long)base_distance * 1ULL) {
15,582✔
677
        result = 4U;
708✔
678
    } else if ((unsigned long long)min_diff * 3ULL
35,060✔
679
               >= (unsigned long long)base_distance * 1ULL) {
14,874✔
680
        result = 2U;
1,950✔
681
    } else {
975✔
682
        result = 0U;
25,848✔
683
    }
684

685
    similarity->cache[cache_pos] = (signed char)result;
38,724✔
686

687
    return result;
38,724✔
688
}
6,461,112✔
689

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

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

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

737
static unsigned short
738
sixel_scale_threshold(unsigned short base, int percent)
24✔
739
{
740
    unsigned long long numerator;
14✔
741
    unsigned long long scaled;
14✔
742

743
    if (percent <= 0) {
24!
744
        percent = 1;
12✔
745
    }
6✔
746

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

757
    return (unsigned short)scaled;
26✔
758
}
2✔
759

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

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

808
    num_pixels = (size_t)width * (size_t)height;
12✔
809

810
    memset(&similarity, 0, sizeof(sixel_similarity_t));
12!
811

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

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

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

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

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

869
    for (y = 0; y < height; ++y) {
5,412✔
870
        for (x = 0; x < width; ++x) {
3,245,400✔
871
            palette_index = indexed_pixels[y * width + x];
3,240,000✔
872
            if (palette_index < 0 || palette_index >= ncolors) {
3,240,000!
873
                palette_index = 0;
×
874
            }
875

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

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

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

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

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

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

928
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
25,920,000✔
929
                    continue;
75,552✔
930
                }
931

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

937
                if (numerator) {
25,844,448!
938
                    weight = sixel_similarity_compare(
25,844,448✔
939
                        &similarity,
940
                        palette_index,
6,461,112✔
941
                        neighbor_index,
6,461,112✔
942
                        numerator,
6,461,112✔
943
                        denominator);
6,461,112✔
944
                    if (weight == 0) {
25,844,448✔
945
                        continue;
1,404,600✔
946
                    }
947

948
                    neighbor_color = palette + neighbor_index * 3;
24,439,848✔
949
                    accum_r += (unsigned int)neighbor_color[0] * weight;
24,439,848✔
950
                    accum_g += (unsigned int)neighbor_color[1] * weight;
24,439,848✔
951
                    accum_b += (unsigned int)neighbor_color[2] * weight;
24,439,848✔
952
                    total_weight += weight;
24,439,848✔
953
                }
6,109,962✔
954
            }
6,109,962✔
955

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

964

965
    *output = rgb;
12✔
966
    rgb = NULL;
12✔
967
    status = SIXEL_OK;
12✔
968

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

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

991
static void
992
decoder_normalise_windows_drive_path(char *path)
30✔
993
{
994
#if defined(_WIN32)
995
    size_t length;
12✔
996

997
    length = 0u;
24✔
998

999
    if (path == NULL) {
24✔
1000
        return;
1001
    }
1002

1003
    length = strlen(path);
24✔
1004
    if (length >= 3u
24✔
1005
            && path[0] == '/'
17✔
1006
            && ((path[1] >= 'A' && path[1] <= 'Z')
6✔
1007
                || (path[1] >= 'a' && path[1] <= 'z'))
4✔
1008
            && path[2] == '/') {
4✔
1009
        path[0] = path[1];
4✔
1010
        path[1] = ':';
4✔
1011
    }
1012
#else
1013
    (void)path;
6✔
1014
#endif
1015
}
9✔
1016

1017
/* set an option flag to decoder object */
1018
SIXELAPI SIXELSTATUS
1019
sixel_decoder_setopt(
1,463✔
1020
    sixel_decoder_t /* in */ *decoder,
1021
    int             /* in */ arg,
1022
    char const      /* in */ *value
1023
)
1024
{
1025
    SIXELSTATUS status = SIXEL_FALSE;
1,463✔
1026
    unsigned int path_flags;
869✔
1027
    int path_check;
869✔
1028
    char const *payload = NULL;
1,463✔
1029
    size_t length;
869✔
1030
    sixel_clipboard_spec_t clipboard_spec;
869✔
1031
    int match_index;
869✔
1032
    sixel_option_choice_result_t match_result;
869✔
1033
    char match_detail[128];
869✔
1034
    char match_message[256];
869✔
1035
    char const *filename = NULL;
1,463✔
1036
    char *p = NULL;
1,463✔
1037
    long bias;
869✔
1038
    long parsed_value;
869✔
1039
    char *endptr;
869✔
1040

1041
    sixel_decoder_ref(decoder);
1,463✔
1042
    path_flags = 0u;
1,463✔
1043
    path_check = 0;
1,463✔
1044

1045
    switch(arg) {
1,463!
1046
    case SIXEL_OPTFLAG_INPUT:  /* i */
505✔
1047
        path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
676✔
1048
            SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
1049
            SIXEL_OPTION_PATH_ALLOW_REMOTE;
1050
        if (value != NULL) {
676!
1051
            path_check = sixel_option_validate_filesystem_path(
676✔
1052
                value,
171✔
1053
                value,
171✔
1054
                path_flags);
171✔
1055
            if (path_check != 0) {
676✔
1056
                status = SIXEL_BAD_ARGUMENT;
12✔
1057
                goto end;
12✔
1058
            }
1059
        }
168✔
1060
        decoder->clipboard_input_active = 0;
664✔
1061
        decoder->clipboard_input_format[0] = '\0';
664✔
1062
        if (value != NULL) {
664!
1063
            clipboard_spec.is_clipboard = 0;
664✔
1064
            clipboard_spec.format[0] = '\0';
664✔
1065
            if (sixel_clipboard_parse_spec(value, &clipboard_spec)
664!
1066
                    && clipboard_spec.is_clipboard) {
175!
1067
                decoder_clipboard_select_format(
10✔
1068
                    decoder->clipboard_input_format,
10✔
1069
                    sizeof(decoder->clipboard_input_format),
1070
                    clipboard_spec.format,
3✔
1071
                    "sixel");
1072
                decoder->clipboard_input_active = 1;
10✔
1073
            }
3✔
1074
        }
168✔
1075
        free(decoder->input);
664✔
1076
        decoder->input = strdup_with_allocator(value, decoder->allocator);
664✔
1077
        if (decoder->input == NULL) {
664!
1078
            sixel_helper_set_additional_message(
×
1079
                "sixel_decoder_setopt: strdup_with_allocator() failed.");
1080
            status = SIXEL_BAD_ALLOCATION;
×
1081
            goto end;
×
1082
        }
1083
        break;
324✔
1084
    case SIXEL_OPTFLAG_OUTPUT:  /* o */
499✔
1085
        decoder->clipboard_output_active = 0;
667✔
1086
        decoder->clipboard_output_format[0] = '\0';
667✔
1087

1088
        payload = value;
667✔
1089
        if (strncmp(value, "png:", 4) == 0) {
667✔
1090
            payload = value + 4;
48✔
1091
            if (payload[0] == '\0') {
48✔
1092
                sixel_helper_set_additional_message(
12✔
1093
                    "missing target after the \"png:\" prefix.");
1094
                return SIXEL_BAD_ARGUMENT;
12✔
1095
            }
1096
            length = strlen(payload);
36✔
1097
            filename = p = malloc(length + 1U);
36✔
1098
            if (p == NULL) {
36!
1099
                sixel_helper_set_additional_message(
×
1100
                    "sixel_decoder_setopt: malloc() failed for png path filename.");
1101
                return SIXEL_BAD_ALLOCATION;
×
1102
            }
1103
            memcpy(p, payload, length + 1U);
36✔
1104
            decoder_normalise_windows_drive_path(p);
36✔
1105
        } else {
9✔
1106
            filename = value;
306✔
1107
        }
1108

1109
        if (filename != NULL) {
342!
1110
            clipboard_spec.is_clipboard = 0;
655✔
1111
            clipboard_spec.format[0] = '\0';
655✔
1112
            if (sixel_clipboard_parse_spec(filename, &clipboard_spec)
655!
1113
                    && clipboard_spec.is_clipboard) {
174!
1114
                decoder_clipboard_select_format(
12✔
1115
                    decoder->clipboard_output_format,
12✔
1116
                    sizeof(decoder->clipboard_output_format),
1117
                    clipboard_spec.format,
3✔
1118
                    "png");
1119
                decoder->clipboard_output_active = 1;
12✔
1120
            }
3✔
1121
        }
165✔
1122
        free(decoder->output);
655✔
1123
        decoder->output = strdup_with_allocator(filename, decoder->allocator);
655✔
1124
        free(p);
655✔
1125
        if (decoder->output == NULL) {
655!
1126
            sixel_helper_set_additional_message(
×
1127
                "sixel_decoder_setopt: strdup_with_allocator() failed.");
1128
            status = SIXEL_BAD_ALLOCATION;
×
1129
            goto end;
×
1130
        }
1131
        break;
324✔
1132
    case SIXEL_OPTFLAG_DEQUANTIZE:  /* d */
18✔
1133
        if (value == NULL) {
24!
1134
            sixel_helper_set_additional_message(
×
1135
                "sixel_decoder_setopt: -d/--dequantize requires an argument.");
1136
            status = SIXEL_BAD_ALLOCATION;
×
1137
            goto end;
×
1138
        }
1139

1140
        match_index = 0;
24✔
1141
        memset(match_detail, 0, sizeof(match_detail));
24✔
1142
        memset(match_message, 0, sizeof(match_message));
24✔
1143

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

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

1185
        decoder->dequantize_similarity_bias = (int)bias;
×
1186
        break;
×
1187

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

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

1218
    case SIXEL_OPTFLAG_DIRECT:  /* D */
54✔
1219
        decoder->direct_color = 1;
72✔
1220
        break;
72✔
1221

1222
    case SIXEL_OPTFLAG_THREADS:  /* = */
9✔
1223
        status = sixel_decoder_parallel_override_threads(value);
12✔
1224
        if (SIXEL_FAILED(status)) {
12!
1225
            goto end;
12✔
1226
        }
1227
        break;
1228

1229
    case '?':
×
1230
    default:
1231
        status = SIXEL_BAD_ARGUMENT;
×
1232
        goto end;
×
1233
    }
1234

1235
    status = SIXEL_OK;
696✔
1236

1237
end:
1,085✔
1238
    sixel_decoder_unref(decoder);
1,451✔
1239

1240
    return status;
1,451✔
1241
}
369✔
1242

1243

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

1285
    sixel_decoder_ref(decoder);
799✔
1286

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

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

1360
        raw_len = 0;
789✔
1361
        max = 64 * 1024;
789✔
1362

1363
        raw_data = (unsigned char *)sixel_allocator_malloc(
789✔
1364
            decoder->allocator,
198✔
1365
            (size_t)max);
198✔
1366
        if (raw_data == NULL) {
789!
1367
            sixel_helper_set_additional_message(
×
1368
                "sixel_decoder_decode: sixel_allocator_malloc() failed.");
1369
            status = SIXEL_BAD_ALLOCATION;
×
1370
            goto end;
×
1371
        }
1372

1373
        for (;;) {
42,346✔
1374
            if ((max - raw_len) < 4096) {
33,602✔
1375
                max *= 2;
630✔
1376
                raw_data = (unsigned char *)sixel_allocator_realloc(
630✔
1377
                    decoder->allocator,
158✔
1378
                    raw_data,
158✔
1379
                    (size_t)max);
158✔
1380
                if (raw_data == NULL) {
630!
1381
                    sixel_helper_set_additional_message(
×
1382
                        "sixel_decoder_decode: sixel_allocator_realloc() failed.");
1383
                    status = SIXEL_BAD_ALLOCATION;
×
1384
                    goto end;
×
1385
                }
1386
            }
158✔
1387
            if ((n = (int)fread(raw_data + raw_len, 1, 4096, input_fp)) <= 0) {
39,417!
1388
                break;
390✔
1389
            }
1390
            raw_len += n;
32,813✔
1391
        }
1392

1393
        if (input_fp != NULL && input_fp != stdin) {
789!
1394
            fclose(input_fp);
621✔
1395
        }
156✔
1396
    }
1397

1398
    if (decoder->direct_color != 0 &&
799✔
1399
            decoder->dequantize_method != SIXEL_DEQUANTIZE_NONE) {
72✔
1400
        sixel_helper_set_additional_message(
12✔
1401
            "sixel_decoder_decode: direct option "
1402
            "cannot be combined with dequantize option.");
1403
        status = SIXEL_BAD_ARGUMENT;
12✔
1404
        goto end;
12✔
1405
    }
1406

1407
    ncolors = 0;
787✔
1408

1409
    if (decoder->direct_color != 0) {
787✔
1410
        status = sixel_decode_direct(
60✔
1411
            raw_data,
15✔
1412
            raw_len,
15✔
1413
            &direct_pixels,
1414
            &sx,
1415
            &sy,
1416
            decoder->allocator);
15✔
1417
    } else {
15✔
1418
        status = sixel_decode_raw(
727✔
1419
            raw_data,
183✔
1420
            raw_len,
183✔
1421
            &indexed_pixels,
1422
            &sx,
1423
            &sy,
1424
            &palette,
1425
            &ncolors,
1426
            decoder->allocator);
183✔
1427
    }
1428
    if (SIXEL_FAILED(status)) {
787!
1429
        goto end;
×
1430
    }
1431

1432
    if (sx > SIXEL_WIDTH_LIMIT || sy > SIXEL_HEIGHT_LIMIT) {
787!
1433
        status = SIXEL_BAD_INPUT;
×
1434
        goto end;
×
1435
    }
1436

1437
    if (decoder->direct_color != 0) {
787✔
1438
        output_pixels = direct_pixels;
60✔
1439
        output_palette = NULL;
60✔
1440
        output_pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
60✔
1441
        frame_ncolors = 0;
60✔
1442
    } else {
15✔
1443
        output_pixels = indexed_pixels;
727✔
1444
        output_palette = palette;
727✔
1445
        output_pixelformat = SIXEL_PIXELFORMAT_PAL8;
727✔
1446

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

1488
        if (output_pixelformat == SIXEL_PIXELFORMAT_PAL8) {
366✔
1489
            frame_ncolors = ncolors;
715✔
1490
        } else {
180✔
1491
            frame_ncolors = 0;
6✔
1492
        }
1493
    }
1494

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

1576
    if (decoder->clipboard_output_active) {
787✔
1577
        clipboard_output_status = decoder_clipboard_create_spool(
12✔
1578
            decoder->allocator,
3✔
1579
            "clipboard-out",
1580
            &clipboard_output_path);
1581
        if (SIXEL_FAILED(clipboard_output_status)) {
12!
1582
            status = clipboard_output_status;
×
1583
            goto end;
×
1584
        }
1585
    }
3✔
1586

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

1623
    if (decoder->clipboard_output_active) {
790✔
1624
        clipboard_output_status = decoder_clipboard_read_file(
12✔
1625
            clipboard_output_path,
3✔
1626
            &clipboard_output_data,
1627
            &clipboard_output_size);
1628
        if (SIXEL_SUCCEEDED(clipboard_output_status)) {
12!
1629
            clipboard_output_status = sixel_clipboard_write(
12✔
1630
                decoder->clipboard_output_format,
12✔
1631
                clipboard_output_data,
3✔
1632
                clipboard_output_size);
3✔
1633
        }
3✔
1634
        if (clipboard_output_data != NULL) {
12!
1635
            free(clipboard_output_data);
12✔
1636
            clipboard_output_data = NULL;
12✔
1637
        }
3✔
1638
        if (SIXEL_FAILED(clipboard_output_status)) {
12!
1639
            status = clipboard_output_status;
2✔
1640
            goto end;
2✔
1641
        }
1642
    }
3✔
1643

1644
end:
587✔
1645
    sixel_frame_unref(frame);
799✔
1646
    sixel_allocator_free(decoder->allocator, raw_data);
799✔
1647
    sixel_allocator_free(decoder->allocator, indexed_pixels);
799✔
1648
    sixel_allocator_free(decoder->allocator, palette);
799✔
1649
    sixel_allocator_free(decoder->allocator, direct_pixels);
799✔
1650
    sixel_allocator_free(decoder->allocator, rgb_pixels);
799✔
1651
    if (clipboard_blob != NULL) {
799!
1652
        free(clipboard_blob);
×
1653
    }
1654
    if (clipboard_output_path != NULL) {
799✔
1655
        (void)sixel_compat_unlink(clipboard_output_path);
12✔
1656
        sixel_allocator_free(decoder->allocator, clipboard_output_path);
12✔
1657
    }
3✔
1658

1659
    sixel_decoder_unref(decoder);
799✔
1660
    if (logger_prepared) {
799!
1661
        sixel_logger_close(&logger);
×
1662
    }
1663

1664
    return status;
867✔
1665
}
68✔
1666

1667

1668
/* Exercise legacy constructor and refcounting for the decoder. */
1669

1670
/* emacs Local Variables:      */
1671
/* emacs mode: c               */
1672
/* emacs tab-width: 4          */
1673
/* emacs indent-tabs-mode: nil */
1674
/* emacs c-basic-offset: 4     */
1675
/* emacs End:                  */
1676
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1677
/* 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