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

saitoha / libsixel / 20153856935

12 Dec 2025 01:43AM UTC coverage: 51.18% (+0.03%) from 51.152%
20153856935

push

github

saitoha
fix: handle unused simd_level when SIMD backends absent

11897 of 41488 branches covered (28.68%)

20210 of 39488 relevant lines covered (51.18%)

3990690.88 hits per line

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

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

22
#include "config.h"
23

24
/* STDC_HEADERS */
25
#include <stdio.h>
26
#include <stdlib.h>
27
#include <string.h>
28

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

53
#include "decoder.h"
54
#include "decoder-parallel.h"
55
#include "clipboard.h"
56
#include "compat_stub.h"
57
#include "options.h"
58

59
static void
60
decoder_clipboard_select_format(char *dest,
5✔
61
                                size_t dest_size,
62
                                char const *format,
63
                                char const *fallback)
64
{
65
    char const *source;
5✔
66
    size_t limit;
5✔
67

68
    if (dest == NULL || dest_size == 0u) {
5!
69
        return;
70
    }
71

72
    source = fallback;
5✔
73
    if (format != NULL && format[0] != '\0') {
5!
74
        source = format;
3✔
75
    }
76

77
    limit = dest_size - 1u;
5✔
78
    if (limit == 0u) {
5!
79
        dest[0] = '\0';
×
80
        return;
×
81
    }
82

83
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
5✔
84
}
2!
85

86

87
static char *
88
decoder_create_temp_template_with_prefix(sixel_allocator_t *allocator,
4✔
89
                                         char const *prefix,
90
                                         size_t *capacity_out)
91
{
92
    char const *tmpdir;
4✔
93
    size_t tmpdir_len;
4✔
94
    size_t prefix_len;
4✔
95
    size_t suffix_len;
4✔
96
    size_t template_len;
4✔
97
    char *template_path;
4✔
98
    int needs_separator;
4✔
99
    size_t maximum_tmpdir_len;
4✔
100

101
    tmpdir = sixel_compat_getenv("TMPDIR");
4✔
102
#if defined(_WIN32)
103
    if (tmpdir == NULL || tmpdir[0] == '\0') {
104
        tmpdir = sixel_compat_getenv("TEMP");
105
    }
106
    if (tmpdir == NULL || tmpdir[0] == '\0') {
107
        tmpdir = sixel_compat_getenv("TMP");
108
    }
109
#endif
110
    if (tmpdir == NULL || tmpdir[0] == '\0') {
4!
111
#if defined(_WIN32)
112
        tmpdir = ".";
113
#else
114
        tmpdir = "/tmp";
3✔
115
#endif
116
    }
117

118
    tmpdir_len = strlen(tmpdir);
4✔
119
    prefix_len = strlen(prefix);
4✔
120
    suffix_len = prefix_len + strlen("-XXXXXX");
4✔
121
    maximum_tmpdir_len = (size_t)INT_MAX;
4✔
122

123
    if (maximum_tmpdir_len <= suffix_len + 2) {
4!
124
        return NULL;
125
    }
126
    if (tmpdir_len > maximum_tmpdir_len - (suffix_len + 2)) {
4!
127
        return NULL;
128
    }
129

130
    needs_separator = 1;
4✔
131
    if (tmpdir_len > 0) {
4!
132
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
4!
133
            needs_separator = 0;
4✔
134
        }
1✔
135
    }
1✔
136

137
    template_len = tmpdir_len + suffix_len + 2;
4✔
138
    template_path = (char *)sixel_allocator_malloc(allocator, template_len);
4✔
139
    if (template_path == NULL) {
4!
140
        return NULL;
141
    }
142

143
    if (needs_separator) {
4!
144
#if defined(_WIN32)
145
        (void)snprintf(template_path, template_len,
146
                       "%s\\%s-XXXXXX", tmpdir, prefix);
147
#else
148
        (void)snprintf(template_path, template_len,
3✔
149
                       "%s/%s-XXXXXX", tmpdir, prefix);
150
#endif
151
    } else {
152
        (void)snprintf(template_path, template_len,
1✔
153
                       "%s%s-XXXXXX", tmpdir, prefix);
154
    }
155

156
    if (capacity_out != NULL) {
4!
157
        *capacity_out = template_len;
4✔
158
    }
1✔
159

160
    return template_path;
1✔
161
}
1✔
162

163

164
static SIXELSTATUS
165
decoder_clipboard_create_spool(sixel_allocator_t *allocator,
4✔
166
                               char const *prefix,
167
                               char **path_out)
168
{
169
    SIXELSTATUS status;
4✔
170
    char *template_path;
4✔
171
    size_t template_capacity;
4✔
172
    int open_flags;
4✔
173
    int open_mode;
4✔
174
    int fd;
4✔
175
    char *tmpname_result;
4✔
176

177
    status = SIXEL_FALSE;
4✔
178
    template_path = NULL;
4✔
179
    template_capacity = 0u;
4✔
180
    open_flags = 0;
4✔
181
    open_mode = 0;
4✔
182
    fd = (-1);
4✔
183
    tmpname_result = NULL;
4✔
184

185
    template_path = decoder_create_temp_template_with_prefix(allocator,
5✔
186
                                                             prefix,
1✔
187
                                                             &template_capacity);
188
    if (template_path == NULL) {
4!
189
        sixel_helper_set_additional_message(
×
190
            "clipboard: failed to allocate spool template.");
191
        status = SIXEL_BAD_ALLOCATION;
×
192
        goto end;
×
193
    }
194

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

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

220
    *path_out = template_path;
4✔
221
    if (fd >= 0) {
4!
222
        (void)sixel_compat_close(fd);
4✔
223
        fd = (-1);
4✔
224
    }
1✔
225

226
    template_path = NULL;
4✔
227
    status = SIXEL_OK;
4✔
228

229
end:
230
    if (fd >= 0) {
4!
231
        (void)sixel_compat_close(fd);
232
    }
233
    if (template_path != NULL) {
4!
234
        sixel_allocator_free(allocator, template_path);
×
235
    }
236

237
    return status;
5✔
238
}
1✔
239

240

241
static SIXELSTATUS
242
decoder_clipboard_read_file(char const *path,
4✔
243
                            unsigned char **data,
244
                            size_t *size)
