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

saitoha / libsixel / 20807202489

08 Jan 2026 06:00AM UTC coverage: 55.056% (-1.6%) from 56.69%
20807202489

push

github

saitoha
ci: refine

20832 of 37838 relevant lines covered (55.06%)

1153472.81 hits per line

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

73.09
/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

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

72
    if (dest == NULL || dest_size == 0u) {
2✔
73
        return;
74
    }
75

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

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

87
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
2✔
88
}
89

90

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

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

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

127
    if (maximum_tmpdir_len <= suffix_len + 2) {
1✔
128
        return NULL;
129
    }
130
    if (tmpdir_len > maximum_tmpdir_len - (suffix_len + 2)) {
1✔
131
        return NULL;
132
    }
133

134
    needs_separator = 1;
1✔
135
    if (tmpdir_len > 0) {
1✔
136
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
1✔
137
            needs_separator = 0;
1✔
138
        }
139
    }
140

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

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

160
    if (capacity_out != NULL) {
1✔
161
        *capacity_out = template_len;
1✔
162
    }
163

164
    return template_path;
165
}
166

167

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

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

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

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

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

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

230
    template_path = NULL;
1✔
231
    status = SIXEL_OK;
1✔
232

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

241
    return status;
1✔
242
}
243

244

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

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

262
    *data = NULL;
1✔
263
    *size = 0u;
1✔
264

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

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

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

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

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

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

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

332
    *data = buffer;
1✔
333
    *size = read_size;
1✔
334

335
    return SIXEL_OK;
1✔
336
}
337

338

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

348
    p = (char *)sixel_allocator_malloc(allocator, (size_t)(strlen(s) + 1));
94✔
349
    if (p) {
94✔
350
        (void)sixel_compat_strcpy(p, strlen(s) + 1, s);
94✔
351
    }
352
    return p;
94✔
353
}
354

355

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

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

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

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

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

407
    status = SIXEL_OK;
408

409
end:
34✔
410
    return status;
34✔
411
}
412

413

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

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

426
end:
×
427
    return decoder;
×
428
}
429

430

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

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

446

447
/* increase reference count of decoder object (thread-unsafe) */
448
SIXELAPI void
449
sixel_decoder_ref(sixel_decoder_t *decoder)
62✔
450
{
451
    /* TODO: be thread safe */
452
    ++decoder->ref;
62✔
453
}
×
454

455

456
/* decrease reference count of decoder object (thread-unsafe) */
457
SIXELAPI void
458
sixel_decoder_unref(sixel_decoder_t *decoder)
95✔
459
{
460
    /* TODO: be thread safe */
461
    if (decoder != NULL && --decoder->ref == 0) {
95✔
462
        sixel_decoder_destroy(decoder);
33✔
463
    }
464
}
95✔
465

466

467
typedef struct sixel_similarity {
468
    const unsigned char *palette;
469
    int ncolors;
470
    int stride;
471
    signed char *cache;
472
    int bias;
473
} sixel_similarity_t;
474

475
static SIXELSTATUS
476
sixel_similarity_init(sixel_similarity_t *similarity,
1✔
477
                      const unsigned char *palette,
478
                      int ncolors,
479
                      int bias,
480
                      sixel_allocator_t *allocator)
481
{
482
    size_t cache_size;
1✔
483
    int i;
1✔
484

485
    if (bias < 1) {
1✔
486
        bias = 1;
487
    }
488

489
    similarity->palette = palette;
1✔
490
    similarity->ncolors = ncolors;
1✔
491
    similarity->stride = ncolors;
1✔
492
    similarity->bias = bias;
1✔
493

494
    cache_size = (size_t)ncolors * (size_t)ncolors;
1✔
495
    if (cache_size == 0) {
1✔
496
        similarity->cache = NULL;
×
497
        return SIXEL_OK;
×
498
    }
499

500
    similarity->cache = (signed char *)sixel_allocator_malloc(
1✔
501
        allocator,
502
        cache_size);
503
    if (similarity->cache == NULL) {
1✔
504
        sixel_helper_set_additional_message(
×
505
            "sixel_similarity_init: sixel_allocator_malloc() failed.");
506
        return SIXEL_BAD_ALLOCATION;
×
507
    }
508
    memset(similarity->cache, -1, cache_size);
1✔
509
    for (i = 0; i < ncolors; ++i) {
168✔
510
        similarity->cache[i * similarity->stride + i] = 7;
167✔
511
    }
512

513
    return SIXEL_OK;
514
}
515

516
static void
517
sixel_similarity_destroy(sixel_similarity_t *similarity,
1✔
518
                         sixel_allocator_t *allocator)
519
{
520
    if (similarity->cache != NULL) {
1✔
521
        sixel_allocator_free(allocator, similarity->cache);
1✔
522
        similarity->cache = NULL;
1✔
523
    }
524
}
525

526
static inline unsigned int
527
sixel_similarity_diff(const unsigned char *a, const unsigned char *b)
535,682✔
528
{
529
    int dr = (int)a[0] - (int)b[0];
535,682✔
530
    int dg = (int)a[1] - (int)b[1];
535,682✔
531
    int db = (int)a[2] - (int)b[2];
535,682✔
532
    return (unsigned int)(dr * dr + dg * dg + db * db);
535,682✔
533
}
534

535
static unsigned int
536
sixel_similarity_compare(sixel_similarity_t *similarity,
2,153,704✔
537
                         int index1,
538
                         int index2,
539
                         int numerator,
540
                         int denominator)
