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

saitoha / libsixel / 19512329812

19 Nov 2025 06:32PM UTC coverage: 41.21% (+1.6%) from 39.633%
19512329812

push

github

saitoha
feat: initial implementation of parallel decoder

9693 of 33280 branches covered (29.13%)

739 of 1117 new or added lines in 5 files covered. (66.16%)

1 existing line in 1 file now uncovered.

12686 of 30784 relevant lines covered (41.21%)

664929.31 hits per line

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

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

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

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

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

83
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
4✔
84
}
2✔
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;
93
    size_t tmpdir_len;
94
    size_t prefix_len;
95
    size_t suffix_len;
96
    size_t template_len;
97
    char *template_path;
98
    int needs_separator;
99
    size_t maximum_tmpdir_len;
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";
2✔
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;
1✔
134
        }
1✔
135
    }
1✔
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,
2✔
149
                       "%s/%s-XXXXXX", tmpdir, prefix);
150
#endif
151
    } else {
152
        (void)snprintf(template_path, template_len,
1✔
153
                       "%s%s-XXXXXX", tmpdir, prefix);
154
    }
155

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

160
    return template_path;
3✔
161
}
1✔
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;
170
    char *template_path;
171
    size_t template_capacity;
172
    int open_flags;
173
    int open_mode;
174
    int fd;
175
    char *tmpname_result;
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,
4✔
186
                                                             prefix,
1✔
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
    }
1✔
240

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

244
end:
2✔
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;
262
    long seek_result;
263
    long file_size;
264
    unsigned char *buffer;
265
    size_t read_size;
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
}
1✔
348

349

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

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

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

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

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

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

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

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

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

415
    sixel_color_lut_initialized = 1;
×
416
}
417

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

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

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

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

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

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

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

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

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

504
    return sixel_linear_to_srgb_lut[index];
×
505
}
506

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

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

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

568
    sixel_color_lut_init();
×
569

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

818

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

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

835

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

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

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

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

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

888
    status = SIXEL_OK;
94✔
889

890
end:
62✔
891
    return status;
94✔
892
}
893

894

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

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

907
end:
908
    return decoder;
×
909
}
910

911

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

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

927

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

936

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

947

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

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

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

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

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

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

994
    return SIXEL_OK;
×
995
}
996

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1158
    return result;
×
1159
}
1160

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1436

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

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

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

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

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

1484
    length = 0u;
1485

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

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

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

1525
    sixel_decoder_ref(decoder);
92✔
1526
    path_flags = 0u;
92✔
1527
    path_check = 0;
92✔
1528

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

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

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

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

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

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

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

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

1692
    case SIXEL_OPTFLAG_DIRECT:  /* D */
14✔
1693
        decoder->direct_color = 1;
21✔
1694
        break;
21✔
1695

1696
    case SIXEL_OPTFLAG_THREADS:  /* = */
6✔
1697
        status = sixel_decoder_parallel_override_threads(value);
9✔
1698
        if (SIXEL_FAILED(status)) {
9✔
1699
            goto end;
3✔
1700
        }
1701
        break;
6✔
1702

UNCOV
1703
    case '?':
×
1704
    default:
1705
        status = SIXEL_BAD_ARGUMENT;
×
1706
        goto end;
×
1707
    }
1708

1709
    status = SIXEL_OK;
77✔
1710

1711
end:
58✔
1712
    sixel_decoder_unref(decoder);
89✔
1713

1714
    return status;
89✔
1715
}
32✔
1716

1717

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

1757
    sixel_decoder_ref(decoder);
70✔
1758

1759
    frame = NULL;
70✔
1760
    new_width = 0;
70✔
1761
    new_height = 0;
70✔
1762
    scaled_width = 0.0;
70✔
1763
    scaled_height = 0.0;
70✔
1764
    max_dimension = 0;
70✔
1765
    thumbnail_size = decoder->thumbnail_size;
70✔
1766
    frame_ncolors = -1;
70✔
1767
    clipboard_blob = NULL;
70✔
1768
    clipboard_blob_size = 0u;
70✔
1769
    clipboard_status = SIXEL_OK;
70✔
1770
    clipboard_output_path = NULL;
70✔
1771
    clipboard_output_data = NULL;
70✔
1772
    clipboard_output_size = 0u;
70✔
1773
    clipboard_output_status = SIXEL_OK;
70✔
1774
    input_fp = NULL;
70✔
1775

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

1833
        raw_len = 0;