245
{
246
    FILE *stream;
4✔
247
    long seek_result;
4✔
248
    long file_size;
4✔
249
    unsigned char *buffer;
4✔
250
    size_t read_size;
4✔
251

252
    if (data == NULL || size == NULL) {
4!
253
        sixel_helper_set_additional_message(
×
254
            "clipboard: read buffer pointers are null.");
255
        return SIXEL_BAD_ARGUMENT;
×
256
    }
257

258
    *data = NULL;
4✔
259
    *size = 0u;
4✔
260

261
    if (path == NULL) {
4!
262
        sixel_helper_set_additional_message(
×
263
            "clipboard: spool path is null.");
264
        return SIXEL_BAD_ARGUMENT;
×
265
    }
266

267
    stream = sixel_compat_fopen(path, "rb");
4✔
268
    if (stream == NULL) {
4!
269
        sixel_helper_set_additional_message(
×
270
            "clipboard: failed to open spool file for read.");
271
        return SIXEL_LIBC_ERROR;
×
272
    }
273

274
    seek_result = fseek(stream, 0L, SEEK_END);
4✔
275
    if (seek_result != 0) {
4!
276
        (void)fclose(stream);
×
277
        sixel_helper_set_additional_message(
×
278
            "clipboard: failed to seek spool file.");
279
        return SIXEL_LIBC_ERROR;
×
280
    }
281

282
    file_size = ftell(stream);
4✔
283
    if (file_size < 0) {
4!
284
        (void)fclose(stream);
×
285
        sixel_helper_set_additional_message(
×
286
            "clipboard: failed to determine spool size.");
287
        return SIXEL_LIBC_ERROR;
×
288
    }
289

290
    seek_result = fseek(stream, 0L, SEEK_SET);
4✔
291
    if (seek_result != 0) {
4!
292
        (void)fclose(stream);
×
293
        sixel_helper_set_additional_message(
×
294
            "clipboard: failed to rewind spool file.");
295
        return SIXEL_LIBC_ERROR;
×
296
    }
297

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

319
    if (fclose(stream) != 0) {
4!
320
        if (buffer != NULL) {
×
321
            free(buffer);
×
322
        }
323
        sixel_helper_set_additional_message(
×
324
            "clipboard: failed to close spool file after read.");
325
        return SIXEL_LIBC_ERROR;
×
326
    }
327

328
    *data = buffer;
4✔
329
    *size = read_size;
4✔
330

331
    return SIXEL_OK;
4✔
332
}
1✔
333

334

