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

saitoha / libsixel / 19460009904

18 Nov 2025 08:54AM UTC coverage: 40.242%. First build
19460009904

push

github

saitoha
fix: prevent -Werror=discarded-qualifiers

8912 of 31653 branches covered (28.16%)

32 of 35 new or added lines in 1 file covered. (91.43%)

11794 of 29308 relevant lines covered (40.24%)

675628.52 hits per line

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

33.77
/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 "clipboard.h"
55
#include "compat_stub.h"
56
#include "options.h"
57

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

67
    if (dest == NULL || dest_size == 0u) {
4!
68
        return;
×
69
    }
70

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

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

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

85

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

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

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

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

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

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

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

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

159
    return template_path;
3✔
160
}
1✔
161

162

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

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

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

194
    if (sixel_compat_mktemp(template_path, template_capacity) != 0) {
3!
195
#if defined(HAVE_TMPNAM)
196

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

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

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

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

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

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

251
    return status;
3✔
252
}
253

254

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

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

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

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

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

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

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

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

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

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

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

345
    return SIXEL_OK;
3✔
346
}
1✔
347

348

349
static float sixel_srgb_to_linear_lut[256];
350
static unsigned char sixel_linear_to_srgb_lut[256];
351
static int sixel_color_lut_initialized = 0;
352

353
static const float sixel_gaussian3x3_kernel[9] = {
354
    0.0625f, 0.1250f, 0.0625f,
355
    0.1250f, 0.2500f, 0.1250f,
356
    0.0625f, 0.1250f, 0.0625f
357
};
358

359
static const float sixel_weak_sharpen3x3_kernel[9] = {
360
    -0.0625f, -0.0625f, -0.0625f,
361
    -0.0625f,  1.5000f, -0.0625f,
362
    -0.0625f, -0.0625f, -0.0625f
363
};
364

365
static const float sixel_sobel_gx_kernel[9] = {
366
    -1.0f, 0.0f, 1.0f,
367
    -2.0f, 0.0f, 2.0f,
368
    -1.0f, 0.0f, 1.0f
369
};
370

371
static const float sixel_sobel_gy_kernel[9] = {
372
    -1.0f, -2.0f, -1.0f,
373
     0.0f,  0.0f,  0.0f,
374
     1.0f,  2.0f,  1.0f
375
};
376

377
static void
378
sixel_color_lut_init(void)
×
379
{
380
    int i;
381
    float srgb;
382
    float linear;
383

384
    if (sixel_color_lut_initialized) {
×
385
        return;
×
386
    }
387

388
    for (i = 0; i < 256; ++i) {
×
389
        srgb = (float)i / 255.0f;
×
390
        if (srgb <= 0.04045f) {
×
391
            linear = srgb / 12.92f;
×
392
        } else {
393
            linear = powf((srgb + 0.055f) / 1.055f, 2.4f);
×
394
        }
395
        sixel_srgb_to_linear_lut[i] = linear * 255.0f;
×
396
    }
397

398
    for (i = 0; i < 256; ++i) {
×
399
        linear = (float)i / 255.0f;
×
400
        if (linear <= 0.0031308f) {
×
401
            srgb = linear * 12.92f;
×
402
        } else {
403
            srgb = 1.055f * powf(linear, 1.0f / 2.4f) - 0.055f;
×
404
        }
405
        srgb *= 255.0f;
×
406
        if (srgb < 0.0f) {
×
407
            srgb = 0.0f;
×
408
        } else if (srgb > 255.0f) {
×
409
            srgb = 255.0f;
×
410
        }
411
        sixel_linear_to_srgb_lut[i] = (unsigned char)(srgb + 0.5f);
×
412
    }
413

414
    sixel_color_lut_initialized = 1;
×
415
}
416

417
static void
418
sixel_convolve3x3(const float *kernel,
×
419
                  float *dst,
420
                  const float *src,
421
                  int width,
422
                  int height)
423
{
424
    int x;
425
    int y;
426
    int kx;
427
    int ky;
428
    int sx;
429
    int sy;
430
    int idx;
431
    int src_index;
432
    int kernel_index;
433
    float sum;
434
    float weight;
435

436
    if (kernel == NULL || dst == NULL || src == NULL) {
×
437
        return;
×
438
    }
439

440
    for (y = 0; y < height; ++y) {
×
441
        for (x = 0; x < width; ++x) {
×
442
            sum = 0.0f;
×
443
            for (ky = -1; ky <= 1; ++ky) {
×
444
                sy = y + ky;
×
445
                if (sy < 0) {
×
446
                    sy = 0;
×
447
                } else if (sy >= height) {
×
448
                    sy = height - 1;
×
449
                }
450
                for (kx = -1; kx <= 1; ++kx) {
×
451
                    sx = x + kx;
×
452
                    if (sx < 0) {
×
453
                        sx = 0;
×
454
                    } else if (sx >= width) {
×
455
                        sx = width - 1;
×
456
                    }
457
                    kernel_index = (ky + 1) * 3 + (kx + 1);
×
458
                    weight = kernel[kernel_index];
×
459
                    src_index = sy * width + sx;
×
460
                    sum += src[src_index] * weight;
×
461
                }
462
            }
463
            idx = y * width + x;
×
464
            dst[idx] = sum;
×
465
        }
466
    }
467
}
468

469
static void
470
sixel_apply_relu(float *buffer, size_t count)
×
471
{
472
    size_t i;
473

474
    if (buffer == NULL) {
×
475
        return;
×
476
    }
477

478
    for (i = 0; i < count; ++i) {
×
479
        if (buffer[i] < 0.0f) {
×
480
            buffer[i] = 0.0f;
×
481
        }
482
    }
483
}
484

485
static unsigned char
486
sixel_linear_to_srgb_value(float value)
×
487
{
488
    int index;
489

490
    if (value < 0.0f) {
×
491
        value = 0.0f;
×
492
    } else if (value > 255.0f) {
×
493
        value = 255.0f;
×
494
    }
495

496
    index = (int)(value + 0.5f);
×
497
    if (index < 0) {
×
498
        index = 0;
×
499
    } else if (index > 255) {
×
500
        index = 255;
×
501
    }
502

503
    return sixel_linear_to_srgb_lut[index];
×
504
}
505

506
static void
507
sixel_post_undither_refine(unsigned char *rgb,
×
508
                           int width,
509
                           int height,
510
                           const float *mask)