541
{
542
    int min_index;
2,153,704✔
543
    int max_index;
2,153,704✔
544
    size_t cache_pos;
2,153,704✔
545
    signed char cached;
2,153,704✔
546
    const unsigned char *palette;
2,153,704✔
547
    const unsigned char *p1;
2,153,704✔
548
    const unsigned char *p2;
2,153,704✔
549
    unsigned char avg_color[3];
2,153,704✔
550
    unsigned int distance;
2,153,704✔
551
    unsigned int base_distance;
2,153,704✔
552
    unsigned long long scaled_distance;
2,153,704✔
553
    int bias;
2,153,704✔
554
    unsigned int min_diff = UINT_MAX;
2,153,704✔
555
    int i;
2,153,704✔
556
    unsigned int result;
2,153,704✔
557
    const unsigned char *pk;
2,153,704✔
558
    unsigned int diff;
2,153,704✔
559

560
    if (similarity->cache == NULL) {
2,153,704✔
561
        return 0;
562
    }
563

564
    if (index1 < 0 || index1 >= similarity->ncolors ||
2,153,704✔
565
        index2 < 0 || index2 >= similarity->ncolors) {
2,153,704✔
566
        return 0;
567
    }
568

569
    if (index1 <= index2) {
2,153,704✔
570
        min_index = index1;
571
        max_index = index2;
572
    } else {
573
        min_index = index2;
648,965✔
574
        max_index = index1;
648,965✔
575
    }
576

577
    cache_pos = (size_t)min_index * (size_t)similarity->stride
2,153,704✔
578
              + (size_t)max_index;
2,153,704✔
579
    cached = similarity->cache[cache_pos];
2,153,704✔
580
    if (cached >= 0) {
2,153,704✔
581
        return (unsigned int)cached;
2,150,477✔
582
    }
583

584
    palette = similarity->palette;
3,227✔
585
    p1 = palette + index1 * 3;
3,227✔
586
    p2 = palette + index2 * 3;
3,227✔
587

588
#if 1
589
   /*    original: n = (p1 + p2) / 2
590
    */
591
    avg_color[0] = (unsigned char)(((unsigned int)p1[0]
3,227✔
592
                                    + (unsigned int)p2[0]) >> 1);
3,227✔
593
    avg_color[1] = (unsigned char)(((unsigned int)p1[1]
3,227✔
594
                                    + (unsigned int)p2[1]) >> 1);
3,227✔
595
    avg_color[2] = (unsigned char)(((unsigned int)p1[2]
3,227✔
596
                                    + (unsigned int)p2[2]) >> 1);
3,227✔
597
    (void) numerator;
3,227✔
598
    (void) denominator;
3,227✔
599
#else
600
   /*
601
    *    diffuse(pos_a, n1) -> p1
602
    *    diffuse(pos_b, n2) -> p2
603
    *
604
    *    when n1 == n2 == n:
605
    *
606
    *    p2 = n + (n - p1) * numerator / denominator
607
    * => p2 * denominator = n * denominator + (n - p1) * numerator
608
    * => p2 * denominator = n * denominator + n * numerator - p1 * numerator
609
    * => n * (denominator + numerator) = p1 * numerator + p2 * denominator
610
    * => n = (p1 * numerator + p2 * denominator) / (denominator + numerator)
611
    *
612
    */
613
    avg_color[0] = (p1[0] * numerator + p2[0] * denominator + (numerator + denominator + 0.5) / 2)
614
                 / (numerator + denominator);
615
    avg_color[1] = (p1[1] * numerator + p2[1] * denominator + (numerator + denominator + 0.5) / 2)
616
                 / (numerator + denominator);
617
    avg_color[2] = (p1[2] * numerator + p2[2] * denominator + (numerator + denominator + 0.5) / 2)
618
                 / (numerator + denominator);
619
#endif
620

621
    distance = sixel_similarity_diff(avg_color, p1);
3,227✔
622
    bias = similarity->bias;
3,227✔
623
    if (bias < 1) {
3,227✔
624
        bias = 1;
625
    }
626
    scaled_distance = (unsigned long long)distance
3,227✔
627
                    * (unsigned long long)bias
3,227✔
628
                    + 50ULL;
629
    base_distance = (unsigned int)(scaled_distance / 100ULL);
3,227✔
630
    if (base_distance == 0U) {
3,227✔
631
        base_distance = 1U;
632
    }
633

634
    for (i = 0; i < similarity->ncolors; ++i) {
542,136✔
635
        if (i == index1 || i == index2) {
538,909✔
636
            continue;
6,454✔
637
        }
638
        pk = palette + i * 3;
532,455✔
639
        diff = sixel_similarity_diff(avg_color, pk);
532,455✔
640
        if (diff < min_diff) {
532,455✔
641
            min_diff = diff;
538,909✔
642
        }
643
    }
644

645
    if (min_diff == UINT_MAX) {
3,227✔
646
        min_diff = base_distance * 2U;
×
647
    }
648

649
    if (min_diff >= base_distance * 2U) {
3,227✔
650
        result = 5U;
651
    } else if (min_diff >= base_distance) {
3,055✔
652
        result = 8U;
653
    } else if ((unsigned long long)min_diff * 6ULL
2,848✔
654
               >= (unsigned long long)base_distance * 5ULL) {
2,848✔
655
        result = 7U;
656
    } else if ((unsigned long long)min_diff * 4ULL
2,774✔
657
               >= (unsigned long long)base_distance * 3ULL) {
2,774✔
658
        result = 7U;
659
    } else if ((unsigned long long)min_diff * 3ULL
2,724✔
660
               >= (unsigned long long)base_distance * 2ULL) {
2,724✔
661
        result = 5U;
662
    } else if ((unsigned long long)min_diff * 5ULL
2,660✔
663
               >= (unsigned long long)base_distance * 3ULL) {
664
        result = 7U;
665
    } else if ((unsigned long long)min_diff * 2ULL
2,597✔
666
               >= (unsigned long long)base_distance * 1ULL) {
667
        result = 4U;
668
    } else if ((unsigned long long)min_diff * 3ULL
2,479✔
669
               >= (unsigned long long)base_distance * 1ULL) {
670
        result = 2U;
671
    } else {
672
        result = 0U;
2,154✔
673
    }
674

675
    similarity->cache[cache_pos] = (signed char)result;
3,227✔
676

677
    return result;
3,227✔
678
}
679