335
/* original version of strdup(3) with allocator object */
336
static char *
337
strdup_with_allocator(
304✔
338
    char const          /* in */ *s,          /* source buffer */
339
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
340
                                                 destination buffer */
341
{
342
    char *p;
304✔
343

344
    p = (char *)sixel_allocator_malloc(allocator, (size_t)(strlen(s) + 1));
304✔
345
    if (p) {
304!
346
        (void)sixel_compat_strcpy(p, strlen(s) + 1, s);
304✔
347
    }
79✔
348
    return p;
383✔
349
}
79✔
350

351

352
/* create decoder object */
353
SIXELAPI SIXELSTATUS
354
sixel_decoder_new(
105✔
355
    sixel_decoder_t    /* out */ **ppdecoder,  /* decoder object to be created */
356
    sixel_allocator_t  /* in */  *allocator)   /* allocator, null if you use
357
                                                  default allocator */
358
{
359
    SIXELSTATUS status = SIXEL_FALSE;
105✔
360

361
    if (allocator == NULL) {
105!
362
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
105✔
363
        if (SIXEL_FAILED(status)) {
105!
364
            goto end;
×
365
        }
366
    } else {
27✔
367
        sixel_allocator_ref(allocator);
×
368
    }
369

370
    *ppdecoder = sixel_allocator_malloc(allocator, sizeof(sixel_decoder_t));
105✔
371
    if (*ppdecoder == NULL) {
105!
372
        sixel_allocator_unref(allocator);
×
373
        sixel_helper_set_additional_message(
×
374
            "sixel_decoder_new: sixel_allocator_malloc() failed.");
375
        status = SIXEL_BAD_ALLOCATION;
×
376
        goto end;
×
377
    }
378

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

393
    if ((*ppdecoder)->output == NULL || (*ppdecoder)->input == NULL) {
105!
394
        sixel_decoder_unref(*ppdecoder);
×
395
        *ppdecoder = NULL;
×
396
        sixel_helper_set_additional_message(
×
397
            "sixel_decoder_new: strdup_with_allocator() failed.");
398
        status = SIXEL_BAD_ALLOCATION;
×
399
        sixel_allocator_unref(allocator);
×
400
        goto end;
×
401
    }
402

403
    status = SIXEL_OK;
27✔
404

405
end:
78✔
406
    return status;
132✔
407
}
27✔
408

409

410
/* deprecated version of sixel_decoder_new() */
411
SIXELAPI /* deprecated */ sixel_decoder_t *
412
sixel_decoder_create(void)
×
413
{
414
    SIXELSTATUS status = SIXEL_FALSE;
×
415
    sixel_decoder_t *decoder = NULL;
×
416

417
    status = sixel_decoder_new(&decoder, NULL);
×
418
    if (SIXEL_FAILED(status)) {
×
419
        goto end;
420
    }
421

422
end:
423
    return decoder;
×
424
}
425

426

427
/* destroy a decoder object */
428
static void
429
sixel_decoder_destroy(sixel_decoder_t *decoder)
101✔
430
{
431
    sixel_allocator_t *allocator;
101✔
432

433
    if (decoder) {
101!
434
        allocator = decoder->allocator;
101✔
435
        sixel_allocator_free(allocator, decoder->input);
101✔
436
        sixel_allocator_free(allocator, decoder->output);
101✔
437
        sixel_allocator_free(allocator, decoder);
101✔
438
        sixel_allocator_unref(allocator);
101✔
439
    }
26✔
440
}
101✔
441

442

443
/* increase reference count of decoder object (thread-unsafe) */
444
SIXELAPI void
445
sixel_decoder_ref(sixel_decoder_t *decoder)
219✔
446
{
447
    /* TODO: be thread safe */
448
    ++decoder->ref;
219✔
449
}
177✔
450

451

452
/* decrease reference count of decoder object (thread-unsafe) */
453
SIXELAPI void
454
sixel_decoder_unref(sixel_decoder_t *decoder)
320✔
455
{
456
    /* TODO: be thread safe */
457
    if (decoder != NULL && --decoder->ref == 0) {
320!
458
        sixel_decoder_destroy(decoder);
101✔
459
    }
26✔
460
}
320✔
461

462

463
typedef struct sixel_similarity {
464
    const unsigned char *palette;
465
    int ncolors;
466
    int stride;
467
    signed char *cache;
468
    int bias;
469
} sixel_similarity_t;
470

471
static SIXELSTATUS
472
sixel_similarity_init(sixel_similarity_t *similarity,
4✔
473
                      const unsigned char *palette,
474
                      int ncolors,
475
                      int bias,
476
                      sixel_allocator_t *allocator)
477
{
478
    size_t cache_size;
4✔
479
    int i;
4✔
480

481
    if (bias < 1) {
4!
482
        bias = 1;
483
    }
484

485
    similarity->palette = palette;
4✔
486
    similarity->ncolors = ncolors;
4✔
487
    similarity->stride = ncolors;
4✔
488
    similarity->bias = bias;
4✔
489

490
    cache_size = (size_t)ncolors * (size_t)ncolors;
4✔
491
    if (cache_size == 0) {
4!
492
        similarity->cache = NULL;
×
493
        return SIXEL_OK;
×
494
    }
495

496
    similarity->cache = (signed char *)sixel_allocator_malloc(
4✔
497
        allocator,
1✔
498
        cache_size);
1✔
499
    if (similarity->cache == NULL) {
4!
500
        sixel_helper_set_additional_message(
×
501
            "sixel_similarity_init: sixel_allocator_malloc() failed.");
502
        return SIXEL_BAD_ALLOCATION;
×
503
    }
504
    memset(similarity->cache, -1, cache_size);
4✔
505
    for (i = 0; i < ncolors; ++i) {
672✔
506
        similarity->cache[i * similarity->stride + i] = 7;
668✔
507
    }
167✔
508

509
    return SIXEL_OK;
1✔
510
}
1✔
511

512
static void
513
sixel_similarity_destroy(sixel_similarity_t *similarity,
4✔
514
                         sixel_allocator_t *allocator)
515
{
516
    if (similarity->cache != NULL) {
4!
517
        sixel_allocator_free(allocator, similarity->cache);
4✔
518
        similarity->cache = NULL;
4✔
519
    }
1✔
520
}
1✔
521

522
static inline unsigned int
523
sixel_similarity_diff(const unsigned char *a, const unsigned char *b)
2,142,728✔
524
{
525
    int dr = (int)a[0] - (int)b[0];
2,142,728✔
526
    int dg = (int)a[1] - (int)b[1];
2,142,728✔
527
    int db = (int)a[2] - (int)b[2];
2,142,728✔
528
    return (unsigned int)(dr * dr + dg * dg + db * db);
2,678,410✔
529
}
535,682✔
530

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

556
    if (similarity->cache == NULL) {
8,614,816!
557
        return 0;
558
    }
559

560
    if (index1 < 0 || index1 >= similarity->ncolors ||
10,768,520!
561
        index2 < 0 || index2 >= similarity->ncolors) {
8,614,816!
562
        return 0;
563
    }
564

565
    if (index1 <= index2) {
8,614,816✔
566
        min_index = index1;
1,504,739✔
567
        max_index = index2;
1,504,739✔
568
    } else {
1,504,739✔
569
        min_index = index2;
2,595,860✔
570
        max_index = index1;
2,595,860✔
571
    }
572

573
    cache_pos = (size_t)min_index * (size_t)similarity->stride
10,768,520✔
574
              + (size_t)max_index;
8,614,816✔
575
    cached = similarity->cache[cache_pos];
8,614,816✔
576
    if (cached >= 0) {
8,614,816✔
577
        return (unsigned int)cached;
8,601,908✔
578
    }
579

580
    palette = similarity->palette;
12,908✔
581
    p1 = palette + index1 * 3;
12,908✔
582
    p2 = palette + index2 * 3;
12,908✔
583

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

617
    distance = sixel_similarity_diff(avg_color, p1);
12,908✔
618
    bias = similarity->bias;
12,908✔
619
    if (bias < 1) {
12,908!
620
        bias = 1;
621
    }
622
    scaled_distance = (unsigned long long)distance
16,135✔
623
                    * (unsigned long long)bias
12,908✔
624
                    + 50ULL;
3,227✔
625
    base_distance = (unsigned int)(scaled_distance / 100ULL);
12,908✔
626
    if (base_distance == 0U) {
12,908!
627
        base_distance = 1U;
628
    }
629

630
    for (i = 0; i < similarity->ncolors; ++i) {
2,168,544✔
631
        if (i == index1 || i == index2) {
2,155,636✔
632
            continue;
25,816✔
633
        }
634
        pk = palette + i * 3;
2,129,820✔
635
        diff = sixel_similarity_diff(avg_color, pk);
2,129,820✔
636
        if (diff < min_diff) {
2,129,820✔
637
            min_diff = diff;
1,641,818✔
638
        }
25,091✔
639
    }
532,455✔
640

641
    if (min_diff == UINT_MAX) {
12,908!
642
        min_diff = base_distance * 2U;
×
643
    }
644

645
    if (min_diff >= base_distance * 2U) {
12,908✔
646
        result = 5U;
172✔
647
    } else if (min_diff >= base_distance) {
12,392✔
648
        result = 8U;
207✔
649
    } else if ((unsigned long long)min_diff * 6ULL
14,447✔
650
               >= (unsigned long long)base_distance * 5ULL) {
11,392✔
651
        result = 7U;
74✔
652
    } else if ((unsigned long long)min_diff * 4ULL
13,944✔
653
               >= (unsigned long long)base_distance * 3ULL) {
11,096✔
654
        result = 7U;
50✔
655
    } else if ((unsigned long long)min_diff * 3ULL
13,670✔
656
               >= (unsigned long long)base_distance * 2ULL) {
10,896✔
657
        result = 5U;
64✔
658
    } else if ((unsigned long long)min_diff * 5ULL
13,364✔
659
               >= (unsigned long long)base_distance * 3ULL) {
2,660✔
660
        result = 7U;
63✔
661
    } else if ((unsigned long long)min_diff * 2ULL
13,048✔
662
               >= (unsigned long long)base_distance * 1ULL) {
2,597✔
663
        result = 4U;
118✔
664
    } else if ((unsigned long long)min_diff * 3ULL
12,513✔
665
               >= (unsigned long long)base_distance * 1ULL) {
2,479✔
666
        result = 2U;
325✔
667
    } else {
325✔
668
        result = 0U;
8,616✔
669
    }
670

671
    similarity->cache[cache_pos] = (signed char)result;
12,908✔
672

673
    return result;
12,908✔
674
}
2,153,704✔
675

