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

saitoha / libsixel / 19934796867

04 Dec 2025 03:42PM UTC coverage: 43.522% (+2.3%) from 41.258%
19934796867

push

github

saitoha
python: update shared api.py

10714 of 38654 branches covered (27.72%)

14673 of 33714 relevant lines covered (43.52%)

2910517.57 hits per line

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

59.91
/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,
3✔
61
                                size_t dest_size,
62
                                char const *format,
63
                                char const *fallback)
64
{
65
    char const *source;
3✔
66
    size_t limit;
3✔
67

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

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

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

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

86

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

101
    tmpdir = sixel_compat_getenv("TMPDIR");
3✔
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') {
3!
111
#if defined(_WIN32)
112
        tmpdir = ".";
113
#else
114
        tmpdir = "/tmp";
3✔
115
#endif
116
    }
117

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

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

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

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

143
    if (needs_separator) {
3!
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,
×
153
                       "%s%s-XXXXXX", tmpdir, prefix);
154
    }
155

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

160
    return template_path;
161
}
162

163

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

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

185
    template_path = decoder_create_temp_template_with_prefix(allocator,
3✔
186
                                                             prefix,
187
                                                             &template_capacity);
188
    if (template_path == NULL) {
3!
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) {
3!
196
#if defined(HAVE_TMPNAM)
197

198
# if defined(__clang__)
199
#  if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
200
#   pragma clang diagnostic push
201
#   pragma clang diagnostic ignored "-Wdeprecated-declarations"
202
#  endif
203
# endif
204
        /* Fall back to tmpnam() when mktemp variants are unavailable. */
205
        tmpname_result = tmpnam(template_path);
×
206
# if defined(__clang__)
207
#  if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
208
#   pragma clang diagnostic pop
209
#  endif
210
# endif
211

212
#endif  /* HAVE_TMPNAM */
213
        if (tmpname_result == NULL) {
×
214
            sixel_helper_set_additional_message(
×
215
                "clipboard: failed to reserve spool template.");
216
            status = SIXEL_LIBC_ERROR;
×
217
            goto end;
×
218
        }
219
        template_capacity = strlen(template_path) + 1u;
×
220
    }
221

222
    open_flags = O_RDWR | O_CREAT | O_TRUNC;
3✔
223
#if defined(O_EXCL)
224
    open_flags |= O_EXCL;
3✔
225
#endif
226
    open_mode = S_IRUSR | S_IWUSR;
3✔
227
    fd = sixel_compat_open(template_path, open_flags, open_mode);
3✔
228
    if (fd < 0) {
3!
229
        sixel_helper_set_additional_message(
×
230
            "clipboard: failed to open spool file.");
231
        status = SIXEL_LIBC_ERROR;
×
232
        goto end;
×
233
    }
234

235
    *path_out = template_path;
3✔
236
    if (fd >= 0) {
3!
237
        (void)sixel_compat_close(fd);
3✔
238
        fd = (-1);
3✔
239
    }
240

241
    template_path = NULL;
3✔
242
    status = SIXEL_OK;
3✔
243

244
end:
×
245
    if (fd >= 0) {
3!
246
        (void)sixel_compat_close(fd);
247
    }
248
    if (template_path != NULL) {
3!
249
        sixel_allocator_free(allocator, template_path);
×
250
    }
251

252
    return status;
3✔
253
}
254

255

256
static SIXELSTATUS
257
decoder_clipboard_read_file(char const *path,
3✔
258
                            unsigned char **data,
259
                            size_t *size)