511
{
512
    size_t num_pixels;
513
    float *Y;
514
    float *Cb;
515
    float *Cr;
516
    float *work0;
517
    float *work1;
518
    float *gate;
519
    float *gradient;
520
    int x;
521
    int y;
522
    int kx;
523
    int ky;
524
    int sx;
525
    int sy;
526
    int idx;
527
    int src_index;
528
    int kernel_index;
529
    size_t i;
530
    size_t base;
531
    float sigma_r;
532
    float beta;
533
    float alpha1;
534
    float alpha2;
535
    float smooth_gate_scale;
536
    float inv_sigma_r2;
537
    float center;
538
    float neighbor;
539
    float sum;
540
    float weight;
541
    float weight_sum;
542
    float diff;
543
    float range_weight;
544
    float gate_value;
545
    float gaussian_weight;
546
    float max_grad;
547
    float scale;
548
    float gx;
549
    float gy;
550
    float value;
551
    float y_value;
552
    float cb_value;
553
    float cr_value;
554
    float r_lin;
555
    float g_lin;
556
    float b_lin;
557
    float magnitude;
558

559
    if (rgb == NULL) {
×
560
        return;
×
561
    }
562

563
    if (width <= 0 || height <= 0) {
×
564
        return;
×
565
    }
566

567
    sixel_color_lut_init();
×
568

569
    num_pixels = (size_t)width * (size_t)height;
×
570
    if (num_pixels == 0) {
×
571
        return;
×
572
    }
573

574
    Y = NULL;
×
575
    Cb = NULL;
×
576
    Cr = NULL;
×
577
    work0 = NULL;
×
578
    work1 = NULL;
×
579
    gate = NULL;
×
580
    gradient = NULL;
×
581

582
    sigma_r = 10.0f;
×
583
    beta = 0.25f;
×
584
    alpha1 = 0.60f;
×
585
    alpha2 = 0.40f;
×
586
    inv_sigma_r2 = 1.0f / (2.0f * sigma_r * sigma_r);
×
587
    smooth_gate_scale = 0.96f;
×
588

589
    Y = (float *)malloc(num_pixels * sizeof(float));
×
590
    Cb = (float *)malloc(num_pixels * sizeof(float));
×
591
    Cr = (float *)malloc(num_pixels * sizeof(float));
×
592
    work0 = (float *)malloc(num_pixels * sizeof(float));
×
593
    work1 = (float *)malloc(num_pixels * sizeof(float));
×
594
    gate = (float *)malloc(num_pixels * sizeof(float));
×
595

596
    if (Y == NULL || Cb == NULL || Cr == NULL ||
×
597
        work0 == NULL || work1 == NULL || gate == NULL) {
×
598
        goto cleanup;
×
599
    }
600

601
    for (i = 0; i < num_pixels; ++i) {
×
602
        base = i * 3;
×
603
        r_lin = sixel_srgb_to_linear_lut[rgb[base + 0]];
×
604
        g_lin = sixel_srgb_to_linear_lut[rgb[base + 1]];
×
605
        b_lin = sixel_srgb_to_linear_lut[rgb[base + 2]];
×
606

607
        y_value = 0.2990f * r_lin + 0.5870f * g_lin + 0.1140f * b_lin;
×
608
        cb_value = (b_lin - y_value) * 0.564383f;
×
609
        cr_value = (r_lin - y_value) * 0.713272f;
×
610

611
        Y[i] = y_value;
×
612
        Cb[i] = cb_value;
×
613
        Cr[i] = cr_value;
×
614
    }
615

616
    if (mask != NULL) {
×
617
        for (i = 0; i < num_pixels; ++i) {
×
618
            value = mask[i];
×
619
            if (value < 0.0f) {
×
620
                value = 0.0f;
×
621
            } else if (value > 1.0f) {
×
622
                value = 1.0f;
×
623
            }
624
            gate[i] = 1.0f - value;
×
625
            if (gate[i] < 0.0f) {
×
626
                gate[i] = 0.0f;
×
627
            }
628
        }
629
    } else {
630
        gradient = (float *)malloc(num_pixels * sizeof(float));
×
631
        if (gradient == NULL) {
×
632
            goto cleanup;
×
633
        }
634

635
        max_grad = 0.0f;
×
636
        for (y = 0; y < height; ++y) {
×
637
            for (x = 0; x < width; ++x) {
×
638
                gx = 0.0f;
×
639
                gy = 0.0f;
×
640
                for (ky = -1; ky <= 1; ++ky) {
×
641
                    sy = y + ky;
×
642
                    if (sy < 0) {
×
643
                        sy = 0;
×
644
                    } else if (sy >= height) {
×
645
                        sy = height - 1;
×
646
                    }
647
                    for (kx = -1; kx <= 1; ++kx) {
×
648
                        sx = x + kx;
×
649
                        if (sx < 0) {
×
650
                            sx = 0;
×
651
                        } else if (sx >= width) {
×
652
                            sx = width - 1;
×
653
                        }
654
                        kernel_index = (ky + 1) * 3 + (kx + 1);
×
655
                        src_index = sy * width + sx;
×
656
                        neighbor = Y[src_index];
×
657
                        gx += neighbor *
×
658
                              sixel_sobel_gx_kernel[kernel_index];
×
659
                        gy += neighbor *
×
660
                              sixel_sobel_gy_kernel[kernel_index];
×
661
                    }
662
                }
663
                idx = y * width + x;
×
664
                magnitude = sqrtf(gx * gx + gy * gy);
×
665
                gradient[idx] = magnitude;
×
666
                if (magnitude > max_grad) {
×
667
                    max_grad = magnitude;
×
668
                }
669
            }
670
        }
671

672
        if (max_grad <= 0.0f) {
×
673
            max_grad = 1.0f;
×
674
        }
675
        scale = 1.0f / max_grad;
×
676

677
        for (i = 0; i < num_pixels; ++i) {
×
678
            value = gradient[i] * scale;
×
679
            if (value < 0.0f) {
×
680
                value = 0.0f;
×
681
            } else if (value > 1.0f) {
×
682
                value = 1.0f;
×
683
            }
684
            gate[i] = 1.0f - value;
×
685
            if (gate[i] < 0.0f) {
×
686
                gate[i] = 0.0f;
×
687
            }
688
        }
689
    }
690

691
    for (y = 0; y < height; ++y) {
×
692
        for (x = 0; x < width; ++x) {
×
693
            idx = y * width + x;
×
694
            center = Y[idx];
×
695
            gate_value = gate[idx];
×
696
            sum = 0.0f;
×
697
            weight_sum = 0.0f;
×
698
            for (ky = -1; ky <= 1; ++ky) {
×
699
                sy = y + ky;
×
700
                if (sy < 0) {
×
701
                    sy = 0;
×
702
                } else if (sy >= height) {
×
703
                    sy = height - 1;
×
704
                }
705
                for (kx = -1; kx <= 1; ++kx) {
×
706
                    sx = x + kx;
×
707
                    if (sx < 0) {
×
708
                        sx = 0;
×
709
                    } else if (sx >= width) {
×
710
                        sx = width - 1;
×
711
                    }
712
                    kernel_index = (ky + 1) * 3 + (kx + 1);
×
713
                    gaussian_weight =
×
714
                        sixel_gaussian3x3_kernel[kernel_index];
715
                    src_index = sy * width + sx;
×
716
                    neighbor = Y[src_index];
×
717
                    if (kx == 0 && ky == 0) {
×
718
                        weight = gaussian_weight;
×
719
                    } else {
720
                        diff = neighbor - center;
×
721
                        range_weight = expf(-(diff * diff) * inv_sigma_r2);
×
722
                        weight = gaussian_weight * gate_value * range_weight;
×
723
                    }
724
                    sum += neighbor * weight;
×
725
                    weight_sum += weight;
×
726
                }
727
            }
728
            if (weight_sum <= 0.0f) {
×
729
                work0[idx] = center;
×
730
            } else {
731
                work0[idx] = sum / weight_sum;
×
732
            }
733
        }
734
    }
735

736
    for (i = 0; i < num_pixels; ++i) {
×
737
        center = Y[i];
×
738
        value = work0[i];
×
739
        Y[i] = (1.0f - beta) * center + beta * value;
×
740
    }
741

742
    sixel_convolve3x3(sixel_gaussian3x3_kernel,
×
743
                      work0,
744
                      Y,
745
                      width,
746
                      height);
747
    for (i = 0; i < num_pixels; ++i) {
×
748
        gate_value = gate[i] * smooth_gate_scale;
×
749
        center = Y[i];
×
750
        value = work0[i];
×
751
        work0[i] = gate_value * value
×
752
                 + (1.0f - gate_value) * center;
×
753
    }
754
    sixel_apply_relu(work0, num_pixels);
×
755
    sixel_convolve3x3(sixel_weak_sharpen3x3_kernel,
×
756
                      work1,
757
                      work0,
758
                      width,
759
                      height);
760

761
    for (i = 0; i < num_pixels; ++i) {
×
762
        center = Y[i];
×
763
        value = work1[i];
×
764
        Y[i] = center + alpha1 * (value - center);
×
765
    }
766

767
    sixel_convolve3x3(sixel_gaussian3x3_kernel,
×
768
                      work0,
769
                      Y,
770
                      width,
771
                      height);
772
    for (i = 0; i < num_pixels; ++i) {
×
773
        gate_value = gate[i] * smooth_gate_scale;
×
774
        center = Y[i];
×
775
        value = work0[i];
×
776
        work0[i] = gate_value * value
×
777
                 + (1.0f - gate_value) * center;
×
778
    }
779
    sixel_apply_relu(work0, num_pixels);
×
780
    sixel_convolve3x3(sixel_weak_sharpen3x3_kernel,
×
781
                      work1,
782
                      work0,
783
                      width,
784
                      height);
785

786
    for (i = 0; i < num_pixels; ++i) {
×
787
        center = Y[i];
×
788
        value = work1[i];
×
789
        Y[i] = center + alpha2 * (value - center);
×
790
    }
791

792
    for (i = 0; i < num_pixels; ++i) {
×
793
        base = i * 3;
×
794
        y_value = Y[i];
×
795
        cb_value = Cb[i];
×
796
        cr_value = Cr[i];
×
797

798
        r_lin = y_value + 1.402000f * cr_value;
×
799
        b_lin = y_value + 1.772000f * cb_value;
×
800
        g_lin = y_value - 0.344136f * cb_value - 0.714136f * cr_value;
×
801

802
        rgb[base + 0] = sixel_linear_to_srgb_value(r_lin);
×
803
        rgb[base + 1] = sixel_linear_to_srgb_value(g_lin);
×
804
        rgb[base + 2] = sixel_linear_to_srgb_value(b_lin);
×
805
    }
806

807
cleanup:
808
    free(Y);
×
809
    free(Cb);
×
810
    free(Cr);
×
811
    free(work0);
×
812
    free(work1);
×
813
    free(gate);
×
814
    free(gradient);
×
815
}
816

817