676
static inline int
677
sixel_clamp(int value, int min_value, int max_value)
×
678
{
679
    if (value < min_value) {
×
680
        return min_value;
681
    }
682
    if (value > max_value) {
×
683
        return max_value;
684
    }
685
    return value;
686
}
687

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

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

723
static unsigned short
724
sixel_scale_threshold(unsigned short base, int percent)
8✔
725
{
726
    unsigned long long numerator;
8✔
727
    unsigned long long scaled;
8✔
728

729
    if (percent <= 0) {
8!
730
        percent = 1;
2✔
731
    }
2✔
732

733
    numerator = (unsigned long long)base * 100ULL
10✔
734
              + (unsigned long long)percent / 2ULL;
5✔
735
    scaled = numerator / (unsigned long long)percent;
8✔
736
    if (scaled == 0ULL) {
8!
737
        scaled = 1ULL;
738
    }
739
    if (scaled > USHRT_MAX) {
8!
740
        scaled = USHRT_MAX;
741
    }
742

743
    return (unsigned short)scaled;
10✔
744
}
2✔
745

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

790
    if (width <= 0 || height <= 0 || palette == NULL || ncolors <= 0) {
4!
791
        return SIXEL_BAD_INPUT;
792
    }
793

794
    num_pixels = (size_t)width * (size_t)height;
4✔
795

796
    memset(&similarity, 0, sizeof(sixel_similarity_t));
4!
797

798
    strong_threshold = sixel_scale_threshold(256U, edge_strength);
4!
799
    detail_threshold = sixel_scale_threshold(160U, edge_strength);
4!
800
    if (strong_threshold < detail_threshold) {
4!
801
        strong_threshold = detail_threshold;
802
    }
803

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

819
    gray = (int *)sixel_allocator_malloc(
4✔
820
        allocator,
1✔
821
        num_pixels * sizeof(int));
1✔
822
    if (gray == NULL) {
4!
823
        sixel_helper_set_additional_message(
×
824
            "sixel_dequantize_k_undither: "
825
            "sixel_allocator_malloc() failed.");
826
        status = SIXEL_BAD_ALLOCATION;
×
827
        goto end;
×
828
    }
829

830
    prewitt = (unsigned short *)sixel_allocator_malloc(
4✔
831
        allocator,
1✔
832
        num_pixels * sizeof(unsigned short));
1✔
833
    if (prewitt == NULL) {
4!
834
        sixel_helper_set_additional_message(
×
835
            "sixel_dequantize_k_undither: "
836
            "sixel_allocator_malloc() failed.");
837
        status = SIXEL_BAD_ALLOCATION;
×
838
        goto end;
×
839
    }
840

841
    /*
842
     * Pre-compute palette distance heuristics so each neighbour lookup reuses
843
     * the k_undither-style similarity table.
844
     */
845
    status = sixel_similarity_init(
4✔
846
        &similarity,
847
        palette,
1✔
848
        ncolors,
1✔
849
        similarity_bias,
1✔
850
        allocator);
1✔
851
    if (SIXEL_FAILED(status)) {
4!
852
        goto end;
×
853
    }
854

855
    for (y = 0; y < height; ++y) {
1,804✔
856
        for (x = 0; x < width; ++x) {
1,081,800✔
857
            palette_index = indexed_pixels[y * width + x];
1,080,000✔
858
            if (palette_index < 0 || palette_index >= ncolors) {
1,080,000!
859
                palette_index = 0;
×
860
            }
861

862
            color = palette + palette_index * 3;
1,080,000✔
863
            out_index = (size_t)(y * width + x) * 3;
1,080,000✔
864
            rgb[out_index + 0] = color[0];
1,080,000✔
865
            rgb[out_index + 1] = color[1];
1,080,000✔
866
            rgb[out_index + 2] = color[2];
1,080,000✔
867

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

883
                gradient = prewitt[y * width + x];
×
884
                if (gradient > strong_threshold) {
×
885
                    continue;
×
886
                }
887

888
                if (gradient > detail_threshold) {
×
889
                    center_weight = 24U;
890
                } else {
891
                    center_weight = 8U;
810,000✔
892
                }
893
            } else {
894
                center_weight = 8U;
270,000✔
895
            }
896

897
            out_index = (size_t)(y * width + x) * 3;
1,080,000✔
898
            accum_r = (unsigned int)rgb[out_index + 0] * center_weight;
1,080,000✔
899
            accum_g = (unsigned int)rgb[out_index + 1] * center_weight;
1,080,000✔
900
            accum_b = (unsigned int)rgb[out_index + 2] * center_weight;
1,080,000✔
901
            total_weight = center_weight;
1,080,000✔
902

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

914
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
8,640,000✔
915
                    continue;
25,184✔
916
                }
917

918
                neighbor_index = indexed_pixels[ny * width + nx];
8,614,816✔
919
                if (neighbor_index < 0 || neighbor_index >= ncolors) {
8,614,816!
920
                    continue;
×
921
                }
922

923
                if (numerator) {
8,614,816!
924
                    weight = sixel_similarity_compare(
8,614,816✔
925
                        &similarity,
926
                        palette_index,
2,153,704✔
927
                        neighbor_index,
2,153,704✔
928
                        numerator,
2,153,704✔
929
                        denominator);
2,153,704✔
930
                    if (weight == 0) {
8,614,816✔
931
                        continue;
468,200✔
932
                    }
933

934
                    neighbor_color = palette + neighbor_index * 3;
8,146,616✔
935
                    accum_r += (unsigned int)neighbor_color[0] * weight;
8,146,616✔
936
                    accum_g += (unsigned int)neighbor_color[1] * weight;
8,146,616✔
937
                    accum_b += (unsigned int)neighbor_color[2] * weight;
8,146,616✔
938
                    total_weight += weight;
8,146,616✔
939
                }
2,036,654✔
940
            }
2,036,654✔
941