680
static inline int
681
sixel_clamp(int value, int min_value, int max_value)
×
682
{
683
    if (value < min_value) {
×
684
        return min_value;
685
    }
686
    if (value > max_value) {
×
687
        return max_value;
688
    }
689
    return value;
690
}
691

692
static inline int
693
sixel_get_gray(const int *gray, int width, int height, int x, int y)
×
694
{
695
    int cx = sixel_clamp(x, 0, width - 1);
×
696
    int cy = sixel_clamp(y, 0, height - 1);
×
697
    return gray[cy * width + cx];
×
698
}
699

700
static unsigned short
701
sixel_prewitt_value(const int *gray, int width, int height, int x, int y)
×
702
{
703
    int top_prev = sixel_get_gray(gray, width, height, x - 1, y - 1);
×
704
    int top_curr = sixel_get_gray(gray, width, height, x, y - 1);
×
705
    int top_next = sixel_get_gray(gray, width, height, x + 1, y - 1);
×
706
    int mid_prev = sixel_get_gray(gray, width, height, x - 1, y);
×
707
    int mid_next = sixel_get_gray(gray, width, height, x + 1, y);
×
708
    int bot_prev = sixel_get_gray(gray, width, height, x - 1, y + 1);
×
709
    int bot_curr = sixel_get_gray(gray, width, height, x, y + 1);
×
710
    int bot_next = sixel_get_gray(gray, width, height, x + 1, y + 1);
×
711
    long gx = (long)top_next - (long)top_prev +
×
712
              (long)mid_next - (long)mid_prev +
×
713
              (long)bot_next - (long)bot_prev;
714
    long gy = (long)bot_prev + (long)bot_curr + (long)bot_next -
×
715
              (long)top_prev - (long)top_curr - (long)top_next;
×
716
    unsigned long long magnitude = (unsigned long long)gx
×
717
                                 * (unsigned long long)gx
×
718
                                 + (unsigned long long)gy
×
719
                                 * (unsigned long long)gy;
×
720
    magnitude /= 256ULL;
×
721
    if (magnitude > 65535ULL) {
×
722
        magnitude = 65535ULL;
723
    }
724
    return (unsigned short)magnitude;
×
725
}
726

727
static unsigned short
728
sixel_scale_threshold(unsigned short base, int percent)
2✔
729
{
730
    unsigned long long numerator;
2✔
731
    unsigned long long scaled;
2✔
732

733
    if (percent <= 0) {
2✔
734
        percent = 1;
735
    }
736

737
    numerator = (unsigned long long)base * 100ULL
2✔
738
              + (unsigned long long)percent / 2ULL;
1✔
739
    scaled = numerator / (unsigned long long)percent;
2✔
740
    if (scaled == 0ULL) {
2✔
741
        scaled = 1ULL;
742
    }
743
    if (scaled > USHRT_MAX) {
2✔
744
        scaled = USHRT_MAX;
745
    }
746

747
    return (unsigned short)scaled;
2✔
748
}
749

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

794
    if (width <= 0 || height <= 0 || palette == NULL || ncolors <= 0) {
1✔
795
        return SIXEL_BAD_INPUT;
796
    }
797

798
    num_pixels = (size_t)width * (size_t)height;
1✔
799

800
    memset(&similarity, 0, sizeof(sixel_similarity_t));
1✔
801

802
    strong_threshold = sixel_scale_threshold(256U, edge_strength);
1✔
803
    detail_threshold = sixel_scale_threshold(160U, edge_strength);
1✔
804
    if (strong_threshold < detail_threshold) {
1✔
805
        strong_threshold = detail_threshold;
806
    }
807

808
    /*
809
     * Build RGB and luminance buffers so we can reuse the similarity cache
810
     * and gradient analysis across the reconstructed image.
811
     */
812
    rgb = (unsigned char *)sixel_allocator_malloc(
1✔
813
        allocator,
814
        num_pixels * 3);
815
    if (rgb == NULL) {
1✔
816
        sixel_helper_set_additional_message(
×
817
            "sixel_dequantize_k_undither: "
818
            "sixel_allocator_malloc() failed.");
819
        status = SIXEL_BAD_ALLOCATION;
×
820
        goto end;
×
821
    }
822

823
    gray = (int *)sixel_allocator_malloc(
2✔
824
        allocator,
825
        num_pixels * sizeof(int));