818
/* original version of strdup(3) with allocator object */
819
static char *
820
strdup_with_allocator(
166✔
821
    char const          /* in */ *s,          /* source buffer */
822
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
823
                                                 destination buffer */
824
{
825
    char *p;
826

827
    p = (char *)sixel_allocator_malloc(allocator, (size_t)(strlen(s) + 1));
166✔
828
    if (p) {
166!
829
        (void)sixel_compat_strcpy(p, strlen(s) + 1, s);
166✔
830
    }
58✔
831
    return p;
166✔
832
}
833

834

835
/* create decoder object */
836
SIXELAPI SIXELSTATUS
837
sixel_decoder_new(
61✔
838
    sixel_decoder_t    /* out */ **ppdecoder,  /* decoder object to be created */
839
    sixel_allocator_t  /* in */  *allocator)   /* allocator, null if you use
840
                                                  default allocator */
841
{
842
    SIXELSTATUS status = SIXEL_FALSE;
61✔
843

844
    if (allocator == NULL) {
61!
845
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
61✔
846
        if (SIXEL_FAILED(status)) {
61!
847
            goto end;
×
848
        }
849
    } else {
21✔
850
        sixel_allocator_ref(allocator);
×
851
    }
852

853
    *ppdecoder = sixel_allocator_malloc(allocator, sizeof(sixel_decoder_t));
61✔
854
    if (*ppdecoder == NULL) {
61!
855
        sixel_allocator_unref(allocator);
×
856
        sixel_helper_set_additional_message(
×
857
            "sixel_decoder_new: sixel_allocator_malloc() failed.");
858
        status = SIXEL_BAD_ALLOCATION;
×
859
        goto end;
×
860
    }
861

862
    (*ppdecoder)->ref          = 1;
61✔
863
    (*ppdecoder)->output       = strdup_with_allocator("-", allocator);
61✔
864
    (*ppdecoder)->input        = strdup_with_allocator("-", allocator);
61✔
865
    (*ppdecoder)->allocator    = allocator;
61✔
866
    (*ppdecoder)->dequantize_method = SIXEL_DEQUANTIZE_NONE;
61✔
867
    (*ppdecoder)->dequantize_similarity_bias = 100;
61✔
868
    (*ppdecoder)->dequantize_edge_strength = 0;
61✔
869
    (*ppdecoder)->dequantize_refine = 1;
61✔
870
    (*ppdecoder)->thumbnail_size = 0;
61✔
871
    (*ppdecoder)->direct_color = 0;
61✔
872
    (*ppdecoder)->clipboard_input_active = 0;
61✔
873
    (*ppdecoder)->clipboard_output_active = 0;
61✔
874
    (*ppdecoder)->clipboard_input_format[0] = '\0';
61✔
875
    (*ppdecoder)->clipboard_output_format[0] = '\0';
61✔
876

877
    if ((*ppdecoder)->output == NULL || (*ppdecoder)->input == NULL) {
61!
878
        sixel_decoder_unref(*ppdecoder);
×
879
        *ppdecoder = NULL;
×
880
        sixel_helper_set_additional_message(
×
881
            "sixel_decoder_new: strdup_with_allocator() failed.");
882
        status = SIXEL_BAD_ALLOCATION;
×
883
        sixel_allocator_unref(allocator);
×
884
        goto end;
×
885
    }
886

887
    status = SIXEL_OK;
61✔
888

889
end:
40✔
890
    return status;
61✔
891
}
892

893

894
/* deprecated version of sixel_decoder_new() */
895
SIXELAPI /* deprecated */ sixel_decoder_t *
896
sixel_decoder_create(void)
×
897
{
898
    SIXELSTATUS status = SIXEL_FALSE;
×
899
    sixel_decoder_t *decoder = NULL;
×
900

901
    status = sixel_decoder_new(&decoder, NULL);
×
902
    if (SIXEL_FAILED(status)) {
×
903
        goto end;
×
904
    }
905

906
end:
907
    return decoder;
×
908
}
909

910

911
/* destroy a decoder object */
912
static void
913
sixel_decoder_destroy(sixel_decoder_t *decoder)
58✔
914
{
915
    sixel_allocator_t *allocator;
916

917
    if (decoder) {
58!
918
        allocator = decoder->allocator;
58✔
919
        sixel_allocator_free(allocator, decoder->input);
58✔
920
        sixel_allocator_free(allocator, decoder->output);
58✔
921
        sixel_allocator_free(allocator, decoder);
58✔
922
        sixel_allocator_unref(allocator);
58✔
923
    }
20✔
924
}
58✔
925

926

927
/* increase reference count of decoder object (thread-unsafe) */
928
SIXELAPI void
929
sixel_decoder_ref(sixel_decoder_t *decoder)
108✔
930
{
931
    /* TODO: be thread safe */
932
    ++decoder->ref;
108✔
933
}
108✔
934

935

936
/* decrease reference count of decoder object (thread-unsafe) */
937
SIXELAPI void
938
sixel_decoder_unref(sixel_decoder_t *decoder)
166✔
939
{
940
    /* TODO: be thread safe */
941
    if (decoder != NULL && --decoder->ref == 0) {
166!
942
        sixel_decoder_destroy(decoder);
58✔
943
    }
20✔
944
}
166✔
945

946

947
typedef struct sixel_similarity {
948
    const unsigned char *palette;
949
    int ncolors;
950
    int stride;
951
    signed char *cache;
952
    int bias;
953
} sixel_similarity_t;
954

955
static SIXELSTATUS
956
sixel_similarity_init(sixel_similarity_t *similarity,
×
957
                      const unsigned char *palette,
958
                      int ncolors,
959
                      int bias,
960
                      sixel_allocator_t *allocator)
961
{
962
    size_t cache_size;
963
    int i;
964

965
    if (bias < 1) {
×
966
        bias = 1;
×
967
    }
968

969
    similarity->palette = palette;
×
970
    similarity->ncolors = ncolors;
×
971
    similarity->stride = ncolors;
×
972
    similarity->bias = bias;
×
973

974
    cache_size = (size_t)ncolors * (size_t)ncolors;
×
975
    if (cache_size == 0) {
×
976
        similarity->cache = NULL;
×
977
        return SIXEL_OK;
×
978
    }
979

980
    similarity->cache = (signed char *)sixel_allocator_malloc(
×
981
        allocator,
982
        cache_size);
983
    if (similarity->cache == NULL) {
×
984
        sixel_helper_set_additional_message(
×
985
            "sixel_similarity_init: sixel_allocator_malloc() failed.");
986
        return SIXEL_BAD_ALLOCATION;
×
987
    }
988
    memset(similarity->cache, -1, cache_size);
×
989
    for (i = 0; i < ncolors; ++i) {
×
990
        similarity->cache[i * similarity->stride + i] = 7;
×
991
    }
992

993
    return SIXEL_OK;
×
994
}
995

996
static void
997
sixel_similarity_destroy(sixel_similarity_t *similarity,
×
998
                         sixel_allocator_t *allocator)
999
{
1000
    if (similarity->cache != NULL) {
×
1001
        sixel_allocator_free(allocator, similarity->cache);
×
1002
        similarity->cache = NULL;
×
1003
    }
1004
}
×
1005

1006
static inline unsigned int
1007
sixel_similarity_diff(const unsigned char *a, const unsigned char *b)
×
1008
{
1009
    int dr = (int)a[0] - (int)b[0];
×
1010
    int dg = (int)a[1] - (int)b[1];
×
1011
    int db = (int)a[2] - (int)b[2];
×
1012
    return (unsigned int)(dr * dr + dg * dg + db * db);
×
1013
}
1014

1015
static unsigned int
1016
sixel_similarity_compare(sixel_similarity_t *similarity,
×
1017
                         int index1,
1018
                         int index2,
1019
                         int numerator,
1020
                         int denominator)
1021
{
1022
    int min_index;
1023
    int max_index;
1024
    size_t cache_pos;
1025
    signed char cached;
1026
    const unsigned char *palette;
1027
    const unsigned char *p1;
1028
    const unsigned char *p2;
1029
    unsigned char avg_color[3];
1030
    unsigned int distance;
1031
    unsigned int base_distance;
1032
    unsigned long long scaled_distance;
1033
    int bias;
1034
    unsigned int min_diff = UINT_MAX;
×
1035
    int i;
1036
    unsigned int result;
1037
    const unsigned char *pk;
1038
    unsigned int diff;
1039

1040
    if (similarity->cache == NULL) {
×
1041
        return 0;
×
1042
    }
1043

1044
    if (index1 < 0 || index1 >= similarity->ncolors ||
×
1045
        index2 < 0 || index2 >= similarity->ncolors) {
×
1046
        return 0;
×
1047
    }
1048

1049
    if (index1 <= index2) {
×
1050
        min_index = index1;
×
1051
        max_index = index2;
×
1052
    } else {
1053
        min_index = index2;
×
1054
        max_index = index1;
×
1055
    }
1056

1057
    cache_pos = (size_t)min_index * (size_t)similarity->stride
×
1058
              + (size_t)max_index;
×
1059
    cached = similarity->cache[cache_pos];
×
1060
    if (cached >= 0) {
×
1061
        return (unsigned int)cached;
×
1062
    }
1063

1064
    palette = similarity->palette;
×
1065
    p1 = palette + index1 * 3;
×
1066
    p2 = palette + index2 * 3;
×
1067

1068
#if 1
1069
   /*    original: n = (p1 + p2) / 2
1070
    */
1071
    avg_color[0] = (unsigned char)(((unsigned int)p1[0]
×
1072
                                    + (unsigned int)p2[0]) >> 1);
×
1073
    avg_color[1] = (unsigned char)(((unsigned int)p1[1]
×
1074
                                    + (unsigned int)p2[1]) >> 1);
×
1075
    avg_color[2] = (unsigned char)(((unsigned int)p1[2]
×
1076
                                    + (unsigned int)p2[2]) >> 1);
×
1077
    (void) numerator;
1078
    (void) denominator;
1079
#else
1080
   /*
1081
    *    diffuse(pos_a, n1) -> p1
1082
    *    diffuse(pos_b, n2) -> p2
1083
    *
1084
    *    when n1 == n2 == n:
1085
    *
1086
    *    p2 = n + (n - p1) * numerator / denominator
1087
    * => p2 * denominator = n * denominator + (n - p1) * numerator
1088
    * => p2 * denominator = n * denominator + n * numerator - p1 * numerator
1089
    * => n * (denominator + numerator) = p1 * numerator + p2 * denominator
1090
    * => n = (p1 * numerator + p2 * denominator) / (denominator + numerator)
1091
    *
1092
    */
1093
    avg_color[0] = (p1[0] * numerator + p2[0] * denominator + (numerator + denominator + 0.5) / 2)
1094
                 / (numerator + denominator);
1095
    avg_color[1] = (p1[1] * numerator + p2[1] * denominator + (numerator + denominator + 0.5) / 2)
1096
                 / (numerator + denominator);
1097
    avg_color[2] = (p1[2] * numerator + p2[2] * denominator + (numerator + denominator + 0.5) / 2)
1098
                 / (numerator + denominator);
1099
#endif
1100

1101
    distance = sixel_similarity_diff(avg_color, p1);
×
1102
    bias = similarity->bias;
×
1103
    if (bias < 1) {
×
1104
        bias = 1;
×
1105
    }
1106
    scaled_distance = (unsigned long long)distance
×
1107
                    * (unsigned long long)bias
×
1108
                    + 50ULL;
1109
    base_distance = (unsigned int)(scaled_distance / 100ULL);
×
1110
    if (base_distance == 0U) {
×
1111
        base_distance = 1U;
×
1112
    }
1113

1114
    for (i = 0; i < similarity->ncolors; ++i) {
×
1115
        if (i == index1 || i == index2) {
×
1116
            continue;
×
1117
        }
1118
        pk = palette + i * 3;
×
1119
        diff = sixel_similarity_diff(avg_color, pk);
×
1120
        if (diff < min_diff) {
×
1121
            min_diff = diff;
×
1122
        }
1123
    }
1124

1125
    if (min_diff == UINT_MAX) {
×
1126
        min_diff = base_distance * 2U;
×
1127
    }
1128

1129
    if (min_diff >= base_distance * 2U) {
×
1130
        result = 5U;
×
1131
    } else if (min_diff >= base_distance) {
×
1132
        result = 8U;
×
1133
    } else if ((unsigned long long)min_diff * 6ULL
×
1134
               >= (unsigned long long)base_distance * 5ULL) {
×
1135
        result = 7U;
×
1136
    } else if ((unsigned long long)min_diff * 4ULL
×
1137
               >= (unsigned long long)base_distance * 3ULL) {
×
1138
        result = 7U;
×
1139
    } else if ((unsigned long long)min_diff * 3ULL
×
1140
               >= (unsigned long long)base_distance * 2ULL) {
×
1141
        result = 5U;
×
1142
    } else if ((unsigned long long)min_diff * 5ULL
×
1143
               >= (unsigned long long)base_distance * 3ULL) {
×
1144
        result = 7U;
×
1145
    } else if ((unsigned long long)min_diff * 2ULL
×
1146
               >= (unsigned long long)base_distance * 1ULL) {
×
1147
        result = 4U;
×
1148
    } else if ((unsigned long long)min_diff * 3ULL
×
1149
               >= (unsigned long long)base_distance * 1ULL) {
×
1150
        result = 2U;
×
1151
    } else {
1152
        result = 0U;
×
1153
    }
1154

1155
    similarity->cache[cache_pos] = (signed char)result;
×
1156

1157
    return result;
×
1158
}
1159

1160
static inline int
1161
sixel_clamp(int value, int min_value, int max_value)
×
1162
{
1163
    if (value < min_value) {
×
1164
        return min_value;
×
1165
    }
1166
    if (value > max_value) {
×
1167
        return max_value;
×
1168
    }
1169
    return value;
×
1170
}
1171

1172
static inline int
1173
sixel_get_gray(const int *gray, int width, int height, int x, int y)
×
1174
{
1175
    int cx = sixel_clamp(x, 0, width - 1);
×
1176
    int cy = sixel_clamp(y, 0, height - 1);
×
1177
    return gray[cy * width + cx];
×
1178
}
1179

1180
static unsigned short
1181
sixel_prewitt_value(const int *gray, int width, int height, int x, int y)
×
1182
{
1183
    int top_prev = sixel_get_gray(gray, width, height, x - 1, y - 1);
×
1184
    int top_curr = sixel_get_gray(gray, width, height, x, y - 1);
×
1185
    int top_next = sixel_get_gray(gray, width, height, x + 1, y - 1);
×
1186
    int mid_prev = sixel_get_gray(gray, width, height, x - 1, y);
×
1187
    int mid_next = sixel_get_gray(gray, width, height, x + 1, y);
×
1188
    int bot_prev = sixel_get_gray(gray, width, height, x - 1, y + 1);
×
1189
    int bot_curr = sixel_get_gray(gray, width, height, x, y + 1);
×
1190
    int bot_next = sixel_get_gray(gray, width, height, x + 1, y + 1);
×
1191
    long gx = (long)top_next - (long)top_prev +
×
1192
              (long)mid_next - (long)mid_prev +
×
1193
              (long)bot_next - (long)bot_prev;
×
1194
    long gy = (long)bot_prev + (long)bot_curr + (long)bot_next -
×
1195
              (long)top_prev - (long)top_curr - (long)top_next;
×
1196
    unsigned long long magnitude = (unsigned long long)gx
×
1197
                                 * (unsigned long long)gx
×
1198
                                 + (unsigned long long)gy
×
1199
                                 * (unsigned long long)gy;
×
1200
    magnitude /= 256ULL;
×
1201
    if (magnitude > 65535ULL) {
×
1202
        magnitude = 65535ULL;
×
1203
    }
1204
    return (unsigned short)magnitude;
×
1205
}
1206

1207
static unsigned short
1208
sixel_scale_threshold(unsigned short base, int percent)
×
1209
{
1210
    unsigned long long numerator;
1211
    unsigned long long scaled;
1212

1213
    if (percent <= 0) {
×
1214
        percent = 1;
×
1215
    }
1216

1217
    numerator = (unsigned long long)base * 100ULL
×
1218
              + (unsigned long long)percent / 2ULL;
×
1219
    scaled = numerator / (unsigned long long)percent;
×
1220
    if (scaled == 0ULL) {
×
1221
        scaled = 1ULL;
×
1222
    }
1223
    if (scaled > USHRT_MAX) {
×
1224
        scaled = USHRT_MAX;
×
1225
    }
1226

1227
    return (unsigned short)scaled;
×
1228
}
1229

1230
static SIXELSTATUS
1231
sixel_dequantize_k_undither(unsigned char *indexed_pixels,
×
1232
                            int width,
1233
                            int height,
1234
                            unsigned char *palette,
1235
                            int ncolors,
1236
                            int similarity_bias,
1237
                            int edge_strength,
1238
                            int enable_refine,
1239
                            sixel_allocator_t *allocator,
1240
                            unsigned char **output)
1241
{
1242
    SIXELSTATUS status = SIXEL_FALSE;
×
1243
    unsigned char *rgb = NULL;
×
1244
    int *gray = NULL;
×
1245
    unsigned short *prewitt = NULL;
×
1246
    sixel_similarity_t similarity;
1247
    size_t num_pixels;
1248
    int x;
1249
    int y;
1250
    unsigned short strong_threshold;
1251
    unsigned short detail_threshold;
1252
    static const int neighbor_offsets[8][4] = {
1253
        {-1, -1,  10, 16}, {0, -1, 16, 16}, {1, -1,   6, 16},
1254
        {-1,  0,  11, 16},                  {1,  0,  11, 16},
1255
        {-1,  1,   6, 16}, {0,  1, 16, 16}, {1,  1,  10, 16}
1256
    };
1257
    const unsigned char *color;
1258
    size_t out_index;
1259
    int palette_index;
1260
    unsigned int center_weight;
1261
    unsigned int total_weight = 0;
×
1262
    unsigned int accum_r;
1263
    unsigned int accum_g;
1264
    unsigned int accum_b;
1265
    unsigned short gradient;
1266
    int neighbor;
1267
    int nx;
1268
    int ny;
1269
    int numerator;
1270
    int denominator;
1271
    unsigned int weight;
1272
    const unsigned char *neighbor_color;
1273
    int neighbor_index;
1274

1275
    if (width <= 0 || height <= 0 || palette == NULL || ncolors <= 0) {
×
1276
        return SIXEL_BAD_INPUT;
×
1277
    }
1278

1279
    num_pixels = (size_t)width * (size_t)height;
×
1280

1281
    memset(&similarity, 0, sizeof(sixel_similarity_t));
×
1282

1283
    strong_threshold = sixel_scale_threshold(256U, edge_strength);
×
1284
    detail_threshold = sixel_scale_threshold(160U, edge_strength);
×
1285
    if (strong_threshold < detail_threshold) {
×
1286
        strong_threshold = detail_threshold;
×
1287
    }
1288

1289
    /*
1290
     * Build RGB and luminance buffers so we can reuse the similarity cache
1291
     * and gradient analysis across the reconstructed image.
1292
     */
1293
    rgb = (unsigned char *)sixel_allocator_malloc(
×
1294
        allocator,
1295
        num_pixels * 3);
1296
    if (rgb == NULL) {
×
1297
        sixel_helper_set_additional_message(
×
1298
            "sixel_dequantize_k_undither: "
1299
            "sixel_allocator_malloc() failed.");
1300
        status = SIXEL_BAD_ALLOCATION;
×
1301
        goto end;
×
1302
    }
1303

1304
    gray = (int *)sixel_allocator_malloc(
×
1305
        allocator,
1306
        num_pixels * sizeof(int));
1307
    if (gray == NULL) {
×
1308
        sixel_helper_set_additional_message(
×
1309
            "sixel_dequantize_k_undither: "
1310
            "sixel_allocator_malloc() failed.");
1311
        status = SIXEL_BAD_ALLOCATION;
×
1312
        goto end;
×
1313
    }
1314

1315
    prewitt = (unsigned short *)sixel_allocator_malloc(
×
1316
        allocator,
1317
        num_pixels * sizeof(unsigned short));
1318
    if (prewitt == NULL) {
×
1319
        sixel_helper_set_additional_message(
×
1320
            "sixel_dequantize_k_undither: "
1321
            "sixel_allocator_malloc() failed.");
1322
        status = SIXEL_BAD_ALLOCATION;
×
1323
        goto end;
×
1324
    }
1325

1326
    /*
1327
     * Pre-compute palette distance heuristics so each neighbour lookup reuses
1328
     * the k_undither-style similarity table.
1329
     */
1330
    status = sixel_similarity_init(
×
1331
        &similarity,
1332
        palette,
1333
        ncolors,
1334
        similarity_bias,
1335
        allocator);
1336
    if (SIXEL_FAILED(status)) {
×
1337
        goto end;
×
1338
    }
1339

1340
    for (y = 0; y < height; ++y) {
×
1341
        for (x = 0; x < width; ++x) {
×
1342
            palette_index = indexed_pixels[y * width + x];
×
1343
            if (palette_index < 0 || palette_index >= ncolors) {
×
1344
                palette_index = 0;
×
1345
            }
1346

1347
            color = palette + palette_index * 3;
×
1348
            out_index = (size_t)(y * width + x) * 3;
×
1349
            rgb[out_index + 0] = color[0];
×
1350
            rgb[out_index + 1] = color[1];
×
1351
            rgb[out_index + 2] = color[2];
×
1352

1353
            if (edge_strength > 0) {
×
1354
                gray[y * width + x] = (int)color[0]
×
1355
                                    + (int)color[1] * 2
×
1356
                                    + (int)color[2];
×
1357
                /*
1358
                 * Edge detection keeps high-frequency content intact while we
1359
                 * smooth dithering noise in flatter regions.
1360
                 */
1361
                prewitt[y * width + x] = sixel_prewitt_value(
×
1362
                    gray,
1363
                    width,
1364
                    height,
1365
                    x,
1366
                    y);
1367

1368
                gradient = prewitt[y * width + x];
×
1369
                if (gradient > strong_threshold) {
×
1370
                    continue;
×
1371
                }
1372

1373
                if (gradient > detail_threshold) {
×
1374
                    center_weight = 24U;
×
1375
                } else {
1376
                    center_weight = 8U;
×
1377
                }
1378
            } else {
1379
                center_weight = 8U;
×
1380
            }
1381

1382
            out_index = (size_t)(y * width + x) * 3;
×
1383
            accum_r = (unsigned int)rgb[out_index + 0] * center_weight;
×
1384
            accum_g = (unsigned int)rgb[out_index + 1] * center_weight;
×
1385
            accum_b = (unsigned int)rgb[out_index + 2] * center_weight;
×
1386
            total_weight = center_weight;
×
1387

1388
            /*
1389
             * Blend neighbours that stay within the palette similarity
1390
             * threshold so Floyd-Steinberg noise is averaged away without
1391
             * bleeding across pronounced edges.
1392
             */
1393
            for (neighbor = 0; neighbor < 8; ++neighbor) {
×
1394
                nx = x + neighbor_offsets[neighbor][0];
×
1395
                ny = y + neighbor_offsets[neighbor][1];
×
1396
                numerator = neighbor_offsets[neighbor][2];
×
1397
                denominator = neighbor_offsets[neighbor][3];
×
1398

1399
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
×
1400
                    continue;
×
1401
                }
1402

1403
                neighbor_index = indexed_pixels[ny * width + nx];
×
1404
                if (neighbor_index < 0 || neighbor_index >= ncolors) {
×
1405
                    continue;
×
1406
                }
1407

1408
                if (numerator) {
×
1409
                    weight = sixel_similarity_compare(
×
1410
                        &similarity,
1411
                        palette_index,
1412
                        neighbor_index,
1413
                        numerator,
1414
                        denominator);
1415
                    if (weight == 0) {
×
1416
                        continue;
×
1417
                    }
1418

1419
                    neighbor_color = palette + neighbor_index * 3;
×
1420
                    accum_r += (unsigned int)neighbor_color[0] * weight;
×
1421
                    accum_g += (unsigned int)neighbor_color[1] * weight;
×
1422
                    accum_b += (unsigned int)neighbor_color[2] * weight;
×
1423
                    total_weight += weight;
×
1424
                }
1425
            }
1426

1427
            if (total_weight > 0U) {
×
1428
                rgb[out_index + 0] = (unsigned char)(accum_r / total_weight);
×
1429
                rgb[out_index + 1] = (unsigned char)(accum_g / total_weight);
×
1430
                rgb[out_index + 2] = (unsigned char)(accum_b / total_weight);
×
1431
            }
1432
        }
1433
    }
1434

1435

1436
    *output = rgb;
×
1437
    rgb = NULL;
×
1438
    if (enable_refine) {
×
1439
        /*
1440
         *  Only run when the caller requested k_undither+.
1441
         */
1442
        sixel_post_undither_refine(*output, width, height, NULL);
×
1443
    }
1444
    status = SIXEL_OK;
×
1445

1446
end:
1447
    sixel_similarity_destroy(&similarity, allocator);
×
1448
    sixel_allocator_free(allocator, rgb);
×
1449
    sixel_allocator_free(allocator, gray);
×
1450
    sixel_allocator_free(allocator, prewitt);
×
1451
    return status;
×
1452
}
1453
/*
1454
 * The dequantizer accepts both a method and an optional refine flag.
1455
 * The shared option helper returns an integer payload, so the decoder
1456
 * keeps a parallel lookup table that translates the matched index into
1457
 * the tuple expected by the execution path.
1458
 */