942
            if (total_weight > 0U) {
1,080,000!
943
                rgb[out_index + 0] = (unsigned char)(accum_r / total_weight);
1,080,000✔
944
                rgb[out_index + 1] = (unsigned char)(accum_g / total_weight);
1,080,000✔
945
                rgb[out_index + 2] = (unsigned char)(accum_b / total_weight);
1,080,000✔
946
            }
270,000✔
947
        }
270,000✔
948
    }
450✔
949

950

951
    *output = rgb;
4✔
952
    rgb = NULL;
4✔
953
    status = SIXEL_OK;
4✔
954

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

972
static sixel_option_choice_t const g_decoder_dequant_choices[] = {
973
    { "none", 0 },
974
    { "k_undither", 1 }
975
};
976

977
static void
978
normalise_windows_drive_path(char *path)
3✔
979
{
980
#if defined(_WIN32)
981
    size_t length;
982

983
    length = 0u;
984

985
    if (path == NULL) {
986
        return;
987
    }
988

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

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

1024
    sixel_decoder_ref(decoder);
134✔
1025
    path_flags = 0u;
134✔
1026
    path_check = 0;
134✔
1027

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

1071
        payload = value;
49✔
1072
        if (strncmp(value, "png:", 4) == 0) {
49✔
1073
            payload = value + 4;
16✔
1074
            if (payload[0] == '\0') {
16✔
1075
                sixel_helper_set_additional_message(
4✔
1076
                    "missing target after the \"png:\" prefix.");
1077
                return SIXEL_BAD_ARGUMENT;
4✔
1078
            }
1079
            length = strlen(payload);
12✔
1080
            filename = p = malloc(length + 1U);
12✔
1081
            if (p == NULL) {
12!
1082
                sixel_helper_set_additional_message(
×
1083
                    "sixel_decoder_setopt: malloc() failed for png path filename.");
1084
                return SIXEL_BAD_ALLOCATION;
×
1085
            }
1086
            memcpy(p, payload, length + 1U);
12✔
1087
            normalise_windows_drive_path(p);
12✔
1088
        } else {
3✔
1089
            filename = value;
9✔
1090
        }
1091

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

1123
        match_index = 0;
8✔
1124
        memset(match_detail, 0, sizeof(match_detail));
8✔
1125
        memset(match_message, 0, sizeof(match_message));
8✔
1126

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

1157
    case SIXEL_OPTFLAG_SIMILARITY:  /* S */
1158
        decoder->dequantize_similarity_bias = atoi(value);
×
1159
        if (decoder->dequantize_similarity_bias < 0 ||
×
1160
            decoder->dequantize_similarity_bias > 1000) {
1161
            sixel_helper_set_additional_message(
×
1162
                "similarity must be between 1 and 1000.");
1163
            status = SIXEL_BAD_ARGUMENT;
×
1164
            goto end;
×
1165
        }
1166
        break;
1167

1168
    case SIXEL_OPTFLAG_SIZE:  /* s */
1169
        decoder->thumbnail_size = atoi(value);
×
1170
        if (decoder->thumbnail_size <= 0) {
×
1171
            sixel_helper_set_additional_message(
×
1172
                "size must be greater than zero.");
1173
            status = SIXEL_BAD_ARGUMENT;
×
1174
            goto end;
×
1175
        }
1176
        break;
1177

1178
    case SIXEL_OPTFLAG_EDGE:  /* e */
1179
        decoder->dequantize_edge_strength = atoi(value);
×
1180
        if (decoder->dequantize_edge_strength < 0 ||
×
1181
            decoder->dequantize_edge_strength > 1000) {
1182
            sixel_helper_set_additional_message(
×
1183
                "edge bias must be between 1 and 1000.");
1184
            status = SIXEL_BAD_ARGUMENT;
×
1185
            goto end;
×
1186
        }
1187
        break;
1188

1189
    case SIXEL_OPTFLAG_DIRECT:  /* D */
15✔
1190
        decoder->direct_color = 1;
20✔
1191
        break;
20✔
1192

1193
    case SIXEL_OPTFLAG_THREADS:  /* = */
3✔
1194
        status = sixel_decoder_parallel_override_threads(value);
4✔
1195
        if (SIXEL_FAILED(status)) {
4!
1196
            goto end;
4✔
1197
        }
1198
        break;
1199

1200
    case '?':
×
1201
    default:
1202
        status = SIXEL_BAD_ARGUMENT;
×
1203
        goto end;
×
1204
    }
1205

1206
    status = SIXEL_OK;
32✔
1207

1208
end:
96✔
1209
    sixel_decoder_unref(decoder);
130✔
1210

1211
    return status;
130✔
1212
}
35✔
1213

1214

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

1256
    sixel_decoder_ref(decoder);
85✔
1257

1258
    frame = NULL;
85✔
1259
    new_width = 0;
85✔
1260
    new_height = 0;
85✔
1261
    scaled_width = 0.0;
85✔
1262
    scaled_height = 0.0;
85✔
1263
    max_dimension = 0;
85✔
1264
    thumbnail_size = decoder->thumbnail_size;
85✔
1265
    frame_ncolors = -1;
85✔
1266
    clipboard_blob = NULL;
85✔
1267
    clipboard_blob_size = 0u;
85✔
1268
    clipboard_status = SIXEL_OK;
85✔
1269
    clipboard_output_path = NULL;
85✔
1270
    clipboard_output_data = NULL;
85✔
1271
    clipboard_output_size = 0u;
85✔
1272
    clipboard_output_status = SIXEL_OK;
85✔
1273
    input_fp = NULL;
85✔
1274
    sixel_logger_init(&logger);
85✔
1275
    (void)sixel_logger_prepare_env(&logger);
85✔
1276
    logger_prepared = logger.active;
85✔
1277

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

1331
        raw_len = 0;
84✔
1332
        max = 64 * 1024;
84✔
1333

1334
        raw_data = (unsigned char *)sixel_allocator_malloc(
84✔
1335
            decoder->allocator,
21✔
1336
            (size_t)max);
21✔
1337
        if (raw_data == NULL) {
84!
1338
            sixel_helper_set_additional_message(
×
1339
                "sixel_decoder_decode: sixel_allocator_malloc() failed.");
1340
            status = SIXEL_BAD_ALLOCATION;
×
1341
            goto end;
×
1342
        }
1343