260
{
261
    FILE *stream;
3✔
262
    long seek_result;
3✔
263
    long file_size;
3✔
264
    unsigned char *buffer;
3✔
265
    size_t read_size;
3✔
266

267
    if (data == NULL || size == NULL) {
3!
268
        sixel_helper_set_additional_message(
×
269
            "clipboard: read buffer pointers are null.");
270
        return SIXEL_BAD_ARGUMENT;
×
271
    }
272

273
    *data = NULL;
3✔
274
    *size = 0u;
3✔
275

276
    if (path == NULL) {
3!
277
        sixel_helper_set_additional_message(
×
278
            "clipboard: spool path is null.");
279
        return SIXEL_BAD_ARGUMENT;
×
280
    }
281

282
    stream = sixel_compat_fopen(path, "rb");
3✔
283
    if (stream == NULL) {
3!
284
        sixel_helper_set_additional_message(
×
285
            "clipboard: failed to open spool file for read.");
286
        return SIXEL_LIBC_ERROR;
×
287
    }
288

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

297
    file_size = ftell(stream);
3✔
298
    if (file_size < 0) {
3!
299
        (void)fclose(stream);
×
300
        sixel_helper_set_additional_message(
×
301
            "clipboard: failed to determine spool size.");
302
        return SIXEL_LIBC_ERROR;
×
303
    }
304

305
    seek_result = fseek(stream, 0L, SEEK_SET);
3✔
306
    if (seek_result != 0) {
3!
307
        (void)fclose(stream);
×
308
        sixel_helper_set_additional_message(
×
309
            "clipboard: failed to rewind spool file.");
310
        return SIXEL_LIBC_ERROR;
×
311
    }
312

313
    if (file_size == 0) {
3!
314
        buffer = NULL;
315
        read_size = 0u;
316
    } else {
317
        buffer = (unsigned char *)malloc((size_t)file_size);
3✔
318
        if (buffer == NULL) {
3!
319
            (void)fclose(stream);
×
320
            sixel_helper_set_additional_message(
×
321
                "clipboard: malloc() failed for spool payload.");
322
            return SIXEL_BAD_ALLOCATION;
×
323
        }
324
        read_size = fread(buffer, 1u, (size_t)file_size, stream);
3!
325
        if (read_size != (size_t)file_size) {
3!
326
            free(buffer);
×
327
            (void)fclose(stream);
×
328
            sixel_helper_set_additional_message(
×
329
                "clipboard: failed to read spool payload.");
330
            return SIXEL_LIBC_ERROR;
×
331
        }
332
    }
333

334
    if (fclose(stream) != 0) {
3!
335
        if (buffer != NULL) {
×
336
            free(buffer);
×
337
        }
338
        sixel_helper_set_additional_message(
×
339
            "clipboard: failed to close spool file after read.");
340
        return SIXEL_LIBC_ERROR;
×
341
    }
342

343
    *data = buffer;
3✔
344
    *size = read_size;
3✔
345

346
    return SIXEL_OK;
3✔
347
}
348

349

