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

saitoha / libsixel / 20578371047

29 Dec 2025 05:06PM UTC coverage: 51.955% (-5.4%) from 57.322%
20578371047

push

github

saitoha
Revert "Merge branch 'refactor/pixelformat' into develop"

This reverts commit 4a6153922, reversing
changes made to 6f3ef3068.

14746 of 45077 branches covered (32.71%)

147 of 262 new or added lines in 15 files covered. (56.11%)

1406 existing lines in 46 files now uncovered.

21419 of 41226 relevant lines covered (51.96%)

3895522.67 hits per line

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

60.43
/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,
4✔
65
                                size_t dest_size,
66
                                char const *format,
67
                                char const *fallback)
68
{
69
    char const *source;
4✔
70
    size_t limit;
4✔
71

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

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

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

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

90

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

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

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

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

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

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

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

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

164
    return template_path;
165
}
166

167

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

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

189
    template_path = decoder_create_temp_template_with_prefix(allocator,
4✔
190
                                                             prefix,
191
                                                             &template_capacity);
192
    if (template_path == NULL) {
4!
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) {
4!
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;
4✔
212
#if defined(O_EXCL)
213
    open_flags |= O_EXCL;
4✔
214
#endif
215
    open_mode = S_IRUSR | S_IWUSR;
4✔
216
    fd = sixel_compat_open(template_path, open_flags, open_mode);
4✔
217
    if (fd < 0) {
4!
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;
4✔
225
    if (fd >= 0) {
4!
226
        (void)sixel_compat_close(fd);
4✔
227
        fd = (-1);
4✔
228
    }
229

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

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

241
    return status;
4✔
242
}
243

244

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

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

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

265
    if (path == NULL) {
4!
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");
4✔
272
    if (stream == NULL) {
4!
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);
4✔
279
    if (seek_result != 0) {
4!
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);
4✔
287
    if (file_size < 0) {
4!
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);
4✔
295
    if (seek_result != 0) {
4!
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) {
4!
303
        buffer = NULL;
304
        read_size = 0u;
305
    } else {
306
        buffer = (unsigned char *)malloc((size_t)file_size);
4✔
307
        if (buffer == NULL) {
4!
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);
4!
314
        if (read_size != (size_t)file_size) {
4!
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) {
4!
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;
4✔
333
    *size = read_size;
4✔
334

335
    return SIXEL_OK;
4✔
336
}
337

338

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

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

355

356
/* create decoder object */
357
SIXELAPI SIXELSTATUS
358
sixel_decoder_new(
128✔
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;
128✔
364

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

374
    *ppdecoder = sixel_allocator_malloc(allocator, sizeof(sixel_decoder_t));
128✔
375
    if (*ppdecoder == NULL) {
128!
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;
128✔
384
    (*ppdecoder)->output       = strdup_with_allocator("-", allocator);
128✔
385
    (*ppdecoder)->input        = strdup_with_allocator("-", allocator);
128✔
386
    (*ppdecoder)->allocator    = allocator;
128✔
387
    (*ppdecoder)->dequantize_method = SIXEL_DEQUANTIZE_NONE;
128✔
388
    (*ppdecoder)->dequantize_similarity_bias = 100;
128✔
389
    (*ppdecoder)->dequantize_edge_strength = 0;
128✔
390
    (*ppdecoder)->thumbnail_size = 0;
128✔
391
    (*ppdecoder)->direct_color = 0;
128✔
392
    (*ppdecoder)->clipboard_input_active = 0;
128✔
393
    (*ppdecoder)->clipboard_output_active = 0;
128✔
394
    (*ppdecoder)->clipboard_input_format[0] = '\0';
128✔
395
    (*ppdecoder)->clipboard_output_format[0] = '\0';
128✔
396

397
    if ((*ppdecoder)->output == NULL || (*ppdecoder)->input == NULL) {
128!
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:
128✔
410
    return status;
128✔
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

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

430

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

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

446

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

455

456
/* decrease reference count of decoder object (thread-unsafe) */
457
SIXELAPI void
458
sixel_decoder_unref(sixel_decoder_t *decoder)
352✔
459
{
460
    /* TODO: be thread safe */
461
    if (decoder != NULL && --decoder->ref == 0) {
352!
462
        sixel_decoder_destroy(decoder);
124✔
463
    }
464
}
352✔
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,
4✔
477
                      const unsigned char *palette,
478
                      int ncolors,
479
                      int bias,
480
                      sixel_allocator_t *allocator)
481
{
482
    size_t cache_size;
4✔
483
    int i;
4✔
484

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

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

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

500
    similarity->cache = (signed char *)sixel_allocator_malloc(
4✔
501
        allocator,
502
        cache_size);
503
    if (similarity->cache == NULL) {
4!
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);
4✔
509
    for (i = 0; i < ncolors; ++i) {
672✔
510
        similarity->cache[i * similarity->stride + i] = 7;
668✔
511
    }
512

513
    return SIXEL_OK;
514
}
515

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

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

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

560
    if (similarity->cache == NULL) {
8,614,816!
561
        return 0;
562
    }
563

564
    if (index1 < 0 || index1 >= similarity->ncolors ||
8,614,816!
565
        index2 < 0 || index2 >= similarity->ncolors) {
8,614,816!
566
        return 0;
567
    }
568

569
    if (index1 <= index2) {
8,614,816✔
570
        min_index = index1;
571
        max_index = index2;
572
    } else {
573
        min_index = index2;
2,595,860✔
574
        max_index = index1;
2,595,860✔
575
    }
576

577
    cache_pos = (size_t)min_index * (size_t)similarity->stride
8,614,816✔
578
              + (size_t)max_index;
8,614,816✔
579
    cached = similarity->cache[cache_pos];
8,614,816✔
580
    if (cached >= 0) {
8,614,816✔
581
        return (unsigned int)cached;
8,601,908✔
582
    }
583

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

588
#if 1
589
   /*    original: n = (p1 + p2) / 2
590
    */
591
    avg_color[0] = (unsigned char)(((unsigned int)p1[0]
12,908✔
592
                                    + (unsigned int)p2[0]) >> 1);
12,908✔
593
    avg_color[1] = (unsigned char)(((unsigned int)p1[1]
12,908✔
594
                                    + (unsigned int)p2[1]) >> 1);
12,908✔
595
    avg_color[2] = (unsigned char)(((unsigned int)p1[2]
12,908✔
596
                                    + (unsigned int)p2[2]) >> 1);
12,908✔
597
    (void) numerator;
12,908✔
598
    (void) denominator;
12,908✔
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);
12,908✔
622
    bias = similarity->bias;
12,908✔
623
    if (bias < 1) {
12,908!
624
        bias = 1;
625
    }
626
    scaled_distance = (unsigned long long)distance
12,908✔
627
                    * (unsigned long long)bias
12,908✔
628
                    + 50ULL;
629
    base_distance = (unsigned int)(scaled_distance / 100ULL);
12,908✔
630
    if (base_distance == 0U) {
12,908!
631
        base_distance = 1U;
632
    }
633

634
    for (i = 0; i < similarity->ncolors; ++i) {
2,168,544✔
635
        if (i == index1 || i == index2) {
2,155,636✔
636
            continue;
25,816✔
637
        }
638
        pk = palette + i * 3;
2,129,820✔
639
        diff = sixel_similarity_diff(avg_color, pk);
2,129,820✔
640
        if (diff < min_diff) {
2,129,820✔
641
            min_diff = diff;
2,155,636✔
642
        }
643
    }
644

645
    if (min_diff == UINT_MAX) {
12,908!
646
        min_diff = base_distance * 2U;
×
647
    }
648

649
    if (min_diff >= base_distance * 2U) {
12,908✔
650
        result = 5U;
651
    } else if (min_diff >= base_distance) {
12,220✔
652
        result = 8U;
653
    } else if ((unsigned long long)min_diff * 6ULL
11,392✔
654
               >= (unsigned long long)base_distance * 5ULL) {
11,392✔
655
        result = 7U;
656
    } else if ((unsigned long long)min_diff * 4ULL
11,096✔
657
               >= (unsigned long long)base_distance * 3ULL) {
11,096✔
658
        result = 7U;
659
    } else if ((unsigned long long)min_diff * 3ULL
10,896✔
660
               >= (unsigned long long)base_distance * 2ULL) {
10,896✔
661
        result = 5U;
662
    } else if ((unsigned long long)min_diff * 5ULL
10,640✔
663
               >= (unsigned long long)base_distance * 3ULL) {
664
        result = 7U;
665
    } else if ((unsigned long long)min_diff * 2ULL
10,388✔
666
               >= (unsigned long long)base_distance * 1ULL) {
667
        result = 4U;
668
    } else if ((unsigned long long)min_diff * 3ULL
9,916✔
669
               >= (unsigned long long)base_distance * 1ULL) {
670
        result = 2U;
671
    } else {
672
        result = 0U;
8,616✔
673
    }
674

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

677
    return result;
12,908✔
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)
8✔
729
{
730
    unsigned long long numerator;
8✔
731
    unsigned long long scaled;
8✔
732

733
    if (percent <= 0) {
8!
734
        percent = 1;
735
    }
736

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

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

750
static SIXELSTATUS
751
sixel_dequantize_k_undither(unsigned char *indexed_pixels,
4✔
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;
4✔
762
    unsigned char *rgb = NULL;
4✔
763
    int *gray = NULL;
4✔
764
    unsigned short *prewitt = NULL;
4✔
765
    sixel_similarity_t similarity;
4✔
766
    size_t num_pixels;
4✔
767
    int x;
4✔
768
    int y;
4✔
769
    unsigned short strong_threshold;
4✔
770
    unsigned short detail_threshold;
4✔
771
    static const int neighbor_offsets[8][4] = {
4✔
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;
4✔
777
    size_t out_index;
4✔
778
    int palette_index;
4✔
779
    unsigned int center_weight;
4✔
780
    unsigned int total_weight = 0;
4✔
781
    unsigned int accum_r;
4✔
782
    unsigned int accum_g;
4✔
783
    unsigned int accum_b;
4✔
784
    unsigned short gradient;
4✔
785
    int neighbor;
4✔
786
    int nx;
4✔
787
    int ny;
4✔
788
    int numerator;
4✔
789
    int denominator;
4✔
790
    unsigned int weight;
4✔
791
    const unsigned char *neighbor_color;
4✔
792
    int neighbor_index;
4✔
793

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

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

800
    memset(&similarity, 0, sizeof(sixel_similarity_t));
4!
801

802
    strong_threshold = sixel_scale_threshold(256U, edge_strength);
4!
803
    detail_threshold = sixel_scale_threshold(160U, edge_strength);
4!
804
    if (strong_threshold < detail_threshold) {
4!
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(
4✔
813
        allocator,
814
        num_pixels * 3);
815
    if (rgb == NULL) {
4!
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(
4✔
824
        allocator,
825
        num_pixels * sizeof(int));
826
    if (gray == NULL) {
4!
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(
4✔
835
        allocator,
836
        num_pixels * sizeof(unsigned short));
837
    if (prewitt == NULL) {
4!
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(
4✔
850
        &similarity,
851
        palette,
852
        ncolors,
853
        similarity_bias,
854
        allocator);
855
    if (SIXEL_FAILED(status)) {
4!
856
        goto end;
×
857
    }
858

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

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

872
            if (edge_strength > 0) {
1,080,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;
1,080,000✔
896
                }
897
            } else {
898
                center_weight = 8U;
899
            }
900

901
            out_index = (size_t)(y * width + x) * 3;
1,080,000✔
902
            accum_r = (unsigned int)rgb[out_index + 0] * center_weight;
1,080,000✔
903
            accum_g = (unsigned int)rgb[out_index + 1] * center_weight;
1,080,000✔
904
            accum_b = (unsigned int)rgb[out_index + 2] * center_weight;
1,080,000✔
905
            total_weight = center_weight;
1,080,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) {
9,720,000✔
913
                nx = x + neighbor_offsets[neighbor][0];
8,640,000✔
914
                ny = y + neighbor_offsets[neighbor][1];
8,640,000✔
915
                numerator = neighbor_offsets[neighbor][2];
8,640,000✔
916
                denominator = neighbor_offsets[neighbor][3];
8,640,000✔
917

918
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
8,640,000✔
919
                    continue;
25,184✔
920
                }
921

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

927
                if (numerator) {
8,614,816!
928
                    weight = sixel_similarity_compare(
8,614,816✔
929
                        &similarity,
930
                        palette_index,
931
                        neighbor_index,
932
                        numerator,
933
                        denominator);
934
                    if (weight == 0) {
8,614,816✔
935
                        continue;
468,200✔
936
                    }
937

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

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

954

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

959
end:
4✔
960
    sixel_similarity_destroy(&similarity, allocator);
4!
961
    sixel_allocator_free(allocator, rgb);
4✔
962
    sixel_allocator_free(allocator, gray);
4✔
963
    sixel_allocator_free(allocator, prewitt);
4✔
964
    return status;
4✔
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)
983
{
984
#if defined(_WIN32)
985
    size_t length;
986

987
    length = 0u;
988

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

993
    length = strlen(path);
994
    if (length >= 3u
995
            && path[0] == '/'
996
            && ((path[1] >= 'A' && path[1] <= 'Z')
997
                || (path[1] >= 'a' && path[1] <= 'z'))
998
            && path[2] == '/') {
999
        path[0] = path[1];
1000
        path[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(
140✔
1010
    sixel_decoder_t /* in */ *decoder,
1011
    int             /* in */ arg,
1012
    char const      /* in */ *value
1013
)
1014
{
1015
    SIXELSTATUS status = SIXEL_FALSE;
140✔
1016
    unsigned int path_flags;
140✔
1017
    int path_check;
140✔
1018
    char const *payload = NULL;
140✔
1019
    size_t length;
140✔
1020
    sixel_clipboard_spec_t clipboard_spec;
140✔
1021
    int match_index;
140✔
1022
    sixel_option_choice_result_t match_result;
140✔
1023
    char match_detail[128];
140✔
1024
    char match_message[256];
140✔
1025
    char const *filename = NULL;
140✔
1026
    char *p = NULL;
140✔
1027
    long bias;
140✔
1028
    char *endptr;
140✔
1029

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

1034
    switch(arg) {
140!
1035
    case SIXEL_OPTFLAG_INPUT:  /* i */
56✔
1036
        path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
56✔
1037
            SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
1038
            SIXEL_OPTION_PATH_ALLOW_REMOTE;
1039
        if (value != NULL) {
56!
1040
            path_check = sixel_option_validate_filesystem_path(
56✔
1041
                value,
1042
                value,
1043
                path_flags);
1044
            if (path_check != 0) {
56✔
1045
                status = SIXEL_BAD_ARGUMENT;
4✔
1046
                goto end;
4✔
1047
            }
1048
        }
1049
        decoder->clipboard_input_active = 0;
52✔
1050
        decoder->clipboard_input_format[0] = '\0';
52✔
1051
        if (value != NULL) {
52!
1052
            clipboard_spec.is_clipboard = 0;
52✔
1053
            clipboard_spec.format[0] = '\0';
52✔
1054
            if (sixel_clipboard_parse_spec(value, &clipboard_spec)
52!
UNCOV
1055
                    && clipboard_spec.is_clipboard) {
×
UNCOV
1056
                decoder_clipboard_select_format(
×
UNCOV
1057
                    decoder->clipboard_input_format,
×
1058
                    sizeof(decoder->clipboard_input_format),
1059
                    clipboard_spec.format,
1060
                    "sixel");
UNCOV
1061
                decoder->clipboard_input_active = 1;
×
1062
            }
1063
        }
1064
        free(decoder->input);
52✔
1065
        decoder->input = strdup_with_allocator(value, decoder->allocator);
52✔
1066
        if (decoder->input == NULL) {
52!
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 */
48✔
1074
        decoder->clipboard_output_active = 0;
48✔
1075
        decoder->clipboard_output_format[0] = '\0';
48✔
1076

1077
        payload = value;
48✔
1078
        if (strncmp(value, "png:", 4) == 0) {
48✔
1079
            payload = value + 4;
16✔
1080
            if (payload[0] == '\0') {
16✔
1081
                sixel_helper_set_additional_message(
4✔
1082
                    "missing target after the \"png:\" prefix.");
1083
                return SIXEL_BAD_ARGUMENT;
4✔
1084
            }
1085
            length = strlen(payload);
12✔
1086
            filename = p = malloc(length + 1U);
12✔
1087
            if (p == NULL) {
12!
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);
12✔
1093
            decoder_normalise_windows_drive_path(p);
12✔
1094
        } else {
1095
            filename = value;
1096
        }
1097

1098
        if (filename != NULL) {
12!
1099
            clipboard_spec.is_clipboard = 0;
44✔
1100
            clipboard_spec.format[0] = '\0';
44✔
1101
            if (sixel_clipboard_parse_spec(filename, &clipboard_spec)
44!
1102
                    && clipboard_spec.is_clipboard) {
4!
1103
                decoder_clipboard_select_format(
4✔
1104
                    decoder->clipboard_output_format,
4✔
1105
                    sizeof(decoder->clipboard_output_format),
1106
                    clipboard_spec.format,
1107
                    "png");
1108
                decoder->clipboard_output_active = 1;
4✔
1109
            }
1110
        }
1111
        free(decoder->output);
44✔
1112
        decoder->output = strdup_with_allocator(filename, decoder->allocator);
44✔
1113
        free(p);
44✔
1114
        if (decoder->output == NULL) {
44!
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 */
8✔
1122
        if (value == NULL) {
8!
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;
8✔
1130
        memset(match_detail, 0, sizeof(match_detail));
8✔
1131
        memset(match_message, 0, sizeof(match_message));
8✔
1132

1133
        match_result = sixel_option_match_choice(
8✔
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) {
8!
1142
            decoder->dequantize_method =
8✔
1143
                g_decoder_dequant_methods[match_index];
8✔
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;
8✔
1162

1163
    case SIXEL_OPTFLAG_SIMILARITY:  /* S */
4✔
1164
        errno = 0;
4✔
1165
        bias = strtol(value, &endptr, 10);
4✔
1166
        if (endptr == value || endptr[0] != '\0' ||
4!
1167
            errno == ERANGE || bias < 0 || bias > 1000) {
×
1168
            sixel_helper_set_additional_message(
4✔
1169
                "similarity must be an integer between 0 and 1000.");
1170
            status = SIXEL_BAD_ARGUMENT;
4✔
1171
            goto end;
4✔
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 */
20✔
1199
        decoder->direct_color = 1;
20✔
1200
        break;
20✔
1201

1202
    case SIXEL_OPTFLAG_THREADS:  /* = */
4✔
1203
        status = sixel_decoder_parallel_override_threads(value);
4✔
1204
        if (SIXEL_FAILED(status)) {
4!
1205
            goto end;
4✔
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:
136✔
1218
    sixel_decoder_unref(decoder);
136✔
1219

1220
    return status;
136✔
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(
88✔
1228
    sixel_decoder_t /* in */ *decoder)
1229
{
1230
    SIXELSTATUS status = SIXEL_FALSE;
88✔
1231
    unsigned char *raw_data = NULL;
88✔
1232
    int sx;
88✔
1233
    int sy;
88✔
1234
    int raw_len;
88✔
1235
    int max;
88✔
1236
    int n;
88✔
1237
    FILE *input_fp = NULL;
88✔
1238
    char message[2048];
88✔
1239
    unsigned char *indexed_pixels = NULL;
88✔
1240
    unsigned char *palette = NULL;
88✔
1241
    unsigned char *rgb_pixels = NULL;
88✔
1242
    unsigned char *direct_pixels = NULL;
88✔
1243
    unsigned char *output_pixels;
88✔
1244
    unsigned char *output_palette;
88✔
1245
    int output_pixelformat;
88✔
1246
    int ncolors;
88✔
1247
    sixel_frame_t *frame;
88✔
1248
    int new_width;
88✔
1249
    int new_height;
88✔
1250
    double scaled_width;
88✔
1251
    double scaled_height;
88✔
1252
    int max_dimension;
88✔
1253
    int thumbnail_size;
88✔
1254
    int frame_ncolors;
88✔
1255
    unsigned char *clipboard_blob;
88✔
1256
    size_t clipboard_blob_size;
88✔
1257
    SIXELSTATUS clipboard_status;
88✔
1258
    char *clipboard_output_path;
88✔
1259
    unsigned char *clipboard_output_data;
88✔
1260
    size_t clipboard_output_size;
88✔
1261
    SIXELSTATUS clipboard_output_status;
88✔
1262
    sixel_logger_t logger;
88✔
1263
    int logger_prepared;
88✔
1264

1265
    sixel_decoder_ref(decoder);
88✔
1266

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

1287
    raw_len = 0;
88✔
1288
    max = 0;
88✔
1289
    if (decoder->clipboard_input_active) {
88!
UNCOV
1290
        clipboard_status = sixel_clipboard_read(
×
UNCOV
1291
            decoder->clipboard_input_format,
×
1292
            &clipboard_blob,
1293
            &clipboard_blob_size,
1294
            decoder->allocator);
UNCOV
1295
        if (SIXEL_FAILED(clipboard_status)) {
×
1296
            status = clipboard_status;
×
1297
            goto end;
×
1298
        }
UNCOV
1299
        max = (int)((clipboard_blob_size > 0u)
×
1300
                    ? clipboard_blob_size
1301
                    : 1u);
UNCOV
1302
        raw_data = (unsigned char *)sixel_allocator_malloc(
×
1303
            decoder->allocator,
1304
            (size_t)max);
UNCOV
1305
        if (raw_data == NULL) {
×
1306
            sixel_helper_set_additional_message(
×
1307
                "sixel_decoder_decode: sixel_allocator_malloc() failed.");
1308
            status = SIXEL_BAD_ALLOCATION;
×
1309
            goto end;
×
1310
        }
UNCOV
1311
        if (clipboard_blob_size > 0u && clipboard_blob != NULL) {
×
UNCOV
1312
            memcpy(raw_data, clipboard_blob, clipboard_blob_size);
×
1313
        }
UNCOV
1314
        raw_len = (int)clipboard_blob_size;
×
UNCOV
1315
        if (clipboard_blob != NULL) {
×
UNCOV
1316
            free(clipboard_blob);
×
UNCOV
1317
            clipboard_blob = NULL;
×
1318
        }
1319
    } else {
1320
        if (strcmp(decoder->input, "-") == 0) {
88✔
1321
            /* for windows */
1322
#if defined(O_BINARY)
1323
            (void)sixel_compat_set_binary(STDIN_FILENO);
1324
#endif  /* defined(O_BINARY) */
1325
            input_fp = stdin;
52✔
1326
        } else {
1327
            input_fp = sixel_compat_fopen(decoder->input, "rb");
36✔
1328
            if (! input_fp) {
36!
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;
88✔
1341
        max = 64 * 1024;
88✔
1342

1343
        raw_data = (unsigned char *)sixel_allocator_malloc(
88✔
1344
            decoder->allocator,
1345
            (size_t)max);
1346
        if (raw_data == NULL) {
88!
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 (;;) {
19,328✔
1354
            if ((max - raw_len) < 4096) {
9,708✔
1355
                max *= 2;
176✔
1356
                raw_data = (unsigned char *)sixel_allocator_realloc(
176✔
1357
                    decoder->allocator,
1358
                    raw_data,
1359
                    (size_t)max);
1360
                if (raw_data == NULL) {
176!
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) {
19,416!
1368
                break;
1369
            }
1370
            raw_len += n;
9,620✔
1371
        }
1372

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

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

1387
    ncolors = 0;
84✔
1388

1389
    if (decoder->direct_color != 0) {
84✔
1390
        status = sixel_decode_direct(
16✔
1391
            raw_data,
1392
            raw_len,
1393
            &direct_pixels,
1394
            &sx,
1395
            &sy,
1396
            decoder->allocator);
1397
    } else {
1398
        status = sixel_decode_raw(
68✔
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)) {
84!
1409
        goto end;
×
1410
    }
1411

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

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

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

1492
        if (output_pixelformat == SIXEL_PIXELFORMAT_PAL8) {
4✔
1493
            frame_ncolors = ncolors;
64✔
1494
        } else {
1495
            frame_ncolors = 0;
1496
        }
1497
    }
1498

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

1580
    if (decoder->clipboard_output_active) {
84✔
1581
        clipboard_output_status = decoder_clipboard_create_spool(
4✔
1582
            decoder->allocator,
1583
            "clipboard-out",
1584
            &clipboard_output_path);
1585
        if (SIXEL_FAILED(clipboard_output_status)) {
4!
1586
            status = clipboard_output_status;
×
1587
            goto end;
×
1588
        }
1589
    }
1590

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

1651
    if (decoder->clipboard_output_active) {
84✔
1652
        clipboard_output_status = decoder_clipboard_read_file(
4✔
1653
            clipboard_output_path,
1654
            &clipboard_output_data,
1655
            &clipboard_output_size);
1656
        if (SIXEL_SUCCEEDED(clipboard_output_status)) {
4!
1657
            clipboard_output_status = sixel_clipboard_write(
4✔
1658
                decoder->clipboard_output_format,
4✔
1659
                clipboard_output_data,
1660
                clipboard_output_size);
1661
        }
1662
        if (clipboard_output_data != NULL) {
4!
1663
            free(clipboard_output_data);
4✔
1664
            clipboard_output_data = NULL;
4✔
1665
        }
1666
        if (SIXEL_FAILED(clipboard_output_status)) {
4!
1667
            status = clipboard_output_status;
4✔
1668
            goto end;
4✔
1669
        }
1670
    }
1671

1672
end:
80✔
1673
    sixel_frame_unref(frame);
88✔
1674
    sixel_allocator_free(decoder->allocator, raw_data);
88✔
1675
    sixel_allocator_free(decoder->allocator, indexed_pixels);
88✔
1676
    sixel_allocator_free(decoder->allocator, palette);
88✔
1677
    sixel_allocator_free(decoder->allocator, direct_pixels);
88✔
1678
    sixel_allocator_free(decoder->allocator, rgb_pixels);
88✔
1679
    if (clipboard_blob != NULL) {
88!
1680
        free(clipboard_blob);
×
1681
    }
1682
    if (clipboard_output_path != NULL) {
88✔
1683
        (void)sixel_compat_unlink(clipboard_output_path);
4✔
1684
        sixel_allocator_free(decoder->allocator, clipboard_output_path);
4✔
1685
    }
1686

1687
    sixel_decoder_unref(decoder);
88✔
1688
    if (logger_prepared) {
88!
1689
        sixel_logger_close(&logger);
×
1690
    }
1691

1692
    return status;
88✔
1693
}
1694

1695

1696
/* Exercise legacy constructor and refcounting for the decoder. */
1697
#if HAVE_TESTS
1698
static int
1699
decoder_test_create_legacy(void)
×
1700
{
1701
    int nret = EXIT_FAILURE;
×
1702
    sixel_decoder_t *decoder = NULL;
×
1703

1704
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1705
#  pragma GCC diagnostic push
1706
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1707
#endif
1708
    decoder = sixel_decoder_create();
×
1709
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1710
#  pragma GCC diagnostic pop
1711
#endif
1712
    if (decoder == NULL) {
×
1713
        goto error;
×
1714
    }
1715
    sixel_decoder_ref(decoder);
×
1716
    sixel_decoder_unref(decoder);
×
1717
    nret = EXIT_SUCCESS;
×
1718

UNCOV
1719
error:
×
1720
    sixel_decoder_unref(decoder);
×
1721
    return nret;
×
1722
}
1723

1724

1725
/* Build a decoder with the default allocator and ensure cleanup paths work. */
1726
static int
1727
decoder_test_new_with_default_allocator(void)
×
1728
{
1729
    int nret = EXIT_FAILURE;
×
1730
    sixel_decoder_t *decoder = NULL;
×
1731
    SIXELSTATUS status;
×
1732

1733
    status = sixel_decoder_new(&decoder, NULL);
×
1734
    if (SIXEL_FAILED(status)) {
×
1735
        goto error;
×
1736
    }
1737

1738
    sixel_decoder_ref(decoder);
×
1739
    sixel_decoder_unref(decoder);
×
1740
    nret = EXIT_SUCCESS;
×
1741

UNCOV
1742
error:
×
1743
    sixel_decoder_unref(decoder);
×
1744
    return nret;
×
1745
}
1746

1747

1748
/* Verify allocation failure during construction bubbles up. */
1749
static int
1750
decoder_test_allocator_failure_on_new(void)
×
1751
{
1752
    int nret = EXIT_FAILURE;
×
1753
    sixel_decoder_t *decoder = NULL;
×
1754
    sixel_allocator_t *allocator = NULL;
×
1755
    SIXELSTATUS status;
×
1756

1757
    sixel_debug_malloc_counter = 1;
×
1758

1759
    status = sixel_allocator_new(
×
1760
        &allocator,
1761
        sixel_bad_malloc,
1762
        NULL,
1763
        NULL,
1764
        NULL);
1765
    if (SIXEL_FAILED(status)) {
×
1766
        goto error;
×
1767
    }
1768

1769
    status = sixel_decoder_new(&decoder, allocator);
×
1770
    if (status != SIXEL_BAD_ALLOCATION) {
×
1771
        goto error;
×
1772
    }
1773

1774
    nret = EXIT_SUCCESS;
1775

UNCOV
1776
error:
×
1777
    return nret;
×
1778
}
1779

1780

1781
/* Trigger allocation failure on the second allocation path in new(). */
1782
static int
1783
decoder_test_allocator_failure_on_new_second(void)
×
1784
{
1785
    int nret = EXIT_FAILURE;
×
1786
    sixel_decoder_t *decoder = NULL;
×
1787
    sixel_allocator_t *allocator = NULL;
×
1788
    SIXELSTATUS status;
×
1789

1790
    sixel_debug_malloc_counter = 2;
×
1791

1792
    status = sixel_allocator_new(
×
1793
        &allocator,
1794
        sixel_bad_malloc,
1795
        NULL,
1796
        NULL,
1797
        NULL);
1798
    if (SIXEL_FAILED(status)) {
×
1799
        goto error;
×
1800
    }
1801

1802
    status = sixel_decoder_new(&decoder, allocator);
×
1803
    if (status != SIXEL_BAD_ALLOCATION) {
×
1804
        goto error;
×
1805
    }
1806

1807
    nret = EXIT_SUCCESS;
1808

UNCOV
1809
error:
×
1810
    return nret;
×
1811
}
1812

1813

1814
/*
1815
 * Force allocation failure when setting the input path on a decoder created
1816
 * with failing allocator hooks.
1817
 */
1818
static int
1819
decoder_test_setopt_input_allocation_failure(void)
×
1820
{
1821
    int nret = EXIT_FAILURE;
×
1822
    sixel_decoder_t *decoder = NULL;
×
1823
    sixel_allocator_t *allocator = NULL;
×
1824
    SIXELSTATUS status;
×
1825

1826
    sixel_debug_malloc_counter = 4;
×
1827

1828
    status = sixel_allocator_new(
×
1829
        &allocator,
1830
        sixel_bad_malloc,
1831
        NULL,
1832
        NULL,
1833
        NULL);
1834
    if (SIXEL_FAILED(status)) {
×
1835
        goto error;
×
1836
    }
1837
    status = sixel_decoder_new(&decoder, allocator);
×
1838
    if (SIXEL_FAILED(status)) {
×
1839
        goto error;
×
1840
    }
1841

1842
    status = sixel_decoder_setopt(
×
1843
        decoder,
1844
        SIXEL_OPTFLAG_INPUT,
1845
        "/");
1846
    if (status != SIXEL_BAD_ALLOCATION) {
×
1847
        goto error;
×
1848
    }
1849

1850
    nret = EXIT_SUCCESS;
1851

UNCOV
1852
error:
×
1853
    return nret;
×
1854
}
1855

1856

1857
/*
1858
 * Force allocation failure when setting the output path on a decoder created
1859
 * with failing allocator hooks.
1860
 */
1861
static int
1862
decoder_test_setopt_output_allocation_failure(void)
×
1863
{
1864
    int nret = EXIT_FAILURE;
×
1865
    sixel_decoder_t *decoder = NULL;
×
1866
    sixel_allocator_t *allocator = NULL;
×
1867
    SIXELSTATUS status;
×
1868

1869
    sixel_debug_malloc_counter = 4;
×
1870

1871
    status = sixel_allocator_new(
×
1872
        &allocator,
1873
        sixel_bad_malloc,
1874
        NULL,
1875
        NULL,
1876
        NULL);
1877
    if (SIXEL_FAILED(status)) {
×
1878
        goto error;
×
1879
    }
1880

1881
    status = sixel_decoder_new(&decoder, allocator);
×
1882
    if (SIXEL_FAILED(status)) {
×
1883
        goto error;
×
1884
    }
1885

1886
    status = sixel_decoder_setopt(
×
1887
        decoder,
1888
        SIXEL_OPTFLAG_OUTPUT,
1889
        "/");
1890
    if (status != SIXEL_BAD_ALLOCATION) {
×
1891
        goto error;
×
1892
    }
1893

1894
    nret = EXIT_SUCCESS;
1895

UNCOV
1896
error:
×
1897
    return nret;
×
1898
}
1899

1900

1901
/* Verify decode reports libc error when input path is invalid. */
1902
static int
1903
decoder_test_decode_missing_file(void)
×
1904
{
1905
    int nret = EXIT_FAILURE;
×
1906
    sixel_decoder_t *decoder = NULL;
×
1907
    sixel_allocator_t *allocator = NULL;
×
1908
    SIXELSTATUS status;
×
1909

1910
    status = sixel_allocator_new(
×
1911
        &allocator,
1912
        NULL,
1913
        NULL,
1914
        NULL,
1915
        NULL);
1916
    if (SIXEL_FAILED(status)) {
×
1917
        goto error;
×
1918
    }
1919

1920
    status = sixel_decoder_new(&decoder, allocator);
×
1921
    if (SIXEL_FAILED(status)) {
×
1922
        goto error;
×
1923
    }
1924

1925
    status = sixel_decoder_setopt(
×
1926
        decoder,
1927
        SIXEL_OPTFLAG_INPUT,
1928
        "../images/file");
1929
    if (SIXEL_FAILED(status)) {
×
1930
        goto error;
×
1931
    }
1932

1933
    status = sixel_decoder_decode(decoder);
×
1934
    if ((status >> 8) != (SIXEL_LIBC_ERROR >> 8)) {
×
1935
        goto error;
×
1936
    }
1937

1938
    nret = EXIT_SUCCESS;
1939

UNCOV
1940
error:
×
1941
    return nret;
×
1942
}
1943

1944

1945
/* Ensure decode surfaces allocator failures when reading a real file. */
1946
static int
1947
decoder_test_decode_allocation_failure(void)
×
1948
{
1949
    int nret = EXIT_FAILURE;
×
1950
    sixel_decoder_t *decoder = NULL;
×
1951
    sixel_allocator_t *allocator = NULL;
×
1952
    SIXELSTATUS status;
×
1953

1954
    sixel_debug_malloc_counter = 5;
×
1955

1956
    status = sixel_allocator_new(
×
1957
        &allocator,
1958
        sixel_bad_malloc,
1959
        NULL,
1960
        NULL,
1961
        NULL);
1962
    if (SIXEL_FAILED(status)) {
×
1963
        goto error;
×
1964
    }
1965

1966
    status = sixel_decoder_new(&decoder, allocator);
×
1967
    if (SIXEL_FAILED(status)) {
×
1968
        goto error;
×
1969
    }
1970

1971
    status = sixel_decoder_setopt(
×
1972
        decoder,
1973
        SIXEL_OPTFLAG_INPUT,
1974
        "../images/map8.six");
1975
    if (SIXEL_FAILED(status)) {
×
1976
        goto error;
×
1977
    }
1978

1979
    status = sixel_decoder_decode(decoder);
×
1980
    if (status != SIXEL_BAD_ALLOCATION) {
×
1981
        goto error;
×
1982
    }
1983

1984
    nret = EXIT_SUCCESS;
1985

UNCOV
1986
error:
×
1987
    return nret;
×
1988
}
1989

1990

1991
SIXELAPI int
1992
sixel_decoder_tests_main(void)
×
1993
{
1994
    int nret = EXIT_FAILURE;
×
1995
    size_t i;
×
UNCOV
1996
    typedef int (* testcase)(void);
×
1997

UNCOV
1998
    static testcase const testcases[] = {
×
1999
        decoder_test_create_legacy,
2000
        decoder_test_new_with_default_allocator,
2001
        decoder_test_allocator_failure_on_new,
2002
        decoder_test_allocator_failure_on_new_second,
2003
        decoder_test_setopt_input_allocation_failure,
2004
        decoder_test_setopt_output_allocation_failure,
2005
        decoder_test_decode_missing_file,
2006
        decoder_test_decode_allocation_failure
2007
    };
2008

2009
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
2010
        nret = testcases[i]();
×
2011
        if (nret != EXIT_SUCCESS) {
×
2012
            goto error;
×
2013
        }
2014
    }
2015

2016
    nret = EXIT_SUCCESS;
2017

UNCOV
2018
error:
×
2019
    return nret;
×
2020
}
2021
#endif  /* HAVE_TESTS */
2022

2023
/* emacs Local Variables:      */
2024
/* emacs mode: c               */
2025
/* emacs tab-width: 4          */
2026
/* emacs indent-tabs-mode: nil */
2027
/* emacs c-basic-offset: 4     */
2028
/* emacs End:                  */
2029
/* vim: set expandtab ts=4 sts=4 sw=4 : */
2030
/* 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

© 2025 Coveralls, Inc