1344
        for (;;) {
16,476✔
1345
            if ((max - raw_len) < 4096) {
9,453✔
1346
                max *= 2;
164✔
1347
                raw_data = (unsigned char *)sixel_allocator_realloc(
164✔
1348
                    decoder->allocator,
41✔
1349
                    raw_data,
41✔
1350
                    (size_t)max);
41✔
1351
                if (raw_data == NULL) {
164!
1352
                    sixel_helper_set_additional_message(
×
1353
                        "sixel_decoder_decode: sixel_allocator_realloc() failed.");
1354
                    status = SIXEL_BAD_ALLOCATION;
×
1355
                    goto end;
×
1356
                }
1357
            }
41✔
1358
            if ((n = (int)fread(raw_data + raw_len, 1, 4096, input_fp)) <= 0) {
16,539!
1359
                break;
21✔
1360
            }
1361
            raw_len += n;
9,369✔
1362
        }
1363

1364
        if (input_fp != NULL && input_fp != stdin) {
84!
1365
            fclose(input_fp);
32✔
1366
        }
8✔
1367
    }
1368

1369
    if (decoder->direct_color != 0 &&
85✔
1370
            decoder->dequantize_method != SIXEL_DEQUANTIZE_NONE) {
20✔
1371
        sixel_helper_set_additional_message(
4✔
1372
            "sixel_decoder_decode: direct option "
1373
            "cannot be combined with dequantize option.");
1374
        status = SIXEL_BAD_ARGUMENT;
4✔
1375
        goto end;
4✔
1376
    }
1377

1378
    ncolors = 0;
81✔
1379

1380
    if (decoder->direct_color != 0) {
81✔
1381
        status = sixel_decode_direct(
16✔
1382
            raw_data,
4✔
1383
            raw_len,
4✔
1384
            &direct_pixels,
1385
            &sx,
1386
            &sy,
1387
            decoder->allocator);
4✔
1388
    } else {
4✔
1389
        status = sixel_decode_raw(
65✔
1390
            raw_data,
17✔
1391
            raw_len,
17✔
1392
            &indexed_pixels,
1393
            &sx,
1394
            &sy,
1395
            &palette,
1396
            &ncolors,
1397
            decoder->allocator);
17✔
1398
    }
1399
    if (SIXEL_FAILED(status)) {
81!
1400
        goto end;
×
1401
    }
1402

1403
    if (sx > SIXEL_WIDTH_LIMIT || sy > SIXEL_HEIGHT_LIMIT) {
81!
1404
        status = SIXEL_BAD_INPUT;
×
1405
        goto end;
×
1406
    }
1407

1408
    if (decoder->direct_color != 0) {
81✔
1409
        output_pixels = direct_pixels;
16✔
1410
        output_palette = NULL;
16✔
1411
        output_pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
16✔
1412
        frame_ncolors = 0;
16✔
1413
    } else {
4✔
1414
        output_pixels = indexed_pixels;
65✔
1415
        output_palette = palette;
65✔
1416
        output_pixelformat = SIXEL_PIXELFORMAT_PAL8;
65✔
1417

1418
        if (decoder->dequantize_method == SIXEL_DEQUANTIZE_K_UNDITHER) {
65✔
1419
            if (logger_prepared) {
4!
1420
                sixel_logger_logf(&logger,
×
1421
                                  "decoder",
1422
                                  "undither",
1423
                                  "start",
1424
                                  0,
1425
                                  0,
1426
                                  0,
1427
                                  sy,
1428
                                  0,
1429
                                  sx,
1430
                                  "k_undither begin %dx%d palette=%d",
1431
                                  sx,
1432
                                  sy,
1433
                                  ncolors);
1434
            }
1435
            status = sixel_dequantize_k_undither(
4✔
1436
                indexed_pixels,
1✔
1437
                sx,
1✔
1438
                sy,
1✔
1439
                palette,
1✔
1440
                ncolors,
1✔
1441
                decoder->dequantize_similarity_bias,
1✔
1442
                decoder->dequantize_edge_strength,
1✔
1443
                decoder->allocator,
1✔
1444
                &rgb_pixels);
1445
            if (SIXEL_FAILED(status)) {
4!
1446
                if (logger_prepared) {
×
1447
                    sixel_logger_logf(
×
1448
                        &logger,
1449
                        "decoder",
1450
                        "undither",
1451
                        "abort",
1452
                        0,
1453
                        0,
1454
                        0,
1455
                        sy,
1456
                        0,
1457
                        sx,
1458
                        "k_undither failed status=%d",
1459
                        status);
1460
                }
1461
                goto end;
×
1462
            }
1463
            if (logger_prepared) {
4!
1464
                sixel_logger_logf(&logger,
×
1465
                                  "decoder",
1466
                                  "undither",
1467
                                  "finish",
1468
                                  0,
1469
                                  0,
1470
                                  0,
1471
                                  sy,
1472
                                  0,
1473
                                  sx,
1474
                                  "k_undither complete %dx%d",
1475
                                  sx,
1476
                                  sy);
1477
            }
1478
            output_pixels = rgb_pixels;
4✔
1479
            output_palette = NULL;
4✔
1480
            output_pixelformat = SIXEL_PIXELFORMAT_RGB888;
4✔
1481
        }
1✔
1482

1483
        if (output_pixelformat == SIXEL_PIXELFORMAT_PAL8) {
20✔
1484
            frame_ncolors = ncolors;
61✔
1485
        } else {
16✔
1486
            frame_ncolors = 0;
1✔
1487
        }
1488
    }
1489

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

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

1582
    if (logger_prepared) {
81!
1583
        sixel_logger_logf(&logger,
×
1584
                          "io",
1585
                          "png",
1586
                          "start",
1587
                          0,
1588
                          0,
1589
                          0,
1590
                          sy,
1591
                          0,
1592
                          sx,
1593
                          "png output begin %dx%d format=%d",
1594
                          sx,
1595
                          sy,
1596
                          output_pixelformat);
1597
    }
1598
    status = sixel_helper_write_image_file(
81✔
1599
        output_pixels,
21✔
1600
        sx,
21✔
1601
        sy,
21✔
1602
        output_palette,
21✔
1603
        output_pixelformat,
21✔
1604
        decoder->clipboard_output_active
81✔
1605
            ? clipboard_output_path
1✔
1606
            : decoder->output,
20✔
1607
        SIXEL_FORMAT_PNG,
1608
        decoder->allocator);