69✔
1834
        max = 64 * 1024;
69✔
1835

1836
        raw_data = (unsigned char *)sixel_allocator_malloc(
69✔
1837
            decoder->allocator,
23✔
1838
            (size_t)max);
23✔
1839
        if (raw_data == NULL) {
69!
1840
            sixel_helper_set_additional_message(
×
1841
                "sixel_decoder_decode: sixel_allocator_malloc() failed.");
1842
            status = SIXEL_BAD_ALLOCATION;
×
1843
            goto end;
×
1844
        }
1845

1846
        for (;;) {
2,386✔
1847
            if ((max - raw_len) < 4096) {
7,158✔
1848
                max *= 2;
147✔
1849
                raw_data = (unsigned char *)sixel_allocator_realloc(
147✔
1850
                    decoder->allocator,
49✔
1851
                    raw_data,
49✔
1852
                    (size_t)max);
49✔
1853
                if (raw_data == NULL) {
147!
1854
                    sixel_helper_set_additional_message(
×
1855
                        "sixel_decoder_decode: sixel_allocator_realloc() failed.");
1856
                    status = SIXEL_BAD_ALLOCATION;
×
1857
                    goto end;
×
1858
                }
1859
            }
49✔
1860
            if ((n = (int)fread(raw_data + raw_len, 1, 4096, input_fp)) <= 0) {
7,158✔
1861
                break;
69✔
1862
            }
1863
            raw_len += n;
7,089✔
1864
        }
1865

1866
        if (input_fp != NULL && input_fp != stdin) {
69!
1867
            fclose(input_fp);
15✔
1868
        }
5✔
1869
    }
1870

1871
    if (decoder->direct_color != 0 &&
70✔
1872
            decoder->dequantize_method != SIXEL_DEQUANTIZE_NONE) {
21✔
1873
        sixel_helper_set_additional_message(
3✔
1874
            "sixel_decoder_decode: direct option "
1875
            "cannot be combined with dequantize option.");
1876
        status = SIXEL_BAD_ARGUMENT;
3✔
1877
        goto end;
3✔
1878
    }
1879

1880
    ncolors = 0;
67✔
1881

1882
    if (decoder->direct_color != 0) {
67✔
1883
        status = sixel_decode_direct(
18✔
1884
            raw_data,
6✔
1885
            raw_len,
6✔
1886
            &direct_pixels,
1887
            &sx,
1888
            &sy,
1889
            decoder->allocator);
6✔
1890
    } else {
6✔
1891
        status = sixel_decode_raw(
49✔
1892
            raw_data,
17✔
1893
            raw_len,
17✔
1894
            &indexed_pixels,
1895
            &sx,
1896
            &sy,
1897
            &palette,
1898
            &ncolors,
1899
            decoder->allocator);
17✔
1900
    }
1901
    if (SIXEL_FAILED(status)) {
67!
1902
        goto end;
×
1903
    }
1904

1905
    if (sx > SIXEL_WIDTH_LIMIT || sy > SIXEL_HEIGHT_LIMIT) {
67!
1906
        status = SIXEL_BAD_INPUT;
×
1907
        goto end;
×
1908
    }
1909

1910
    if (decoder->direct_color != 0) {
67✔
1911
        output_pixels = direct_pixels;
18✔
1912
        output_palette = NULL;
18✔
1913
        output_pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
18✔
1914
        frame_ncolors = 0;
18✔
1915
    } else {
6✔
1916
        output_pixels = indexed_pixels;
49✔
1917
        output_palette = palette;
49✔
1918
        output_pixelformat = SIXEL_PIXELFORMAT_PAL8;
49✔
1919

1920
        if (decoder->dequantize_method == SIXEL_DEQUANTIZE_K_UNDITHER ||
49!
1921
            decoder->dequantize_method == SIXEL_DEQUANTIZE_K_UNDITHER_PLUS) {
49!
1922
            status = sixel_dequantize_k_undither(
×
1923
                indexed_pixels,
1924
                sx,
1925
                sy,
1926
                palette,
1927
                ncolors,
1928
                decoder->dequantize_similarity_bias,
1929
                decoder->dequantize_edge_strength,
1930
                decoder->dequantize_refine,
1931
                decoder->allocator,
1932
                &rgb_pixels);
1933
            if (SIXEL_FAILED(status)) {
×
1934
                goto end;
×
1935
            }
1936
            output_pixels = rgb_pixels;
×
1937
            output_palette = NULL;
×
1938
            output_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1939
        }
1940

1941
        if (output_pixelformat == SIXEL_PIXELFORMAT_PAL8) {
49!
1942
            frame_ncolors = ncolors;
49✔
1943
        } else {
17✔
1944
            frame_ncolors = 0;
×
1945
        }
1946
    }