1459
typedef struct sixel_decoder_dequant_mapping {
1460
    int method;
1461
    int refine;
1462
} sixel_decoder_dequant_mapping_t;
1463

1464
static sixel_decoder_dequant_mapping_t const
1465
g_decoder_dequant_mappings[] = {
1466
    { SIXEL_DEQUANTIZE_NONE, 0 },
1467
    { SIXEL_DEQUANTIZE_K_UNDITHER, 0 },
1468
    { SIXEL_DEQUANTIZE_K_UNDITHER_PLUS, 1 }
1469
};
1470

1471
static sixel_option_choice_t const g_decoder_dequant_choices[] = {
1472
    { "none", 0 },
1473
    { "k_undither", 1 },
1474
    { "k_undither+", 2 }
1475
};
1476

1477
static void
1478
normalise_windows_drive_path(char *path)
9✔
1479
{
1480
#if defined(_WIN32)
1481
    size_t length;
1482

1483
    length = 0u;
1484

1485
    if (path == NULL) {
1486
        return;
1487
    }
1488

1489
    length = strlen(path);
1490
    if (length >= 3u
1491
            && path[0] == '/'
1492
            && ((path[1] >= 'A' && path[1] <= 'Z')
1493
                || (path[1] >= 'a' && path[1] <= 'z'))
1494
            && path[2] == '/') {
1495
        path[0] = path[1];
1496
        path[1] = ':';
1497
    }
1498
#else
1499
    (void)path;
3✔
1500
#endif
1501
}
9✔
1502