1✔
826
    if (gray == NULL) {
1✔
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
    prewitt = (unsigned short *)sixel_allocator_malloc(
2✔
835
        allocator,
836
        num_pixels * sizeof(unsigned short));
1✔
837
    if (prewitt == NULL) {
1✔
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
    /*
846
     * Pre-compute palette distance heuristics so each neighbour lookup reuses
847
     * the k_undither-style similarity table.
848
     */
849
    status = sixel_similarity_init(
1✔
850
        &similarity,
851
        palette,
852
        ncolors,
853
        similarity_bias,
854
        allocator);
855
    if (SIXEL_FAILED(status)) {
1✔
856
        goto end;
×
857
    }
858

859
    for (y = 0; y < height; ++y) {
451✔
860
        for (x = 0; x < width; ++x) {
270,450✔
861
            palette_index = indexed_pixels[y * width + x];
270,000✔
862
            if (palette_index < 0 || palette_index >= ncolors) {
270,000✔
863
                palette_index = 0;
×
864
            }
865

866
            color = palette + palette_index * 3;
270,000✔
867
            out_index = (size_t)(y * width + x) * 3;
270,000✔
868
            rgb[out_index + 0] = color[0];
270,000✔
869
            rgb[out_index + 1] = color[1];
270,000✔
870
            rgb[out_index + 2] = color[2];
270,000✔
871

872
            if (edge_strength > 0) {
270,000✔
873
                gray[y * width + x] = (int)color[0]
×
874
                                    + (int)color[1] * 2
×
875
                                    + (int)color[2];
×
876
                /*
877
                 * Edge detection keeps high-frequency content intact while we
878
                 * smooth dithering noise in flatter regions.
879
                 */
880
                prewitt[y * width + x] = sixel_prewitt_value(
×
881
                    gray,
882
                    width,
883
                    height,
884
                    x,
885
                    y);
886

887
                gradient = prewitt[y * width + x];
×
888
                if (gradient > strong_threshold) {
×
889
                    continue;
×
890
                }
891

892
                if (gradient > detail_threshold) {
×
893
                    center_weight = 24U;
894
                } else {
895
                    center_weight = 8U;
270,000✔
896
                }
897
            } else {
898
                center_weight = 8U;
899
            }
900

901
            out_index = (size_t)(y * width + x) * 3;
270,000✔
902
            accum_r = (unsigned int)rgb[out_index + 0] * center_weight;
270,000✔
903
            accum_g = (unsigned int)rgb[out_index + 1] * center_weight;
270,000✔
904
            accum_b = (unsigned int)rgb[out_index + 2] * center_weight;
270,000✔
905
            total_weight = center_weight;
270,000✔
906

907
            /*
908
             * Blend neighbours that stay within the palette similarity
909
             * threshold so Floyd-Steinberg noise is averaged away without
910
             * bleeding across pronounced edges.
911
             */
912
            for (neighbor = 0; neighbor < 8; ++neighbor) {
2,430,000✔
913
                nx = x + neighbor_offsets[neighbor][0];
2,160,000✔
914
                ny = y + neighbor_offsets[neighbor][1];
2,160,000✔
915
                numerator = neighbor_offsets[neighbor][2];
2,160,000✔
916
                denominator = neighbor_offsets[neighbor][3];
2,160,000✔
917

918
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
2,160,000✔
919
                    continue;
6,296✔
920
                }
921

922
                neighbor_index = indexed_pixels[ny * width + nx];
2,153,704✔
923
                if (neighbor_index < 0 || neighbor_index >= ncolors) {
2,153,704✔
924
                    continue;
×
925
                }
926

927
                if (numerator) {
2,153,704✔
928
                    weight = sixel_similarity_compare(
2,153,704✔
929
                        &similarity,
930
                        palette_index,
931
                        neighbor_index,
932
                        numerator,
933
                        denominator);
934
                    if (weight == 0) {
2,153,704✔
935
                        continue;
117,050✔
936
                    }
937

938
                    neighbor_color = palette + neighbor_index * 3;
2,036,654✔
939
                    accum_r += (unsigned int)neighbor_color[0] * weight;
2,036,654✔
940
                    accum_g += (unsigned int)neighbor_color[1] * weight;
2,036,654✔
941
                    accum_b += (unsigned int)neighbor_color[2] * weight;
2,036,654✔
942
                    total_weight += weight;
2,036,654✔
943
                }
944
            }
945

946
            if (total_weight > 0U) {
270,000✔
947
                rgb[out_index + 0] = (unsigned char)(accum_r / total_weight);
270,000✔
948
                rgb[out_index + 1] = (unsigned char)(accum_g / total_weight);
270,000✔
949
                rgb[out_index + 2] = (unsigned char)(accum_b / total_weight);
270,000✔
950
            }
951
        }
952
    }
953

954

955
    *output = rgb;
1✔
956
    rgb = NULL;
1✔
957
    status = SIXEL_OK;
1✔
958

959
end:
1✔
960
    sixel_similarity_destroy(&similarity, allocator);
1✔
961
    sixel_allocator_free(allocator, rgb);
1✔
962
    sixel_allocator_free(allocator, gray);
1✔
963
    sixel_allocator_free(allocator, prewitt);
1✔
964
    return status;
1✔
965
}
966
/*
967
 * The dequantizer accepts a method supplied by the shared option helper. The
968
 * decoder keeps a parallel lookup table that translates the matched index
969
 * into the execution constant.
970
 */
971
static int const g_decoder_dequant_methods[] = {
972
    SIXEL_DEQUANTIZE_NONE,
973
    SIXEL_DEQUANTIZE_K_UNDITHER
974
};
975

976
static sixel_option_choice_t const g_decoder_dequant_choices[] = {
977
    { "none", 0 },
978
    { "k_undither", 1 }
979
};
980

981
static void
982
decoder_normalise_windows_drive_path(char *path)
3✔
983
{
984
#if defined(_WIN32)
985
    size_t length;
3✔
986

987
    length = 0u;
3✔
988

989
    if (path == NULL) {
3✔
990
        return;
991
    }
992

993
    length = strlen(path);
3✔
994
    if (length >= 3u
3✔
995
            && path[0] == '/'
2✔
996
            && ((path[1] >= 'A' && path[1] <= 'Z')
1✔
997
                || (path[1] >= 'a' && path[1] <= 'z'))
1✔
998
            && path[2] == '/') {
1✔
999
        path[0] = path[1];
1✔
1000
        path[1] = ':';
1✔
1001
    }
1002
#else
1003
    (void)path;
1004
#endif
1005
}
1006