1947

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

2029
    if (decoder->clipboard_output_active) {
67✔
2030
        clipboard_output_status = decoder_clipboard_create_spool(
3✔
2031
            decoder->allocator,
1✔
2032
            "clipboard-out",
2033
            &clipboard_output_path);
2034
        if (SIXEL_FAILED(clipboard_output_status)) {
3!
2035
            status = clipboard_output_status;
×
2036
            goto end;
×
2037
        }
2038
    }
1✔
2039

2040
    status = sixel_helper_write_image_file(
67✔
2041
        output_pixels,
23✔
2042
        sx,
23✔
2043
        sy,
23✔
2044
        output_palette,
23✔
2045
        output_pixelformat,
23✔
2046
        decoder->clipboard_output_active
67✔
2047
            ? clipboard_output_path
1✔
2048
            : decoder->output,
22✔
2049
        SIXEL_FORMAT_PNG,
2050
        decoder->allocator);
23✔
2051

2052
    if (SIXEL_FAILED(status)) {
67!
2053
        goto end;
×
2054
    }
2055

2056
    if (decoder->clipboard_output_active) {
68✔
2057
        clipboard_output_status = decoder_clipboard_read_file(
3✔
2058
            clipboard_output_path,
1✔
2059
            &clipboard_output_data,
2060
            &clipboard_output_size);
2061
        if (SIXEL_SUCCEEDED(clipboard_output_status)) {
3!
2062
            clipboard_output_status = sixel_clipboard_write(
3✔
2063
                decoder->clipboard_output_format,
3✔
2064
                clipboard_output_data,
1✔
2065
                clipboard_output_size);
1✔
2066
        }
1✔
2067
        if (clipboard_output_data != NULL) {
3!
2068
            free(clipboard_output_data);
3✔
2069
            clipboard_output_data = NULL;
3✔
2070
        }
1✔
2071
        if (SIXEL_FAILED(clipboard_output_status)) {
3!
2072
            status = clipboard_output_status;
2✔
2073
            goto end;
2✔
2074
        }
2075
    }
1✔
2076

2077
end:
42✔
2078
    sixel_frame_unref(frame);
70✔
2079
    sixel_allocator_free(decoder->allocator, raw_data);
70✔
2080
    sixel_allocator_free(decoder->allocator, indexed_pixels);
70✔
2081
    sixel_allocator_free(decoder->allocator, palette);
70✔
2082
    sixel_allocator_free(decoder->allocator, direct_pixels);
70✔
2083
    sixel_allocator_free(decoder->allocator, rgb_pixels);
70✔
2084
    if (clipboard_blob != NULL) {
70!
2085
        free(clipboard_blob);
×
2086
    }
2087
    if (clipboard_output_path != NULL) {
70✔
2088
        (void)sixel_compat_unlink(clipboard_output_path);
3✔
2089
        sixel_allocator_free(decoder->allocator, clipboard_output_path);
3✔
2090
    }
1✔
2091

2092
    sixel_decoder_unref(decoder);
70✔
2093

2094
    return status;
70✔
2095
}
2096

2097

2098
#if HAVE_TESTS
2099
static int
2100
test1(void)
×
2101
{
2102
    int nret = EXIT_FAILURE;
×
2103
    sixel_decoder_t *decoder = NULL;
×
2104

2105
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2106
#  pragma GCC diagnostic push
2107
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
2108
#endif
2109
    decoder = sixel_decoder_create();
×
2110
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2111
#  pragma GCC diagnostic pop
2112
#endif
2113
    if (decoder == NULL) {
×
2114
        goto error;
×
2115
    }
2116
    sixel_decoder_ref(decoder);
×
2117
    sixel_decoder_unref(decoder);
×
2118
    nret = EXIT_SUCCESS;
×
2119

2120
error:
2121
    sixel_decoder_unref(decoder);
×
2122
    return nret;
×
2123
}
2124

2125