1503
/* set an option flag to decoder object */
1504
SIXELAPI SIXELSTATUS
1505
sixel_decoder_setopt(
68✔
1506
    sixel_decoder_t /* in */ *decoder,
1507
    int             /* in */ arg,
1508
    char const      /* in */ *value
1509
)
1510
{
1511
    SIXELSTATUS status = SIXEL_FALSE;
68✔
1512
    unsigned int path_flags;
1513
    int path_check;
1514
    char const *payload = NULL;
68✔
1515
    size_t length;
1516
    sixel_clipboard_spec_t clipboard_spec;
1517
    int match_index;
1518
    sixel_option_choice_result_t match_result;
1519
    char match_detail[128];
1520
    char match_message[256];
1521
    char const *filename = NULL;
68✔
1522
    char *p = NULL;
68✔
1523

1524
    sixel_decoder_ref(decoder);
68✔
1525
    path_flags = 0u;
68✔
1526
    path_check = 0;
68✔
1527

1528
    switch(arg) {
68!
1529
    case SIXEL_OPTFLAG_INPUT:  /* i */
16✔
1530
        path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
25✔
1531
            SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
1532
            SIXEL_OPTION_PATH_ALLOW_REMOTE;
1533
        if (value != NULL) {
25!
1534
            path_check = sixel_option_validate_filesystem_path(
25✔
1535
                value,
9✔
1536
                value,
9✔
1537
                path_flags);
9✔
1538
            if (path_check != 0) {
25✔
1539
                status = SIXEL_BAD_ARGUMENT;
6✔
1540
                goto end;
6✔
1541
            }
1542
        }
7✔
1543
        decoder->clipboard_input_active = 0;
19✔
1544
        decoder->clipboard_input_format[0] = '\0';
19✔
1545
        if (value != NULL) {
19!
1546
            clipboard_spec.is_clipboard = 0;
19✔
1547
            clipboard_spec.format[0] = '\0';
19✔
1548
            if (sixel_clipboard_parse_spec(value, &clipboard_spec)
19!
1549
                    && clipboard_spec.is_clipboard) {
7!
1550
                decoder_clipboard_select_format(
1✔
1551
                    decoder->clipboard_input_format,
1✔
1552
                    sizeof(decoder->clipboard_input_format),
1553
                    clipboard_spec.format,
1✔
1554
                    "sixel");
1555
                decoder->clipboard_input_active = 1;
1✔
1556
            }
1✔
1557
        }
7✔
1558
        free(decoder->input);
19✔
1559
        decoder->input = strdup_with_allocator(value, decoder->allocator);
19✔
1560
        if (decoder->input == NULL) {
19!
1561
            sixel_helper_set_additional_message(
×
1562
                "sixel_decoder_setopt: strdup_with_allocator() failed.");
1563
            status = SIXEL_BAD_ALLOCATION;
×
1564
            goto end;
×
1565
        }
1566
        break;
19✔
1567
    case SIXEL_OPTFLAG_OUTPUT:  /* o */
18✔
1568
        decoder->clipboard_output_active = 0;
28✔
1569
        decoder->clipboard_output_format[0] = '\0';
28✔
1570

1571
        payload = value;
28✔
1572
        if (strncmp(value, "png:", 4) == 0) {
28✔
1573
            payload = value + 4;
12✔
1574
            if (payload[0] == '\0') {
12✔
1575
                sixel_helper_set_additional_message(
3✔
1576
                    "missing target after the \"png:\" prefix.");
1577
                return SIXEL_BAD_ARGUMENT;
3✔
1578
            }
1579
            length = strlen(payload);
9✔
1580
            filename = p = malloc(length + 1U);
9✔
1581
            if (p == NULL) {
9!
NEW
1582
                sixel_helper_set_additional_message(
×
1583
                    "sixel_decoder_setopt: malloc() failed for png path filename.");
NEW
1584
                return SIXEL_BAD_ALLOCATION;
×
1585
            }
1586
            memcpy(p, payload, length + 1U);
9✔
1587
            normalise_windows_drive_path(p);
9✔
1588
        } else {
3✔
1589
            filename = value;
16✔
1590
        }
1591

1592
        if (filename != NULL) {
25!
1593
            clipboard_spec.is_clipboard = 0;
25✔
1594
            clipboard_spec.format[0] = '\0';
25✔
1595
            if (sixel_clipboard_parse_spec(filename, &clipboard_spec)
25!
1596
                    && clipboard_spec.is_clipboard) {
11!
1597
                decoder_clipboard_select_format(
3✔
1598
                    decoder->clipboard_output_format,
3✔
1599
                    sizeof(decoder->clipboard_output_format),
1600
                    clipboard_spec.format,
1✔
1601
                    "png");
1602
                decoder->clipboard_output_active = 1;
3✔
1603
            }
1✔
1604
        }
9✔
1605
        free(decoder->output);
25✔
1606
        decoder->output = strdup_with_allocator(filename, decoder->allocator);
25✔
1607
        free(p);
25✔
1608
        if (decoder->output == NULL) {
25!
1609
            sixel_helper_set_additional_message(
×
1610
                "sixel_decoder_setopt: strdup_with_allocator() failed.");
1611
            status = SIXEL_BAD_ALLOCATION;
×
1612
            goto end;
×
1613
        }
1614
        break;
25✔
1615
    case SIXEL_OPTFLAG_DEQUANTIZE:  /* d */
6✔
1616
        if (value == NULL) {
9!
1617
            sixel_helper_set_additional_message(
×
1618
                "sixel_decoder_setopt: -d/--dequantize requires an argument.");
1619
            status = SIXEL_BAD_ALLOCATION;
×
1620
            goto end;
×
1621
        }
1622

1623
        match_index = 0;
9✔
1624
        memset(match_detail, 0, sizeof(match_detail));
9✔
1625
        memset(match_message, 0, sizeof(match_message));
9✔
1626

1627
        match_result = sixel_option_match_choice(
9✔
1628
            value,
3✔
1629
            g_decoder_dequant_choices,
1630
            sizeof(g_decoder_dequant_choices) /
1631
            sizeof(g_decoder_dequant_choices[0]),
1632
            &match_index,
1633
            match_detail,
3✔
1634
            sizeof(match_detail));
1635
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
9✔
1636
            decoder->dequantize_method =
6✔
1637
                g_decoder_dequant_mappings[match_index].method;
6✔
1638
            decoder->dequantize_refine =
6✔
1639
                g_decoder_dequant_mappings[match_index].refine;
6✔
1640
        } else {
2✔
1641
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
1642
                sixel_option_report_ambiguous_prefix(
3✔
1643
                    value,
1✔
1644
                    match_detail,
1✔
1645
                    match_message,
1✔
1646
                    sizeof(match_message));
1647
            } else {
1✔
NEW
1648
                sixel_option_report_invalid_choice(
×
1649
                    "unsupported dequantize method.",
1650
                    match_detail,
1651
                    match_message,
1652
                    sizeof(match_message));
1653
            }
1654
            status = SIXEL_BAD_ARGUMENT;
3✔
1655
            goto end;
3✔
1656
        }