350
/* original version of strdup(3) with allocator object */
351
static char *
352
strdup_with_allocator(
180✔
353
    char const          /* in */ *s,          /* source buffer */
354
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
355
                                                 destination buffer */
356
{
357
    char *p;
180✔
358

359
    p = (char *)sixel_allocator_malloc(allocator, (size_t)(strlen(s) + 1));
180✔
360
    if (p) {
180!
361
        (void)sixel_compat_strcpy(p, strlen(s) + 1, s);
180✔
362
    }
363
    return p;
180✔
364
}
365

366

367
/* create decoder object */
368
SIXELAPI SIXELSTATUS
369
sixel_decoder_new(
69✔
370
    sixel_decoder_t    /* out */ **ppdecoder,  /* decoder object to be created */
371
    sixel_allocator_t  /* in */  *allocator)   /* allocator, null if you use
372
                                                  default allocator */
373
{
374
    SIXELSTATUS status = SIXEL_FALSE;
69✔
375

376
    if (allocator == NULL) {
69!
377
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
69✔
378
        if (SIXEL_FAILED(status)) {
69!
379
            goto end;
×
380
        }
381
    } else {
382
        sixel_allocator_ref(allocator);
×
383
    }
384

385
    *ppdecoder = sixel_allocator_malloc(allocator, sizeof(sixel_decoder_t));
69✔
386
    if (*ppdecoder == NULL) {
69!
387
        sixel_allocator_unref(allocator);
×
388
        sixel_helper_set_additional_message(
×
389
            "sixel_decoder_new: sixel_allocator_malloc() failed.");
390
        status = SIXEL_BAD_ALLOCATION;
×
391
        goto end;
×
392
    }
393

394
    (*ppdecoder)->ref          = 1;
69✔
395
    (*ppdecoder)->output       = strdup_with_allocator("-", allocator);
69✔
396
    (*ppdecoder)->input        = strdup_with_allocator("-", allocator);
69✔
397
    (*ppdecoder)->allocator    = allocator;
69✔
398
    (*ppdecoder)->dequantize_method = SIXEL_DEQUANTIZE_NONE;
69✔
399
    (*ppdecoder)->dequantize_similarity_bias = 100;
69✔
400
    (*ppdecoder)->dequantize_edge_strength = 0;
69✔
401
    (*ppdecoder)->thumbnail_size = 0;
69✔
402
    (*ppdecoder)->direct_color = 0;
69✔
403
    (*ppdecoder)->clipboard_input_active = 0;
69✔
404
    (*ppdecoder)->clipboard_output_active = 0;
69✔
405
    (*ppdecoder)->clipboard_input_format[0] = '\0';
69✔
406
    (*ppdecoder)->clipboard_output_format[0] = '\0';
69✔
407

408
    if ((*ppdecoder)->output == NULL || (*ppdecoder)->input == NULL) {
69!
409
        sixel_decoder_unref(*ppdecoder);
×
410
        *ppdecoder = NULL;
×
411
        sixel_helper_set_additional_message(
×
412
            "sixel_decoder_new: strdup_with_allocator() failed.");
413
        status = SIXEL_BAD_ALLOCATION;
×
414
        sixel_allocator_unref(allocator);
×
415
        goto end;
×
416
    }
417

418
    status = SIXEL_OK;
419

420
end:
69✔
421
    return status;
69✔
422
}
423

424

425
/* deprecated version of sixel_decoder_new() */
426
SIXELAPI /* deprecated */ sixel_decoder_t *
427
sixel_decoder_create(void)
×
428
{
429
    SIXELSTATUS status = SIXEL_FALSE;
×
430
    sixel_decoder_t *decoder = NULL;
×
431

432
    status = sixel_decoder_new(&decoder, NULL);
×
433
    if (SIXEL_FAILED(status)) {
×
434
        goto end;
435
    }
436

437
end:
×
438
    return decoder;
×
439
}
440

441

442
/* destroy a decoder object */
443
static void
444
sixel_decoder_destroy(sixel_decoder_t *decoder)
66✔
445
{
446
    sixel_allocator_t *allocator;
66✔
447

448
    if (decoder) {
66!
449
        allocator = decoder->allocator;
66✔
450
        sixel_allocator_free(allocator, decoder->input);
66✔
451
        sixel_allocator_free(allocator, decoder->output);
66✔
452
        sixel_allocator_free(allocator, decoder);
66✔
453
        sixel_allocator_unref(allocator);
66✔
454
    }
455
}
66✔
456

457

458
/* increase reference count of decoder object (thread-unsafe) */
459
SIXELAPI void
460
sixel_decoder_ref(sixel_decoder_t *decoder)
126✔
461
{
462
    /* TODO: be thread safe */
463
    ++decoder->ref;
126✔
464
}
84✔
465

466

467
/* decrease reference count of decoder object (thread-unsafe) */
468
SIXELAPI void
469
sixel_decoder_unref(sixel_decoder_t *decoder)
192✔
470
{
471
    /* TODO: be thread safe */
472
    if (decoder != NULL && --decoder->ref == 0) {
192!
473
        sixel_decoder_destroy(decoder);
66✔
474
    }
475
}
192✔
476

477

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

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

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

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

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

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

524
    return SIXEL_OK;
525
}
526

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

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

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

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

575
    if (index1 < 0 || index1 >= similarity->ncolors ||
6,461,112!
576
        index2 < 0 || index2 >= similarity->ncolors) {
6,461,112!
577
        return 0;
578
    }
579

580
    if (index1 <= index2) {
6,461,112✔
581
        min_index = index1;
582
        max_index = index2;
583
    } else {
584
        min_index = index2;
1,946,895✔
585
        max_index = index1;
1,946,895✔
586
    }
587

588
    cache_pos = (size_t)min_index * (size_t)similarity->stride
6,461,112✔
589
              + (size_t)max_index;
6,461,112✔
590
    cached = similarity->cache[cache_pos];