21✔
1609
    if (SIXEL_FAILED(status)) {
81!
1610
        if (logger_prepared) {
×
1611
            sixel_logger_logf(&logger,
×
1612
                              "io",
1613
                              "png",
1614
                              "abort",
1615
                              0,
1616
                              0,
1617
                              0,
1618
                              sy,
1619
                              0,
1620
                              sx,
1621
                              "png output failed status=%d",
1622
                              status);
1623
        }
1624
        goto end;
×
1625
    }
1626
    if (logger_prepared) {
81!
1627
        sixel_logger_logf(&logger,
×
1628
                          "io",
1629
                          "png",
1630
                          "finish",
1631
                          0,
1632
                          0,
1633
                          0,
1634
                          sy,
1635
                          0,
1636
                          sx,
1637
                          "png output complete %dx%d",
1638
                          sx,
1639
                          sy);
1640
    }
1641

1642
    if (decoder->clipboard_output_active) {
82✔
1643
        clipboard_output_status = decoder_clipboard_read_file(
4✔
1644
            clipboard_output_path,
1✔
1645
            &clipboard_output_data,
1646
            &clipboard_output_size);
1647
        if (SIXEL_SUCCEEDED(clipboard_output_status)) {
4!
1648
            clipboard_output_status = sixel_clipboard_write(
4✔
1649
                decoder->clipboard_output_format,
4✔
1650
                clipboard_output_data,
1✔
1651
                clipboard_output_size);
1✔
1652
        }
1✔
1653
        if (clipboard_output_data != NULL) {
4!
1654
            free(clipboard_output_data);
4✔
1655
            clipboard_output_data = NULL;
4✔
1656
        }
1✔
1657
        if (SIXEL_FAILED(clipboard_output_status)) {
4!
1658
            status = clipboard_output_status;
3✔
1659
            goto end;
3✔
1660
        }
1661
    }
1✔
1662

1663
end:
57✔
1664
    sixel_frame_unref(frame);
85✔
1665
    sixel_allocator_free(decoder->allocator, raw_data);
85✔
1666
    sixel_allocator_free(decoder->allocator, indexed_pixels);
85✔
1667
    sixel_allocator_free(decoder->allocator, palette);
85✔
1668
    sixel_allocator_free(decoder->allocator, direct_pixels);
85✔
1669
    sixel_allocator_free(decoder->allocator, rgb_pixels);
85✔
1670
    if (clipboard_blob != NULL) {
85!
1671
        free(clipboard_blob);
×
1672
    }
1673
    if (clipboard_output_path != NULL) {
85✔
1674
        (void)sixel_compat_unlink(clipboard_output_path);
4✔
1675
        sixel_allocator_free(decoder->allocator, clipboard_output_path);
4✔
1676
    }
1✔
1677

1678
    sixel_decoder_unref(decoder);
85✔
1679
    if (logger_prepared) {
85!
1680
        sixel_logger_close(&logger);
×
1681
    }
1682

1683
    return status;
107✔
1684
}
22✔
1685

1686

1687
#if HAVE_TESTS
1688
static int
1689
test1(void)
×
1690
{
1691
    int nret = EXIT_FAILURE;
×
1692
    sixel_decoder_t *decoder = NULL;
×
1693

1694
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1695
#  pragma GCC diagnostic push
1696
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1697
#endif
1698
    decoder = sixel_decoder_create();
×
1699
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1700
#  pragma GCC diagnostic pop
1701
#endif
1702
    if (decoder == NULL) {
×
1703
        goto error;
×
1704
    }
1705
    sixel_decoder_ref(decoder);
×
1706
    sixel_decoder_unref(decoder);
×
1707
    nret = EXIT_SUCCESS;
×
1708

1709
error:
1710
    sixel_decoder_unref(decoder);
×
1711
    return nret;
×
1712
}
1713

1714

1715
static int
1716
test2(void)
×
1717
{
1718
    int nret = EXIT_FAILURE;
×
1719
    sixel_decoder_t *decoder = NULL;
×
1720
    SIXELSTATUS status;
×
1721

1722
    status = sixel_decoder_new(&decoder, NULL);
×
1723
    if (SIXEL_FAILED(status)) {
×
1724
        goto error;
×
1725
    }
1726

1727
    sixel_decoder_ref(decoder);
×
1728
    sixel_decoder_unref(decoder);
×
1729
    nret = EXIT_SUCCESS;
×
1730

1731
error:
1732
    sixel_decoder_unref(decoder);
×
1733
    return nret;
×
1734
}
1735

1736

1737
static int
1738
test3(void)
×
1739
{
1740
    int nret = EXIT_FAILURE;
×
1741
    sixel_decoder_t *decoder = NULL;
×
1742
    sixel_allocator_t *allocator = NULL;
×
1743
    SIXELSTATUS status;
×
1744

1745
    sixel_debug_malloc_counter = 1;
×
1746

1747
    status = sixel_allocator_new(
×
1748
        &allocator,
1749
        sixel_bad_malloc,
1750
        NULL,
1751
        NULL,
1752
        NULL);
1753
    if (SIXEL_FAILED(status)) {
×
1754
        goto error;
×
1755
    }
1756

1757
    status = sixel_decoder_new(&decoder, allocator);
×
1758
    if (status != SIXEL_BAD_ALLOCATION) {
×
1759
        goto error;
×
1760
    }
1761

1762
    nret = EXIT_SUCCESS;
1763

1764
error:
1765
    return nret;
×
1766
}
1767

1768

1769
static int
1770
test4(void)
×
1771
{
1772
    int nret = EXIT_FAILURE;
×
1773
    sixel_decoder_t *decoder = NULL;
×
1774
    sixel_allocator_t *allocator = NULL;
×
1775
    SIXELSTATUS status;
×
1776

1777
    sixel_debug_malloc_counter = 2;
×
1778

1779
    status = sixel_allocator_new(
×
1780
        &allocator,
1781
        sixel_bad_malloc,
1782
        NULL,
1783
        NULL,
1784
        NULL);
1785
    if (SIXEL_FAILED(status)) {
×
1786
        goto error;
×
1787
    }
1788

1789
    status = sixel_decoder_new(&decoder, allocator);
×
1790
    if (status != SIXEL_BAD_ALLOCATION) {
×
1791
        goto error;
×
1792
    }
1793

1794
    nret = EXIT_SUCCESS;
1795

1796
error:
1797
    return nret;
×
1798
}
1799

1800