1657
        break;
6✔
1658

1659
    case SIXEL_OPTFLAG_SIMILARITY:  /* S */
1660
        decoder->dequantize_similarity_bias = atoi(value);
×
1661
        if (decoder->dequantize_similarity_bias < 0 ||
×
1662
            decoder->dequantize_similarity_bias > 1000) {
×
1663
            sixel_helper_set_additional_message(
×
1664
                "similarity must be between 1 and 1000.");
1665
            status = SIXEL_BAD_ARGUMENT;
×
1666
            goto end;
×
1667
        }
1668
        break;
×
1669

1670
    case SIXEL_OPTFLAG_SIZE:  /* s */
1671
        decoder->thumbnail_size = atoi(value);
×
1672
        if (decoder->thumbnail_size <= 0) {
×
1673
            sixel_helper_set_additional_message(
×
1674
                "size must be greater than zero.");
1675
            status = SIXEL_BAD_ARGUMENT;
×
1676
            goto end;
×
1677
        }
1678
        break;
×
1679

1680
    case SIXEL_OPTFLAG_EDGE:  /* e */
1681
        decoder->dequantize_edge_strength = atoi(value);
×
1682
        if (decoder->dequantize_edge_strength < 0 ||
×
1683
            decoder->dequantize_edge_strength > 1000) {
×
1684
            sixel_helper_set_additional_message(
×
1685
                "edge bias must be between 1 and 1000.");
1686
            status = SIXEL_BAD_ARGUMENT;
×
1687
            goto end;
×
1688
        }
1689
        break;
×
1690

1691
    case SIXEL_OPTFLAG_DIRECT:  /* D */
4✔
1692
        decoder->direct_color = 1;
6✔
1693
        break;
6✔
1694

1695
    case '?':
×
1696
    default:
1697
        status = SIXEL_BAD_ARGUMENT;
×
1698
        goto end;
×
1699
    }
1700

1701
    status = SIXEL_OK;
56✔
1702

1703
end:
42✔
1704
    sixel_decoder_unref(decoder);
65✔
1705

1706
    return status;
65✔
1707
}
24✔
1708

1709

1710
/* load source data from stdin or the file specified with
1711
   SIXEL_OPTFLAG_INPUT flag, and decode it */
1712
SIXELAPI SIXELSTATUS
1713
sixel_decoder_decode(
40✔
1714
    sixel_decoder_t /* in */ *decoder)