6,461,112✔
591
    if (cached >= 0) {
6,461,112✔
592
        return (unsigned int)cached;
6,451,431✔
593
    }
594

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

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

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

645
    for (i = 0; i < similarity->ncolors; ++i) {
1,626,408✔
646
        if (i == index1 || i == index2) {
1,616,727✔
647
            continue;
19,362✔
648
        }
649
        pk = palette + i * 3;
1,597,365✔
650
        diff = sixel_similarity_diff(avg_color, pk);
1,597,365✔
651
        if (diff < min_diff) {
1,597,365✔
652
            min_diff = diff;
1,616,727✔
653
        }
654
    }
655

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

660
    if (min_diff >= base_distance * 2U) {
9,681✔
661
        result = 5U;
662
    } else if (min_diff >= base_distance) {
9,165✔
663
        result = 8U;
664
    } else if ((unsigned long long)min_diff * 6ULL
8,544✔
665
               >= (unsigned long long)base_distance * 5ULL) {
8,544✔
666
        result = 7U;
667
    } else if ((unsigned long long)min_diff * 4ULL
8,322✔
668
               >= (unsigned long long)base_distance * 3ULL) {
8,322✔
669
        result = 7U;
670
    } else if ((unsigned long long)min_diff * 3ULL
8,172✔
671
               >= (unsigned long long)base_distance * 2ULL) {
8,172✔
672
        result = 5U;
673
    } else if ((unsigned long long)min_diff * 5ULL
7,980✔
674
               >= (unsigned long long)base_distance * 3ULL) {
675
        result = 7U;
676
    } else if ((unsigned long long)min_diff * 2ULL
7,791✔
677
               >= (unsigned long long)base_distance * 1ULL) {
678
        result = 4U;
679
    } else if ((unsigned long long)min_diff * 3ULL
7,437✔
680
               >= (unsigned long long)base_distance * 1ULL) {
681
        result = 2U;
682
    } else {
683
        result = 0U;
6,462✔
684
    }
685

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

688
    return result;
9,681✔
689
}
690

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

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

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

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

744
    if (percent <= 0) {
6!
745
        percent = 1;
746
    }
747

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

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

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

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

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

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

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

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

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

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

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