1801
static int
1802
test5(void)
×
1803
{
1804
    int nret = EXIT_FAILURE;
×
1805
    sixel_decoder_t *decoder = NULL;
×
1806
    sixel_allocator_t *allocator = NULL;
×
1807
    SIXELSTATUS status;
×
1808

1809
    sixel_debug_malloc_counter = 4;
×
1810

1811
    status = sixel_allocator_new(
×
1812
        &allocator,
1813
        sixel_bad_malloc,
1814
        NULL,
1815
        NULL,
1816
        NULL);
1817
    if (SIXEL_FAILED(status)) {
×
1818
        goto error;
×
1819
    }
1820
    status = sixel_decoder_new(&decoder, allocator);
×
1821
    if (SIXEL_FAILED(status)) {
×
1822
        goto error;
×
1823
    }
1824

1825
    status = sixel_decoder_setopt(
×
1826
        decoder,
1827
        SIXEL_OPTFLAG_INPUT,
1828
        "/");
1829
    if (status != SIXEL_BAD_ALLOCATION) {
×
1830
        goto error;
×
1831
    }
1832

1833
    nret = EXIT_SUCCESS;
1834

1835
error:
1836
    return nret;
×
1837
}
1838

1839

1840
static int
1841
test6(void)
×
1842
{
1843
    int nret = EXIT_FAILURE;
×
1844
    sixel_decoder_t *decoder = NULL;
×
1845
    sixel_allocator_t *allocator = NULL;
×
1846
    SIXELSTATUS status;
×
1847

1848
    sixel_debug_malloc_counter = 4;
×
1849

1850
    status = sixel_allocator_new(
×
1851
        &allocator,
1852
        sixel_bad_malloc,
1853
        NULL,
1854
        NULL,
1855
        NULL);
1856
    if (SIXEL_FAILED(status)) {
×
1857
        goto error;
×
1858
    }
1859

1860
    status = sixel_decoder_new(&decoder, allocator);
×
1861
    if (SIXEL_FAILED(status)) {
×
1862
        goto error;
×
1863
    }
1864

1865
    status = sixel_decoder_setopt(
×
1866
        decoder,
1867
        SIXEL_OPTFLAG_OUTPUT,
1868
        "/");
1869
    if (status != SIXEL_BAD_ALLOCATION) {
×
1870
        goto error;
×
1871
    }
1872

1873
    nret = EXIT_SUCCESS;
1874

1875
error:
1876
    return nret;
×
1877
}
1878

1879

1880
static int
1881
test7(void)
×
1882
{
1883
    int nret = EXIT_FAILURE;
×
1884
    sixel_decoder_t *decoder = NULL;
×
1885
    sixel_allocator_t *allocator = NULL;
×
1886
    SIXELSTATUS status;
×
1887

1888
    status = sixel_allocator_new(
×
1889
        &allocator,
1890
        NULL,
1891
        NULL,
1892
        NULL,
1893
        NULL);
1894
    if (SIXEL_FAILED(status)) {
×
1895
        goto error;
×
1896
    }
1897

1898
    status = sixel_decoder_new(&decoder, allocator);
×
1899
    if (SIXEL_FAILED(status)) {
×
1900
        goto error;
×
1901
    }
1902

1903
    status = sixel_decoder_setopt(
×
1904
        decoder,
1905
        SIXEL_OPTFLAG_INPUT,
1906
        "../images/file");
1907
    if (SIXEL_FAILED(status)) {
×
1908
        goto error;
×
1909
    }
1910

1911
    status = sixel_decoder_decode(decoder);
×
1912
    if ((status >> 8) != (SIXEL_LIBC_ERROR >> 8)) {
×
1913
        goto error;
×
1914
    }
1915

1916
    nret = EXIT_SUCCESS;
1917

1918
error:
1919
    return nret;
×
1920
}
1921

1922

1923
static int
1924
test8(void)
×
1925
{
1926
    int nret = EXIT_FAILURE;
×
1927
    sixel_decoder_t *decoder = NULL;
×
1928
    sixel_allocator_t *allocator = NULL;
×
1929
    SIXELSTATUS status;
×
1930

1931
    sixel_debug_malloc_counter = 5;
×
1932

1933
    status = sixel_allocator_new(
×
1934
        &allocator,
1935
        sixel_bad_malloc,
1936
        NULL,
1937
        NULL,
1938
        NULL);
1939
    if (SIXEL_FAILED(status)) {
×
1940
        goto error;
×
1941
    }
1942

1943
    status = sixel_decoder_new(&decoder, allocator);
×
1944
    if (SIXEL_FAILED(status)) {
×
1945
        goto error;
×
1946
    }
1947

1948
    status = sixel_decoder_setopt(
×
1949
        decoder,
1950
        SIXEL_OPTFLAG_INPUT,
1951
        "../images/map8.six");
1952
    if (SIXEL_FAILED(status)) {
×
1953
        goto error;
×
1954
    }
1955

1956
    status = sixel_decoder_decode(decoder);
×
1957
    if (status != SIXEL_BAD_ALLOCATION) {
×
1958
        goto error;
×
1959
    }
1960

1961
    nret = EXIT_SUCCESS;
1962

1963
error:
1964
    return nret;
×
1965
}
1966

1967

1968
SIXELAPI int
1969
sixel_decoder_tests_main(void)
×
1970
{
1971
    int nret = EXIT_FAILURE;
×
1972
    size_t i;
×
1973
    typedef int (* testcase)(void);
1974

1975
    static testcase const testcases[] = {
1976
        test1,
1977
        test2,
1978
        test3,
1979
        test4,
1980
        test5,
1981
        test6,
1982
        test7,
1983
        test8
1984
    };
1985

1986
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
1987
        nret = testcases[i]();
×
1988
        if (nret != EXIT_SUCCESS) {
×
1989
            goto error;
×
1990
        }
1991
    }
1992

1993
    nret = EXIT_SUCCESS;
1994

1995
error:
1996
    return nret;
×
1997
}
1998
#endif  /* HAVE_TESTS */
1999

2000
/* emacs Local Variables:      */
2001
/* emacs mode: c               */
2002
/* emacs tab-width: 4          */
2003
/* emacs indent-tabs-mode: nil */
2004
/* emacs c-basic-offset: 4     */
2005
/* emacs End:                  */
2006
/* vim: set expandtab ts=4 sts=4 sw=4 : */
2007
/* 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