1715
{
1716
    SIXELSTATUS status = SIXEL_FALSE;
40✔
1717
    unsigned char *raw_data = NULL;
40✔
1718
    int sx;
1719
    int sy;
1720
    int raw_len;
1721
    int max;
1722
    int n;
1723
    FILE *input_fp = NULL;
40✔
1724
    char message[2048];
1725
    unsigned char *indexed_pixels = NULL;
40✔
1726
    unsigned char *palette = NULL;
40✔
1727
    unsigned char *rgb_pixels = NULL;
40✔
1728
    unsigned char *direct_pixels = NULL;
40✔
1729
    unsigned char *output_pixels;
1730
    unsigned char *output_palette;
1731
    int output_pixelformat;
1732
    int ncolors;
1733
    sixel_frame_t *frame;
1734
    int new_width;
1735
    int new_height;
1736
    double scaled_width;
1737
    double scaled_height;
1738
    int max_dimension;
1739
    int thumbnail_size;
1740
    int frame_ncolors;
1741
    unsigned char *clipboard_blob;
1742
    size_t clipboard_blob_size;
1743
    SIXELSTATUS clipboard_status;
1744
    char *clipboard_output_path;
1745
    unsigned char *clipboard_output_data;
1746
    size_t clipboard_output_size;
1747
    SIXELSTATUS clipboard_output_status;
1748

1749
    sixel_decoder_ref(decoder);
40✔
1750

1751
    frame = NULL;
40✔
1752
    new_width = 0;
40✔
1753
    new_height = 0;
40✔
1754
    scaled_width = 0.0;
40✔
1755
    scaled_height = 0.0;
40✔
1756
    max_dimension = 0;
40✔
1757
    thumbnail_size = decoder->thumbnail_size;
40✔
1758
    frame_ncolors = -1;
40✔
1759
    clipboard_blob = NULL;
40✔
1760
    clipboard_blob_size = 0u;
40✔
1761
    clipboard_status = SIXEL_OK;
40✔
1762
    clipboard_output_path = NULL;
40✔
1763
    clipboard_output_data = NULL;
40✔
1764
    clipboard_output_size = 0u;
40✔
1765
    clipboard_output_status = SIXEL_OK;
40✔
1766
    input_fp = NULL;
40✔
1767

1768
    raw_len = 0;
40✔
1769
    max = 0;
40✔
1770
    if (decoder->clipboard_input_active) {
40!
1771
        clipboard_status = sixel_clipboard_read(
1✔
1772
            decoder->clipboard_input_format,
1✔
1773
            &clipboard_blob,
1774
            &clipboard_blob_size,
1775
            decoder->allocator);
1✔
1776
        if (SIXEL_FAILED(clipboard_status)) {
1!
1777
            status = clipboard_status;
×
1778
            goto end;
×
1779
        }
1780
        max = (int)((clipboard_blob_size > 0u)
1!
1781
                    ? clipboard_blob_size
1✔
1782
                    : 1u);
1783
        raw_data = (unsigned char *)sixel_allocator_malloc(
1✔
1784
            decoder->allocator,
1✔
1785
            (size_t)max);
1✔
1786
        if (raw_data == NULL) {
1!
1787
            sixel_helper_set_additional_message(
×
1788
                "sixel_decoder_decode: sixel_allocator_malloc() failed.");
1789
            status = SIXEL_BAD_ALLOCATION;
×
1790
            goto end;
×
1791
        }
1792
        if (clipboard_blob_size > 0u && clipboard_blob != NULL) {
1!
1793
            memcpy(raw_data, clipboard_blob, clipboard_blob_size);
1✔
1794
        }
1✔
1795
        raw_len = (int)clipboard_blob_size;
1✔
1796
        if (clipboard_blob != NULL) {
1!
1797
            free(clipboard_blob);
1✔
1798
            clipboard_blob = NULL;
1✔
1799
        }
1✔
1800
    } else {
1✔
1801
        if (strcmp(decoder->input, "-") == 0) {
39✔
1802
            /* for windows */
1803
#if defined(O_BINARY)
1804
# if HAVE__SETMODE
1805
            _setmode(STDIN_FILENO, O_BINARY);
1806
# elif HAVE_SETMODE
1807
            setmode(STDIN_FILENO, O_BINARY);
1808
# endif  /* HAVE_SETMODE */
1809
#endif  /* defined(O_BINARY) */
1810
            input_fp = stdin;
24✔
1811
        } else {
8✔
1812
            input_fp = sixel_compat_fopen(decoder->input, "rb");
15✔
1813
            if (! input_fp) {
15!
1814
                (void)snprintf(
×
1815
                    message,
1816
                    sizeof(message) - 1,
1817
                    "sixel_decoder_decode: failed to open input file: %s.",
1818
                    decoder->input);
1819
                sixel_helper_set_additional_message(message);
×
1820
                status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1821
                goto end;
×
1822
            }
1823
        }
1824

1825
        raw_len = 0;
39✔
1826
        max = 64 * 1024;
39✔
1827

1828
        raw_data = (unsigned char *)sixel_allocator_malloc(
39✔
1829
            decoder->allocator,
13✔
1830
            (size_t)max);
13✔
1831
        if (raw_data == NULL) {
39!
1832
            sixel_helper_set_additional_message(
×
1833
                "sixel_decoder_decode: sixel_allocator_malloc() failed.");
1834
            status = SIXEL_BAD_ALLOCATION;
×
1835
            goto end;
×
1836
        }
1837

1838
        for (;;) {
2,110✔
1839
            if ((max - raw_len) < 4096) {
6,330✔
1840
                max *= 2;
111✔
1841
                raw_data = (unsigned char *)sixel_allocator_realloc(
111✔
1842
                    decoder->allocator,
37✔
1843
                    raw_data,
37✔
1844
                    (size_t)max);
37✔
1845
                if (raw_data == NULL) {
111!
1846
                    sixel_helper_set_additional_message(
×
1847
                        "sixel_decoder_decode: sixel_allocator_realloc() failed.");
1848
                    status = SIXEL_BAD_ALLOCATION;
×
1849
                    goto end;
×
1850
                }
1851
            }
37✔
1852
            if ((n = (int)fread(raw_data + raw_len, 1, 4096, input_fp)) <= 0) {
6,330✔
1853
                break;
39✔
1854
            }
1855
            raw_len += n;
6,291✔
1856
        }
1857

1858
        if (input_fp != NULL && input_fp != stdin) {
39!
1859
            fclose(input_fp);
15✔
1860
        }
5✔
1861
    }
1862

1863
    if (decoder->direct_color != 0 &&
40✔
1864
            decoder->dequantize_method != SIXEL_DEQUANTIZE_NONE) {
6✔
1865
        sixel_helper_set_additional_message(
3✔
1866
            "sixel_decoder_decode: direct option "
1867
            "cannot be combined with dequantize option.");
1868
        status = SIXEL_BAD_ARGUMENT;
3✔
1869
        goto end;
3✔
1870
    }
1871

1872
    ncolors = 0;
37✔
1873

1874
    if (decoder->direct_color != 0) {
37✔
1875
        status = sixel_decode_direct(
3✔
1876
            raw_data,
1✔
1877
            raw_len,
1✔
1878
            &direct_pixels,
1879
            &sx,
1880
            &sy,
1881
            decoder->allocator);
1✔
1882
    } else {
1✔
1883
        status = sixel_decode_raw(
34✔
1884
            raw_data,
12✔
1885
            raw_len,
12✔
1886
            &indexed_pixels,
1887
            &sx,
1888
            &sy,
1889
            &palette,
1890
            &ncolors,
1891
            decoder->allocator);
12✔
1892
    }
1893
    if (SIXEL_FAILED(status)) {
37!
1894
        goto end;
×
1895
    }
1896

1897
    if (sx > SIXEL_WIDTH_LIMIT || sy > SIXEL_HEIGHT_LIMIT) {
37!
1898
        status = SIXEL_BAD_INPUT;
×
1899
        goto end;
×
1900
    }
1901

1902
    if (decoder->direct_color != 0) {
37✔
1903
        output_pixels = direct_pixels;
3✔
1904
        output_palette = NULL;
3✔
1905
        output_pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
3✔
1906
        frame_ncolors = 0;
3✔
1907
    } else {
1✔
1908
        output_pixels = indexed_pixels;
34✔
1909
        output_palette = palette;
34✔
1910
        output_pixelformat = SIXEL_PIXELFORMAT_PAL8;
34✔
1911

1912
        if (decoder->dequantize_method == SIXEL_DEQUANTIZE_K_UNDITHER ||
34!
1913
            decoder->dequantize_method == SIXEL_DEQUANTIZE_K_UNDITHER_PLUS) {
34!
1914
            status = sixel_dequantize_k_undither(
×
1915
                indexed_pixels,
1916
                sx,
1917
                sy,
1918
                palette,
1919
                ncolors,
1920
                decoder->dequantize_similarity_bias,
1921
                decoder->dequantize_edge_strength,
1922
                decoder->dequantize_refine,
1923
                decoder->allocator,
1924
                &rgb_pixels);
1925
            if (SIXEL_FAILED(status)) {
×
1926
                goto end;
×
1927
            }
1928
            output_pixels = rgb_pixels;
×
1929
            output_palette = NULL;
×
1930
            output_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1931
        }
1932

1933
        if (output_pixelformat == SIXEL_PIXELFORMAT_PAL8) {
34!
1934
            frame_ncolors = ncolors;
34✔
1935
        } else {
12✔
1936
            frame_ncolors = 0;
×
1937
        }
1938
    }
1939

1940
    if (thumbnail_size > 0) {
37!
1941
        /*
1942
         * When the caller requests a thumbnail, compute the new geometry
1943
         * while preserving the original aspect ratio. We only allocate a
1944
         * frame when the dimensions actually change, so the fast path for
1945
         * matching sizes still avoids any additional allocations.
1946
         */
1947
        max_dimension = sx;
×
1948
        if (sy > max_dimension) {
×
1949
            max_dimension = sy;
×
1950
        }
1951
        if (max_dimension > 0) {
×
1952
            if (sx >= sy) {
×
1953
                new_width = thumbnail_size;
×
1954
                scaled_height = (double)sy * (double)thumbnail_size /
×
1955
                    (double)sx;
×
1956
                new_height = (int)(scaled_height + 0.5);
×
1957
            } else {
1958
                new_height = thumbnail_size;
×
1959
                scaled_width = (double)sx * (double)thumbnail_size /
×
1960
                    (double)sy;
×
1961
                new_width = (int)(scaled_width + 0.5);
×
1962
            }
1963
            if (new_width < 1) {
×
1964
                new_width = 1;
×
1965
            }
1966
            if (new_height < 1) {
×
1967
                new_height = 1;
×
1968
            }
1969
            if (new_width != sx || new_height != sy) {
×
1970
                /*
1971
                 * Wrap the decoded pixels in a frame so we can reuse the
1972
                 * central scaling helper. Ownership transfers to the frame,
1973
                 * which keeps the lifetime rules identical on both paths.
1974
                 */
1975
                status = sixel_frame_new(&frame, decoder->allocator);
×
1976
                if (SIXEL_FAILED(status)) {
×
1977
                    goto end;
×
1978
                }
1979
                status = sixel_frame_init(
×
1980
                    frame,
1981
                    output_pixels,
1982
                    sx,
1983
                    sy,
1984
                    output_pixelformat,
1985
                    output_palette,
1986
                    frame_ncolors);
1987
                if (SIXEL_FAILED(status)) {
×
1988
                    goto end;
×
1989
                }
1990
                if (output_pixels == indexed_pixels) {
×
1991
                    indexed_pixels = NULL;
×
1992
                }
1993
                if (output_pixels == rgb_pixels) {
×
1994
                    rgb_pixels = NULL;
×
1995
                }
1996
                if (output_palette == palette) {
×
1997
                    palette = NULL;
×
1998
                }
1999
                status = sixel_frame_resize(
×
2000
                    frame,
2001
                    new_width,
2002
                    new_height,
2003
                    SIXEL_RES_BILINEAR);
2004
                if (SIXEL_FAILED(status)) {
×
2005
                    goto end;
×
2006
                }
2007
                /*
2008
                 * The resized frame already exposes a tightly packed RGB
2009
                 * buffer, so write the updated dimensions and references
2010
                 * back to the main encoder path.
2011
                 */
2012
                sx = sixel_frame_get_width(frame);
×
2013
                sy = sixel_frame_get_height(frame);
×
2014
                output_pixels = sixel_frame_get_pixels(frame);
×
2015
                output_palette = NULL;
×
2016
                output_pixelformat = sixel_frame_get_pixelformat(frame);
×
2017
            }
2018
        }
2019
    }
2020

2021
    if (decoder->clipboard_output_active) {
37✔
2022
        clipboard_output_status = decoder_clipboard_create_spool(
3✔
2023
            decoder->allocator,
1✔
2024
            "clipboard-out",
2025
            &clipboard_output_path);
2026
        if (SIXEL_FAILED(clipboard_output_status)) {
3!
2027
            status = clipboard_output_status;
×
2028
            goto end;
×
2029
        }
2030
    }
1✔
2031

2032
    status = sixel_helper_write_image_file(
37✔
2033
        output_pixels,
13✔
2034
        sx,
13✔
2035
        sy,
13✔
2036
        output_palette,
13✔
2037
        output_pixelformat,
13✔
2038
        decoder->clipboard_output_active
37✔
2039
            ? clipboard_output_path
1✔
2040
            : decoder->output,
12✔
2041
        SIXEL_FORMAT_PNG,
2042
        decoder->allocator);
13✔
2043

2044
    if (SIXEL_FAILED(status)) {
37!
2045
        goto end;
×
2046
    }
2047

2048
    if (decoder->clipboard_output_active) {
38✔
2049
        clipboard_output_status = decoder_clipboard_read_file(
3✔
2050
            clipboard_output_path,
1✔
2051
            &clipboard_output_data,
2052
            &clipboard_output_size);
2053
        if (SIXEL_SUCCEEDED(clipboard_output_status)) {
3!
2054
            clipboard_output_status = sixel_clipboard_write(
3✔
2055
                decoder->clipboard_output_format,
3✔
2056
                clipboard_output_data,
1✔
2057
                clipboard_output_size);
1✔
2058
        }
1✔
2059
        if (clipboard_output_data != NULL) {
3!
2060
            free(clipboard_output_data);
3✔
2061
            clipboard_output_data = NULL;
3✔
2062
        }
1✔
2063
        if (SIXEL_FAILED(clipboard_output_status)) {
3!
2064
            status = clipboard_output_status;
2✔
2065
            goto end;
2✔
2066
        }
2067
    }
1✔
2068

2069
end:
22✔
2070
    sixel_frame_unref(frame);
40✔
2071
    sixel_allocator_free(decoder->allocator, raw_data);
40✔
2072
    sixel_allocator_free(decoder->allocator, indexed_pixels);
40✔
2073
    sixel_allocator_free(decoder->allocator, palette);
40✔
2074
    sixel_allocator_free(decoder->allocator, direct_pixels);
40✔
2075
    sixel_allocator_free(decoder->allocator, rgb_pixels);
40✔
2076
    if (clipboard_blob != NULL) {
40!
2077
        free(clipboard_blob);
×
2078
    }
2079
    if (clipboard_output_path != NULL) {
40✔
2080
        (void)sixel_compat_unlink(clipboard_output_path);
3✔
2081
        sixel_allocator_free(decoder->allocator, clipboard_output_path);
3✔
2082
    }
1✔
2083

2084
    sixel_decoder_unref(decoder);
40✔
2085

2086
    return status;
40✔
2087
}
2088