870
    for (y = 0; y < height; ++y) {
1,353✔
871
        for (x = 0; x < width; ++x) {
811,350✔
872
            palette_index = indexed_pixels[y * width + x];
810,000✔
873
            if (palette_index < 0 || palette_index >= ncolors) {
810,000!
874
                palette_index = 0;
×
875
            }
876

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

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

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

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

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

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

929
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
6,480,000✔
930
                    continue;
18,888✔
931
                }
932

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

938
                if (numerator) {
6,461,112!
939
                    weight = sixel_similarity_compare(
6,461,112✔
940
                        &similarity,
941
                        palette_index,
942
                        neighbor_index,
943
                        numerator,
944
                        denominator);
945
                    if (weight == 0) {
6,461,112✔
946
                        continue;
351,150✔
947
                    }
948

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

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

965

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

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

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

992
static void
993
normalise_windows_drive_path(char *path)
994
{
995
#if defined(_WIN32)
996
    size_t length;
997

998
    length = 0u;
999

1000
    if (path == NULL) {
1001
        return;
1002
    }
1003

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

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

1039
    sixel_decoder_ref(decoder);
72✔
1040
    path_flags = 0u;
72✔
1041
    path_check = 0;
72✔
1042

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

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

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

1138
        match_index = 0;
6✔
1139
        memset(match_detail, 0, sizeof(match_detail));
6✔
1140
        memset(match_message, 0, sizeof(match_message));
6✔
1141

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

1172
    case SIXEL_OPTFLAG_SIMILARITY:  /* S */
1173
        decoder->dequantize_similarity_bias = atoi(value);
×
1174
        if (decoder->dequantize_similarity_bias < 0 ||
×
1175
            decoder->dequantize_similarity_bias > 1000) {
1176
            sixel_helper_set_additional_message(
×
1177
                "similarity must be between 1 and 1000.");
1178
            status = SIXEL_BAD_ARGUMENT;
×
1179
            goto end;
×
1180
        }
1181
        break;
1182

1183
    case SIXEL_OPTFLAG_SIZE:  /* s */
1184
        decoder->thumbnail_size = atoi(value);
×
1185
        if (decoder->thumbnail_size <= 0) {
×
1186
            sixel_helper_set_additional_message(
×
1187
                "size must be greater than zero.");
1188
            status = SIXEL_BAD_ARGUMENT;
×
1189
            goto end;
×
1190
        }
1191
        break;
1192

1193
    case SIXEL_OPTFLAG_EDGE:  /* e */
1194
        decoder->dequantize_edge_strength = atoi(value);
×
1195
        if (decoder->dequantize_edge_strength < 0 ||
×
1196
            decoder->dequantize_edge_strength > 1000) {
1197
            sixel_helper_set_additional_message(
×
1198
                "edge bias must be between 1 and 1000.");
1199
            status = SIXEL_BAD_ARGUMENT;
×
1200
            goto end;
×
1201
        }
1202
        break;
1203

1204
    case SIXEL_OPTFLAG_DIRECT:  /* D */
15✔
1205
        decoder->direct_color = 1;
15✔
1206
        break;
15✔
1207

1208
    case SIXEL_OPTFLAG_THREADS:  /* = */
3✔
1209
        status = sixel_decoder_parallel_override_threads(value);
3✔
1210
        if (SIXEL_FAILED(status)) {
3!
1211
            goto end;
3✔
1212
        }
1213
        break;
1214

1215
    case '?':
×
1216
    default:
1217
        status = SIXEL_BAD_ARGUMENT;
×
1218
        goto end;
×
1219
    }
1220

1221
    status = SIXEL_OK;
1222

1223
end:
69✔
1224
    sixel_decoder_unref(decoder);
69✔
1225

1226
    return status;
69✔
1227
}
1228

1229

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

1271
    sixel_decoder_ref(decoder);
54✔
1272

1273
    frame = NULL;
54✔
1274
    new_width = 0;
54✔
1275
    new_height = 0;
54✔
1276
    scaled_width = 0.0;
54✔
1277
    scaled_height = 0.0;
54✔
1278
    max_dimension = 0;
54✔
1279
    thumbnail_size = decoder->thumbnail_size;
54✔
1280
    frame_ncolors = -1;
54✔
1281
    clipboard_blob = NULL;
54✔
1282
    clipboard_blob_size = 0u;
54✔
1283
    clipboard_status = SIXEL_OK;
54✔
1284
    clipboard_output_path = NULL;
54✔
1285
    clipboard_output_data = NULL;
54✔
1286
    clipboard_output_size = 0u;
54✔
1287
    clipboard_output_status = SIXEL_OK;
54✔
1288
    input_fp = NULL;
54✔
1289
    sixel_logger_init(&logger);
54✔
1290
    (void)sixel_logger_prepare_env(&logger);
54✔
1291
    logger_prepared = logger.active;
54✔
1292

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

1350
        raw_len = 0;
54✔
1351
        max = 64 * 1024;
54✔
1352

1353
        raw_data = (unsigned char *)sixel_allocator_malloc(
54✔
1354
            decoder->allocator,
1355
            (size_t)max);
1356
        if (raw_data == NULL) {
54!
1357
            sixel_helper_set_additional_message(
×
1358
                "sixel_decoder_decode: sixel_allocator_malloc() failed.");
1359
            status = SIXEL_BAD_ALLOCATION;
×
1360
            goto end;
×
1361
        }
1362

1363
        for (;;) {
12,666✔
1364
            if ((max - raw_len) < 4096) {
6,360✔
1365
                max *= 2;
111✔
1366
                raw_data = (unsigned char *)sixel_allocator_realloc(
111✔
1367
                    decoder->allocator,
1368
                    raw_data,
1369
                    (size_t)max);
1370
                if (raw_data == NULL) {
111!
1371
                    sixel_helper_set_additional_message(
×
1372
                        "sixel_decoder_decode: sixel_allocator_realloc() failed.");
1373
                    status = SIXEL_BAD_ALLOCATION;
×
1374
                    goto end;
×
1375
                }
1376
            }
1377
            if ((n = (int)fread(raw_data + raw_len, 1, 4096, input_fp)) <= 0) {
12,720!
1378
                break;
1379
            }
1380
            raw_len += n;
6,306✔
1381
        }
1382

1383
        if (input_fp != NULL && input_fp != stdin) {
54!
1384
            fclose(input_fp);
15✔
1385
        }
1386
    }