2126
static int
2127
test2(void)
×
2128
{
2129
    int nret = EXIT_FAILURE;
×
2130
    sixel_decoder_t *decoder = NULL;
×
2131
    SIXELSTATUS status;
2132

2133
    status = sixel_decoder_new(&decoder, NULL);
×
2134
    if (SIXEL_FAILED(status)) {
×
2135
        goto error;
×
2136
    }
2137

2138
    sixel_decoder_ref(decoder);
×
2139
    sixel_decoder_unref(decoder);
×
2140
    nret = EXIT_SUCCESS;
×
2141

2142
error:
2143
    sixel_decoder_unref(decoder);
×
2144
    return nret;
×
2145
}
2146

2147

2148
static int
2149
test3(void)
×
2150
{
2151
    int nret = EXIT_FAILURE;
×
2152
    sixel_decoder_t *decoder = NULL;
×
2153
    sixel_allocator_t *allocator = NULL;
×
2154
    SIXELSTATUS status;
2155

2156
    sixel_debug_malloc_counter = 1;
×
2157

2158
    status = sixel_allocator_new(
×
2159
        &allocator,
2160
        sixel_bad_malloc,
2161
        NULL,
2162
        NULL,
2163
        NULL);
2164
    if (SIXEL_FAILED(status)) {
×
2165
        goto error;
×
2166
    }
2167

2168
    status = sixel_decoder_new(&decoder, allocator);
×
2169
    if (status != SIXEL_BAD_ALLOCATION) {
×
2170
        goto error;
×
2171
    }
2172

2173
    nret = EXIT_SUCCESS;
×
2174

2175
error:
2176
    return nret;
×
2177
}
2178

2179

2180
static int
2181
test4(void)
×
2182
{
2183
    int nret = EXIT_FAILURE;
×
2184
    sixel_decoder_t *decoder = NULL;
×
2185
    sixel_allocator_t *allocator = NULL;
×
2186
    SIXELSTATUS status;
2187

2188
    sixel_debug_malloc_counter = 2;
×
2189

2190
    status = sixel_allocator_new(
×
2191
        &allocator,
2192
        sixel_bad_malloc,
2193
        NULL,
2194
        NULL,
2195
        NULL);
2196
    if (SIXEL_FAILED(status)) {
×
2197
        goto error;
×
2198
    }
2199

2200
    status = sixel_decoder_new(&decoder, allocator);
×
2201
    if (status != SIXEL_BAD_ALLOCATION) {
×
2202
        goto error;
×
2203
    }
2204

2205
    nret = EXIT_SUCCESS;
×
2206

2207
error:
2208
    return nret;
×
2209
}
2210

2211

2212
static int
2213
test5(void)
×
2214
{
2215
    int nret = EXIT_FAILURE;
×
2216
    sixel_decoder_t *decoder = NULL;
×
2217
    sixel_allocator_t *allocator = NULL;
×
2218
    SIXELSTATUS status;
2219

2220
    sixel_debug_malloc_counter = 4;
×
2221

2222
    status = sixel_allocator_new(
×
2223
        &allocator,
2224
        sixel_bad_malloc,
2225
        NULL,
2226
        NULL,
2227
        NULL);
2228
    if (SIXEL_FAILED(status)) {
×
2229
        goto error;
×
2230
    }
2231
    status = sixel_decoder_new(&decoder, allocator);
×
2232
    if (SIXEL_FAILED(status)) {
×
2233
        goto error;
×
2234
    }
2235

2236
    status = sixel_decoder_setopt(
×
2237
        decoder,
2238
        SIXEL_OPTFLAG_INPUT,
2239
        "/");
2240
    if (status != SIXEL_BAD_ALLOCATION) {
×
2241
        goto error;
×
2242
    }
2243

2244
    nret = EXIT_SUCCESS;
×
2245

2246
error:
2247
    return nret;
×
2248
}
2249

2250

2251
static int
2252
test6(void)
×
2253
{
2254
    int nret = EXIT_FAILURE;
×
2255
    sixel_decoder_t *decoder = NULL;
×
2256
    sixel_allocator_t *allocator = NULL;
×
2257
    SIXELSTATUS status;
2258

2259
    sixel_debug_malloc_counter = 4;
×
2260

2261
    status = sixel_allocator_new(
×
2262
        &allocator,
2263
        sixel_bad_malloc,
2264
        NULL,
2265
        NULL,
2266
        NULL);
2267
    if (SIXEL_FAILED(status)) {
×
2268
        goto error;
×
2269
    }
2270

2271
    status = sixel_decoder_new(&decoder, allocator);
×
2272
    if (SIXEL_FAILED(status)) {
×
2273
        goto error;
×
2274
    }
2275

2276
    status = sixel_decoder_setopt(
×
2277
        decoder,
2278
        SIXEL_OPTFLAG_OUTPUT,
2279
        "/");
2280
    if (status != SIXEL_BAD_ALLOCATION) {
×
2281
        goto error;
×
2282
    }
2283

2284
    nret = EXIT_SUCCESS;
×
2285

2286
error:
2287
    return nret;
×
2288
}
2289