2089

2090
#if HAVE_TESTS
2091
static int
2092
test1(void)
×
2093
{
2094
    int nret = EXIT_FAILURE;
×
2095
    sixel_decoder_t *decoder = NULL;
×
2096

2097
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2098
#  pragma GCC diagnostic push
2099
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
2100
#endif
2101
    decoder = sixel_decoder_create();
×
2102
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2103
#  pragma GCC diagnostic pop
2104
#endif
2105
    if (decoder == NULL) {
×
2106
        goto error;
×
2107
    }
2108
    sixel_decoder_ref(decoder);
×
2109
    sixel_decoder_unref(decoder);
×
2110
    nret = EXIT_SUCCESS;
×
2111

2112
error:
2113
    sixel_decoder_unref(decoder);
×
2114
    return nret;
×
2115
}
2116

2117

2118
static int
2119
test2(void)
×
2120
{
2121
    int nret = EXIT_FAILURE;
×
2122
    sixel_decoder_t *decoder = NULL;
×
2123
    SIXELSTATUS status;
2124

2125
    status = sixel_decoder_new(&decoder, NULL);
×
2126
    if (SIXEL_FAILED(status)) {
×
2127
        goto error;
×
2128
    }
2129

2130
    sixel_decoder_ref(decoder);
×
2131
    sixel_decoder_unref(decoder);
×
2132
    nret = EXIT_SUCCESS;
×
2133

2134
error:
2135
    sixel_decoder_unref(decoder);
×
2136
    return nret;
×
2137
}
2138

2139

2140
static int
2141
test3(void)
×
2142
{
2143
    int nret = EXIT_FAILURE;
×
2144
    sixel_decoder_t *decoder = NULL;
×
2145
    sixel_allocator_t *allocator = NULL;
×
2146
    SIXELSTATUS status;
2147

2148
    sixel_debug_malloc_counter = 1;
×
2149

2150
    status = sixel_allocator_new(
×
2151
        &allocator,
2152
        sixel_bad_malloc,
2153
        NULL,
2154
        NULL,
2155
        NULL);
2156
    if (SIXEL_FAILED(status)) {
×
2157
        goto error;
×
2158
    }
2159

2160
    status = sixel_decoder_new(&decoder, allocator);
×
2161
    if (status != SIXEL_BAD_ALLOCATION) {
×
2162
        goto error;
×
2163
    }
2164

2165
    nret = EXIT_SUCCESS;
×
2166

2167
error:
2168
    return nret;
×
2169
}
2170

2171

2172
static int
2173
test4(void)
×
2174
{
2175
    int nret = EXIT_FAILURE;
×
2176
    sixel_decoder_t *decoder = NULL;
×
2177
    sixel_allocator_t *allocator = NULL;
×
2178
    SIXELSTATUS status;
2179

2180
    sixel_debug_malloc_counter = 2;
×
2181

2182
    status = sixel_allocator_new(
×
2183
        &allocator,
2184
        sixel_bad_malloc,
2185
        NULL,
2186
        NULL,
2187
        NULL);
2188
    if (SIXEL_FAILED(status)) {
×
2189
        goto error;
×
2190
    }
2191

2192
    status = sixel_decoder_new(&decoder, allocator);
×
2193
    if (status != SIXEL_BAD_ALLOCATION) {
×
2194
        goto error;
×
2195
    }
2196

2197
    nret = EXIT_SUCCESS;
×
2198

2199
error:
2200
    return nret;
×
2201
}
2202

2203

2204
static int
2205
test5(void)
×
2206
{
2207
    int nret = EXIT_FAILURE;
×
2208
    sixel_decoder_t *decoder = NULL;
×
2209
    sixel_allocator_t *allocator = NULL;
×
2210
    SIXELSTATUS status;
2211

2212
    sixel_debug_malloc_counter = 4;
×
2213

2214
    status = sixel_allocator_new(
×
2215
        &allocator,
2216
        sixel_bad_malloc,
2217
        NULL,
2218
        NULL,
2219
        NULL);
2220
    if (SIXEL_FAILED(status)) {
×
2221
        goto error;
×
2222
    }
2223
    status = sixel_decoder_new(&decoder, allocator);
×
2224
    if (SIXEL_FAILED(status)) {
×
2225
        goto error;
×
2226
    }
2227

2228
    status = sixel_decoder_setopt(
×
2229
        decoder,
2230
        SIXEL_OPTFLAG_INPUT,
2231
        "/");
2232
    if (status != SIXEL_BAD_ALLOCATION) {
×
2233
        goto error;
×
2234
    }
2235

2236
    nret = EXIT_SUCCESS;
×
2237

2238
error:
2239
    return nret;
×
2240
}
2241

2242

2243
static int
2244
test6(void)
×
2245
{
2246
    int nret = EXIT_FAILURE;
×
2247
    sixel_decoder_t *decoder = NULL;
×
2248
    sixel_allocator_t *allocator = NULL;
×
2249
    SIXELSTATUS status;
2250

2251
    sixel_debug_malloc_counter = 4;
×
2252

2253
    status = sixel_allocator_new(
×
2254
        &allocator,
2255
        sixel_bad_malloc,
2256
        NULL,
2257
        NULL,
2258
        NULL);
2259
    if (SIXEL_FAILED(status)) {
×
2260
        goto error;
×
2261
    }
2262

2263
    status = sixel_decoder_new(&decoder, allocator);
×
2264
    if (SIXEL_FAILED(status)) {
×
2265
        goto error;
×
2266
    }
2267

2268
    status = sixel_decoder_setopt(
×
2269
        decoder,
2270
        SIXEL_OPTFLAG_OUTPUT,
2271
        "/");
2272
    if (status != SIXEL_BAD_ALLOCATION) {
×
2273
        goto error;
×
2274
    }
2275

2276
    nret = EXIT_SUCCESS;
×
2277

2278
error:
2279
    return nret;
×
2280
}
2281

2282

2283
static int
2284
test7(void)
×
2285
{
2286
    int nret = EXIT_FAILURE;
×
2287
    sixel_decoder_t *decoder = NULL;
×
2288
    sixel_allocator_t *allocator = NULL;
×
2289
    SIXELSTATUS status;
2290

2291
    status = sixel_allocator_new(
×
2292
        &allocator,
2293
        NULL,
2294
        NULL,
2295
        NULL,
2296
        NULL);
2297
    if (SIXEL_FAILED(status)) {
×
2298
        goto error;
×
2299
    }
2300

2301
    status = sixel_decoder_new(&decoder, allocator);
×
2302
    if (SIXEL_FAILED(status)) {
×
2303
        goto error;
×
2304
    }
2305

2306
    status = sixel_decoder_setopt(
×
2307
        decoder,
2308
        SIXEL_OPTFLAG_INPUT,
2309
        "../images/file");
2310
    if (SIXEL_FAILED(status)) {
×
2311
        goto error;
×
2312
    }
2313

2314
    status = sixel_decoder_decode(decoder);
×
2315
    if ((status >> 8) != (SIXEL_LIBC_ERROR >> 8)) {
×
2316
        goto error;
×
2317
    }
2318

2319
    nret = EXIT_SUCCESS;
×
2320

2321
error:
2322
    return nret;
×
2323
}
2324

2325

2326
static int
2327
test8(void)
×
2328
{
2329
    int nret = EXIT_FAILURE;
×
2330
    sixel_decoder_t *decoder = NULL;
×
2331
    sixel_allocator_t *allocator = NULL;
×
2332
    SIXELSTATUS status;
2333

2334
    sixel_debug_malloc_counter = 5;
×
2335

2336
    status = sixel_allocator_new(
×
2337
        &allocator,
2338
        sixel_bad_malloc,
2339
        NULL,
2340
        NULL,
2341
        NULL);
2342
    if (SIXEL_FAILED(status)) {
×
2343
        goto error;
×
2344
    }
2345

2346
    status = sixel_decoder_new(&decoder, allocator);
×
2347
    if (SIXEL_FAILED(status)) {
×
2348
        goto error;
×
2349
    }
2350

2351
    status = sixel_decoder_setopt(
×
2352
        decoder,
2353
        SIXEL_OPTFLAG_INPUT,
2354
        "../images/map8.six");
2355
    if (SIXEL_FAILED(status)) {
×
2356
        goto error;
×
2357
    }
2358

2359
    status = sixel_decoder_decode(decoder);
×
2360
    if (status != SIXEL_BAD_ALLOCATION) {
×
2361
        goto error;
×
2362
    }
2363

2364
    nret = EXIT_SUCCESS;
×
2365

2366
error:
2367
    return nret;
×
2368
}
2369

2370

2371
SIXELAPI int
2372
sixel_decoder_tests_main(void)
×
2373
{
2374
    int nret = EXIT_FAILURE;
×
2375
    size_t i;
2376
    typedef int (* testcase)(void);
2377

2378
    static testcase const testcases[] = {
2379
        test1,
2380
        test2,
2381
        test3,
2382
        test4,
2383
        test5,
2384
        test6,
2385
        test7,
2386
        test8
2387
    };
2388

2389
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
2390
        nret = testcases[i]();
×
2391
        if (nret != EXIT_SUCCESS) {
×
2392
            goto error;
×
2393
        }
2394
    }
2395

2396
    nret = EXIT_SUCCESS;
×
2397

2398
error:
2399
    return nret;
×
2400
}
2401
#endif  /* HAVE_TESTS */
2402

2403
/* emacs Local Variables:      */
2404
/* emacs mode: c               */
2405
/* emacs tab-width: 4          */
2406
/* emacs indent-tabs-mode: nil */
2407
/* emacs c-basic-offset: 4     */
2408
/* emacs End:                  */
2409
/* vim: set expandtab ts=4 sts=4 sw=4 : */
2410
/* 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