1387

1388
    if (decoder->direct_color != 0 &&
54✔
1389
            decoder->dequantize_method != SIXEL_DEQUANTIZE_NONE) {
15✔
1390
        sixel_helper_set_additional_message(
3✔
1391
            "sixel_decoder_decode: direct option "
1392
            "cannot be combined with dequantize option.");
1393
        status = SIXEL_BAD_ARGUMENT;
3✔
1394
        goto end;
3✔
1395
    }
1396

1397
    ncolors = 0;
51✔
1398

1399
    if (decoder->direct_color != 0) {
51✔
1400
        status = sixel_decode_direct(
12✔
1401
            raw_data,
1402
            raw_len,
1403
            &direct_pixels,
1404
            &sx,
1405
            &sy,
1406
            decoder->allocator);
1407
    } else {
1408
        status = sixel_decode_raw(
39✔
1409
            raw_data,
1410
            raw_len,
1411
            &indexed_pixels,
1412
            &sx,
1413
            &sy,
1414
            &palette,
1415
            &ncolors,
1416
            decoder->allocator);
1417
    }
1418
    if (SIXEL_FAILED(status)) {
51!
1419
        goto end;
×
1420
    }
1421

1422
    if (sx > SIXEL_WIDTH_LIMIT || sy > SIXEL_HEIGHT_LIMIT) {
51!
1423
        status = SIXEL_BAD_INPUT;
×
1424
        goto end;
×
1425
    }
1426

1427
    if (decoder->direct_color != 0) {
51✔
1428
        output_pixels = direct_pixels;
12✔
1429
        output_palette = NULL;
12✔
1430
        output_pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
12✔
1431
        frame_ncolors = 0;
12✔
1432
    } else {
1433
        output_pixels = indexed_pixels;
39✔
1434
        output_palette = palette;
39✔
1435
        output_pixelformat = SIXEL_PIXELFORMAT_PAL8;
39✔
1436

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

1502
        if (output_pixelformat == SIXEL_PIXELFORMAT_PAL8) {
3✔
1503
            frame_ncolors = ncolors;
36✔
1504
        } else {
1505
            frame_ncolors = 0;
1506
        }
1507
    }
1508

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

1590
    if (decoder->clipboard_output_active) {
51✔
1591
        clipboard_output_status = decoder_clipboard_create_spool(
3✔
1592
            decoder->allocator,
1593
            "clipboard-out",
1594
            &clipboard_output_path);
1595
        if (SIXEL_FAILED(clipboard_output_status)) {
3!
1596
            status = clipboard_output_status;
×
1597
            goto end;
×
1598
        }
1599
    }
1600

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

1661
    if (decoder->clipboard_output_active) {
51✔
1662
        clipboard_output_status = decoder_clipboard_read_file(
3✔
1663
            clipboard_output_path,
1664
            &clipboard_output_data,
1665
            &clipboard_output_size);
1666
        if (SIXEL_SUCCEEDED(clipboard_output_status)) {
3!
1667
            clipboard_output_status = sixel_clipboard_write(
3✔
1668
                decoder->clipboard_output_format,
3✔
1669
                clipboard_output_data,
1670
                clipboard_output_size);
1671
        }
1672
        if (clipboard_output_data != NULL) {
3!
1673
            free(clipboard_output_data);
3✔
1674
            clipboard_output_data = NULL;
3✔
1675
        }
1676
        if (SIXEL_FAILED(clipboard_output_status)) {
3!
1677
            status = clipboard_output_status;
3✔
1678
            goto end;
3✔
1679
        }
1680
    }
1681

1682
end:
48✔
1683
    sixel_frame_unref(frame);
54✔
1684
    sixel_allocator_free(decoder->allocator, raw_data);
54✔
1685
    sixel_allocator_free(decoder->allocator, indexed_pixels);
54✔
1686
    sixel_allocator_free(decoder->allocator, palette);