1007
/* set an option flag to decoder object */
1008
SIXELAPI SIXELSTATUS
1009
sixel_decoder_setopt(
38✔
1010
    sixel_decoder_t /* in */ *decoder,
1011
    int             /* in */ arg,
1012
    char const      /* in */ *value
1013
)
1014
{
1015
    SIXELSTATUS status = SIXEL_FALSE;
38✔
1016
    unsigned int path_flags;
38✔
1017
    int path_check;
38✔
1018
    char const *payload = NULL;
38✔
1019
    size_t length;
38✔
1020
    sixel_clipboard_spec_t clipboard_spec;
38✔
1021
    int match_index;
38✔
1022
    sixel_option_choice_result_t match_result;
38✔
1023
    char match_detail[128];
38✔
1024
    char match_message[256];
38✔
1025
    char const *filename = NULL;
38✔
1026
    char *p = NULL;
38✔
1027
    long bias;
38✔
1028
    char *endptr;
38✔
1029

1030
    sixel_decoder_ref(decoder);
38✔
1031
    path_flags = 0u;
38✔
1032
    path_check = 0;
38✔
1033

1034
    switch(arg) {
38✔
1035
    case SIXEL_OPTFLAG_INPUT:  /* i */
15✔
1036
        path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
15✔
1037
            SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
1038
            SIXEL_OPTION_PATH_ALLOW_REMOTE;
1039
        if (value != NULL) {
15✔
1040
            path_check = sixel_option_validate_filesystem_path(
15✔
1041
                value,
1042
                value,
1043
                path_flags);
1044
            if (path_check != 0) {
15✔
1045
                status = SIXEL_BAD_ARGUMENT;
1✔
1046
                goto end;
1✔
1047
            }
1048
        }
1049
        decoder->clipboard_input_active = 0;
14✔
1050
        decoder->clipboard_input_format[0] = '\0';
14✔
1051
        if (value != NULL) {
14✔
1052
            clipboard_spec.is_clipboard = 0;
14✔
1053
            clipboard_spec.format[0] = '\0';
14✔
1054
            if (sixel_clipboard_parse_spec(value, &clipboard_spec)
14✔
1055
                    && clipboard_spec.is_clipboard) {
1✔
1056
                decoder_clipboard_select_format(
1✔
1057
                    decoder->clipboard_input_format,
1✔
1058
                    sizeof(decoder->clipboard_input_format),
1059
                    clipboard_spec.format,
1060
                    "sixel");
1061
                decoder->clipboard_input_active = 1;
1✔
1062
            }
1063
        }
1064
        free(decoder->input);
14✔
1065
        decoder->input = strdup_with_allocator(value, decoder->allocator);
14✔
1066
        if (decoder->input == NULL) {
14✔
1067
            sixel_helper_set_additional_message(
×
1068
                "sixel_decoder_setopt: strdup_with_allocator() failed.");
1069
            status = SIXEL_BAD_ALLOCATION;
×
1070
            goto end;
×
1071
        }
1072
        break;
1073
    case SIXEL_OPTFLAG_OUTPUT:  /* o */
13✔
1074
        decoder->clipboard_output_active = 0;
13✔
1075
        decoder->clipboard_output_format[0] = '\0';
13✔
1076

1077
        payload = value;
13✔
1078
        if (strncmp(value, "png:", 4) == 0) {
13✔
1079
            payload = value + 4;
4✔
1080
            if (payload[0] == '\0') {
4✔
1081
                sixel_helper_set_additional_message(
1✔
1082
                    "missing target after the \"png:\" prefix.");
1083
                return SIXEL_BAD_ARGUMENT;
1✔
1084
            }
1085
            length = strlen(payload);
3✔
1086
            filename = p = malloc(length + 1U);
3✔
1087
            if (p == NULL) {
3✔
1088
                sixel_helper_set_additional_message(
×
1089
                    "sixel_decoder_setopt: malloc() failed for png path filename.");
1090
                return SIXEL_BAD_ALLOCATION;
×
1091
            }
1092
            memcpy(p, payload, length + 1U);
3✔
1093
            decoder_normalise_windows_drive_path(p);
3✔
1094
        } else {
1095
            filename = value;
1096
        }
1097

1098
        if (filename != NULL) {
3✔
1099
            clipboard_spec.is_clipboard = 0;
12✔
1100
            clipboard_spec.format[0] = '\0';
12✔
1101
            if (sixel_clipboard_parse_spec(filename, &clipboard_spec)
12✔
1102
                    && clipboard_spec.is_clipboard) {
1✔
1103
                decoder_clipboard_select_format(
1✔
1104
                    decoder->clipboard_output_format,
1✔
1105
                    sizeof(decoder->clipboard_output_format),
1106
                    clipboard_spec.format,
1107
                    "png");
1108
                decoder->clipboard_output_active = 1;
1✔
1109
            }
1110
        }
1111
        free(decoder->output);
12✔
1112
        decoder->output = strdup_with_allocator(filename, decoder->allocator);
12✔
1113
        free(p);
12✔
1114
        if (decoder->output == NULL) {
12✔
1115
            sixel_helper_set_additional_message(
×
1116
                "sixel_decoder_setopt: strdup_with_allocator() failed.");
1117
            status = SIXEL_BAD_ALLOCATION;
×
1118
            goto end;
×
1119
        }
1120
        break;
1121
    case SIXEL_OPTFLAG_DEQUANTIZE:  /* d */
2✔
1122
        if (value == NULL) {
2✔
1123
            sixel_helper_set_additional_message(
×
1124
                "sixel_decoder_setopt: -d/--dequantize requires an argument.");
1125
            status = SIXEL_BAD_ALLOCATION;
×
1126
            goto end;
×
1127
        }
1128

1129
        match_index = 0;
2✔
1130
        memset(match_detail, 0, sizeof(match_detail));
2✔
1131
        memset(match_message, 0, sizeof(match_message));
2✔
1132

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

1163
    case SIXEL_OPTFLAG_SIMILARITY:  /* S */
1✔
1164
        errno = 0;
1✔
1165
        bias = strtol(value, &endptr, 10);
1✔
1166
        if (endptr == value || endptr[0] != '\0' ||
1✔
1167
            errno == ERANGE || bias < 0 || bias > 1000) {
×
1168
            sixel_helper_set_additional_message(
1✔
1169
                "similarity must be an integer between 0 and 1000.");
1170
            status = SIXEL_BAD_ARGUMENT;
1✔
1171
            goto end;
1✔
1172
        }
1173

1174
        decoder->dequantize_similarity_bias = (int)bias;
×
1175
        break;
×
1176

1177
    case SIXEL_OPTFLAG_SIZE:  /* s */
×
1178
        decoder->thumbnail_size = atoi(value);
×
1179
        if (decoder->thumbnail_size <= 0) {
×
1180
            sixel_helper_set_additional_message(
×
1181
                "size must be greater than zero.");
1182
            status = SIXEL_BAD_ARGUMENT;
×
1183
            goto end;
×
1184
        }
1185
        break;
1186

1187
    case SIXEL_OPTFLAG_EDGE:  /* e */
×
1188
        decoder->dequantize_edge_strength = atoi(value);
×
1189
        if (decoder->dequantize_edge_strength < 0 ||
×
1190
            decoder->dequantize_edge_strength > 1000) {
1191
            sixel_helper_set_additional_message(
×
1192
                "edge bias must be between 1 and 1000.");
1193
            status = SIXEL_BAD_ARGUMENT;
×
1194
            goto end;
×
1195
        }
1196
        break;
1197

1198
    case SIXEL_OPTFLAG_DIRECT:  /* D */
6✔
1199
        decoder->direct_color = 1;
6✔
1200
        break;
6✔
1201

1202
    case SIXEL_OPTFLAG_THREADS:  /* = */
1✔
1203
        status = sixel_decoder_parallel_override_threads(value);
1✔
1204
        if (SIXEL_FAILED(status)) {
1✔
1205
            goto end;
1✔
1206
        }
1207
        break;
1208

1209
    case '?':
×
1210
    default:
1211
        status = SIXEL_BAD_ARGUMENT;
×
1212
        goto end;
×
1213
    }
1214

1215
    status = SIXEL_OK;
1216

1217
end:
37✔
1218
    sixel_decoder_unref(decoder);
37✔
1219

1220
    return status;
37✔
1221
}
1222