2290

2291
static int
2292
test7(void)
×
2293
{
2294
    int nret = EXIT_FAILURE;
×
2295
    sixel_decoder_t *decoder = NULL;
×
2296
    sixel_allocator_t *allocator = NULL;
×
2297
    SIXELSTATUS status;
2298

2299
    status = sixel_allocator_new(
×
2300
        &allocator,
2301
        NULL,
2302
        NULL,
2303
        NULL,
2304
        NULL);
2305
    if (SIXEL_FAILED(status)) {
×
2306
        goto error;
×
2307
    }
2308

2309
    status = sixel_decoder_new(&decoder, allocator);
×
2310
    if (SIXEL_FAILED(status)) {
×
2311
        goto error;
×
2312
    }
2313

2314
    status = sixel_decoder_setopt(
×
2315
        decoder,
2316
        SIXEL_OPTFLAG_INPUT,
2317
        "../images/file");
2318
    if (SIXEL_FAILED(status)) {
×
2319
        goto error;
×
2320
    }
2321

2322
    status = sixel_decoder_decode(decoder);
×
2323
    if ((status >> 8) != (SIXEL_LIBC_ERROR >> 8)) {
×
2324
        goto error;
×
2325
    }
2326

2327
    nret = EXIT_SUCCESS;
×
2328

2329
error:
2330
    return nret;
×
2331
}
2332

2333

2334
static int
2335
test8(void)
×
2336
{
2337
    int nret = EXIT_FAILURE;
×
2338
    sixel_decoder_t *decoder = NULL;
×
2339
    sixel_allocator_t *allocator = NULL;
×
2340
    SIXELSTATUS status;
2341

2342
    sixel_debug_malloc_counter = 5;
×
2343

2344
    status = sixel_allocator_new(
×
2345
        &allocator,
2346
        sixel_bad_malloc,
2347
        NULL,
2348
        NULL,
2349
        NULL);
2350
    if (SIXEL_FAILED(status)) {
×
2351
        goto error;
×
2352
    }
2353

2354
    status = sixel_decoder_new(&decoder, allocator);
×
2355
    if (SIXEL_FAILED(status)) {
×
2356
        goto error;
×
2357
    }
2358

2359
    status = sixel_decoder_setopt(
×
2360
        decoder,
2361
        SIXEL_OPTFLAG_INPUT,
2362
        "../images/map8.six");
2363
    if (SIXEL_FAILED(status)) {
×
2364
        goto error;
×
2365
    }
2366

2367
    status = sixel_decoder_decode(decoder);
×
2368
    if (status != SIXEL_BAD_ALLOCATION) {
×
2369
        goto error;
×
2370
    }
2371

2372
    nret = EXIT_SUCCESS;
×
2373

2374
error:
2375
    return nret;
×
2376
}
2377

2378

2379
SIXELAPI int
2380
sixel_decoder_tests_main(void)
×
2381
{
2382
    int nret = EXIT_FAILURE;
×
2383
    size_t i;
2384
    typedef int (* testcase)(void);
2385

2386
    static testcase const testcases[] = {
2387
        test1,
2388
        test2,
2389
        test3,
2390
        test4,
2391
        test5,
2392
        test6,
2393
        test7,
2394
        test8
2395
    };
2396

2397
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
2398
        nret = testcases[i]();
×
2399
        if (nret != EXIT_SUCCESS) {
×
2400
            goto error;
×
2401
        }
2402
    }
2403

2404
    nret = EXIT_SUCCESS;
×
2405

2406
error:
2407
    return nret;
×
2408
}
2409
#endif  /* HAVE_TESTS */
2410

2411
/* emacs Local Variables:      */
2412
/* emacs mode: c               */
2413
/* emacs tab-width: 4          */
2414
/* emacs indent-tabs-mode: nil */
2415
/* emacs c-basic-offset: 4     */
2416
/* emacs End:                  */
2417
/* vim: set expandtab ts=4 sts=4 sw=4 : */
2418
/* 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