54✔
1687
    sixel_allocator_free(decoder->allocator, direct_pixels);
54✔
1688
    sixel_allocator_free(decoder->allocator, rgb_pixels);
54✔
1689
    if (clipboard_blob != NULL) {
54!
1690
        free(clipboard_blob);
×
1691
    }
1692
    if (clipboard_output_path != NULL) {
54✔
1693
        (void)sixel_compat_unlink(clipboard_output_path);
3✔
1694
        sixel_allocator_free(decoder->allocator, clipboard_output_path);
3✔
1695
    }
1696

1697
    sixel_decoder_unref(decoder);
54✔
1698
    if (logger_prepared) {
54!
1699
        sixel_logger_close(&logger);
×
1700
    }
1701

1702
    return status;
54✔
1703
}
1704

1705

1706
#if HAVE_TESTS
1707
static int
1708
test1(void)
×
1709
{
1710
    int nret = EXIT_FAILURE;
×
1711
    sixel_decoder_t *decoder = NULL;
×
1712

1713
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1714
#  pragma GCC diagnostic push
1715
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1716
#endif
1717
    decoder = sixel_decoder_create();
×
1718
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1719
#  pragma GCC diagnostic pop
1720
#endif
1721
    if (decoder == NULL) {
×
1722
        goto error;
×
1723
    }
1724
    sixel_decoder_ref(decoder);
×
1725
    sixel_decoder_unref(decoder);
×
1726
    nret = EXIT_SUCCESS;
×
1727

1728
error:
×
1729
    sixel_decoder_unref(decoder);
×
1730
    return nret;
×
1731
}
1732

1733

1734
static int
1735
test2(void)
×
1736
{
1737
    int nret = EXIT_FAILURE;
×
1738
    sixel_decoder_t *decoder = NULL;
×
1739
    SIXELSTATUS status;
×
1740

1741
    status = sixel_decoder_new(&decoder, NULL);
×
1742
    if (SIXEL_FAILED(status)) {
×
1743
        goto error;
×
1744
    }
1745

1746
    sixel_decoder_ref(decoder);
×
1747
    sixel_decoder_unref(decoder);
×
1748
    nret = EXIT_SUCCESS;
×
1749

1750
error:
×
1751
    sixel_decoder_unref(decoder);
×
1752
    return nret;
×
1753
}
1754

1755

1756
static int
1757
test3(void)
×
1758
{
1759
    int nret = EXIT_FAILURE;
×
1760
    sixel_decoder_t *decoder = NULL;
×
1761
    sixel_allocator_t *allocator = NULL;
×
1762
    SIXELSTATUS status;
×
1763

1764
    sixel_debug_malloc_counter = 1;
×
1765

1766
    status = sixel_allocator_new(
×
1767
        &allocator,
1768
        sixel_bad_malloc,
1769
        NULL,
1770
        NULL,
1771
        NULL);
1772
    if (SIXEL_FAILED(status)) {
×
1773
        goto error;
×
1774
    }
1775

1776
    status = sixel_decoder_new(&decoder, allocator);
×
1777
    if (status != SIXEL_BAD_ALLOCATION) {
×
1778
        goto error;
×
1779
    }
1780

1781
    nret = EXIT_SUCCESS;
1782

1783
error:
×
1784
    return nret;
×
1785
}
1786

1787

1788
static int
1789
test4(void)
×
1790
{
1791
    int nret = EXIT_FAILURE;
×
1792
    sixel_decoder_t *decoder = NULL;
×
1793
    sixel_allocator_t *allocator = NULL;
×
1794
    SIXELSTATUS status;
×
1795

1796
    sixel_debug_malloc_counter = 2;
×
1797

1798
    status = sixel_allocator_new(
×
1799
        &allocator,
1800
        sixel_bad_malloc,
1801
        NULL,
1802
        NULL,
1803
        NULL);
1804
    if (SIXEL_FAILED(status)) {
×
1805
        goto error;
×
1806
    }
1807

1808
    status = sixel_decoder_new(&decoder, allocator);
×
1809
    if (status != SIXEL_BAD_ALLOCATION) {
×
1810
        goto error;
×
1811
    }
1812

1813
    nret = EXIT_SUCCESS;
1814

1815
error:
×
1816
    return nret;
×
1817
}
1818