1223

1224
/* load source data from stdin or the file specified with
1225
   SIXEL_OPTFLAG_INPUT flag, and decode it */
1226
SIXELAPI SIXELSTATUS
1227
sixel_decoder_decode(
24✔
1228
    sixel_decoder_t /* in */ *decoder)
1229
{
1230
    SIXELSTATUS status = SIXEL_FALSE;
24✔
1231
    unsigned char *raw_data = NULL;
24✔
1232
    int sx;
24✔
1233
    int sy;
24✔
1234
    int raw_len;
24✔
1235
    int max;
24✔
1236
    int n;
24✔
1237
    FILE *input_fp = NULL;
24✔
1238
    char message[2048];
24✔
1239
    unsigned char *indexed_pixels = NULL;
24✔
1240
    unsigned char *palette = NULL;
24✔
1241
    unsigned char *rgb_pixels = NULL;
24✔
1242
    unsigned char *direct_pixels = NULL;
24✔
1243
    unsigned char *output_pixels;
24✔
1244
    unsigned char *output_palette;
24✔
1245
    int output_pixelformat;
24✔
1246
    int ncolors;
24✔
1247
    sixel_frame_t *frame;
24✔
1248
    int new_width;
24✔
1249
    int new_height;
24✔
1250
    double scaled_width;
24✔
1251
    double scaled_height;
24✔
1252
    int max_dimension;
24✔
1253
    int thumbnail_size;
24✔
1254
    int frame_ncolors;
24✔
1255
    unsigned char *clipboard_blob;
24✔
1256
    size_t clipboard_blob_size;
24✔
1257
    SIXELSTATUS clipboard_status;
24✔
1258
    char *clipboard_output_path;
24✔
1259
    unsigned char *clipboard_output_data;
24✔
1260
    size_t clipboard_output_size;
24✔
1261
    SIXELSTATUS clipboard_output_status;
24✔
1262
    sixel_logger_t logger;
24✔
1263
    int logger_prepared;
24✔
1264

1265
    sixel_decoder_ref(decoder);
24✔
1266

1267
    frame = NULL;
24✔
1268
    new_width = 0;
24✔
1269
    new_height = 0;
24✔
1270
    scaled_width = 0.0;
24✔
1271
    scaled_height = 0.0;
24✔
1272
    max_dimension = 0;
24✔
1273
    thumbnail_size = decoder->thumbnail_size;
24✔
1274
    frame_ncolors = -1;
24✔
1275
    clipboard_blob = NULL;
24✔
1276
    clipboard_blob_size = 0u;
24✔
1277
    clipboard_status = SIXEL_OK;
24✔
1278
    clipboard_output_path = NULL;
24✔
1279
    clipboard_output_data = NULL;
24✔
1280
    clipboard_output_size = 0u;
24✔
1281
    clipboard_output_status = SIXEL_OK;
24✔
1282
    input_fp = NULL;
24✔
1283
    sixel_logger_init(&logger);
24✔
1284
    (void)sixel_logger_prepare_env(&logger);
24✔
1285
    logger_prepared = logger.active;
24✔
1286

1287
    raw_len = 0;
24✔
1288
    max = 0;
24✔
1289
    if (decoder->clipboard_input_active) {
24✔
1290
        clipboard_status = sixel_clipboard_read(
2✔
1291
            decoder->clipboard_input_format,
1✔
1292
            &clipboard_blob,
1293
            &clipboard_blob_size,
1294
            decoder->allocator);
1295
        if (SIXEL_FAILED(clipboard_status)) {
1✔
1296
            status = clipboard_status;
×
1297
            goto end;
×
1298
        }
1299
        max = (int)((clipboard_blob_size > 0u)
1✔
1300
                    ? clipboard_blob_size
1301
                    : 1u);
1302
        raw_data = (unsigned char *)sixel_allocator_malloc(
1✔
1303
            decoder->allocator,
1304
            (size_t)max);
1305
        if (raw_data == NULL) {
1✔
1306
            sixel_helper_set_additional_message(
×
1307
                "sixel_decoder_decode: sixel_allocator_malloc() failed.");
1308
            status = SIXEL_BAD_ALLOCATION;
×
1309
            goto end;
×
1310
        }
1311
        if (clipboard_blob_size > 0u && clipboard_blob != NULL) {
1✔
1312
            memcpy(raw_data, clipboard_blob, clipboard_blob_size);
1✔
1313
        }
1314
        raw_len = (int)clipboard_blob_size;
1✔
1315
        if (clipboard_blob != NULL) {
1✔
1316
            free(clipboard_blob);
1✔
1317
            clipboard_blob = NULL;
1✔
1318
        }
1319
    } else {
1320
        if (strcmp(decoder->input, "-") == 0) {
23✔
1321
            /* for windows */
1322
#if defined(O_BINARY)
1323
            (void)sixel_compat_set_binary(STDIN_FILENO);
14✔
1324
#endif  /* defined(O_BINARY) */
1325
            input_fp = stdin;
14✔
1326
        } else {
1327
            input_fp = sixel_compat_fopen(decoder->input, "rb");
9✔
1328
            if (! input_fp) {
9✔
1329
                (void)snprintf(
×
1330
                    message,
1331
                    sizeof(message) - 1,
1332
                    "sixel_decoder_decode: failed to open input file: %s.",
1333
                    decoder->input);
1334
                sixel_helper_set_additional_message(message);
×
1335
                status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1336
                goto end;
×
1337
            }
1338
        }
1339

1340
        raw_len = 0;
23✔
1341
        max = 64 * 1024;
23✔
1342

1343
        raw_data = (unsigned char *)sixel_allocator_malloc(
23✔
1344
            decoder->allocator,
1345
            (size_t)max);
1346
        if (raw_data == NULL) {
23✔
1347
            sixel_helper_set_additional_message(
×
1348
                "sixel_decoder_decode: sixel_allocator_malloc() failed.");
1349
            status = SIXEL_BAD_ALLOCATION;
×
1350
            goto end;
×
1351
        }
1352

1353
        for (;;) {
4,799✔
1354
            if ((max - raw_len) < 4096) {
2,411✔
1355
                max *= 2;
44✔
1356
                raw_data = (unsigned char *)sixel_allocator_realloc(
44✔
1357
                    decoder->allocator,
1358
                    raw_data,
1359
                    (size_t)max);
1360
                if (raw_data == NULL) {
44✔
1361
                    sixel_helper_set_additional_message(
×
1362
                        "sixel_decoder_decode: sixel_allocator_realloc() failed.");
1363
                    status = SIXEL_BAD_ALLOCATION;
×
1364
                    goto end;
×
1365
                }
1366
            }
1367
            if ((n = (int)fread(raw_data + raw_len, 1, 4096, input_fp)) <= 0) {
2,411✔
1368
                break;
1369
            }
1370
            raw_len += n;
2,388✔
1371
        }
1372

1373
        if (input_fp != NULL && input_fp != stdin) {
23✔
1374
            fclose(input_fp);
9✔
1375
        }
1376
    }
1377

1378
    if (decoder->direct_color != 0 &&
24✔
1379
            decoder->dequantize_method != SIXEL_DEQUANTIZE_NONE) {
6✔
1380
        sixel_helper_set_additional_message(
1✔
1381
            "sixel_decoder_decode: direct option "
1382
            "cannot be combined with dequantize option.");
1383
        status = SIXEL_BAD_ARGUMENT;
1✔
1384
        goto end;
1✔
1385
    }
1386

1387
    ncolors = 0;
23✔
1388

1389
    if (decoder->direct_color != 0) {
23✔
1390
        status = sixel_decode_direct(
5✔
1391
            raw_data,
1392
            raw_len,
1393
            &direct_pixels,
1394
            &sx,
1395
            &sy,
1396
            decoder->allocator);
1397
    } else {
1398
        status = sixel_decode_raw(
18✔
1399
            raw_data,
1400
            raw_len,
1401
            &indexed_pixels,
1402
            &sx,
1403
            &sy,
1404
            &palette,
1405
            &ncolors,
1406
            decoder->allocator);
1407
    }
1408
    if (SIXEL_FAILED(status)) {
23✔
1409
        goto end;
×
1410
    }
1411

1412
    if (sx > SIXEL_WIDTH_LIMIT || sy > SIXEL_HEIGHT_LIMIT) {
23✔
1413
        status = SIXEL_BAD_INPUT;
×
1414
        goto end;
×
1415
    }
1416

1417
    if (decoder->direct_color != 0) {
23✔
1418
        output_pixels = direct_pixels;
5✔
1419
        output_palette = NULL;
5✔
1420
        output_pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
5✔
1421
        frame_ncolors = 0;
5✔
1422
    } else {
1423
        output_pixels = indexed_pixels;
18✔
1424
        output_palette = palette;
18✔
1425
        output_pixelformat = SIXEL_PIXELFORMAT_PAL8;
18✔
1426

1427
        if (decoder->dequantize_method == SIXEL_DEQUANTIZE_K_UNDITHER) {
18✔
1428
            if (logger_prepared) {
1✔
1429
                sixel_logger_logf(&logger,
×
1430
                                  "decoder",
1431
                                  "undither",
1432
                                  "start",
1433
                                  0);
1434
            }
1435
            status = sixel_dequantize_k_undither(
1✔
1436
                indexed_pixels,
1437
                sx,
1438
                sy,
1439
                palette,
1440
                ncolors,
1441
                decoder->dequantize_similarity_bias,
1442
                decoder->dequantize_edge_strength,
1443
                decoder->allocator,
1444
                &rgb_pixels);
1445
            if (SIXEL_FAILED(status)) {
1✔
1446
                if (logger_prepared) {
×
1447
                    sixel_logger_logf(
×
1448
                        &logger,
1449
                        "decoder",
1450
                        "undither",
1451
                        "abort",
1452
                        0);
1453
                }
1454
                goto end;
×
1455
            }
1456
            if (logger_prepared) {
1✔
1457
                sixel_logger_logf(&logger,
×
1458
                                  "decoder",
1459
                                  "undither",
1460
                                  "finish",
1461
                                  0);
1462
            }
1463
            output_pixels = rgb_pixels;
1✔
1464
            output_palette = NULL;
1✔
1465
            output_pixelformat = SIXEL_PIXELFORMAT_RGB888;
1✔
1466
        }
1467

1468
        if (output_pixelformat == SIXEL_PIXELFORMAT_PAL8) {
1✔
1469
            frame_ncolors = ncolors;
17✔
1470
        } else {
1471
            frame_ncolors = 0;
1472
        }
1473
    }
1474

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

1556
    if (decoder->clipboard_output_active) {
23✔
1557
        clipboard_output_status = decoder_clipboard_create_spool(
1✔
1558
            decoder->allocator,
1559
            "clipboard-out",
1560
            &clipboard_output_path);
1561
        if (SIXEL_FAILED(clipboard_output_status)) {
1✔
1562
            status = clipboard_output_status;
×
1563
            goto end;
×
1564
        }
1565
    }
1566

1567
    if (logger_prepared) {
23✔
1568
        sixel_logger_logf(&logger,
×
1569
                          "io",
1570
                          "png",
1571
                          "start",
1572
                          0);
1573
    }
1574
    status = sixel_helper_write_image_file(
23✔
1575
        output_pixels,
1576
        sx,
1577
        sy,
1578
        output_palette,
1579
        output_pixelformat,
1580
        decoder->clipboard_output_active
23✔
1581
            ? clipboard_output_path
1582
            : decoder->output,
1583
        SIXEL_FORMAT_PNG,
1584
        decoder->allocator);
1585
    if (SIXEL_FAILED(status)) {
23✔
1586
        if (logger_prepared) {
×
1587
            sixel_logger_logf(&logger,
×
1588
                              "io",
1589
                              "png",
1590
                              "abort",
1591
                              0);
1592
        }
1593
        goto end;
×
1594
    }
1595
    if (logger_prepared) {
23✔
1596
        sixel_logger_logf(&logger,
×
1597
                          "io",
1598
                          "png",
1599
                          "finish",
1600
                          0);
1601
    }
1602

1603
    if (decoder->clipboard_output_active) {
23✔
1604
        clipboard_output_status = decoder_clipboard_read_file(
1✔
1605
            clipboard_output_path,
1606
            &clipboard_output_data,
1607
            &clipboard_output_size);
1608
        if (SIXEL_SUCCEEDED(clipboard_output_status)) {
1✔
1609
            clipboard_output_status = sixel_clipboard_write(
1✔
1610
                decoder->clipboard_output_format,
1✔
1611
                clipboard_output_data,
1612
                clipboard_output_size);
1613
        }
1614
        if (clipboard_output_data != NULL) {
1✔
1615
            free(clipboard_output_data);
1✔
1616
            clipboard_output_data = NULL;
1✔
1617
        }
1618
        if (SIXEL_FAILED(clipboard_output_status)) {
1✔
1619
            status = clipboard_output_status;
×
1620
            goto end;
×
1621
        }
1622
    }
1623

1624
end:
23✔
1625
    sixel_frame_unref(frame);
24✔
1626
    sixel_allocator_free(decoder->allocator, raw_data);
24✔
1627
    sixel_allocator_free(decoder->allocator, indexed_pixels);
24✔
1628
    sixel_allocator_free(decoder->allocator, palette);
24✔
1629
    sixel_allocator_free(decoder->allocator, direct_pixels);
24✔
1630
    sixel_allocator_free(decoder->allocator, rgb_pixels);
24✔
1631
    if (clipboard_blob != NULL) {
24✔
1632
        free(clipboard_blob);
×
1633
    }
1634
    if (clipboard_output_path != NULL) {
24✔
1635
        (void)sixel_compat_unlink(clipboard_output_path);
1✔
1636
        sixel_allocator_free(decoder->allocator, clipboard_output_path);
1✔
1637
    }
1638

1639
    sixel_decoder_unref(decoder);
24✔
1640
    if (logger_prepared) {
24✔
1641
        sixel_logger_close(&logger);
×
1642
    }
1643

1644
    return status;
24✔
1645
}
1646

1647

1648
/* Exercise legacy constructor and refcounting for the decoder. */
1649

1650
/* emacs Local Variables:      */
1651
/* emacs mode: c               */
1652
/* emacs tab-width: 4          */
1653
/* emacs indent-tabs-mode: nil */
1654
/* emacs c-basic-offset: 4     */
1655
/* emacs End:                  */
1656
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1657
/* 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