1819

1820
static int
1821
test5(void)
×
1822
{
1823
    int nret = EXIT_FAILURE;
×
1824
    sixel_decoder_t *decoder = NULL;
×
1825
    sixel_allocator_t *allocator = NULL;
×
1826
    SIXELSTATUS status;
×
1827

1828
    sixel_debug_malloc_counter = 4;
×
1829

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

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

1852
    nret = EXIT_SUCCESS;
1853

1854
error:
×
1855
    return nret;
×
1856
}
1857

1858

1859
static int
1860
test6(void)
×
1861
{
1862
    int nret = EXIT_FAILURE;
×
1863
    sixel_decoder_t *decoder = NULL;
×
1864
    sixel_allocator_t *allocator = NULL;
×
1865
    SIXELSTATUS status;
×
1866

1867
    sixel_debug_malloc_counter = 4;
×
1868

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

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

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

1892
    nret = EXIT_SUCCESS;
1893

1894
error:
×
1895
    return nret;
×
1896
}
1897

1898

1899
static int
1900
test7(void)
×
1901
{
1902
    int nret = EXIT_FAILURE;
×
1903
    sixel_decoder_t *decoder = NULL;
×
1904
    sixel_allocator_t *allocator = NULL;
×
1905
    SIXELSTATUS status;
×
1906

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

1917
    status = sixel_decoder_new(&decoder, allocator);
×
1918
    if (SIXEL_FAILED(status)) {
×
1919
        goto error;
×
1920
    }
1921

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

1930
    status = sixel_decoder_decode(decoder);
×
1931
    if ((status >> 8) != (SIXEL_LIBC_ERROR >> 8)) {
×
1932
        goto error;
×
1933
    }
1934

1935
    nret = EXIT_SUCCESS;
1936

1937
error:
×
1938
    return nret;
×
1939
}
1940

1941

1942
static int
1943
test8(void)
×
1944
{
1945
    int nret = EXIT_FAILURE;
×
1946
    sixel_decoder_t *decoder = NULL;
×
1947
    sixel_allocator_t *allocator = NULL;
×
1948
    SIXELSTATUS status;
×
1949

1950
    sixel_debug_malloc_counter = 5;
×
1951

1952
    status = sixel_allocator_new(
×
1953
        &allocator,
1954
        sixel_bad_malloc,
1955
        NULL,
1956
        NULL,
1957
        NULL);
1958
    if (SIXEL_FAILED(status)) {
×
1959
        goto error;
×
1960
    }
1961

1962
    status = sixel_decoder_new(&decoder, allocator);
×
1963
    if (SIXEL_FAILED(status)) {
×
1964
        goto error;
×
1965
    }
1966

1967
    status = sixel_decoder_setopt(
×
1968
        decoder,
1969
        SIXEL_OPTFLAG_INPUT,
1970
        "../images/map8.six");
1971
    if (SIXEL_FAILED(status)) {
×
1972
        goto error;
×
1973
    }
1974

1975
    status = sixel_decoder_decode(decoder);
×
1976
    if (status != SIXEL_BAD_ALLOCATION) {
×
1977
        goto error;
×
1978
    }
1979

1980
    nret = EXIT_SUCCESS;
1981

1982
error:
×
1983
    return nret;
×
1984
}
1985

1986

1987
SIXELAPI int
1988
sixel_decoder_tests_main(void)
×
1989
{
1990
    int nret = EXIT_FAILURE;
×
1991
    size_t i;
×
1992
    typedef int (* testcase)(void);
×
1993

1994
    static testcase const testcases[] = {
×
1995
        test1,
1996
        test2,
1997
        test3,
1998
        test4,
1999
        test5,
2000
        test6,
2001
        test7,
2002
        test8
2003
    };
2004

2005
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
2006
        nret = testcases[i]();
×
2007
        if (nret != EXIT_SUCCESS) {
×
2008
            goto error;
×
2009
        }
2010
    }
2011

2012
    nret = EXIT_SUCCESS;
2013

2014
error:
×
2015
    return nret;
×
2016
}
2017
#endif  /* HAVE_TESTS */
2018

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