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

saitoha / libsixel / 29651119742

18 Jul 2026 02:41PM UTC coverage: 85.03% (-0.3%) from 85.292%
29651119742

push

github

saitoha
fix: clip size fill for transparent offset bands

79318 of 144774 branches covered (54.79%)

4 of 6 new or added lines in 1 file covered. (66.67%)

3156 existing lines in 22 files now uncovered.

141353 of 166238 relevant lines covered (85.03%)

6269019.35 hits per line

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

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

24
#if defined(HAVE_CONFIG_H)
25
#include "config.h"
26
#endif
27

28
/* STDC_HEADERS */
29
#include <stdio.h>
30
#include <stdlib.h>
31
#include <stdint.h>
32
#include <string.h>
33

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

58
#include "decoder.h"
59
#include "decoder-parallel.h"
60
#include "sixel_decode_pixels.h"
61
#include "frame-factory.h"
62
#include "clipboard.h"
63
#include "compat_stub.h"
64
#include "gpu-dequant.h"
65
#include "path.h"
66
#include "options.h"
67
#include "cpu.h"
68
#include "sixel_atomic.h"
69
#include "threading.h"
70

71
#if defined(HAVE_NEON) && HAVE_NEON && \
72
    defined(HAVE_ARM_NEON_H) && HAVE_ARM_NEON_H && \
73
    (defined(__ARM_NEON) || defined(__ARM_NEON__))
74
# include <arm_neon.h>
75
# define SIXEL_KUNDITHER_USE_NEON 1
76
#endif
77

78
#define SIXEL_DECODER_GPU_POLICY_ENVVAR "SIXEL_GPU_POLICY"
79

80
static sixel_option_choice_t const g_decoder_gpu_policy_choices[] = {
81
    { "off", SIXEL_GPU_POLICY_OFF },
82
    { "auto", SIXEL_GPU_POLICY_AUTO },
83
    { "force", SIXEL_GPU_POLICY_FORCE }
84
};
85

86
static void
87
decoder_clipboard_select_format(char *dest,
57✔
88
                                size_t dest_size,
89
                                char const *format,
90
                                char const *fallback)
91
{
92
    char const *source;
25✔
93
    size_t limit;
25✔
94

95
    if (dest == NULL || dest_size == 0u) {
57!
96
        return;
97
    }
98

99
    source = fallback;
57✔
100
    if (format != NULL && format[0] != '\0') {
57!
101
        source = format;
25✔
102
    }
103

104
    limit = dest_size - 1u;
57✔
105
    if (limit == 0u) {
57!
UNCOV
106
        dest[0] = '\0';
×
UNCOV
107
        return;
×
108
    }
109

110
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
57✔
111
}
4✔
112

113

114
static char *
115
decoder_create_temp_template_with_prefix(sixel_allocator_t *allocator,
32✔
116
                                         char const *prefix,
117
                                         size_t *capacity_out)
118
{
119
    char const *tmpdir;
14✔
120
    size_t tmpdir_len;
14✔
121
    size_t prefix_len;
14✔
122
    size_t suffix_len;
14✔
123
    size_t template_len;
14✔
124
    char *template_path;
14✔
125
    int needs_separator;
14✔
126
    size_t maximum_tmpdir_len;
14✔
127

128
#if defined(_WIN32)
129
    /*
130
     * MinGW runtimes under Wine can reject host-side TMPDIR values
131
     * (for example "/home/..."). Prefer TEMP/TMP first and then
132
     * fall back to TMPDIR.
133
     */
134
    tmpdir = sixel_compat_getenv("TEMP");
18✔
135
    if (tmpdir == NULL || tmpdir[0] == '\0') {
18✔
136
        tmpdir = sixel_compat_getenv("TMP");
137
    }
138
    if (tmpdir == NULL || tmpdir[0] == '\0') {
18✔
139
        tmpdir = sixel_compat_getenv("TMPDIR");
140
    }
141
#else
142
    tmpdir = sixel_compat_getenv("TMPDIR");
14✔
143
#endif
144
    if (tmpdir == NULL || tmpdir[0] == '\0') {
32!
145
#if defined(_WIN32)
146
        tmpdir = ".";
147
#else
148
        tmpdir = "/tmp";
14✔
149
#endif
150
    }
151

152
    tmpdir_len = strlen(tmpdir);
32✔
153
    prefix_len = strlen(prefix);
32✔
154
    suffix_len = prefix_len + strlen("-XXXXXX");
32✔
155
    maximum_tmpdir_len = (size_t)INT_MAX;
32✔
156

157
    if (maximum_tmpdir_len <= suffix_len + 2) {
32!
158
        return NULL;
159
    }
160
    if (tmpdir_len > maximum_tmpdir_len - (suffix_len + 2)) {
32!
161
        return NULL;
162
    }
163

164
    needs_separator = 1;
32✔
165
    if (tmpdir_len > 0) {
32!
166
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
32!
167
            needs_separator = 0;
14✔
168
        }
169
    }
2✔
170

171
    template_len = tmpdir_len + suffix_len + 2;
32✔
172
    template_path = (char *)sixel_allocator_malloc(allocator, template_len);
32✔
173
    if (template_path == NULL) {
32!
174
        return NULL;
175
    }
176

177
    if (needs_separator) {
32!
178
#if defined(_WIN32)
179
        (void)snprintf(template_path, template_len,
20✔
180
                       "%s\\%s-XXXXXX", tmpdir, prefix);
2✔
181
#else
182
        (void)snprintf(template_path, template_len,
14✔
183
                       "%s/%s-XXXXXX", tmpdir, prefix);
184
#endif
185
    } else {
2✔
UNCOV
186
        (void)snprintf(template_path, template_len,
×
187
                       "%s%s-XXXXXX", tmpdir, prefix);
188
    }
189

190
    if (capacity_out != NULL) {
32!
191
        *capacity_out = template_len;
32✔
192
    }
2✔
193

194
    return template_path;
18✔
195
}
2✔
196

197

198
static SIXELSTATUS
199
decoder_clipboard_create_spool(sixel_allocator_t *allocator,
32✔
200
                               char const *prefix,
201
                               char **path_out)
202
{
203
    SIXELSTATUS status;
14✔
204
    char *template_path;
14✔
205
    size_t template_capacity;
14✔
206
    int open_flags;
14✔
207
    int open_mode;
14✔
208
    int open_attempt;
14✔
209
    int open_errno;
14✔
210
    int fd;
14✔
211
    char *tmpname_result;
14✔
212

213
    status = SIXEL_FALSE;
32✔
214
    template_path = NULL;
32✔
215
    template_capacity = 0u;
32✔
216
    open_flags = 0;
32✔
217
    open_mode = 0;
32✔
218
    fd = (-1);
32✔
219
    tmpname_result = NULL;
32✔
220

221
    template_path = decoder_create_temp_template_with_prefix(allocator,
34✔
222
                                                             prefix,
2✔
223
                                                             &template_capacity);
224
    if (template_path == NULL) {
32!
225
        sixel_helper_set_additional_message(
×
226
            "clipboard: failed to allocate spool template.");
UNCOV
227
        status = SIXEL_BAD_ALLOCATION;
×
228
        goto end;
×
229
    }
230

231
    if (sixel_compat_mktemp(template_path, template_capacity) != 0) {
32!
UNCOV
232
        tmpname_result = sixel_compat_tmpnam(template_path,
×
233
                                             template_capacity);
UNCOV
234
        if (tmpname_result == NULL) {
×
UNCOV
235
            sixel_helper_set_additional_message(
×
236
                "clipboard: failed to reserve spool template.");
UNCOV
237
            status = SIXEL_LIBC_ERROR;
×
UNCOV
238
            goto end;
×
239
        }
UNCOV
240
        template_capacity = strlen(template_path) + 1u;
×
241
    }
242

243
    open_flags = O_RDWR | O_CREAT | O_TRUNC;
26✔
244
#if defined(O_BINARY)
245
    open_flags |= O_BINARY;
20✔
246
#endif
247
    /*
248
     * Emscripten + NODERAWFS can report false EEXIST for O_EXCL on freshly
249
     * generated temp paths. Keep O_EXCL on native runtimes and drop it for
250
     * emscripten to preserve clipboard spool creation reliability.
251
     */
252
#if defined(O_EXCL) && !defined(__EMSCRIPTEN__)
253
    open_flags |= O_EXCL;
32✔
254
#endif
255
    open_mode = S_IRUSR | S_IWUSR;
32✔
256
    open_errno = 0;
26✔
257
    open_attempt = 0;
26✔
258
    for (open_attempt = 0; open_attempt < 4; ++open_attempt) {
32!
259
        fd = sixel_compat_open(template_path, open_flags, open_mode);
32✔
260
        if (fd >= 0) {
32✔
261
            break;
18✔
262
        }
263
        open_errno = errno;
×
UNCOV
264
        if (open_errno != EEXIST) {
×
265
            break;
266
        }
267
        /*
268
         * Emscripten mktemp implementations can return reused names.
269
         * Regenerate the path and retry when the generated file already exists.
270
         */
271
        if (sixel_compat_mktemp(template_path, template_capacity) != 0) {
×
UNCOV
272
            tmpname_result = sixel_compat_tmpnam(template_path,
×
273
                                                 template_capacity);
UNCOV
274
            if (tmpname_result == NULL) {
×
275
                sixel_helper_set_additional_message(
×
276
                    "clipboard: failed to reserve spool template.");
UNCOV
277
                status = SIXEL_LIBC_ERROR;
×
278
                goto end;
×
279
            }
280
            template_capacity = strlen(template_path) + 1u;
×
281
        }
282
    }
283
    if (fd < 0) {
32!
UNCOV
284
        if (open_errno != 0) {
×
UNCOV
285
            errno = open_errno;
×
286
        }
UNCOV
287
        sixel_helper_set_additional_message(
×
288
            "clipboard: failed to open spool file.");
UNCOV
289
        status = SIXEL_LIBC_ERROR;
×
UNCOV
290
        goto end;
×
291
    }
292

293
    *path_out = template_path;
32✔
294
    if (fd >= 0) {
32!
295
        (void)sixel_compat_close(fd);
32✔
296
        fd = (-1);
32✔
297
    }
2✔
298

299
    template_path = NULL;
24✔
300
    status = SIXEL_OK;
24✔
301

302
end:
24✔
303
    if (fd >= 0) {
32!
304
        (void)sixel_compat_close(fd);
305
    }
306
    if (template_path != NULL) {
32!
UNCOV
307
        sixel_allocator_free(allocator, template_path);
×
308
    }
309

310
    return status;
32✔
311
}
312

313

314
static SIXELSTATUS
315
decoder_clipboard_read_file(char const *path,
32✔
316
                            unsigned char **data,
317
                            size_t *size)
318
{
319
    FILE *stream;
14✔
320
    long seek_result;
14✔
321
    long file_size;
14✔
322
    unsigned char *buffer;
14✔
323
    size_t read_size;
14✔
324

325
    if (data == NULL || size == NULL) {
32!
326
        sixel_helper_set_additional_message(
×
327
            "clipboard: read buffer pointers are null.");
328
        return SIXEL_BAD_ARGUMENT;
×
329
    }
330

331
    *data = NULL;
32✔
332
    *size = 0u;
32✔
333

334
    if (path == NULL) {
32!
335
        sixel_helper_set_additional_message(
×
336
            "clipboard: spool path is null.");
UNCOV
337
        return SIXEL_BAD_ARGUMENT;
×
338
    }
339

340
    stream = sixel_compat_fopen(path, "rb");
32✔
341
    if (stream == NULL) {
32!
UNCOV
342
        sixel_helper_set_additional_message(
×
343
            "clipboard: failed to open spool file for read.");
UNCOV
344
        return SIXEL_LIBC_ERROR;
×
345
    }
346

347
    seek_result = fseek(stream, 0L, SEEK_END);
32✔
348
    if (seek_result != 0) {
32!
349
        (void)fclose(stream);
×
UNCOV
350
        sixel_helper_set_additional_message(
×
351
            "clipboard: failed to seek spool file.");
UNCOV
352
        return SIXEL_LIBC_ERROR;
×
353
    }
354

355
    file_size = ftell(stream);
32✔
356
    if (file_size < 0) {
32!
357
        (void)fclose(stream);
×
UNCOV
358
        sixel_helper_set_additional_message(
×
359
            "clipboard: failed to determine spool size.");
UNCOV
360
        return SIXEL_LIBC_ERROR;
×
361
    }
362

363
    seek_result = fseek(stream, 0L, SEEK_SET);
32✔
364
    if (seek_result != 0) {
32!
UNCOV
365
        (void)fclose(stream);
×
UNCOV
366
        sixel_helper_set_additional_message(
×
367
            "clipboard: failed to rewind spool file.");
368
        return SIXEL_LIBC_ERROR;
×
369
    }
370

371
    if (file_size == 0) {
32!
372
        buffer = NULL;
373
        read_size = 0u;
374
    } else {
375
        buffer = (unsigned char *)malloc((size_t)file_size);
32✔
376
        if (buffer == NULL) {
32!
377
            (void)fclose(stream);
×
UNCOV
378
            sixel_helper_set_additional_message(
×
379
                "clipboard: malloc() failed for spool payload.");
UNCOV
380
            return SIXEL_BAD_ALLOCATION;
×
381
        }
382
        read_size = fread(buffer, 1u, (size_t)file_size, stream);
32!
383
        if (read_size != (size_t)file_size) {
32!
384
            free(buffer);
×
385
            (void)fclose(stream);
×
UNCOV
386
            sixel_helper_set_additional_message(
×
387
                "clipboard: failed to read spool payload.");
UNCOV
388
            return SIXEL_LIBC_ERROR;
×
389
        }
390
    }
391

392
    if (fclose(stream) != 0) {
32!
UNCOV
393
        if (buffer != NULL) {
×
UNCOV
394
            free(buffer);
×
395
        }
UNCOV
396
        sixel_helper_set_additional_message(
×
397
            "clipboard: failed to close spool file after read.");
UNCOV
398
        return SIXEL_LIBC_ERROR;
×
399
    }
400

401
    *data = buffer;
32✔
402
    *size = read_size;
32✔
403

404
    return SIXEL_OK;
32✔
405
}
2✔
406

407

408
/* original version of strdup(3) with allocator object */
409
static char *
410
strdup_with_allocator(
3,988✔
411
    char const          /* in */ *s,          /* source buffer */
412
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
413
                                                 destination buffer */
414
{
415
    char *p;
1,741✔
416

417
    if (s == NULL || allocator == NULL) {
3,988!
418
        return NULL;
419
    }
420
    p = (char *)sixel_allocator_malloc(allocator, (size_t)(strlen(s) + 1));
3,988✔
421
    if (p) {
3,988!
422
        (void)sixel_compat_strcpy(p, strlen(s) + 1, s);
3,988✔
423
    }
255✔
424
    return p;
2,247✔
425
}
255✔
426

427
static SIXELSTATUS
UNCOV
428
sixel_decoder_parse_gpu_policy_argument(char const *value,
×
429
                                        int *policy)
430
{
431
    sixel_option_choice_result_t match_result;
432
    char match_detail[256];
433
    int match_value;
434

UNCOV
435
    match_value = SIXEL_GPU_POLICY_OFF;
×
UNCOV
436
    match_detail[0] = '\0';
×
UNCOV
437
    match_result = sixel_option_match_choice(
×
438
        value,
439
        g_decoder_gpu_policy_choices,
440
        sizeof(g_decoder_gpu_policy_choices) /
441
        sizeof(g_decoder_gpu_policy_choices[0]),
442
        &match_value,
443
        match_detail,
444
        sizeof(match_detail));
UNCOV
445
    if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
UNCOV
446
        *policy = match_value;
×
UNCOV
447
        return SIXEL_OK;
×
448
    }
UNCOV
449
    if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
UNCOV
450
        sixel_option_report_ambiguous_prefix(
×
451
            value,
452
            match_detail,
453
            match_detail,
454
            sizeof(match_detail));
UNCOV
455
        return SIXEL_BAD_ARGUMENT;
×
456
    }
UNCOV
457
    sixel_option_report_invalid_choice(
×
458
        "cannot parse gpu policy option.",
459
        match_detail,
460
        match_detail,
461
        sizeof(match_detail));
462
    return SIXEL_BAD_ARGUMENT;
×
463
}
464

465

466
/* create decoder object */
467
SIXELAPI SIXELSTATUS
468
sixel_decoder_new(
1,305✔
469
    sixel_decoder_t    /* out */ **ppdecoder,  /* decoder object to be created */
470
    sixel_allocator_t  /* in */  *allocator)   /* allocator, null if you use
471
                                                  default allocator */
472
{
473
    SIXELSTATUS status = SIXEL_FALSE;
1,305✔
474
    char const *env_gpu_policy;
570✔
475
    char match_detail[256];
570✔
476
    int env_match_value;
570✔
477

478
    if (allocator == NULL) {
1,305✔
479
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
1,041✔
480
        if (SIXEL_FAILED(status)) {
1,041!
481
            goto end;
×
482
        }
483
    } else {
66✔
484
        sixel_allocator_ref(allocator);
264✔
485
    }
486

487
    *ppdecoder = sixel_allocator_malloc(allocator, sizeof(sixel_decoder_t));
1,305✔
488
    if (*ppdecoder == NULL) {
1,305!
UNCOV
489
        sixel_allocator_unref(allocator);
×
490
        sixel_helper_set_additional_message(
×
491
            "sixel_decoder_new: sixel_allocator_malloc() failed.");
UNCOV
492
        status = SIXEL_BAD_ALLOCATION;
×
UNCOV
493
        goto end;
×
494
    }
495

496
    (*ppdecoder)->ref          = 1U;
1,305✔
497
    (*ppdecoder)->output       = strdup_with_allocator("-", allocator);
1,305✔
498
    (*ppdecoder)->input        = strdup_with_allocator("-", allocator);
1,305✔
499
    (*ppdecoder)->allocator    = allocator;
1,305✔
500
    (*ppdecoder)->dequantize_method = SIXEL_DEQUANTIZE_NONE;
1,305✔
501
    (*ppdecoder)->dequantize_similarity_bias = 100;
1,305✔
502
    (*ppdecoder)->dequantize_edge_strength = 0;
1,305✔
503
    (*ppdecoder)->dequantize_selective_blur_threshold =
1,305✔
504
        SIXEL_DEQUANTIZE_SELECTIVE_BLUR_THRESHOLD_DEFAULT;
505
    (*ppdecoder)->gpu_policy = SIXEL_GPU_POLICY_OFF;
1,305✔
506
    (*ppdecoder)->thumbnail_size = 0;
1,305✔
507
    (*ppdecoder)->direct_color = 0;
1,305✔
508
    (*ppdecoder)->clipboard_input_active = 0;
1,305✔
509
    (*ppdecoder)->clipboard_output_active = 0;
1,305✔
510
    (*ppdecoder)->clipboard_input_format[0] = '\0';
1,305✔
511
    (*ppdecoder)->clipboard_output_format[0] = '\0';
1,305✔
512

513
    if ((*ppdecoder)->output == NULL || (*ppdecoder)->input == NULL) {
1,305!
UNCOV
514
        sixel_decoder_unref(*ppdecoder);
×
UNCOV
515
        *ppdecoder = NULL;
×
UNCOV
516
        sixel_helper_set_additional_message(
×
517
            "sixel_decoder_new: strdup_with_allocator() failed.");
UNCOV
518
        status = SIXEL_BAD_ALLOCATION;
×
UNCOV
519
        sixel_allocator_unref(allocator);
×
UNCOV
520
        goto end;
×
521
    }
522

523
    /*
524
     * Keep decoder GPU acceleration opt-in.  Bad environment values are
525
     * ignored so an inherited process environment cannot make decoder_new()
526
     * fail before the caller has a chance to set explicit options.
527
     */
528
    env_gpu_policy = sixel_compat_getenv(SIXEL_DECODER_GPU_POLICY_ENVVAR);
1,305✔
529
    if (env_gpu_policy != NULL) {
1,305✔
530
        match_detail[0] = '\0';
16✔
531
        if (sixel_option_match_choice(
16✔
532
                env_gpu_policy,
1✔
533
                g_decoder_gpu_policy_choices,
534
                sizeof(g_decoder_gpu_policy_choices) /
535
                sizeof(g_decoder_gpu_policy_choices[0]),
536
                &env_match_value,
537
                match_detail,
1✔
538
                sizeof(match_detail)) == SIXEL_OPTION_CHOICE_MATCH) {
1✔
539
            (*ppdecoder)->gpu_policy = env_match_value;
16✔
540
        }
1✔
541
    }
1✔
542

543
    status = SIXEL_OK;
735✔
544

545
end:
1,222✔
546
    return status;
1,305✔
547
}
548

549

550
/* deprecated version of sixel_decoder_new() */
551
SIXELAPI /* deprecated */ sixel_decoder_t *
UNCOV
552
sixel_decoder_create(void)
×
553
{
UNCOV
554
    SIXELSTATUS status = SIXEL_FALSE;
×
UNCOV
555
    sixel_decoder_t *decoder = NULL;
×
556

UNCOV
557
    status = sixel_decoder_new(&decoder, NULL);
×
UNCOV
558
    if (SIXEL_FAILED(status)) {
×
559
        goto end;
560
    }
561

562
end:
UNCOV
563
    return decoder;
×
564
}
565

566

567
/* destroy a decoder object */
568
static void
569
sixel_decoder_destroy(sixel_decoder_t *decoder)
1,289✔
570
{
571
    sixel_allocator_t *allocator;
563✔
572

573
    if (decoder) {
1,289!
574
        allocator = decoder->allocator;
1,289✔
575
        sixel_allocator_free(allocator, decoder->input);
1,289✔
576
        sixel_allocator_free(allocator, decoder->output);
1,289✔
577
        sixel_allocator_free(allocator, decoder);
1,289✔
578
        sixel_allocator_unref(allocator);
1,289✔
579
    }
82✔
580
}
1,289✔
581

582

583
/* increase reference count of decoder object (thread-safe) */
584
SIXELAPI void
585
sixel_decoder_ref(sixel_decoder_t *decoder)
3,243✔
586
{
587
    if (decoder == NULL) {
3,243✔
588
        return;
589
    }
590

591
    (void)sixel_atomic_fetch_add_u32(&decoder->ref, 1U);
3,243✔
592
}
208✔
593

594

595
/* decrease reference count of decoder object (thread-safe) */
596
SIXELAPI void
597
sixel_decoder_unref(sixel_decoder_t *decoder)
4,532✔
598
{
599
    unsigned int previous;
1,978✔
600

601
    if (decoder == NULL) {
4,532✔
602
        return;
603
    }
604

605
    previous = sixel_atomic_fetch_sub_u32(&decoder->ref, 1U);
4,532✔
606
    if (previous == 1U) {
4,532✔
607
        sixel_decoder_destroy(decoder);
1,289✔
608
    }
82✔
609
}
290✔
610

611

612
typedef struct sixel_similarity {
613
    const unsigned char *palette;
614
    int ncolors;
615
    int stride;
616
    signed char *cache;
617
    int bias;
618
#if defined(SIXEL_KUNDITHER_USE_NEON)
619
    int simd_level;
620
#endif
621
} sixel_similarity_t;
622

623
typedef struct sixel_kundither_filter_context {
624
    unsigned char const *indexed_pixels;
625
    unsigned char const *paint_mask;
626
    int width;
627
    int height;
628
    unsigned char const *palette;
629
    int ncolors;
630
    sixel_similarity_t *similarity;
631
    unsigned char *pixels;
632
    int pixel_size;
633
    int y_start;
634
    int y_end;
635
    int const (*neighbor_offsets)[4];
636
    int neighbor_count;
637
} sixel_kundither_filter_context_t;
638

639
#if SIXEL_ENABLE_THREADS
640
static const int g_kundither_neighbor_offsets[8][4] = {
641
    {-1, -1,  10, 16}, {0, -1, 16, 16}, {1, -1,   6, 16},
642
    {-1,  0,  11, 16},                  {1,  0,  11, 16},
643
    {-1,  1,   6, 16}, {0,  1, 16, 16}, {1,  1,  10, 16}
644
};
645
#endif
646

647
static const int g_kundither_fast4_neighbor_offsets[4][4] = {
648
    {-1, -1,  10, 16}, {0, -1, 16, 16}, {1, -1,   6, 16},
649
    {-1,  0,  11, 16}
650
};
651

652
static const int g_selective_blur_neighbor_offsets[8][3] = {
653
    {-1, -1, 1}, {0, -1, 2}, {1, -1, 1},
654
    {-1,  0, 2},             {1,  0, 2},
655
    {-1,  1, 1}, {0,  1, 2}, {1,  1, 1}
656
};
657

658
static SIXELSTATUS
659
sixel_similarity_init(sixel_similarity_t *similarity,
708✔
660
                      const unsigned char *palette,
661
                      int ncolors,
662
                      int bias,
663
                      sixel_allocator_t *allocator)
664
{
665
    size_t cache_size;
303✔
666
    int i;
303✔
667

668
    if (bias < 1) {
708!
669
        bias = 1;
670
    }
671

672
    similarity->palette = palette;
708✔
673
    similarity->ncolors = ncolors;
708✔
674
    similarity->stride = ncolors;
708✔
675
    similarity->bias = bias;
708✔
676
#if defined(SIXEL_KUNDITHER_USE_NEON)
677
    similarity->simd_level = SIXEL_SIMD_LEVEL_SCALAR;
678
#endif
679

680
    cache_size = (size_t)ncolors * (size_t)ncolors;
708✔
681
    if (cache_size == 0) {
708!
UNCOV
682
        similarity->cache = NULL;
×
UNCOV
683
        return SIXEL_OK;
×
684
    }
685

686
    similarity->cache = (signed char *)sixel_allocator_malloc(
708✔
687
        allocator,
51✔
688
        cache_size);
51✔
689
    if (similarity->cache == NULL) {
708!
UNCOV
690
        sixel_helper_set_additional_message(
×
691
            "sixel_similarity_init: sixel_allocator_malloc() failed.");
UNCOV
692
        return SIXEL_BAD_ALLOCATION;
×
693
    }
694
    memset(similarity->cache, -1, cache_size);
708✔
695
    for (i = 0; i < ncolors; ++i) {
80,552✔
696
        similarity->cache[i * similarity->stride + i] = 7;
79,844✔
697
    }
6,344✔
698

699
    return SIXEL_OK;
405✔
700
}
51✔
701

702
static void
703
sixel_similarity_destroy(sixel_similarity_t *similarity,
708✔
704
                         sixel_allocator_t *allocator)
705
{
706
    if (similarity->cache != NULL) {
708!
707
        sixel_allocator_free(allocator, similarity->cache);
453✔
708
        similarity->cache = NULL;
708✔
709
    }
51✔
710
}
405✔
711

712
static inline unsigned int
713
sixel_similarity_diff(const unsigned char *a, const unsigned char *b)
83,469,753✔
714
{
715
    int dr = (int)a[0] - (int)b[0];
83,469,753✔
716
    int dg = (int)a[1] - (int)b[1];
83,469,753✔
717
    int db = (int)a[2] - (int)b[2];
83,469,753✔
718
    return (unsigned int)(dr * dr + dg * dg + db * db);
83,469,753✔
719
}
720

721
static inline unsigned int
722
sixel_similarity_min_diff_scalar(const unsigned char *palette,
575,692✔
723
                                 int ncolors,
724
                                 int index1,
725
                                 int index2,
726
                                 const unsigned char *avg_color)
727
{
728
    unsigned int min_diff;
242,395✔
729
    const unsigned char *pk;
242,395✔
730
    unsigned int diff;
242,395✔
731
    int i;
242,395✔
732

733
    min_diff = UINT_MAX;
575,692✔
734
    for (i = 0; i < ncolors; ++i) {
84,618,118✔
735
        if (i == index1 || i == index2) {
84,030,950✔
736
            continue;
1,147,636✔
737
        }
738
        pk = palette + i * 3;
82,959,026✔
739
        diff = sixel_similarity_diff(avg_color, pk);
82,959,026✔
740
        if (diff < min_diff) {
82,894,790✔
741
            min_diff = diff;
38,471,319✔
742
        }
357,641✔
743
    }
6,246,415✔
744

745
    return min_diff;
587,168✔
746
}
747

748
#if defined(SIXEL_KUNDITHER_USE_NEON)
749
static inline unsigned int
750
sixel_similarity_min_diff_neon(const unsigned char *palette,
751
                               int ncolors,
752
                               int index1,
753
                               int index2,
754
                               const unsigned char *avg_color)
755
{
756
    uint16x8_t avg_r_vec;
757
    uint16x8_t avg_g_vec;
758
    uint16x8_t avg_b_vec;
759
    uint32x4_t min_vec;
760
    uint8x8x3_t colors;
761
    uint16x8_t r_u16;
762
    uint16x8_t g_u16;
763
    uint16x8_t b_u16;
764
    int16x8_t dr;
765
    int16x8_t dg;
766
    int16x8_t db;
767
    int32x4_t r2_lo;
768
    int32x4_t r2_hi;
769
    int32x4_t g2_lo;
770
    int32x4_t g2_hi;
771
    int32x4_t b2_lo;
772
    int32x4_t b2_hi;
773
    uint32x4_t sum_lo;
774
    uint32x4_t sum_hi;
775
    uint32_t lanes[4];
776
    unsigned int min_diff;
777
    const unsigned char *pk;
778
    unsigned int diff;
779
    int i;
780
    int k;
781

782
    min_diff = UINT_MAX;
783
    avg_r_vec = vdupq_n_u16((uint16_t)avg_color[0]);
784
    avg_g_vec = vdupq_n_u16((uint16_t)avg_color[1]);
785
    avg_b_vec = vdupq_n_u16((uint16_t)avg_color[2]);
786
    min_vec = vdupq_n_u32(UINT_MAX);
787
    i = 0;
788

789
    /*
790
     * vld3_u8 maps the interleaved RGB palette to three byte vectors.  Chunks
791
     * containing either endpoint fall back to scalar so the historical
792
     * "ignore index1/index2" rule stays byte-for-byte identical.
793
     */
794
    for (; i + 8 <= ncolors; i += 8) {
795
        if ((index1 >= i && index1 < i + 8) ||
796
            (index2 >= i && index2 < i + 8)) {
797
            for (k = i; k < i + 8; ++k) {
798
                if (k == index1 || k == index2) {
799
                    continue;
800
                }
801
                pk = palette + k * 3;
802
                diff = sixel_similarity_diff(avg_color, pk);
803
                if (diff < min_diff) {
804
                    min_diff = diff;
805
                }
806
            }
807
            continue;
808
        }
809

810
        colors = vld3_u8(palette + i * 3);
811
        r_u16 = vmovl_u8(colors.val[0]);
812
        g_u16 = vmovl_u8(colors.val[1]);
813
        b_u16 = vmovl_u8(colors.val[2]);
814
        dr = vreinterpretq_s16_u16(vsubq_u16(r_u16, avg_r_vec));
815
        dg = vreinterpretq_s16_u16(vsubq_u16(g_u16, avg_g_vec));
816
        db = vreinterpretq_s16_u16(vsubq_u16(b_u16, avg_b_vec));
817

818
        r2_lo = vmull_s16(vget_low_s16(dr), vget_low_s16(dr));
819
        r2_hi = vmull_s16(vget_high_s16(dr), vget_high_s16(dr));
820
        g2_lo = vmull_s16(vget_low_s16(dg), vget_low_s16(dg));
821
        g2_hi = vmull_s16(vget_high_s16(dg), vget_high_s16(dg));
822
        b2_lo = vmull_s16(vget_low_s16(db), vget_low_s16(db));
823
        b2_hi = vmull_s16(vget_high_s16(db), vget_high_s16(db));
824

825
        sum_lo = vreinterpretq_u32_s32(vaddq_s32(
826
            vaddq_s32(r2_lo, g2_lo),
827
            b2_lo));
828
        sum_hi = vreinterpretq_u32_s32(vaddq_s32(
829
            vaddq_s32(r2_hi, g2_hi),
830
            b2_hi));
831
        min_vec = vminq_u32(min_vec, sum_lo);
832
        min_vec = vminq_u32(min_vec, sum_hi);
833
    }
834

835
    vst1q_u32(lanes, min_vec);
836
    if (lanes[0] < min_diff) {
837
        min_diff = lanes[0];
838
    }
839
    if (lanes[1] < min_diff) {
840
        min_diff = lanes[1];
841
    }
842
    if (lanes[2] < min_diff) {
843
        min_diff = lanes[2];
844
    }
845
    if (lanes[3] < min_diff) {
846
        min_diff = lanes[3];
847
    }
848

849
    for (; i < ncolors; ++i) {
850
        if (i == index1 || i == index2) {
851
            continue;
852
        }
853
        pk = palette + i * 3;
854
        diff = sixel_similarity_diff(avg_color, pk);
855
        if (diff < min_diff) {
856
            min_diff = diff;
857
        }
858
    }
859

860
    return min_diff;
861
}
862
#endif
863

864
static unsigned int
865
sixel_similarity_compare(sixel_similarity_t *similarity,
575,681✔
866
                         int index1,
867
                         int index2,
868
                         int numerator,
869
                         int denominator)
870
{
871
    int min_index;
242,400✔
872
    int max_index;
242,400✔
873
    size_t cache_pos;
242,400✔
874
    signed char cached;
242,400✔
875
    const unsigned char *palette;
242,400✔
876
    const unsigned char *p1;
242,400✔
877
    const unsigned char *p2;
242,400✔
878
    unsigned char avg_color[3];
242,400✔
879
    unsigned int distance;
242,400✔
880
    unsigned int base_distance;
242,400✔
881
    unsigned long long scaled_distance;
242,400✔
882
    int bias;
242,400✔
883
    unsigned int min_diff;
242,400✔
884
    unsigned int result;
242,400✔
885

886
    if (similarity->cache == NULL) {
575,681✔
887
        return 0;
888
    }
889

890
    if (index1 < 0 || index1 >= similarity->ncolors ||
575,681!
891
        index2 < 0 || index2 >= similarity->ncolors) {
575,702!
892
        return 0;
11✔
893
    }
894

895
    if (index1 <= index2) {
575,711✔
896
        min_index = index1;
166,402✔
897
        max_index = index2;
166,402✔
898
    } else {
22,715✔
899
        min_index = index2;
288,365✔
900
        max_index = index1;
288,365✔
901
    }
902

903
    cache_pos = (size_t)min_index * (size_t)similarity->stride
621,154✔
904
              + (size_t)max_index;
575,711✔
905
    cached = similarity->cache[cache_pos];
575,711✔
906
    if (cached >= 0) {
575,711!
907
        return (unsigned int)cached;
×
908
    }
909

910
    palette = similarity->palette;
575,711✔
911
    p1 = palette + index1 * 3;
575,711✔
912
    p2 = palette + index2 * 3;
575,711✔
913

914
#if 1
915
   /*    original: n = (p1 + p2) / 2
916
    */
917
    avg_color[0] = (unsigned char)(((unsigned int)p1[0]
621,154✔
918
                                    + (unsigned int)p2[0]) >> 1);
575,711✔
919
    avg_color[1] = (unsigned char)(((unsigned int)p1[1]
621,154✔
920
                                    + (unsigned int)p2[1]) >> 1);
575,711✔
921
    avg_color[2] = (unsigned char)(((unsigned int)p1[2]
621,154✔
922
                                    + (unsigned int)p2[2]) >> 1);
575,711✔
923
    (void) numerator;
287,843✔
924
    (void) denominator;
287,843✔
925
#else
926
   /*
927
    *    diffuse(pos_a, n1) -> p1
928
    *    diffuse(pos_b, n2) -> p2
929
    *
930
    *    when n1 == n2 == n:
931
    *
932
    *    p2 = n + (n - p1) * numerator / denominator
933
    * => p2 * denominator = n * denominator + (n - p1) * numerator
934
    * => p2 * denominator = n * denominator + n * numerator - p1 * numerator
935
    * => n * (denominator + numerator) = p1 * numerator + p2 * denominator
936
    * => n = (p1 * numerator + p2 * denominator) / (denominator + numerator)
937
    *
938
    */
939
    avg_color[0] = (p1[0] * numerator + p2[0] * denominator + (numerator + denominator + 0.5) / 2)
940
                 / (numerator + denominator);
941
    avg_color[1] = (p1[1] * numerator + p2[1] * denominator + (numerator + denominator + 0.5) / 2)
942
                 / (numerator + denominator);
943
    avg_color[2] = (p1[2] * numerator + p2[2] * denominator + (numerator + denominator + 0.5) / 2)
944
                 / (numerator + denominator);
945
#endif
946

947
    distance = sixel_similarity_diff(avg_color, p1);
575,711✔
948
    bias = similarity->bias;
575,704✔
949
    if (bias < 1) {
575,704!
950
        bias = 1;
951
    }
952
    scaled_distance = (unsigned long long)distance
621,147✔
953
                    * (unsigned long long)bias
575,704✔
954
                    + 50ULL;
45,443✔
955
    base_distance = (unsigned int)(scaled_distance / 100ULL);
575,704✔
956
    if (base_distance == 0U) {
575,704!
957
        base_distance = 1U;
958
    }
959

960
#if defined(SIXEL_KUNDITHER_USE_NEON)
961
    if (similarity->simd_level >= SIXEL_SIMD_LEVEL_NEON) {
962
        min_diff = sixel_similarity_min_diff_neon(
963
            palette,
964
            similarity->ncolors,
965
            index1,
966
            index2,
967
            avg_color);
968
    } else {
969
        min_diff = sixel_similarity_min_diff_scalar(
970
            palette,
971
            similarity->ncolors,
972
            index1,
973
            index2,
974
            avg_color);
975
    }
976
#else
977
    min_diff = sixel_similarity_min_diff_scalar(
575,704✔
978
        palette,
45,443✔
979
        similarity->ncolors,
45,443✔
980
        index1,
45,443✔
981
        index2,
45,443✔
982
        avg_color);
45,443✔
983
#endif
984

985
    if (min_diff == UINT_MAX) {
575,709!
UNCOV
986
        min_diff = base_distance * 2U;
×
987
    }
988

989
    if (min_diff >= base_distance * 2U) {
575,709✔
990
        result = 5U;
13,640✔
991
    } else if (min_diff >= base_distance) {
553,860✔
992
        result = 8U;
19,577✔
993
    } else if ((unsigned long long)min_diff * 6ULL
520,885✔
994
               >= (unsigned long long)base_distance * 5ULL) {
518,227✔
995
        result = 7U;
8,279✔
996
    } else if ((unsigned long long)min_diff * 4ULL
505,044✔
997
               >= (unsigned long long)base_distance * 3ULL) {
503,919✔
998
        result = 7U;
3,182✔
999
    } else if ((unsigned long long)min_diff * 3ULL
498,832✔
1000
               >= (unsigned long long)base_distance * 2ULL) {
498,406✔
1001
        result = 5U;
5,636✔
1002
    } else if ((unsigned long long)min_diff * 5ULL
489,445✔
1003
               >= (unsigned long long)base_distance * 3ULL) {
282,995✔
1004
        result = 7U;
6,103✔
1005
    } else if ((unsigned long long)min_diff * 2ULL
478,968✔
1006
               >= (unsigned long long)base_distance * 1ULL) {
276,892✔
1007
        result = 4U;
11,858✔
1008
    } else if ((unsigned long long)min_diff * 3ULL
459,285✔
1009
               >= (unsigned long long)base_distance * 1ULL) {
265,034✔
1010
        result = 2U;
34,108✔
1011
    } else {
4,704✔
1012
        result = 0U;
398,858✔
1013
    }
1014

1015
    similarity->cache[cache_pos] = (signed char)result;
575,709✔
1016

1017
    return result;
575,709✔
1018
}
45,454✔
1019

1020
#if SIXEL_ENABLE_THREADS
1021
static void
1022
sixel_similarity_enable_simd(sixel_similarity_t *similarity)
35✔
1023
{
1024
#if defined(SIXEL_KUNDITHER_USE_NEON)
1025
    if (similarity != NULL && similarity->ncolors >= 80) {
1026
        /*
1027
         * NEON only amortizes in the precomputed high-color path.  Lazy scalar
1028
         * decoding is dominated by cache-hit lookups, and 32-64 color images
1029
         * benchmark better with the compiler's scalar loop on Apple Silicon.
1030
         */
1031
        similarity->simd_level = sixel_cpu_simd_level();
1032
    }
1033
#else
1034
    (void)similarity;
5✔
1035
#endif
1036
}
35✔
1037

1038
static void
1039
sixel_similarity_prepare_image_order_offsets(
60✔
1040
    sixel_similarity_t *similarity,
1041
    unsigned char const *indexed_pixels,
1042
    unsigned char const *paint_mask,
1043
    int width,
1044
    int height,
1045
    int ncolors,
1046
    int const (*neighbor_offsets)[4],
1047
    int neighbor_count)
1048
{
1049
    size_t prepared_pairs;
25✔
1050
    size_t target_pairs;
25✔
1051
    size_t cache_pos;
25✔
1052
    int palette_index;
25✔
1053
    int neighbor_index;
25✔
1054
    int min_index;
25✔
1055
    int max_index;
25✔
1056
    int neighbor;
25✔
1057
    int numerator;
25✔
1058
    int denominator;
25✔
1059
    int nx;
25✔
1060
    int ny;
25✔
1061
    int x;
25✔
1062
    int y;
25✔
1063

1064
    if (similarity == NULL || similarity->cache == NULL) {
60!
1065
        return;
1066
    }
1067
    if (neighbor_offsets == NULL || neighbor_count <= 0) {
60!
1068
        return;
1069
    }
1070

1071
    /*
1072
     * The similarity cache is indexed by an unordered palette pair, but the
1073
     * midpoint rounding in sixel_similarity_compare() is historically order
1074
     * sensitive.  Prepare cache entries by walking the image in scalar scan
1075
     * order so parallel workers read the same first-seen orientation.
1076
     */
1077
    prepared_pairs = 0u;
60✔
1078
    target_pairs = ((size_t)ncolors * (size_t)(ncolors - 1)) / 2u;
60✔
1079
    for (y = 0; y < height; ++y) {
30,036✔
1080
        for (x = 0; x < width; ++x) {
15,852,888✔
1081
            if (paint_mask != NULL &&
15,822,912✔
1082
                    paint_mask[y * width + x] == 0U) {
6,291,456✔
1083
                continue;
527,952✔
1084
            }
1085
            palette_index = indexed_pixels[y * width + x];
15,294,960✔
1086
            if (palette_index < 0 || palette_index >= ncolors) {
15,294,960!
1087
                palette_index = 0;
1088
            }
1089

1090
            for (neighbor = 0; neighbor < neighbor_count; ++neighbor) {
100,496,736✔
1091
                nx = x + neighbor_offsets[neighbor][0];
85,201,776✔
1092
                ny = y + neighbor_offsets[neighbor][1];
85,201,776✔
1093
                numerator = neighbor_offsets[neighbor][2];
85,201,776✔
1094
                denominator = neighbor_offsets[neighbor][3];
85,201,776✔
1095

1096
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
85,201,776✔
1097
                    continue;
249,276✔
1098
                }
1099
                if (paint_mask != NULL &&
84,952,500✔
1100
                        paint_mask[ny * width + nx] == 0U) {
34,392,060✔
1101
                    continue;
3,246,192✔
1102
                }
1103

1104
                neighbor_index = indexed_pixels[ny * width + nx];
81,706,308✔
1105
                if (neighbor_index < 0 || neighbor_index >= ncolors) {
81,706,308!
1106
                    continue;
1107
                }
1108
                if (palette_index == neighbor_index) {
81,706,308✔
1109
                    continue;
5,146,200✔
1110
                }
1111

1112
                if (palette_index <= neighbor_index) {
76,560,108✔
1113
                    min_index = palette_index;
20,410,047✔
1114
                    max_index = neighbor_index;
20,410,047✔
1115
                } else {
2,915,721✔
1116
                    min_index = neighbor_index;
41,571,456✔
1117
                    max_index = palette_index;
41,571,456✔
1118
                }
1119

1120
                cache_pos = (size_t)min_index * (size_t)similarity->stride
82,940,117✔
1121
                          + (size_t)max_index;
76,560,108✔
1122
                if (similarity->cache[cache_pos] >= 0) {
76,560,108✔
1123
                    continue;
76,372,788✔
1124
                }
1125

1126
                (void)sixel_similarity_compare(
187,320✔
1127
                    similarity,
15,610✔
1128
                    palette_index,
15,610✔
1129
                    neighbor_index,
15,610✔
1130
                    numerator,
15,610✔
1131
                    denominator);
15,610✔
1132
                prepared_pairs += 1u;
187,320✔
1133
                if (prepared_pairs >= target_pairs) {
187,320!
1134
                    return;
1135
                }
1136
            }
15,610✔
1137
        }
1,274,580✔
1138
    }
2,498✔
1139
}
5✔
1140
#endif
1141

1142
static inline unsigned int
1143
sixel_similarity_cached_compare(sixel_similarity_t *similarity,
166,194,006✔
1144
                                int index1,
1145
                                int index2,
1146
                                int numerator,
1147
                                int denominator)
1148
{
1149
    int min_index;
68,752,304✔
1150
    int max_index;
68,752,304✔
1151
    size_t cache_pos;
68,752,304✔
1152
    signed char cached;
68,752,304✔
1153

1154
    if (similarity == NULL || similarity->cache == NULL) {
166,194,006!
1155
        return 0U;
734,096✔
1156
    }
1157
    if (index1 < 0 || index1 >= similarity->ncolors ||
167,351,106!
1158
        index2 < 0 || index2 >= similarity->ncolors) {
167,712,579!
1159
        return 0U;
933,316✔
1160
    }
1161

1162
    if (index1 <= index2) {
167,809,628✔
1163
        min_index = index1;
51,944,762✔
1164
        max_index = index2;
51,944,762✔
1165
    } else {
6,220,816✔
1166
        min_index = index2;
81,404,662✔
1167
        max_index = index1;
81,404,662✔
1168
    }
1169

1170
    cache_pos = (size_t)min_index * (size_t)similarity->stride
180,629,804✔
1171
              + (size_t)max_index;
167,809,628✔
1172
    cached = similarity->cache[cache_pos];
167,809,628✔
1173
    if (cached >= 0) {
167,809,628✔
1174
        return (unsigned int)cached;
167,492,614✔
1175
    }
1176

1177
    return sixel_similarity_compare(
411,376✔
1178
        similarity,
29,823✔
1179
        index1,
29,823✔
1180
        index2,
29,823✔
1181
        numerator,
29,823✔
1182
        denominator);
29,823✔
1183
}
12,820,176✔
1184

1185
static inline int
1186
sixel_clamp(int value, int min_value, int max_value)
4,464✔
1187
{
1188
    if (value < min_value) {
4,464✔
1189
        return min_value;
270✔
1190
    }
1191
    if (value > max_value) {
4,124✔
1192
        return max_value;
270✔
1193
    }
1194
    return value;
2,916✔
1195
}
384✔
1196

1197
static inline int
1198
sixel_get_gray(const int *gray, int width, int height, int x, int y)
2,736✔
1199
{
1200
    int cx = sixel_clamp(x, 0, width - 1);
2,904✔
1201
    int cy = sixel_clamp(y, 0, height - 1);
1,896✔
1202
    return gray[cy * width + cx];
2,568✔
1203
}
1204

1205
static unsigned short
1206
sixel_prewitt_value(const int *gray, int width, int height, int x, int y)
384✔
1207
{
1208
    int top_prev = sixel_get_gray(gray, width, height, x - 1, y - 1);
384✔
1209
    int top_curr = sixel_get_gray(gray, width, height, x, y - 1);
384!
1210
    int top_next = sixel_get_gray(gray, width, height, x + 1, y - 1);
384!
1211
    int mid_prev = sixel_get_gray(gray, width, height, x - 1, y);
384!
1212
    int mid_next = sixel_get_gray(gray, width, height, x + 1, y);
384✔
1213
    int bot_prev = sixel_get_gray(gray, width, height, x - 1, y + 1);
384!
1214
    int bot_curr = sixel_get_gray(gray, width, height, x, y + 1);
384✔
1215
    int bot_next = sixel_get_gray(gray, width, height, x + 1, y + 1);
384✔
1216
    long gx = (long)top_next - (long)top_prev +
432✔
1217
              (long)mid_next - (long)mid_prev +
432✔
1218
              (long)bot_next - (long)bot_prev;
216✔
1219
    long gy = (long)bot_prev + (long)bot_curr + (long)bot_next -
432✔
1220
              (long)top_prev - (long)top_curr - (long)top_next;
408✔
1221
    unsigned long long ux;
168✔
1222
    unsigned long long uy;
168✔
1223
    unsigned long long magnitude;
168✔
1224

1225
    /*
1226
     * gx and gy are signed Prewitt gradients. Convert their absolute values
1227
     * before squaring so unsigned-overflow sanitizers do not see the modulo
1228
     * product that comes from casting a negative long directly to unsigned.
1229
     */
1230
    if (gx < 0L) {
384✔
1231
        ux = (unsigned long long)(-gx);
81✔
1232
    } else {
9✔
1233
        ux = (unsigned long long)gx;
135✔
1234
    }
1235
    if (gy < 0L) {
384✔
1236
        uy = (unsigned long long)(-gy);
108✔
1237
    } else {
12✔
1238
        uy = (unsigned long long)gy;
108✔
1239
    }
1240

1241
    magnitude = ux * ux + uy * uy;
384✔
1242
    magnitude /= 256ULL;
384✔
1243
    if (magnitude > 65535ULL) {
384!
1244
        magnitude = 65535ULL;
1245
    }
1246
    return (unsigned short)magnitude;
384✔
1247
}
1248

1249
static int
1250
sixel_get_gray_masked(const int *gray,
×
1251
                      unsigned char const *paint_mask,
1252
                      int width,
1253
                      int height,
1254
                      int center_x,
1255
                      int center_y,
1256
                      int x,
1257
                      int y)
1258
{
1259
    int cx;
1260
    int cy;
1261
    int center;
1262

UNCOV
1263
    cx = sixel_clamp(x, 0, width - 1);
×
UNCOV
1264
    cy = sixel_clamp(y, 0, height - 1);
×
UNCOV
1265
    center = center_y * width + center_x;
×
UNCOV
1266
    if (paint_mask != NULL && paint_mask[cy * width + cx] == 0U) {
×
UNCOV
1267
        return gray[center];
×
1268
    }
UNCOV
1269
    return gray[cy * width + cx];
×
1270
}
1271

1272
static unsigned short
UNCOV
1273
sixel_prewitt_value_masked(const int *gray,
×
1274
                           unsigned char const *paint_mask,
1275
                           int width,
1276
                           int height,
1277
                           int x,
1278
                           int y)
1279
{
1280
    int top_prev;
1281
    int top_curr;
1282
    int top_next;
1283
    int mid_prev;
1284
    int mid_next;
1285
    int bot_prev;
1286
    int bot_curr;
1287
    int bot_next;
1288
    long gx;
1289
    long gy;
1290
    unsigned long long ux;
1291
    unsigned long long uy;
1292
    unsigned long long magnitude;
1293

UNCOV
1294
    top_prev = sixel_get_gray_masked(gray, paint_mask, width, height,
×
1295
                                     x, y, x - 1, y - 1);
UNCOV
1296
    top_curr = sixel_get_gray_masked(gray, paint_mask, width, height,
×
1297
                                     x, y, x, y - 1);
UNCOV
1298
    top_next = sixel_get_gray_masked(gray, paint_mask, width, height,
×
1299
                                     x, y, x + 1, y - 1);
UNCOV
1300
    mid_prev = sixel_get_gray_masked(gray, paint_mask, width, height,
×
1301
                                     x, y, x - 1, y);
UNCOV
1302
    mid_next = sixel_get_gray_masked(gray, paint_mask, width, height,
×
1303
                                     x, y, x + 1, y);
UNCOV
1304
    bot_prev = sixel_get_gray_masked(gray, paint_mask, width, height,
×
1305
                                     x, y, x - 1, y + 1);
UNCOV
1306
    bot_curr = sixel_get_gray_masked(gray, paint_mask, width, height,
×
1307
                                     x, y, x, y + 1);
UNCOV
1308
    bot_next = sixel_get_gray_masked(gray, paint_mask, width, height,
×
1309
                                     x, y, x + 1, y + 1);
UNCOV
1310
    gx = (long)top_next - (long)top_prev +
×
UNCOV
1311
         (long)mid_next - (long)mid_prev +
×
1312
         (long)bot_next - (long)bot_prev;
UNCOV
1313
    gy = (long)bot_prev + (long)bot_curr + (long)bot_next -
×
UNCOV
1314
         (long)top_prev - (long)top_curr - (long)top_next;
×
1315

UNCOV
1316
    if (gx < 0L) {
×
1317
        ux = (unsigned long long)(-gx);
1318
    } else {
1319
        ux = (unsigned long long)gx;
1320
    }
UNCOV
1321
    if (gy < 0L) {
×
1322
        uy = (unsigned long long)(-gy);
1323
    } else {
1324
        uy = (unsigned long long)gy;
1325
    }
1326

UNCOV
1327
    magnitude = ux * ux + uy * uy;
×
UNCOV
1328
    magnitude /= 256ULL;
×
UNCOV
1329
    if (magnitude > 65535ULL) {
×
1330
        magnitude = 65535ULL;
1331
    }
UNCOV
1332
    return (unsigned short)magnitude;
×
1333
}
1334

1335
static unsigned short
1336
sixel_scale_threshold(unsigned short base, int percent)
624✔
1337
{
1338
    unsigned long long numerator;
272✔
1339
    unsigned long long scaled;
272✔
1340

1341
    if (percent <= 0) {
624✔
1342
        percent = 1;
334✔
1343
    }
38✔
1344

1345
    numerator = (unsigned long long)base * 100ULL
664✔
1346
              + (unsigned long long)percent / 2ULL;
488✔
1347
    scaled = numerator / (unsigned long long)percent;
624✔
1348
    if (scaled == 0ULL) {
624!
1349
        scaled = 1ULL;
1350
    }
1351
    if (scaled > USHRT_MAX) {
624!
1352
        scaled = USHRT_MAX;
1353
    }
1354

1355
    return (unsigned short)scaled;
624✔
1356
}
1357

1358
static inline void
1359
sixel_kundither_filter_noedge_range_offsets(
552✔
1360
    unsigned char const *indexed_pixels,
1361
    unsigned char const *paint_mask,
1362
    int width,
1363
    int height,
1364
    unsigned char const *palette,
1365
    int ncolors,
1366
    sixel_similarity_t *similarity,
1367
    unsigned char *pixels,
1368
    int pixel_size,
1369
    int y_start,
1370
    int y_end,
1371
    int const (*neighbor_offsets)[4],
1372
    int neighbor_count)
1373
{
1374
    const unsigned char *color;
232✔
1375
    size_t out_index;
232✔
1376
    size_t pixel_pos;
232✔
1377
    int palette_index;
232✔
1378
    unsigned int total_weight;
232✔
1379
    unsigned int accum_r;
232✔
1380
    unsigned int accum_g;
232✔
1381
    unsigned int accum_b;
232✔
1382
    int neighbor;
232✔
1383
    int nx;
232✔
1384
    int ny;
232✔
1385
    int numerator;
232✔
1386
    int denominator;
232✔
1387
    unsigned int weight;
232✔
1388
    const unsigned char *neighbor_color;
232✔
1389
    int neighbor_index;
232✔
1390
    int x;
232✔
1391
    int y;
232✔
1392

1393
    /*
1394
     * This path is intentionally independent per output pixel.  The edge
1395
     * preserving mode below has historical scan-order behaviour, but the
1396
     * common no-edge filter only depends on the indexed source and palette.
1397
     */
1398
    if (neighbor_offsets == NULL || neighbor_count <= 0) {
552!
1399
        return;
1400
    }
1401
    for (y = y_start; y < y_end; ++y) {
54,268✔
1402
        for (x = 0; x < width; ++x) {
28,328,148✔
1403
            pixel_pos = (size_t)y * (size_t)width + (size_t)x;
28,982,175✔
1404
            out_index = pixel_pos * (size_t)pixel_size;
28,982,175✔
1405
            if (paint_mask != NULL && paint_mask[pixel_pos] == 0U) {
28,982,175✔
1406
                pixels[out_index + 0U] = 0U;
766,729✔
1407
                pixels[out_index + 1U] = 0U;
766,729✔
1408
                pixels[out_index + 2U] = 0U;
766,729✔
1409
                if (pixel_size == 4) {
766,729!
1410
                    pixels[out_index + 3U] = 0U;
766,842✔
1411
                }
63,860✔
1412
                continue;
766,729✔
1413
            }
1414

1415
            palette_index = indexed_pixels[pixel_pos];
28,215,446✔
1416
            if (palette_index < 0 || palette_index >= ncolors) {
28,215,446!
1417
                palette_index = 0;
90,038✔
1418
            }
90,038✔
1419

1420
            color = palette + palette_index * 3;
28,125,408✔
1421
            accum_r = (unsigned int)color[0] * 8U;
28,125,408✔
1422
            accum_g = (unsigned int)color[1] * 8U;
28,125,408✔
1423
            accum_b = (unsigned int)color[2] * 8U;
28,125,408✔
1424
            total_weight = 8U;
28,125,408✔
1425

1426
            for (neighbor = 0; neighbor < neighbor_count; ++neighbor) {
152,231,315✔
1427
                nx = x + neighbor_offsets[neighbor][0];
124,727,292✔
1428
                ny = y + neighbor_offsets[neighbor][1];
124,727,292✔
1429
                numerator = neighbor_offsets[neighbor][2];
124,727,292✔
1430
                denominator = neighbor_offsets[neighbor][3];
124,727,292✔
1431

1432
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
124,727,292✔
1433
                    continue;
1,829,100✔
1434
                }
1435

1436
                pixel_pos = (size_t)ny * (size_t)width + (size_t)nx;
124,359,546✔
1437
                if (paint_mask != NULL && paint_mask[pixel_pos] == 0U) {
124,359,546✔
1438
                    continue;
4,092,112✔
1439
                }
1440

1441
                neighbor_index = indexed_pixels[pixel_pos];
120,267,434✔
1442
                if (neighbor_index < 0 || neighbor_index >= ncolors) {
120,267,434!
1443
                    continue;
849,688✔
1444
                }
1445

1446
                weight = sixel_similarity_cached_compare(
121,061,363✔
1447
                    similarity,
9,321,463✔
1448
                    palette_index,
9,321,463✔
1449
                    neighbor_index,
9,321,463✔
1450
                    numerator,
9,321,463✔
1451
                    denominator);
9,321,463✔
1452
                if (weight == 0U) {
120,369,468✔
1453
                    continue;
54,994,023✔
1454
                }
1455

1456
                neighbor_color = palette + neighbor_index * 3;
65,375,445✔
1457
                accum_r += (unsigned int)neighbor_color[0] * weight;
65,375,445✔
1458
                accum_g += (unsigned int)neighbor_color[1] * weight;
65,375,445✔
1459
                accum_b += (unsigned int)neighbor_color[2] * weight;
65,375,445✔
1460
                total_weight += weight;
65,375,445✔
1461
            }
5,159,354✔
1462

1463
            out_index = ((size_t)y * (size_t)width + (size_t)x) *
29,738,628✔
1464
                (size_t)pixel_size;
15,804,383✔
1465
            pixels[out_index + 0U] =
27,504,023✔
1466
                (unsigned char)(accum_r / total_weight);
27,504,023✔
1467
            pixels[out_index + 1U] =
27,504,023✔
1468
                (unsigned char)(accum_g / total_weight);
27,504,023✔
1469
            pixels[out_index + 2U] =
27,504,023✔
1470
                (unsigned char)(accum_b / total_weight);
27,504,023✔
1471
            if (pixel_size == 4) {
27,504,023✔
1472
                pixels[out_index + 3U] = 0xffU;
8,301,954✔
1473
            }
692,573✔
1474
        }
2,234,605✔
1475
    }
14,926✔
1476
}
10,346✔
1477

1478
#if SIXEL_ENABLE_THREADS
1479
static int
1480
sixel_kundither_filter_noedge_worker(void *arg)
216✔
1481
{
1482
    sixel_kundither_filter_context_t *context;
90✔
1483

1484
    context = (sixel_kundither_filter_context_t *)arg;
216✔
1485
    if (context == NULL) {
216✔
1486
        return SIXEL_BAD_ARGUMENT;
1487
    }
1488

1489
    sixel_kundither_filter_noedge_range_offsets(
216✔
1490
        context->indexed_pixels,
18✔
1491
        context->paint_mask,
18✔
1492
        context->width,
18✔
1493
        context->height,
18✔
1494
        context->palette,
18✔
1495
        context->ncolors,
18✔
1496
        context->similarity,
18✔
1497
        context->pixels,
18✔
1498
        context->pixel_size,
18✔
1499
        context->y_start,
18✔
1500
        context->y_end,
18✔
1501
        context->neighbor_offsets,
18✔
1502
        context->neighbor_count);
18✔
1503

1504
    return SIXEL_OK;
216✔
1505
}
18✔
1506

1507
static SIXELSTATUS
1508
sixel_kundither_filter_noedge_parallel_offsets(
60✔
1509
    unsigned char const *indexed_pixels,
1510
    unsigned char const *paint_mask,
1511
    int width,
1512
    int height,
1513
    unsigned char const *palette,
1514
    int ncolors,
1515
    sixel_similarity_t *similarity,
1516
    unsigned char *pixels,
1517
    int pixel_size,
1518
    int threads,
1519
    int const (*neighbor_offsets)[4],
1520
    int neighbor_count)
1521
{
1522
    sixel_thread_t *workers;
25✔
1523
    sixel_kundither_filter_context_t *contexts;
25✔
1524
    size_t num_pixels;
25✔
1525
    int rows_per_thread;
25✔
1526
    int y_start;
25✔
1527
    int y_end;
25✔
1528
    int created;
25✔
1529
    int i;
25✔
1530
    int status;
25✔
1531
    int failed;
25✔
1532

1533
    workers = NULL;
60✔
1534
    contexts = NULL;
60✔
1535
    num_pixels = (size_t)width * (size_t)height;
60✔
1536
    rows_per_thread = 0;
60✔
1537
    y_start = 0;
60✔
1538
    y_end = 0;
60✔
1539
    created = 0;
60✔
1540
    status = SIXEL_FALSE;
60✔
1541
    failed = 0;
60✔
1542

1543
    if (threads < 2 || num_pixels < 65536U) {
60!
1544
        return SIXEL_FALSE;
1545
    }
1546
    if (neighbor_offsets == NULL || neighbor_count <= 0) {
60!
1547
        return SIXEL_FALSE;
1548
    }
1549
    if (threads > height) {
60!
1550
        threads = height;
1551
    }
1552
    if (threads < 2) {
60!
1553
        return SIXEL_FALSE;
1554
    }
1555

1556
    sixel_similarity_enable_simd(similarity);
60✔
1557
    sixel_similarity_prepare_image_order_offsets(
60✔
1558
        similarity,
5✔
1559
        indexed_pixels,
5✔
1560
        paint_mask,
5✔
1561
        width,
5✔
1562
        height,
5✔
1563
        ncolors,
5✔
1564
        neighbor_offsets,
5✔
1565
        neighbor_count);
5✔
1566

1567
    workers = (sixel_thread_t *)calloc((size_t)threads,
60✔
1568
                                       sizeof(sixel_thread_t));
1569
    contexts = (sixel_kundither_filter_context_t *)calloc(
60✔
1570
        (size_t)threads,
5✔
1571
        sizeof(sixel_kundither_filter_context_t));
1572
    if (workers == NULL || contexts == NULL) {
60!
1573
        free(workers);
1574
        free(contexts);
1575
        return SIXEL_FALSE;
1576
    }
1577

1578
    rows_per_thread = (height + threads - 1) / threads;
60✔
1579
    for (i = 0; i < threads; ++i) {
276✔
1580
        y_start = i * rows_per_thread;
216✔
1581
        y_end = y_start + rows_per_thread;
216✔
1582
        if (y_start >= height) {
216!
1583
            break;
1584
        }
1585
        if (y_end > height) {
216!
1586
            y_end = height;
1587
        }
1588

1589
        contexts[i].indexed_pixels = indexed_pixels;
216✔
1590
        contexts[i].paint_mask = paint_mask;
216✔
1591
        contexts[i].width = width;
216✔
1592
        contexts[i].height = height;
216✔
1593
        contexts[i].palette = palette;
216✔
1594
        contexts[i].ncolors = ncolors;
216✔
1595
        contexts[i].similarity = similarity;
216✔
1596
        contexts[i].pixels = pixels;
216✔
1597
        contexts[i].pixel_size = pixel_size;
216✔
1598
        contexts[i].y_start = y_start;
216✔
1599
        contexts[i].y_end = y_end;
216✔
1600
        contexts[i].neighbor_offsets = neighbor_offsets;
216✔
1601
        contexts[i].neighbor_count = neighbor_count;
216✔
1602

1603
        status = sixel_thread_create(
306✔
1604
            &workers[i],
216✔
1605
            sixel_kundither_filter_noedge_worker,
1606
            &contexts[i]);
126✔
1607
        if (SIXEL_FAILED(status)) {
216!
1608
            failed = 1;
1609
            break;
1610
        }
1611
        created += 1;
216✔
1612
    }
18✔
1613

1614
    for (i = 0; i < created; ++i) {
276✔
1615
        sixel_thread_join(&workers[i]);
216✔
1616
    }
18✔
1617

1618
    free(workers);
60✔
1619
    free(contexts);
60✔
1620

1621
    if (failed || created < 2) {
60!
1622
        return SIXEL_FALSE;
1623
    }
1624

1625
    return SIXEL_OK;
35✔
1626
}
5✔
1627

1628
static SIXELSTATUS
1629
sixel_kundither_filter_noedge_parallel(unsigned char const *indexed_pixels,
12✔
1630
                                       int width,
1631
                                       int height,
1632
                                       unsigned char const *palette,
1633
                                       int ncolors,
1634
                                       sixel_similarity_t *similarity,
1635
                                       unsigned char *rgb,
1636
                                       int threads)
1637
{
1638
    return sixel_kundither_filter_noedge_parallel_offsets(
12✔
1639
        indexed_pixels,
1✔
1640
        NULL,
1641
        width,
1✔
1642
        height,
1✔
1643
        palette,
1✔
1644
        ncolors,
1✔
1645
        similarity,
1✔
1646
        rgb,
1✔
1647
        3,
1648
        threads,
1✔
1649
        g_kundither_neighbor_offsets,
1650
        8);
1651
}
1652
#endif  /* SIXEL_ENABLE_THREADS */
1653

1654
static SIXELSTATUS
1655
sixel_dequantize_k_undither_scalar_common(
312✔
1656
    unsigned char *indexed_pixels,
1657
    unsigned char const *paint_mask,
1658
    int width,
1659
    int height,
1660
    unsigned char *palette,
1661
    int ncolors,
1662
    int similarity_bias,
1663
    int edge_strength,
1664
    int pixel_size,
1665
    sixel_allocator_t *allocator,
1666
    unsigned char **output)
1667
{
1668
    SIXELSTATUS status = SIXEL_FALSE;
312✔
1669
    unsigned char *pixels = NULL;
312✔
1670
    int *gray = NULL;
312✔
1671
    unsigned short *prewitt = NULL;
312✔
1672
    sixel_similarity_t similarity;
136✔
1673
    size_t num_pixels;
136✔
1674
    int x;
136✔
1675
    int y;
136✔
1676
    unsigned short strong_threshold;
136✔
1677
    unsigned short detail_threshold;
136✔
1678
    static const int neighbor_offsets[8][4] = {
136✔
1679
        {-1, -1,  10, 16}, {0, -1, 16, 16}, {1, -1,   6, 16},
1680
        {-1,  0,  11, 16},                  {1,  0,  11, 16},
1681
        {-1,  1,   6, 16}, {0,  1, 16, 16}, {1,  1,  10, 16}
1682
    };
1683
    const unsigned char *color;
136✔
1684
    size_t out_index;
136✔
1685
    size_t pixel_pos;
136✔
1686
    int palette_index;
136✔
1687
    unsigned int center_weight;
136✔
1688
    unsigned int total_weight = 0;
312✔
1689
    unsigned int accum_r;
136✔
1690
    unsigned int accum_g;
136✔
1691
    unsigned int accum_b;
136✔
1692
    unsigned short gradient;
136✔
1693
    int neighbor;
136✔
1694
    int nx;
136✔
1695
    int ny;
136✔
1696
    int numerator;
136✔
1697
    int denominator;
136✔
1698
    unsigned int weight;
136✔
1699
    const unsigned char *neighbor_color;
136✔
1700
    int neighbor_index;
136✔
1701

1702
    if (width <= 0 || height <= 0 || palette == NULL || ncolors <= 0 ||
312!
1703
            pixel_size < 3 || pixel_size > 4 || output == NULL) {
312!
1704
        return SIXEL_BAD_INPUT;
1705
    }
1706

1707
    if ((size_t)width > ((size_t)-1 / (size_t)height)) {
312!
1708
        return SIXEL_BAD_ALLOCATION;
1709
    }
1710

1711
    num_pixels = (size_t)width * (size_t)height;
312✔
1712
    if (num_pixels > ((size_t)-1 / (size_t)pixel_size)) {
312!
1713
        return SIXEL_BAD_ALLOCATION;
1714
    }
1715

1716
    memset(&similarity, 0, sizeof(sixel_similarity_t));
312!
1717

1718
    strong_threshold = sixel_scale_threshold(256U, edge_strength);
312!
1719
    detail_threshold = sixel_scale_threshold(160U, edge_strength);
312!
1720
    if (strong_threshold < detail_threshold) {
312!
1721
        strong_threshold = detail_threshold;
1722
    }
1723

1724
    /*
1725
     * Build RGB and luminance buffers so we can reuse the similarity cache
1726
     * and gradient analysis across the reconstructed image.
1727
     */
1728
    pixels = (unsigned char *)sixel_allocator_malloc(
410✔
1729
        allocator,
20✔
1730
        num_pixels * (size_t)pixel_size);
274✔
1731
    if (pixels == NULL) {
312!
UNCOV
1732
        sixel_helper_set_additional_message(
×
1733
            "sixel_dequantize_k_undither: "
1734
            "sixel_allocator_malloc() failed.");
UNCOV
1735
        status = SIXEL_BAD_ALLOCATION;
×
UNCOV
1736
        goto end;
×
1737
    }
1738

1739
    gray = (int *)sixel_allocator_malloc(
410✔
1740
        allocator,
20✔
1741
        num_pixels * sizeof(int));
216✔
1742
    if (gray == NULL) {
312!
UNCOV
1743
        sixel_helper_set_additional_message(
×
1744
            "sixel_dequantize_k_undither: "
1745
            "sixel_allocator_malloc() failed.");
UNCOV
1746
        status = SIXEL_BAD_ALLOCATION;
×
1747
        goto end;
×
1748
    }
1749

1750
    prewitt = (unsigned short *)sixel_allocator_malloc(
410✔
1751
        allocator,
20✔
1752
        num_pixels * sizeof(unsigned short));
216✔
1753
    if (prewitt == NULL) {
312!
UNCOV
1754
        sixel_helper_set_additional_message(
×
1755
            "sixel_dequantize_k_undither: "
1756
            "sixel_allocator_malloc() failed.");
UNCOV
1757
        status = SIXEL_BAD_ALLOCATION;
×
UNCOV
1758
        goto end;
×
1759
    }
1760

1761
    /*
1762
     * Pre-compute palette distance heuristics so each neighbour lookup reuses
1763
     * the k_undither-style similarity table.
1764
     */
1765
    status = sixel_similarity_init(
312✔
1766
        &similarity,
1767
        palette,
20✔
1768
        ncolors,
20✔
1769
        similarity_bias,
20✔
1770
        allocator);
20✔
1771
    if (SIXEL_FAILED(status)) {
312!
UNCOV
1772
        goto end;
×
1773
    }
1774

1775
    for (y = 0; y < height; ++y) {
18,104✔
1776
        for (x = 0; x < width; ++x) {
6,676,896✔
1777
            pixel_pos = (size_t)y * (size_t)width + (size_t)x;
6,659,104✔
1778
            out_index = pixel_pos * (size_t)pixel_size;
6,659,104✔
1779
            if (paint_mask != NULL && paint_mask[pixel_pos] == 0U) {
6,659,104✔
1780
                pixels[out_index + 0U] = 0U;
286,036✔
1781
                pixels[out_index + 1U] = 0U;
286,036✔
1782
                pixels[out_index + 2U] = 0U;
286,036✔
1783
                if (pixel_size == 4) {
286,036!
1784
                    pixels[out_index + 3U] = 0U;
286,036✔
1785
                }
23,835✔
1786
                if (edge_strength > 0) {
286,036!
UNCOV
1787
                    gray[pixel_pos] = 0;
×
1788
                }
1789
                continue;
286,036✔
1790
            }
1791

1792
            palette_index = indexed_pixels[pixel_pos];
6,373,068✔
1793
            if (palette_index < 0 || palette_index >= ncolors) {
6,373,068!
UNCOV
1794
                palette_index = 0;
×
1795
            }
1796

1797
            color = palette + palette_index * 3;
6,373,068✔
1798
            pixels[out_index + 0U] = color[0];
6,373,068✔
1799
            pixels[out_index + 1U] = color[1];
6,373,068✔
1800
            pixels[out_index + 2U] = color[2];
6,373,068✔
1801
            if (pixel_size == 4) {
6,373,068✔
1802
                pixels[out_index + 3U] = 0xffU;
2,859,884✔
1803
            }
238,321✔
1804

1805
            if (edge_strength > 0) {
6,373,068✔
1806
                gray[pixel_pos] = (int)color[0]
408✔
1807
                                 + (int)color[1] * 2
384✔
1808
                                 + (int)color[2];
384✔
1809
            }
24✔
1810
        }
523,431✔
1811
    }
1,368✔
1812

1813
    if (edge_strength > 0) {
312✔
1814
        /*
1815
         * Prewitt samples neighbouring pixels, including rows and columns that
1816
         * appear later in scan order.  Fill the whole luminance buffer before
1817
         * computing gradients so the edge decision is deterministic under
1818
         * sanitizer and threaded builds.
1819
         */
1820
        for (y = 0; y < height; ++y) {
80✔
1821
            for (x = 0; x < width; ++x) {
448✔
1822
                pixel_pos = (size_t)y * (size_t)width + (size_t)x;
384✔
1823
                if (paint_mask != NULL && paint_mask[pixel_pos] == 0U) {
384!
1824
                    prewitt[pixel_pos] = 0U;
×
1825
                } else if (paint_mask != NULL) {
216!
UNCOV
1826
                    prewitt[pixel_pos] = sixel_prewitt_value_masked(
×
1827
                        gray,
1828
                        paint_mask,
1829
                        width,
1830
                        height,
1831
                        x,
1832
                        y);
1833
                } else {
1834
                    prewitt[pixel_pos] = sixel_prewitt_value(
384✔
1835
                        gray,
24✔
1836
                        width,
24✔
1837
                        height,
24✔
1838
                        x,
24✔
1839
                        y);
24✔
1840
                }
1841
            }
24✔
1842
        }
4✔
1843
    }
1✔
1844

1845
    for (y = 0; y < height; ++y) {
18,104✔
1846
        for (x = 0; x < width; ++x) {
6,676,896✔
1847
            pixel_pos = (size_t)y * (size_t)width + (size_t)x;
6,659,104✔
1848
            out_index = pixel_pos * (size_t)pixel_size;
6,659,104✔
1849
            if (paint_mask != NULL && paint_mask[pixel_pos] == 0U) {
6,659,104✔
1850
                continue;
286,036✔
1851
            }
1852

1853
            palette_index = indexed_pixels[pixel_pos];
6,373,068✔
1854
            if (palette_index < 0 || palette_index >= ncolors) {
6,373,068!
UNCOV
1855
                palette_index = 0;
×
1856
            }
1857

1858
            if (edge_strength > 0) {
6,373,068✔
1859
                gradient = prewitt[pixel_pos];
384✔
1860
                if (gradient > strong_threshold) {
384✔
1861
                    continue;
208✔
1862
                }
1863

1864
                if (gradient > detail_threshold) {
176✔
1865
                    center_weight = 24U;
54✔
1866
                } else {
6✔
1867
                    center_weight = 8U;
2,663,015✔
1868
                }
1869
            } else {
11✔
1870
                center_weight = 8U;
3,709,749✔
1871
            }
1872

1873
            accum_r = (unsigned int)pixels[out_index + 0U] *
6,896,278✔
1874
                center_weight;
523,418✔
1875
            accum_g = (unsigned int)pixels[out_index + 1U] *
6,896,278✔
1876
                center_weight;
523,418✔
1877
            accum_b = (unsigned int)pixels[out_index + 2U] *
6,896,278✔
1878
                center_weight;
523,418✔
1879
            total_weight = center_weight;
6,372,860✔
1880

1881
            /*
1882
             * Blend neighbours that stay within the palette similarity
1883
             * threshold so Floyd-Steinberg noise is averaged away without
1884
             * bleeding across pronounced edges.
1885
             */
1886
            for (neighbor = 0; neighbor < 8; ++neighbor) {
57,355,740✔
1887
                nx = x + neighbor_offsets[neighbor][0];
50,982,880✔
1888
                ny = y + neighbor_offsets[neighbor][1];
50,982,880✔
1889
                numerator = neighbor_offsets[neighbor][2];
50,982,880✔
1890
                denominator = neighbor_offsets[neighbor][3];
50,982,880✔
1891

1892
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
50,982,880✔
1893
                    continue;
243,716✔
1894
                }
1895

1896
                pixel_pos = (size_t)ny * (size_t)width + (size_t)nx;
50,739,164✔
1897
                if (paint_mask != NULL && paint_mask[pixel_pos] == 0U) {
50,739,164✔
1898
                    continue;
2,281,156✔
1899
                }
1900

1901
                neighbor_index = indexed_pixels[pixel_pos];
48,458,008✔
1902
                if (neighbor_index < 0 || neighbor_index >= ncolors) {
48,458,008!
UNCOV
1903
                    continue;
×
1904
                }
1905

1906
                if (numerator) {
48,458,008!
1907
                    weight = sixel_similarity_cached_compare(
48,458,008✔
1908
                        &similarity,
1909
                        palette_index,
3,979,086✔
1910
                        neighbor_index,
3,979,086✔
1911
                        numerator,
3,979,086✔
1912
                        denominator);
3,979,086✔
1913
                    if (weight == 0) {
48,458,008✔
1914
                        continue;
35,490,200✔
1915
                    }
1916

1917
                    neighbor_color = palette + neighbor_index * 3;
12,967,808✔
1918
                    accum_r += (unsigned int)neighbor_color[0] * weight;
12,967,808✔
1919
                    accum_g += (unsigned int)neighbor_color[1] * weight;
12,967,808✔
1920
                    accum_b += (unsigned int)neighbor_color[2] * weight;
12,967,808✔
1921
                    total_weight += weight;
12,967,808✔
1922
                }
1,027,450✔
1923
            }
1,027,450✔
1924

1925
            if (total_weight > 0U) {
6,372,860!
1926
                out_index = ((size_t)y * (size_t)width + (size_t)x) *
6,896,278✔
1927
                    (size_t)pixel_size;
3,709,848✔
1928
                pixels[out_index + 0U] =
6,372,860✔
1929
                    (unsigned char)(accum_r / total_weight);
6,372,860✔
1930
                pixels[out_index + 1U] =
6,372,860✔
1931
                    (unsigned char)(accum_g / total_weight);
6,372,860✔
1932
                pixels[out_index + 2U] =
6,372,860✔
1933
                    (unsigned char)(accum_b / total_weight);
6,372,860✔
1934
            }
523,418✔
1935
        }
523,418✔
1936
    }
1,368✔
1937

1938

1939
    *output = pixels;
312✔
1940
    pixels = NULL;
312✔
1941
    status = SIXEL_OK;
312✔
1942

1943
end:
292✔
1944
    sixel_similarity_destroy(&similarity, allocator);
312!
1945
    sixel_allocator_free(allocator, pixels);
312✔
1946
    sixel_allocator_free(allocator, gray);
312✔
1947
    sixel_allocator_free(allocator, prewitt);
312✔
1948
    return status;
312✔
1949
}
20✔
1950

1951
static SIXELSTATUS
1952
sixel_dequantize_k_undither_scalar(unsigned char *indexed_pixels,
204✔
1953
                                   int width,
1954
                                   int height,
1955
                                   unsigned char *palette,
1956
                                   int ncolors,
1957
                                   int similarity_bias,
1958
                                   int edge_strength,
1959
                                   sixel_allocator_t *allocator,
1960
                                   unsigned char **output)
1961
{
1962
    return sixel_dequantize_k_undither_scalar_common(
204✔
1963
        indexed_pixels,
13✔
1964
        NULL,
1965
        width,
13✔
1966
        height,
13✔
1967
        palette,
13✔
1968
        ncolors,
13✔
1969
        similarity_bias,
13✔
1970
        edge_strength,
13✔
1971
        3,
1972
        allocator,
13✔
1973
        output);
13✔
1974
}
1975

1976
SIXEL_INTERNAL_API SIXELSTATUS
1977
sixel_dequantize_k_undither(unsigned char *indexed_pixels,
216✔
1978
                            int width,
1979
                            int height,
1980
                            unsigned char *palette,
1981
                            int ncolors,
1982
                            int similarity_bias,
1983
                            int edge_strength,
1984
                            sixel_allocator_t *allocator,
1985
                            unsigned char **output)
1986
{
1987
#if SIXEL_ENABLE_THREADS
1988
    SIXELSTATUS status;
70✔
1989
    sixel_similarity_t similarity;
70✔
1990
    unsigned char *rgb;
70✔
1991
    size_t num_pixels;
70✔
1992
    SIXELSTATUS parallel_status;
70✔
1993
    int parallel_threads;
70✔
1994

1995
    memset(&similarity, 0, sizeof(sixel_similarity_t));
168✔
1996
    rgb = NULL;
168✔
1997
    parallel_threads = 1;
168✔
1998

1999
    /*
2000
     * The no-edge filter reads only the source index image and palette, so
2001
     * row bands are independent.  Edge-preserving mode intentionally keeps
2002
     * the historical scalar scan order because its gradient buffer is filled
2003
     * as the loop advances.
2004
     */
2005
    if (edge_strength <= 0 &&
168!
2006
        width > 0 &&
161!
2007
        height > 0 &&
156!
2008
        palette != NULL &&
156!
2009
        ncolors > 0 &&
91✔
2010
        (size_t)width <= ((size_t)-1 / (size_t)height)) {
156!
2011
        num_pixels = (size_t)width * (size_t)height;
156✔
2012
        parallel_threads = sixel_threads_resolve();
156✔
2013
        if (parallel_threads >= 2 && num_pixels >= 65536U) {
156✔
2014
            rgb = (unsigned char *)sixel_allocator_malloc(
12✔
2015
                allocator,
1✔
2016
                num_pixels * 3);
1✔
2017
            if (rgb == NULL) {
12!
2018
                sixel_helper_set_additional_message(
2019
                    "sixel_dequantize_k_undither: "
2020
                    "sixel_allocator_malloc() failed.");
2021
                return SIXEL_BAD_ALLOCATION;
2022
            }
2023

2024
            status = sixel_similarity_init(
12✔
2025
                &similarity,
2026
                palette,
1✔
2027
                ncolors,
1✔
2028
                similarity_bias,
1✔
2029
                allocator);
1✔
2030
            if (SIXEL_FAILED(status)) {
12!
2031
                sixel_allocator_free(allocator, rgb);
2032
                return status;
2033
            }
2034

2035
            parallel_status = sixel_kundither_filter_noedge_parallel(
12✔
2036
                indexed_pixels,
1✔
2037
                width,
1✔
2038
                height,
1✔
2039
                palette,
1✔
2040
                ncolors,
1✔
2041
                &similarity,
2042
                rgb,
1✔
2043
                parallel_threads);
1✔
2044
            if (parallel_status == SIXEL_OK) {
12!
2045
                sixel_similarity_destroy(&similarity, allocator);
12!
2046
                *output = rgb;
12✔
2047
                return SIXEL_OK;
12✔
2048
            }
2049

2050
            sixel_similarity_destroy(&similarity, allocator);
×
2051
            sixel_allocator_free(allocator, rgb);
2052
        }
2053
    }
12✔
2054
#endif
2055

2056
    return sixel_dequantize_k_undither_scalar(
198✔
2057
        indexed_pixels,
13✔
2058
        width,
13✔
2059
        height,
13✔
2060
        palette,
13✔
2061
        ncolors,
13✔
2062
        similarity_bias,
13✔
2063
        edge_strength,
13✔
2064
        allocator,
13✔
2065
        output);
13✔
2066
}
14✔
2067

2068
SIXEL_INTERNAL_API SIXELSTATUS
2069
sixel_dequantize_k_undither_rgba(unsigned char *indexed_pixels,
120✔
2070
                                 unsigned char const *paint_mask,
2071
                                 int width,
2072
                                 int height,
2073
                                 unsigned char *palette,
2074
                                 int ncolors,
2075
                                 int similarity_bias,
2076
                                 int edge_strength,
2077
                                 sixel_allocator_t *allocator,
2078
                                 unsigned char **output)
2079
{
2080
#if SIXEL_ENABLE_THREADS
2081
    SIXELSTATUS status;
40✔
2082
    sixel_similarity_t similarity;
40✔
2083
    unsigned char *rgba;
40✔
2084
    size_t num_pixels;
40✔
2085
    SIXELSTATUS parallel_status;
40✔
2086
    int parallel_threads;
40✔
2087

2088
    memset(&similarity, 0, sizeof(sixel_similarity_t));
96!
2089
    rgba = NULL;
96✔
2090
    parallel_threads = 1;
96✔
2091

2092
    if (edge_strength <= 0 &&
96!
2093
        width > 0 &&
96!
2094
        height > 0 &&
96!
2095
        palette != NULL &&
96!
2096
        ncolors > 0 &&
96!
2097
        output != NULL &&
96!
2098
        (size_t)width <= ((size_t)-1 / (size_t)height)) {
96!
2099
        num_pixels = (size_t)width * (size_t)height;
96✔
2100
        parallel_threads = sixel_threads_resolve();
96✔
2101
        if (parallel_threads >= 2 && num_pixels >= 65536U) {
96✔
2102
            if (num_pixels > ((size_t)-1 / 4u)) {
12!
2103
                return SIXEL_BAD_ALLOCATION;
2104
            }
2105
            rgba = (unsigned char *)sixel_allocator_malloc(
12✔
2106
                allocator,
1✔
2107
                num_pixels * 4u);
1✔
2108
            if (rgba == NULL) {
12!
2109
                sixel_helper_set_additional_message(
2110
                    "sixel_dequantize_k_undither_rgba: "
2111
                    "sixel_allocator_malloc() failed.");
2112
                return SIXEL_BAD_ALLOCATION;
2113
            }
2114

2115
            status = sixel_similarity_init(
12✔
2116
                &similarity,
2117
                palette,
1✔
2118
                ncolors,
1✔
2119
                similarity_bias,
1✔
2120
                allocator);
1✔
2121
            if (SIXEL_FAILED(status)) {
12!
2122
                sixel_allocator_free(allocator, rgba);
2123
                return status;
2124
            }
2125

2126
            parallel_status = sixel_kundither_filter_noedge_parallel_offsets(
12✔
2127
                indexed_pixels,
1✔
2128
                paint_mask,
1✔
2129
                width,
1✔
2130
                height,
1✔
2131
                palette,
1✔
2132
                ncolors,
1✔
2133
                &similarity,
2134
                rgba,
1✔
2135
                4,
2136
                parallel_threads,
1✔
2137
                g_kundither_neighbor_offsets,
2138
                8);
2139
            if (parallel_status == SIXEL_OK) {
12!
2140
                sixel_similarity_destroy(&similarity, allocator);
12!
2141
                *output = rgba;
12✔
2142
                return SIXEL_OK;
12✔
2143
            }
2144

2145
            sixel_similarity_destroy(&similarity, allocator);
×
2146
            sixel_allocator_free(allocator, rgba);
2147
        }
2148
    }
7✔
2149
#endif
2150

2151
    return sixel_dequantize_k_undither_scalar_common(
102✔
2152
        indexed_pixels,
7✔
2153
        paint_mask,
7✔
2154
        width,
7✔
2155
        height,
7✔
2156
        palette,
7✔
2157
        ncolors,
7✔
2158
        similarity_bias,
7✔
2159
        edge_strength,
7✔
2160
        4,
2161
        allocator,
7✔
2162
        output);
7✔
2163
}
8✔
2164

2165
SIXEL_INTERNAL_API SIXELSTATUS
2166
sixel_dequantize_k_undither_fast4_rows(
216✔
2167
    unsigned char const *indexed_pixels,
2168
    int width,
2169
    int height,
2170
    unsigned char const *palette,
2171
    int ncolors,
2172
    int similarity_bias,
2173
    int y_start,
2174
    int y_end,
2175
    sixel_allocator_t *allocator,
2176
    unsigned char *rgb)
2177
{
2178
    SIXELSTATUS status;
90✔
2179
    sixel_similarity_t similarity;
90✔
2180

2181
    memset(&similarity, 0, sizeof(similarity));
216!
2182

2183
    if (indexed_pixels == NULL || width <= 0 || height <= 0 ||
216!
2184
            palette == NULL || ncolors <= 0 || rgb == NULL ||
215!
2185
            y_start < 0 || y_end < y_start || y_end > height) {
215!
2186
        return SIXEL_BAD_INPUT;
1✔
2187
    }
2188

2189
    /*
2190
     * Decode-fused workers call this helper on a local image that includes
2191
     * the previous SIXEL band as a top halo.  The caller chooses y_start so
2192
     * only body rows are copied to the final output.
2193
     */
2194
    status = sixel_similarity_init(
215✔
2195
        &similarity,
2196
        palette,
18✔
2197
        ncolors,
18✔
2198
        similarity_bias,
18✔
2199
        allocator);
18✔
2200
    if (SIXEL_FAILED(status)) {
216!
2201
        return status;
2202
    }
2203

2204
    sixel_kundither_filter_noedge_range_offsets(
216✔
2205
        indexed_pixels,
18✔
2206
        NULL,
2207
        width,
18✔
2208
        height,
18✔
2209
        palette,
18✔
2210
        ncolors,
18✔
2211
        &similarity,
2212
        rgb,
18✔
2213
        3,
2214
        y_start,
18✔
2215
        y_end,
18✔
2216
        g_kundither_fast4_neighbor_offsets,
2217
        4);
2218

2219
    sixel_similarity_destroy(&similarity, allocator);
216!
2220
    return SIXEL_OK;
125✔
2221
}
18✔
2222

2223
SIXEL_INTERNAL_API SIXELSTATUS
2224
sixel_dequantize_k_undither_fast4_rgba(unsigned char *indexed_pixels,
72✔
2225
                                       unsigned char const *paint_mask,
2226
                                       int width,
2227
                                       int height,
2228
                                       unsigned char *palette,
2229
                                       int ncolors,
2230
                                       int similarity_bias,
2231
                                       sixel_allocator_t *allocator,
2232
                                       unsigned char **output)
2233
{
2234
    SIXELSTATUS status;
31✔
2235
    sixel_similarity_t similarity;
31✔
2236
    unsigned char *rgba;
31✔
2237
    size_t num_pixels;
31✔
2238
#if SIXEL_ENABLE_THREADS
2239
    SIXELSTATUS parallel_status;
25✔
2240
    int parallel_threads;
25✔
2241
#endif
2242

2243
    memset(&similarity, 0, sizeof(similarity));
72!
2244
    rgba = NULL;
72✔
2245
    num_pixels = 0u;
72✔
2246

2247
    if (indexed_pixels == NULL || width <= 0 || height <= 0 ||
72!
2248
            palette == NULL || ncolors <= 0 || output == NULL) {
72!
2249
        return SIXEL_BAD_INPUT;
2250
    }
2251
    if ((size_t)width > ((size_t)-1 / (size_t)height)) {
72!
2252
        return SIXEL_BAD_ALLOCATION;
2253
    }
2254

2255
    num_pixels = (size_t)width * (size_t)height;
72✔
2256
    if (num_pixels > ((size_t)-1 / 4u)) {
72!
2257
        return SIXEL_BAD_ALLOCATION;
2258
    }
2259

2260
    rgba = (unsigned char *)sixel_allocator_malloc(allocator,
77✔
2261
                                                   num_pixels * 4u);
5✔
2262
    if (rgba == NULL) {
72!
UNCOV
2263
        sixel_helper_set_additional_message(
×
2264
            "sixel_dequantize_k_undither_fast4_rgba: "
2265
            "sixel_allocator_malloc() failed.");
UNCOV
2266
        return SIXEL_BAD_ALLOCATION;
×
2267
    }
2268

2269
    status = sixel_similarity_init(
72✔
2270
        &similarity,
2271
        palette,
5✔
2272
        ncolors,
5✔
2273
        similarity_bias,
5✔
2274
        allocator);
5✔
2275
    if (SIXEL_FAILED(status)) {
72!
UNCOV
2276
        sixel_allocator_free(allocator, rgba);
×
UNCOV
2277
        return status;
×
2278
    }
2279

2280
#if SIXEL_ENABLE_THREADS
2281
    parallel_threads = sixel_threads_resolve();
60✔
2282
    if (parallel_threads >= 2 && num_pixels >= 65536U) {
60✔
2283
        parallel_status = sixel_kundither_filter_noedge_parallel_offsets(
12✔
2284
            indexed_pixels,
1✔
2285
            paint_mask,
1✔
2286
            width,
1✔
2287
            height,
1✔
2288
            palette,
1✔
2289
            ncolors,
1✔
2290
            &similarity,
2291
            rgba,
1✔
2292
            4,
2293
            parallel_threads,
1✔
2294
            g_kundither_fast4_neighbor_offsets,
2295
            4);
2296
        if (parallel_status == SIXEL_OK) {
12!
2297
            sixel_similarity_destroy(&similarity, allocator);
12!
2298
            *output = rgba;
12✔
2299
            return SIXEL_OK;
12✔
2300
        }
2301
    }
2302
#endif
2303

2304
    sixel_kundither_filter_noedge_range_offsets(
60✔
2305
        indexed_pixels,
4✔
2306
        paint_mask,
4✔
2307
        width,
4✔
2308
        height,
4✔
2309
        palette,
4✔
2310
        ncolors,
4✔
2311
        &similarity,
2312
        rgba,
4✔
2313
        4,
2314
        0,
2315
        height,
4✔
2316
        g_kundither_fast4_neighbor_offsets,
2317
        4);
2318

2319
    sixel_similarity_destroy(&similarity, allocator);
60!
2320
    *output = rgba;
60✔
2321
    return SIXEL_OK;
60✔
2322
}
5✔
2323

2324
SIXEL_INTERNAL_API SIXELSTATUS
2325
sixel_dequantize_k_undither_fast4(unsigned char *indexed_pixels,
84✔
2326
                                  int width,
2327
                                  int height,
2328
                                  unsigned char *palette,
2329
                                  int ncolors,
2330
                                  int similarity_bias,
2331
                                  sixel_allocator_t *allocator,
2332
                                  unsigned char **output)
2333
{
2334
    SIXELSTATUS status;
36✔
2335
    sixel_similarity_t similarity;
36✔
2336
    unsigned char *rgb;
36✔
2337
    size_t num_pixels;
36✔
2338
#if SIXEL_ENABLE_THREADS
2339
    SIXELSTATUS parallel_status;
30✔
2340
    int parallel_threads;
30✔
2341
#endif
2342

2343
    memset(&similarity, 0, sizeof(similarity));
84!
2344
    rgb = NULL;
84✔
2345
    num_pixels = 0u;
84✔
2346

2347
    if (indexed_pixels == NULL || width <= 0 || height <= 0 ||
84!
2348
            palette == NULL || ncolors <= 0 || output == NULL) {
84!
2349
        return SIXEL_BAD_INPUT;
2350
    }
2351
    if ((size_t)width > ((size_t)-1 / (size_t)height)) {
84!
2352
        return SIXEL_BAD_ALLOCATION;
2353
    }
2354

2355
    num_pixels = (size_t)width * (size_t)height;
84✔
2356
    if (num_pixels > ((size_t)-1 / 3u)) {
84!
2357
        return SIXEL_BAD_ALLOCATION;
2358
    }
2359

2360
    rgb = (unsigned char *)sixel_allocator_malloc(
84✔
2361
        allocator,
6✔
2362
        num_pixels * 3u);
6✔
2363
    if (rgb == NULL) {
84!
UNCOV
2364
        sixel_helper_set_additional_message(
×
2365
            "sixel_dequantize_k_undither_fast4: "
2366
            "sixel_allocator_malloc() failed.");
UNCOV
2367
        return SIXEL_BAD_ALLOCATION;
×
2368
    }
2369

2370
    /*
2371
     * Fast4 is a causal no-edge filter.  It keeps the palette-similarity
2372
     * heuristic from k_undither while avoiding the right and lower neighbours
2373
     * that make true decode-time fusion wait for later rows.
2374
     */
2375
    status = sixel_similarity_init(
84✔
2376
        &similarity,
2377
        palette,
6✔
2378
        ncolors,
6✔
2379
        similarity_bias,
6✔
2380
        allocator);
6✔
2381
    if (SIXEL_FAILED(status)) {
84!
UNCOV
2382
        sixel_allocator_free(allocator, rgb);
×
UNCOV
2383
        return status;
×
2384
    }
2385

2386
#if SIXEL_ENABLE_THREADS
2387
    parallel_threads = sixel_threads_resolve();
72✔
2388
    if (parallel_threads >= 2 && num_pixels >= 65536U) {
72✔
2389
        parallel_status = sixel_kundither_filter_noedge_parallel_offsets(
24✔
2390
            indexed_pixels,
2✔
2391
            NULL,
2392
            width,
2✔
2393
            height,
2✔
2394
            palette,
2✔
2395
            ncolors,
2✔
2396
            &similarity,
2397
            rgb,
2✔
2398
            3,
2399
            parallel_threads,
2✔
2400
            g_kundither_fast4_neighbor_offsets,
2401
            4);
2402
        if (parallel_status == SIXEL_OK) {
24!
2403
            sixel_similarity_destroy(&similarity, allocator);
24!
2404
            *output = rgb;
24✔
2405
            return SIXEL_OK;
24✔
2406
        }
2407
    }
2408
#endif
2409

2410
    sixel_kundither_filter_noedge_range_offsets(
60✔
2411
        indexed_pixels,
4✔
2412
        NULL,
2413
        width,
4✔
2414
        height,
4✔
2415
        palette,
4✔
2416
        ncolors,
4✔
2417
        &similarity,
2418
        rgb,
4✔
2419
        3,
2420
        0,
2421
        height,
4✔
2422
        g_kundither_fast4_neighbor_offsets,
2423
        4);
2424

2425
    sixel_similarity_destroy(&similarity, allocator);
60!
2426
    *output = rgb;
60✔
2427
    return SIXEL_OK;
60✔
2428
}
6✔
2429

2430
static unsigned int
2431
sixel_selective_blur_color_diff(unsigned char const *a,
69,231,616✔
2432
                                unsigned char const *b)
2433
{
2434
    int dr;
30,288,832✔
2435
    int dg;
30,288,832✔
2436
    int db;
30,288,832✔
2437

2438
    dr = (int)a[0] - (int)b[0];
69,231,616✔
2439
    dg = (int)a[1] - (int)b[1];
69,231,616✔
2440
    db = (int)a[2] - (int)b[2];
69,231,616✔
2441
    return (unsigned int)(dr * dr + dg * dg + db * db);
69,231,616✔
2442
}
2443

2444
static SIXELSTATUS
2445
sixel_dequantize_selective_blur_common(
192✔
2446
    unsigned char *indexed_pixels,
2447
    unsigned char const *paint_mask,
2448
    int width,
2449
    int height,
2450
    unsigned char *palette,
2451
    int ncolors,
2452
    int threshold,
2453
    int pixel_size,
2454
    sixel_allocator_t *allocator,
2455
    unsigned char **output)
2456
{
2457
    unsigned char *pixels;
84✔
2458
    unsigned char const *center_color;
84✔
2459
    unsigned char const *neighbor_color;
84✔
2460
    size_t num_pixels;
84✔
2461
    size_t pixel_pos;
84✔
2462
    size_t neighbor_pos;
84✔
2463
    size_t out_index;
84✔
2464
    unsigned int threshold_squared;
84✔
2465
    unsigned int accum_r;
84✔
2466
    unsigned int accum_g;
84✔
2467
    unsigned int accum_b;
84✔
2468
    unsigned int total_weight;
84✔
2469
    int palette_index;
84✔
2470
    int neighbor_index;
84✔
2471
    int neighbor;
84✔
2472
    int weight;
84✔
2473
    int x;
84✔
2474
    int y;
84✔
2475
    int nx;
84✔
2476
    int ny;
84✔
2477

2478
    pixels = NULL;
192✔
2479
    center_color = NULL;
192✔
2480
    neighbor_color = NULL;
192✔
2481
    num_pixels = 0u;
192✔
2482
    pixel_pos = 0u;
192✔
2483
    neighbor_pos = 0u;
192✔
2484
    out_index = 0u;
192✔
2485
    threshold_squared = 0u;
192✔
2486
    accum_r = 0u;
192✔
2487
    accum_g = 0u;
192✔
2488
    accum_b = 0u;
192✔
2489
    total_weight = 0u;
192✔
2490
    palette_index = 0;
192✔
2491
    neighbor_index = 0;
192✔
2492
    neighbor = 0;
192✔
2493
    weight = 0;
192✔
2494
    x = 0;
192✔
2495
    y = 0;
192✔
2496
    nx = 0;
192✔
2497
    ny = 0;
192✔
2498

2499
    if (indexed_pixels == NULL || width <= 0 || height <= 0 ||
192!
2500
            palette == NULL || ncolors <= 0 || output == NULL ||
192!
2501
            pixel_size < 3 || pixel_size > 4 ||
192!
2502
            threshold < 0 ||
192!
2503
            threshold >
12✔
2504
                SIXEL_DEQUANTIZE_SELECTIVE_BLUR_THRESHOLD_MAX) {
2505
        return SIXEL_BAD_INPUT;
2506
    }
2507
    if ((size_t)width > ((size_t)-1 / (size_t)height)) {
192!
2508
        return SIXEL_BAD_ALLOCATION;
2509
    }
2510
    num_pixels = (size_t)width * (size_t)height;
192✔
2511
    if (num_pixels > ((size_t)-1 / (size_t)pixel_size)) {
192!
2512
        return SIXEL_BAD_ALLOCATION;
2513
    }
2514

2515
    pixels = (unsigned char *)sixel_allocator_malloc(
252✔
2516
        allocator,
12✔
2517
        num_pixels * (size_t)pixel_size);
168✔
2518
    if (pixels == NULL) {
192!
UNCOV
2519
        sixel_helper_set_additional_message(
×
2520
            "sixel_dequantize_selective_blur: "
2521
            "sixel_allocator_malloc() failed.");
2522
        return SIXEL_BAD_ALLOCATION;
×
2523
    }
2524

2525
    threshold_squared = (unsigned int)(threshold * threshold);
192✔
2526
    for (y = 0; y < height; ++y) {
15,168✔
2527
        for (x = 0; x < width; ++x) {
8,696,928✔
2528
            pixel_pos = (size_t)y * (size_t)width + (size_t)x;
8,681,952✔
2529
            out_index = pixel_pos * (size_t)pixel_size;
8,681,952✔
2530
            if (paint_mask != NULL && paint_mask[pixel_pos] == 0U) {
8,681,952✔
2531
                pixels[out_index + 0u] = 0u;
64✔
2532
                pixels[out_index + 1u] = 0u;
64✔
2533
                pixels[out_index + 2u] = 0u;
64✔
2534
                if (pixel_size == 4) {
64!
2535
                    pixels[out_index + 3u] = 0u;
64✔
2536
                }
4✔
2537
                continue;
64✔
2538
            }
2539

2540
            palette_index = indexed_pixels[pixel_pos];
8,681,888✔
2541
            if (palette_index < 0 || palette_index >= ncolors) {
8,681,888!
UNCOV
2542
                palette_index = 0;
×
2543
            }
2544
            center_color = palette + palette_index * 3;
8,681,888✔
2545
            accum_r = (unsigned int)center_color[0] * 4u;
8,681,888✔
2546
            accum_g = (unsigned int)center_color[1] * 4u;
8,681,888✔
2547
            accum_b = (unsigned int)center_color[2] * 4u;
8,681,888✔
2548
            total_weight = 4u;
8,681,888✔
2549

2550
            /*
2551
             * This is an ImageMagick-style selective blur target, not a
2552
             * palette-aware inverse dither.  It keeps the GPU-friendly shape:
2553
             * only center/neighbor RGB distance gates the fixed 3x3 binomial
2554
             * kernel, so runtime does not grow with palette size.
2555
             */
2556
            for (neighbor = 0; neighbor < 8; ++neighbor) {
78,136,992✔
2557
                nx = x + g_selective_blur_neighbor_offsets[neighbor][0];
69,455,104✔
2558
                ny = y + g_selective_blur_neighbor_offsets[neighbor][1];
69,455,104✔
2559
                weight = g_selective_blur_neighbor_offsets[neighbor][2];
69,455,104✔
2560
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
69,455,104✔
2561
                    continue;
223,424✔
2562
                }
2563

2564
                neighbor_pos = (size_t)ny * (size_t)width + (size_t)nx;
69,231,680✔
2565
                if (paint_mask != NULL && paint_mask[neighbor_pos] == 0U) {
69,231,680✔
2566
                    continue;
64✔
2567
                }
2568
                neighbor_index = indexed_pixels[neighbor_pos];
69,231,616✔
2569
                if (neighbor_index < 0 || neighbor_index >= ncolors) {
69,231,616!
UNCOV
2570
                    continue;
×
2571
                }
2572

2573
                neighbor_color = palette + neighbor_index * 3;
69,231,616✔
2574
                if (sixel_selective_blur_color_diff(center_color,
73,558,592✔
2575
                                                    neighbor_color)
4,326,976✔
2576
                        > threshold_squared) {
4,326,976✔
2577
                    continue;
16,760,480✔
2578
                }
2579

2580
                accum_r += (unsigned int)neighbor_color[0] *
55,750,582✔
2581
                    (unsigned int)weight;
52,471,136✔
2582
                accum_g += (unsigned int)neighbor_color[1] *
55,750,582✔
2583
                    (unsigned int)weight;
29,515,014✔
2584
                accum_b += (unsigned int)neighbor_color[2] *
55,750,582✔
2585
                    (unsigned int)weight;
29,515,014✔
2586
                total_weight += (unsigned int)weight;
52,471,136✔
2587
            }
3,279,446✔
2588

2589
            pixels[out_index + 0u] =
8,681,888✔
2590
                (unsigned char)(accum_r / total_weight);
8,681,888✔
2591
            pixels[out_index + 1u] =
8,681,888✔
2592
                (unsigned char)(accum_g / total_weight);
8,681,888✔
2593
            pixels[out_index + 2u] =
8,681,888✔
2594
                (unsigned char)(accum_b / total_weight);
8,681,888✔
2595
            if (pixel_size == 4) {
8,681,888✔
2596
                pixels[out_index + 3u] = 0xffu;
128✔
2597
            }
8✔
2598
        }
542,618✔
2599
    }
936✔
2600

2601
    *output = pixels;
192✔
2602
    return SIXEL_OK;
192✔
2603
}
12✔
2604

2605
SIXEL_INTERNAL_API SIXELSTATUS
2606
sixel_dequantize_selective_blur(unsigned char *indexed_pixels,
96✔
2607
                                int width,
2608
                                int height,
2609
                                unsigned char *palette,
2610
                                int ncolors,
2611
                                int threshold,
2612
                                sixel_allocator_t *allocator,
2613
                                unsigned char **output)
2614
{
2615
    return sixel_dequantize_selective_blur_common(indexed_pixels,
92✔
2616
                                                  NULL,
2617
                                                  width,
6✔
2618
                                                  height,
6✔
2619
                                                  palette,
6✔
2620
                                                  ncolors,
6✔
2621
                                                  threshold,
6✔
2622
                                                  3,
2623
                                                  allocator,
6✔
2624
                                                  output);
6✔
2625
}
2626

2627
SIXEL_INTERNAL_API SIXELSTATUS
2628
sixel_dequantize_selective_blur_rgba(unsigned char *indexed_pixels,
96✔
2629
                                     unsigned char const *paint_mask,
2630
                                     int width,
2631
                                     int height,
2632
                                     unsigned char *palette,
2633
                                     int ncolors,
2634
                                     int threshold,
2635
                                     sixel_allocator_t *allocator,
2636
                                     unsigned char **output)
2637
{
2638
    return sixel_dequantize_selective_blur_common(indexed_pixels,
72✔
2639
                                                  paint_mask,
6✔
2640
                                                  width,
6✔
2641
                                                  height,
6✔
2642
                                                  palette,
6✔
2643
                                                  ncolors,
6✔
2644
                                                  threshold,
6✔
2645
                                                  4,
2646
                                                  allocator,
6✔
2647
                                                  output);
6✔
2648
}
2649
/* set an option flag to decoder object */
2650
SIXELAPI SIXELSTATUS
2651
sixel_decoder_setopt(
1,986✔
2652
    sixel_decoder_t /* in */ *decoder,
2653
    int             /* in */ arg,
2654
    char const      /* in */ *value
2655
)
2656
{
2657
    SIXELSTATUS status = SIXEL_FALSE;
1,986✔
2658
    unsigned int path_flags;
866✔
2659
    int path_check;
866✔
2660
    char const *payload = NULL;
1,986✔
2661
    sixel_clipboard_spec_t clipboard_spec;
866✔
2662
    char const *filename = NULL;
1,986✔
2663
    size_t libc_buffer_size;
866✔
2664
    char *libc_buffer;
866✔
2665
    char const *libc_path;
866✔
2666
    long bias;
866✔
2667
    long parsed_value;
866✔
2668
    char *endptr;
866✔
2669

2670
    sixel_decoder_ref(decoder);
1,986✔
2671
    path_flags = 0u;
1,986✔
2672
    path_check = 0;
1,986✔
2673
    libc_buffer_size = 0u;
1,986✔
2674
    libc_buffer = NULL;
1,986✔
2675
    libc_path = NULL;
1,986✔
2676

2677
    switch(arg) {
1,986!
2678
    case SIXEL_OPTFLAG_INPUT:  /* i */
652✔
2679
        path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
697✔
2680
            SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
2681
            SIXEL_OPTION_PATH_ALLOW_REMOTE;
2682
        if (value != NULL) {
697!
2683
            path_check = sixel_option_validate_filesystem_path(
697✔
2684
                value,
45✔
2685
                value,
45✔
2686
                path_flags);
45✔
2687
            if (path_check != 0) {
697!
UNCOV
2688
                status = SIXEL_BAD_ARGUMENT;
×
UNCOV
2689
                goto end;
×
2690
            }
2691
        }
45✔
2692
        decoder->clipboard_input_active = 0;
697✔
2693
        decoder->clipboard_input_format[0] = '\0';
697✔
2694
        if (value != NULL) {
697!
2695
            clipboard_spec.is_clipboard = 0;
697✔
2696
            clipboard_spec.format[0] = '\0';
697✔
2697
            if (sixel_clipboard_parse_spec(value, &clipboard_spec)
697✔
2698
                    && clipboard_spec.is_clipboard) {
68!
2699
                decoder_clipboard_select_format(
25✔
2700
                    decoder->clipboard_input_format,
25✔
2701
                    sizeof(decoder->clipboard_input_format),
2702
                    clipboard_spec.format,
2✔
2703
                    "sixel");
2704
                decoder->clipboard_input_active = 1;
25✔
2705
            }
2✔
2706
        }
45✔
2707
        free(decoder->input);
697✔
2708
        decoder->input = strdup_with_allocator(value, decoder->allocator);
697✔
2709
        if (decoder->input == NULL) {
697!
UNCOV
2710
            sixel_helper_set_additional_message(
×
2711
                "sixel_decoder_setopt: strdup_with_allocator() failed.");
UNCOV
2712
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
2713
            goto end;
×
2714
        }
2715
        break;
393✔
2716
    case SIXEL_OPTFLAG_OUTPUT:  /* o */
652✔
2717
        decoder->clipboard_output_active = 0;
697✔
2718
        decoder->clipboard_output_format[0] = '\0';
697✔
2719

2720
        payload = value;
697✔
2721
        if (strncmp(value, "png:", 4) == 0) {
697✔
2722
            payload = value + 4;
80✔
2723
            if (payload[0] == '\0') {
80✔
2724
                sixel_helper_set_additional_message(
16✔
2725
                    "missing target after the \"png:\" prefix.");
2726
                return SIXEL_BAD_ARGUMENT;
16✔
2727
            }
2728
            libc_buffer_size = sixel_path_to_libc_buffer_size(payload);
64✔
2729
            if (libc_buffer_size > 0u) {
64!
2730
                libc_buffer = (char *)malloc(libc_buffer_size);
6✔
2731
                if (libc_buffer == NULL) {
6!
UNCOV
2732
                    sixel_helper_set_additional_message(
×
2733
                        "sixel_decoder_setopt: malloc() failed for png path "
2734
                        "buffer.");
UNCOV
2735
                    return SIXEL_BAD_ALLOCATION;
×
2736
                }
2737
                libc_path = sixel_path_to_libc(payload,
6✔
2738
                                               libc_buffer,
2739
                                               libc_buffer_size);
2740
                if (libc_path == NULL) {
6!
UNCOV
2741
                    sixel_helper_set_additional_message(
×
2742
                        "sixel_decoder_setopt: invalid png output path.");
UNCOV
2743
                    free(libc_buffer);
×
2744
                    return SIXEL_BAD_ARGUMENT;
×
2745
                }
2746
                filename = libc_path;
1✔
2747
            } else {
2748
                filename = payload;
35✔
2749
            }
2750
        } else {
4✔
2751
            filename = value;
348✔
2752
        }
2753

2754
        if (filename != NULL) {
384!
2755
            clipboard_spec.is_clipboard = 0;
681✔
2756
            clipboard_spec.format[0] = '\0';
681✔
2757
            if (sixel_clipboard_parse_spec(filename, &clipboard_spec)
681✔
2758
                    && clipboard_spec.is_clipboard) {
74!
2759
                decoder_clipboard_select_format(
32✔
2760
                    decoder->clipboard_output_format,
32✔
2761
                    sizeof(decoder->clipboard_output_format),
2762
                    clipboard_spec.format,
2✔
2763
                    "png");
2764
                decoder->clipboard_output_active = 1;
32✔
2765
            }
2✔
2766
        }
44✔
2767
        free(decoder->output);
681✔
2768
        decoder->output = strdup_with_allocator(filename, decoder->allocator);
681✔
2769
        if (libc_buffer != NULL) {
681!
2770
            free(libc_buffer);
6✔
2771
            libc_buffer = NULL;
6✔
2772
        }
2773
        if (decoder->output == NULL) {
681!
2774
            sixel_helper_set_additional_message(
×
2775
                "sixel_decoder_setopt: strdup_with_allocator() failed.");
UNCOV
2776
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
2777
            goto end;
×
2778
        }
2779
        break;
384✔
2780
    case SIXEL_OPTFLAG_DEQUANTIZE:  /* d */
412✔
2781
        if (value == NULL) {
440!
UNCOV
2782
            sixel_helper_set_additional_message(
×
2783
                "sixel_decoder_setopt: -d/--dequantize requires an argument.");
UNCOV
2784
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
2785
            goto end;
×
2786
        }
2787

2788
        status = sixel_option_parse_dequantize_argument_with_options(
440✔
2789
            value,
28✔
2790
            &decoder->dequantize_method,
28✔
2791
            &decoder->dequantize_selective_blur_threshold,
28✔
2792
            NULL,
2793
            0u);
2794
        if (SIXEL_FAILED(status)) {
440!
UNCOV
2795
            goto end;
×
2796
        }
2797
        break;
248✔
2798

2799
    case SIXEL_OPTFLAG_SIMILARITY:  /* S */
15✔
2800
        errno = 0;
16✔
2801
        bias = strtol(value, &endptr, 10);
16✔
2802
        if (endptr == value || endptr[0] != '\0' ||
16!
UNCOV
2803
            errno == ERANGE || bias < 0 || bias > 1000) {
×
2804
            sixel_helper_set_additional_message(
16✔
2805
                "similarity must be an integer between 0 and 1000.");
2806
            status = SIXEL_BAD_ARGUMENT;
16✔
2807
            goto end;
16✔
2808
        }
2809

UNCOV
2810
        decoder->dequantize_similarity_bias = (int)bias;
×
UNCOV
2811
        break;
×
2812

2813
    case SIXEL_OPTFLAG_SIZE:  /* s */
UNCOV
2814
        parsed_value = 0L;
×
UNCOV
2815
        endptr = NULL;
×
UNCOV
2816
        errno = 0;
×
UNCOV
2817
        parsed_value = strtol(value, &endptr, 10);
×
UNCOV
2818
        if (endptr == value || *endptr != '\0' || errno == ERANGE ||
×
2819
            parsed_value < 1L || parsed_value > (long)INT_MAX) {
×
UNCOV
2820
            sixel_helper_set_additional_message(
×
2821
                "size must be greater than zero.");
UNCOV
2822
            status = SIXEL_BAD_ARGUMENT;
×
UNCOV
2823
            goto end;
×
2824
        }
UNCOV
2825
        decoder->thumbnail_size = (int)parsed_value;
×
UNCOV
2826
        break;
×
2827

2828
    case SIXEL_OPTFLAG_EDGE:  /* e */
UNCOV
2829
        parsed_value = 0L;
×
UNCOV
2830
        endptr = NULL;
×
2831
        errno = 0;
×
UNCOV
2832
        parsed_value = strtol(value, &endptr, 10);
×
UNCOV
2833
        if (endptr == value || *endptr != '\0' || errno == ERANGE ||
×
UNCOV
2834
            parsed_value < 0L || parsed_value > 1000L) {
×
UNCOV
2835
            sixel_helper_set_additional_message(
×
2836
                "edge bias must be between 1 and 1000.");
UNCOV
2837
            status = SIXEL_BAD_ARGUMENT;
×
UNCOV
2838
            goto end;
×
2839
        }
UNCOV
2840
        decoder->dequantize_edge_strength = (int)parsed_value;
×
2841
        break;
×
2842

2843
    case SIXEL_OPTFLAG_DIRECT:  /* D */
112✔
2844
        decoder->direct_color = 1;
120✔
2845
        break;
120✔
2846

2847
    case SIXEL_OPTFLAG_THREADS:  /* = */
15✔
2848
        status = sixel_decoder_parallel_override_threads(value);
16✔
2849
        if (SIXEL_FAILED(status)) {
16!
2850
            goto end;
16✔
2851
        }
2852
        break;
2853

2854
    case SIXEL_OPTFLAG_GPU_POLICY:  /* G */
UNCOV
2855
        status = sixel_decoder_parse_gpu_policy_argument(
×
2856
            value,
2857
            &decoder->gpu_policy);
2858
        if (SIXEL_FAILED(status)) {
×
UNCOV
2859
            goto end;
×
2860
        }
2861
        break;
2862

UNCOV
2863
    case '?':
×
2864
    default:
2865
        status = SIXEL_BAD_ARGUMENT;
×
UNCOV
2866
        goto end;
×
2867
    }
2868

2869
    status = SIXEL_OK;
1,093✔
2870

2871
end:
1,843✔
2872
    sixel_decoder_unref(decoder);
1,970✔
2873

2874
    return status;
1,970✔
2875
}
128✔
2876

2877

2878
static SIXELSTATUS
2879
sixel_decoder_promote_rgb888_to_rgba8888(unsigned char **out_pixels,
16✔
2880
                                         unsigned char const *rgb_pixels,
2881
                                         int width,
2882
                                         int height,
2883
                                         sixel_allocator_t *allocator)
2884
{
2885
    unsigned char *rgba_pixels = NULL;
16✔
2886
    size_t num_pixels;
7✔
2887
    size_t pixel_index;
7✔
2888
    size_t rgb_index;
7✔
2889
    size_t rgba_index;
7✔
2890

2891
    if (out_pixels == NULL || rgb_pixels == NULL ||
16!
2892
            width <= 0 || height <= 0) {
16!
2893
        return SIXEL_BAD_INPUT;
2894
    }
2895

2896
    num_pixels = (size_t)width * (size_t)height;
16✔
2897
    if (num_pixels > ((size_t)-1 / 4u)) {
16!
2898
        return SIXEL_BAD_ALLOCATION;
2899
    }
2900

2901
    rgba_pixels = (unsigned char *)sixel_allocator_malloc(
16✔
2902
        allocator,
1✔
2903
        num_pixels * 4u);
1✔
2904
    if (rgba_pixels == NULL) {
16!
UNCOV
2905
        sixel_helper_set_additional_message(
×
2906
            "sixel_decoder_promote_rgb888_to_rgba8888: "
2907
            "sixel_allocator_malloc() failed.");
UNCOV
2908
        return SIXEL_BAD_ALLOCATION;
×
2909
    }
2910

2911
    /*
2912
     * Command-line --direct output has no separate alpha mask in the
2913
     * dequantizer path, so keep the historical opaque PNG contract here.
2914
     */
2915
    for (pixel_index = 0u; pixel_index < num_pixels; ++pixel_index) {
65,552✔
2916
        rgb_index = pixel_index * 3u;
65,536✔
2917
        rgba_index = pixel_index * 4u;
65,536✔
2918
        rgba_pixels[rgba_index + 0u] = rgb_pixels[rgb_index + 0u];
65,536✔
2919
        rgba_pixels[rgba_index + 1u] = rgb_pixels[rgb_index + 1u];
65,536✔
2920
        rgba_pixels[rgba_index + 2u] = rgb_pixels[rgb_index + 2u];
65,536✔
2921
        rgba_pixels[rgba_index + 3u] = 0xffu;
65,536✔
2922
    }
4,096✔
2923

2924
    *out_pixels = rgba_pixels;
16✔
2925
    return SIXEL_OK;
16✔
2926
}
1✔
2927

2928
static SIXELSTATUS
2929
sixel_decoder_decode_pixels_gpu_fast4_try(
240✔
2930
    sixel_decoder_t *decoder,
2931
    unsigned char const *data,
2932
    size_t size,
2933
    unsigned int decode_flags,
2934
    unsigned char **out_pixels,
2935
    int *out_width,
2936
    int *out_height,
2937
    unsigned int *result_flags)
2938
{
2939
    SIXELSTATUS status;
105✔
2940
    sixel_gpu_dequant_request_t request;
105✔
2941
    unsigned char *direct_pixels;
105✔
2942
    unsigned char *palette;
105✔
2943
    unsigned char *dequant_pixels;
105✔
2944
    unsigned char *buffer;
105✔
2945
    size_t pixel_count;
105✔
2946
    int ncolors;
105✔
2947

2948
    status = SIXEL_FALSE;
240✔
2949
    direct_pixels = NULL;
240✔
2950
    palette = NULL;
240✔
2951
    dequant_pixels = NULL;
240✔
2952
    buffer = NULL;
240✔
2953
    pixel_count = 0U;
240✔
2954
    ncolors = 0;
240✔
2955
    memset(&request, 0, sizeof(request));
240✔
2956

2957
    if (decoder->dequantize_method !=
240✔
2958
            SIXEL_DEQUANTIZE_LSO_UNDITHER_VLIGHT ||
60✔
2959
            decoder->gpu_policy == SIXEL_GPU_POLICY_OFF) {
48✔
2960
        return SIXEL_FALSE;
135✔
2961
    }
2962

UNCOV
2963
    buffer = (unsigned char *)(void const *)data;
×
UNCOV
2964
    status = sixel_decode_direct_with_options(
×
2965
        buffer,
2966
        (int)size,
2967
        decode_flags,
2968
        &direct_pixels,
2969
        out_width,
2970
        out_height,
2971
        &palette,
2972
        &ncolors,
2973
        result_flags,
2974
        decoder->allocator);
UNCOV
2975
    if (SIXEL_FAILED(status)) {
×
UNCOV
2976
        goto end;
×
2977
    }
2978

UNCOV
2979
    if (*out_width <= 0 || *out_height <= 0) {
×
UNCOV
2980
        status = SIXEL_BAD_INPUT;
×
UNCOV
2981
        goto end;
×
2982
    }
UNCOV
2983
    if ((size_t)*out_width > ((size_t)-1 / (size_t)*out_height)) {
×
2984
        status = SIXEL_BAD_ALLOCATION;
2985
        goto end;
2986
    }
UNCOV
2987
    pixel_count = (size_t)*out_width * (size_t)*out_height;
×
UNCOV
2988
    if (pixel_count > ((size_t)-1 / 4U)) {
×
2989
        status = SIXEL_BAD_ALLOCATION;
2990
        goto end;
2991
    }
UNCOV
2992
    dequant_pixels = (unsigned char *)sixel_allocator_malloc(
×
2993
        decoder->allocator,
2994
        pixel_count * 4U);
UNCOV
2995
    if (dequant_pixels == NULL) {
×
UNCOV
2996
        sixel_helper_set_additional_message(
×
2997
            "sixel_decoder_decode_pixels: GPU dequant allocation failed.");
UNCOV
2998
        status = SIXEL_BAD_ALLOCATION;
×
UNCOV
2999
        goto end;
×
3000
    }
3001

UNCOV
3002
    request.policy = decoder->gpu_policy;
×
UNCOV
3003
    request.dest = dequant_pixels;
×
UNCOV
3004
    request.rgba = direct_pixels;
×
UNCOV
3005
    request.pixel_count = pixel_count;
×
UNCOV
3006
    request.width = *out_width;
×
UNCOV
3007
    request.height = *out_height;
×
UNCOV
3008
    request.pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
×
UNCOV
3009
    request.palette = palette;
×
UNCOV
3010
    request.palette_size = (size_t)ncolors * 3U;
×
UNCOV
3011
    request.palette_depth = 3;
×
UNCOV
3012
    request.ncolors = ncolors;
×
UNCOV
3013
    request.similarity_bias = decoder->dequantize_similarity_bias;
×
UNCOV
3014
    status = sixel_gpu_dequant_fast4_rgba(&request);
×
UNCOV
3015
    if (status == SIXEL_OK) {
×
UNCOV
3016
        *out_pixels = dequant_pixels;
×
UNCOV
3017
        dequant_pixels = NULL;
×
UNCOV
3018
        goto end;
×
3019
    }
3020

3021
end:
UNCOV
3022
    sixel_allocator_free(decoder->allocator, direct_pixels);
×
3023
    sixel_allocator_free(decoder->allocator, palette);
×
UNCOV
3024
    sixel_allocator_free(decoder->allocator, dequant_pixels);
×
3025
    return status;
×
3026
}
15✔
3027

3028
static SIXELSTATUS
3029
sixel_decoder_decode_pixels_dequant_try(
240✔
3030
    sixel_decoder_t *decoder,
3031
    unsigned char const *data,
3032
    size_t size,
3033
    unsigned int decode_flags,
3034
    unsigned char **out_pixels,
3035
    int *out_width,
3036
    int *out_height,
3037
    unsigned int *result_flags)
3038
{
3039
    SIXELSTATUS status;
105✔
3040
    unsigned char *indexed_pixels;
105✔
3041
    unsigned char *paint_mask;
105✔
3042
    unsigned char *palette;
105✔
3043
    unsigned char *rgba_pixels;
105✔
3044
    unsigned char *buffer;
105✔
3045
    int ncolors;
105✔
3046

3047
    status = SIXEL_FALSE;
240✔
3048
    indexed_pixels = NULL;
240✔
3049
    paint_mask = NULL;
240✔
3050
    palette = NULL;
240✔
3051
    rgba_pixels = NULL;
240✔
3052
    buffer = NULL;
240✔
3053
    ncolors = 0;
240✔
3054

3055
    if (out_pixels == NULL || out_width == NULL || out_height == NULL ||
240!
3056
            result_flags == NULL || data == NULL) {
240!
3057
        return SIXEL_BAD_ARGUMENT;
3058
    }
3059
    *out_pixels = NULL;
240✔
3060
    *out_width = 0;
240✔
3061
    *out_height = 0;
240✔
3062
    *result_flags = 0U;
240✔
3063
    if (size == 0 || size > (size_t)(INT_MAX - 2)) {
240!
UNCOV
3064
        sixel_helper_set_additional_message(
×
3065
            "sixel_decoder_decode_pixels: invalid input size.");
UNCOV
3066
        return SIXEL_BAD_INPUT;
×
3067
    }
3068

3069
    if (decoder->dequantize_method == SIXEL_DEQUANTIZE_LSO_UNDITHER_VLIGHT ||
240✔
3070
            decoder->dequantize_method == SIXEL_DEQUANTIZE_SELECTIVE_BLUR) {
108✔
3071
        /* handled below after the one-pass indexed decode */
3072
    } else if (decoder->dequantize_method != SIXEL_DEQUANTIZE_K_UNDITHER) {
105!
UNCOV
3073
        sixel_helper_set_additional_message(
×
3074
            "sixel_decoder_decode_pixels: invalid dequantize method.");
UNCOV
3075
        return SIXEL_BAD_ARGUMENT;
×
3076
    }
3077
    if (decoder->gpu_policy == SIXEL_GPU_POLICY_FORCE &&
240!
3078
            decoder->dequantize_method !=
×
3079
            SIXEL_DEQUANTIZE_LSO_UNDITHER_VLIGHT) {
UNCOV
3080
        sixel_helper_set_additional_message(
×
3081
            "sixel_decoder_decode_pixels: GPU dequant supports fast4 only.");
UNCOV
3082
        return SIXEL_BAD_ARGUMENT;
×
3083
    }
3084

3085
    status = sixel_decoder_decode_pixels_gpu_fast4_try(decoder,
255✔
3086
                                                       data,
15✔
3087
                                                       size,
15✔
3088
                                                       decode_flags,
15✔
3089
                                                       out_pixels,
15✔
3090
                                                       out_width,
15✔
3091
                                                       out_height,
15✔
3092
                                                       result_flags);
15✔
3093
    if (status == SIXEL_OK) {
240!
3094
        return SIXEL_OK;
3095
    }
3096
    if (status != SIXEL_FALSE) {
240!
UNCOV
3097
        goto end;
×
3098
    }
3099

3100
    /*
3101
     * The raw decoder reads from the input buffer only.  Preserve the caller's
3102
     * storage on the first attempt; a mutable copy is needed only when the
3103
     * retry path appends a synthetic terminator.
3104
     */
3105
    buffer = (unsigned char *)(void const *)data;
240✔
3106
    status = sixel_decode_raw_with_options_mask(buffer,
255✔
3107
                                                (int)size,
15✔
3108
                                                decode_flags,
15✔
3109
                                                &indexed_pixels,
3110
                                                &paint_mask,
3111
                                                out_width,
15✔
3112
                                                out_height,
15✔
3113
                                                &palette,
3114
                                                &ncolors,
3115
                                                result_flags,
15✔
3116
                                                decoder->allocator);
15✔
3117
    if (SIXEL_FAILED(status)) {
240!
UNCOV
3118
        goto end;
×
3119
    }
3120

3121
    if (decoder->dequantize_method == SIXEL_DEQUANTIZE_LSO_UNDITHER_VLIGHT) {
240✔
3122
        status = sixel_dequantize_k_undither_fast4_rgba(
48✔
3123
            indexed_pixels,
3✔
3124
            paint_mask,
3✔
3125
            *out_width,
3✔
3126
            *out_height,
3✔
3127
            palette,
3✔
3128
            ncolors,
3✔
3129
            decoder->dequantize_similarity_bias,
3✔
3130
            decoder->allocator,
3✔
3131
            &rgba_pixels);
3132
    } else if (decoder->dequantize_method ==
195✔
3133
            SIXEL_DEQUANTIZE_SELECTIVE_BLUR) {
3134
        status = sixel_dequantize_selective_blur_rgba(
96✔
3135
            indexed_pixels,
6✔
3136
            paint_mask,
6✔
3137
            *out_width,
6✔
3138
            *out_height,
6✔
3139
            palette,
6✔
3140
            ncolors,
6✔
3141
            decoder->dequantize_selective_blur_threshold,
6✔
3142
            decoder->allocator,
6✔
3143
            &rgba_pixels);
3144
    } else {
6✔
3145
        status = sixel_dequantize_k_undither_rgba(
96✔
3146
            indexed_pixels,
6✔
3147
            paint_mask,
6✔
3148
            *out_width,
6✔
3149
            *out_height,
6✔
3150
            palette,
6✔
3151
            ncolors,
6✔
3152
            decoder->dequantize_similarity_bias,
6✔
3153
            decoder->dequantize_edge_strength,
6✔
3154
            decoder->allocator,
6✔
3155
            &rgba_pixels);
3156
    }
3157
    if (SIXEL_FAILED(status)) {
240!
UNCOV
3158
        goto end;
×
3159
    }
3160

3161
    *out_pixels = rgba_pixels;
240✔
3162
    rgba_pixels = NULL;
240✔
3163
    status = SIXEL_OK;
240✔
3164

3165
end:
225✔
3166
    sixel_allocator_free(decoder->allocator, indexed_pixels);
240✔
3167
    sixel_allocator_free(decoder->allocator, paint_mask);
240✔
3168
    sixel_allocator_free(decoder->allocator, palette);
240✔
3169
    sixel_allocator_free(decoder->allocator, rgba_pixels);
240✔
3170
    return status;
240✔
3171
}
15✔
3172

3173
static SIXELSTATUS
UNCOV
3174
sixel_decoder_decode_pixels_dequant_terminated_attempts(
×
3175
    sixel_decoder_t *decoder,
3176
    unsigned char *workbuf,
3177
    size_t size,
3178
    unsigned int decode_flags,
3179
    unsigned char **out_pixels,
3180
    int *out_width,
3181
    int *out_height,
3182
    unsigned int *result_flags)
3183
{
3184
    SIXELSTATUS status;
3185
    unsigned int second_flags;
3186
    unsigned int third_flags;
3187

UNCOV
3188
    second_flags = 0U;
×
UNCOV
3189
    third_flags = 0U;
×
3190

3191
    /* Retry with a synthetic BEL terminator for truncated streams. */
UNCOV
3192
    workbuf[size] = 0x07U;
×
UNCOV
3193
    status = sixel_decoder_decode_pixels_dequant_try(decoder,
×
3194
                                                     workbuf,
3195
                                                     size + 1U,
3196
                                                     decode_flags,
3197
                                                     out_pixels,
3198
                                                     out_width,
3199
                                                     out_height,
3200
                                                     &second_flags);
UNCOV
3201
    if (status == SIXEL_OK) {
×
UNCOV
3202
        *result_flags = second_flags;
×
UNCOV
3203
        return status;
×
3204
    }
3205

3206
    /* Retry with ESC \ (ST) in case BEL is not accepted. */
UNCOV
3207
    workbuf[size] = 0x1bU;
×
3208
    workbuf[size + 1U] = '\\';
×
3209
    status = sixel_decoder_decode_pixels_dequant_try(decoder,
×
3210
                                                     workbuf,
3211
                                                     size + 2U,
3212
                                                     decode_flags,
3213
                                                     out_pixels,
3214
                                                     out_width,
3215
                                                     out_height,
3216
                                                     &third_flags);
3217
    if (status == SIXEL_OK) {
×
UNCOV
3218
        *result_flags = third_flags;
×
3219
    }
3220

3221
    return status;
3222
}
3223

3224
SIXELAPI SIXELSTATUS
3225
sixel_decoder_decode_pixels(sixel_decoder_t *decoder,
240✔
3226
                            unsigned char const *data,
3227
                            size_t size,
3228
                            sixel_decode_options_t const *options,
3229
                            sixel_decode_result_t *result)
3230
{
3231
    SIXELSTATUS status;
105✔
3232
    unsigned char *workbuf;
105✔
3233
    unsigned char *rgba_pixels;
105✔
3234
    unsigned char const default_bg[3] = { 0U, 0U, 0U };
240✔
3235
    unsigned char const *bg;
105✔
3236
    unsigned int decode_flags;
105✔
3237
    unsigned int result_flags;
105✔
3238
    unsigned int first_flags;
105✔
3239
    int pixelformat;
105✔
3240
    int width;
105✔
3241
    int height;
105✔
3242

3243
    status = SIXEL_FALSE;
240✔
3244
    workbuf = NULL;
240✔
3245
    rgba_pixels = NULL;
240✔
3246
    bg = default_bg;
240✔
3247
    decode_flags = 0U;
240✔
3248
    result_flags = 0U;
240✔
3249
    first_flags = 0U;
240✔
3250
    pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
240✔
3251
    width = 0;
240✔
3252
    height = 0;
240✔
3253

3254
    if (decoder == NULL || data == NULL || result == NULL) {
240!
3255
        return SIXEL_BAD_ARGUMENT;
3256
    }
3257

3258
    sixel_decoder_ref(decoder);
240✔
3259
    result->pixels = NULL;
240✔
3260
    result->width = 0;
240✔
3261
    result->height = 0;
240✔
3262
    result->pixelformat = 0;
240✔
3263
    result->stride = 0;
240✔
3264
    result->flags = 0U;
240✔
3265

3266
    if (options != NULL) {
240!
3267
        decode_flags = options->flags;
240✔
3268
        if (options->preferred_pixelformat != 0) {
240!
3269
            pixelformat = options->preferred_pixelformat;
240✔
3270
        }
15✔
3271
        bg = options->bgcolor;
240✔
3272
    }
15✔
3273

3274
    if (decoder->dequantize_method == SIXEL_DEQUANTIZE_NONE) {
240!
UNCOV
3275
        status = sixel_decode_pixels(data,
×
3276
                                     size,
3277
                                     options,
3278
                                     result,
3279
                                     decoder->allocator);
UNCOV
3280
        goto end;
×
3281
    }
3282

3283
    if (size == 0 || size > (size_t)(INT_MAX - 2) ||
240!
3284
            size > (SIZE_MAX - 2U)) {
15✔
3285
        sixel_helper_set_additional_message(
×
3286
            "sixel_decoder_decode_pixels: invalid input size.");
UNCOV
3287
        status = SIXEL_BAD_INPUT;
×
UNCOV
3288
        goto end;
×
3289
    }
3290

3291
    status = sixel_decoder_decode_pixels_dequant_try(decoder,
255✔
3292
                                                     data,
15✔
3293
                                                     size,
15✔
3294
                                                     decode_flags,
15✔
3295
                                                     &rgba_pixels,
3296
                                                     &width,
3297
                                                     &height,
3298
                                                     &first_flags);
3299
    if (status == SIXEL_OK) {
240!
3300
        result_flags = first_flags;
240✔
3301
    } else {
15✔
UNCOV
3302
        workbuf = (unsigned char *)sixel_allocator_malloc(decoder->allocator,
×
3303
                                                          size + 2U);
UNCOV
3304
        if (workbuf == NULL) {
×
UNCOV
3305
            sixel_helper_set_additional_message(
×
3306
                "sixel_decoder_decode_pixels: allocation failed for "
3307
                "input copy.");
UNCOV
3308
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
3309
            goto end;
×
3310
        }
UNCOV
3311
        memcpy(workbuf, data, size);
×
3312

UNCOV
3313
        status = sixel_decoder_decode_pixels_dequant_terminated_attempts(
×
3314
            decoder,
3315
            workbuf,
3316
            size,
3317
            decode_flags,
3318
            &rgba_pixels,
3319
            &width,
3320
            &height,
3321
            &result_flags);
UNCOV
3322
        if (SIXEL_FAILED(status)) {
×
UNCOV
3323
            goto end;
×
3324
        }
3325
    }
3326

3327
    status = sixel_decode_pixels_finish_rgba(&rgba_pixels,
240✔
3328
                                             width,
15✔
3329
                                             height,
15✔
3330
                                             pixelformat,
15✔
3331
                                             bg,
15✔
3332
                                             result_flags,
15✔
3333
                                             result,
15✔
3334
                                             decoder->allocator);
15✔
3335

3336
end:
225✔
3337
    sixel_allocator_free(decoder->allocator, rgba_pixels);
240✔
3338
    sixel_allocator_free(decoder->allocator, workbuf);
240✔
3339
    sixel_decoder_unref(decoder);
240✔
3340
    return status;
240✔
3341
}
15✔
3342

3343
/* load source data from stdin or the file specified with
3344
   SIXEL_OPTFLAG_INPUT flag, and decode it */
3345
SIXELAPI SIXELSTATUS
3346
sixel_decoder_decode(
1,017✔
3347
    sixel_decoder_t /* in */ *decoder)
3348
{
3349
    SIXELSTATUS status = SIXEL_FALSE;
1,017✔
3350
    unsigned char *raw_data = NULL;
1,017✔
3351
    int sx;
444✔
3352
    int sy;
444✔
3353
    int raw_len;
444✔
3354
    int max;
444✔
3355
    int n;
444✔
3356
    FILE *input_fp = NULL;
1,017✔
3357
    char message[2048];
444✔
3358
    unsigned char *indexed_pixels = NULL;
1,017✔
3359
    unsigned char *palette = NULL;
1,017✔
3360
    unsigned char *rgb_pixels = NULL;
1,017✔
3361
    unsigned char *direct_pixels = NULL;
1,017✔
3362
    unsigned char *fast4_pixels = NULL;
1,017✔
3363
    unsigned char *output_pixels;
444✔
3364
    unsigned char *output_palette;
444✔
3365
    int output_pixelformat;
444✔
3366
    int ncolors;
444✔
3367
    sixel_frame_t *frame;
444✔
3368
    int new_width;
444✔
3369
    int new_height;
444✔
3370
    double scaled_width;
444✔
3371
    double scaled_height;
444✔
3372
    int max_dimension;
444✔
3373
    int thumbnail_size;
444✔
3374
    int frame_ncolors;
444✔
3375
    unsigned char *clipboard_blob;
444✔
3376
    size_t clipboard_blob_size;
444✔
3377
    SIXELSTATUS clipboard_status;
444✔
3378
    char *clipboard_output_path;
444✔
3379
    unsigned char *clipboard_output_data;
444✔
3380
    size_t clipboard_output_size;
444✔
3381
    SIXELSTATUS clipboard_output_status;
444✔
3382
    sixel_timeline_logger_t *logger;
444✔
3383
    int logger_prepared;
444✔
3384
    unsigned int gpu_result_flags;
444✔
3385

3386
    sx = 0;
1,017✔
3387
    sy = 0;
1,017✔
3388
    sixel_decoder_ref(decoder);
1,017✔
3389

3390
    frame = NULL;
1,017✔
3391
    new_width = 0;
1,017✔
3392
    new_height = 0;
1,017✔
3393
    scaled_width = 0.0;
1,017✔
3394
    scaled_height = 0.0;
1,017✔
3395
    max_dimension = 0;
1,017✔
3396
    thumbnail_size = decoder->thumbnail_size;
1,017✔
3397
    frame_ncolors = -1;
1,017✔
3398
    clipboard_blob = NULL;
1,017✔
3399
    clipboard_blob_size = 0u;
1,017✔
3400
    clipboard_status = SIXEL_OK;
1,017✔
3401
    clipboard_output_path = NULL;
1,017✔
3402
    clipboard_output_data = NULL;
1,017✔
3403
    clipboard_output_size = 0u;
1,017✔
3404
    clipboard_output_status = SIXEL_OK;
1,017✔
3405
    input_fp = NULL;
1,017✔
3406
    logger = NULL;
1,017✔
3407
    gpu_result_flags = 0U;
1,017✔
3408
    (void)sixel_timeline_logger_prepare_env(decoder->allocator, &logger);
1,017✔
3409
    logger_prepared = logger != NULL;
1,017✔
3410

3411
    raw_len = 0;
1,017✔
3412
    max = 0;
1,017✔
3413
    if (decoder->clipboard_input_active) {
1,017✔
3414
        clipboard_status = sixel_clipboard_read(
36✔
3415
            decoder->clipboard_input_format,
25✔
3416
            &clipboard_blob,
3417
            &clipboard_blob_size,
3418
            decoder->allocator);
2✔
3419
        if (SIXEL_FAILED(clipboard_status)) {
25!
UNCOV
3420
            status = clipboard_status;
×
UNCOV
3421
            goto end;
×
3422
        }
3423
        max = (int)((clipboard_blob_size > 0u)
25!
3424
                    ? clipboard_blob_size
2✔
3425
                    : 1u);
3426
        raw_data = (unsigned char *)sixel_allocator_malloc(
25✔
3427
            decoder->allocator,
2✔
3428
            (size_t)max);
2✔
3429
        if (raw_data == NULL) {
25!
UNCOV
3430
            sixel_helper_set_additional_message(
×
3431
                "sixel_decoder_decode: sixel_allocator_malloc() failed.");
UNCOV
3432
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
3433
            goto end;
×
3434
        }
3435
        if (clipboard_blob_size > 0u && clipboard_blob != NULL) {
25!
3436
            memcpy(raw_data, clipboard_blob, clipboard_blob_size);
25✔
3437
        }
2✔
3438
        raw_len = (int)clipboard_blob_size;
25✔
3439
        if (clipboard_blob != NULL) {
25!
3440
            free(clipboard_blob);
25✔
3441
            clipboard_blob = NULL;
25✔
3442
        }
2✔
3443
    } else {
2✔
3444
        if (strcmp(decoder->input, "-") == 0) {
992✔
3445
            /* for windows */
3446
#if defined(O_BINARY)
3447
            (void)sixel_compat_set_binary(STDIN_FILENO);
264✔
3448
#endif  /* defined(O_BINARY) */
3449
            input_fp = stdin;
384✔
3450
        } else {
24✔
3451
            input_fp = sixel_compat_fopen(decoder->input, "rb");
608✔
3452
            if (! input_fp) {
608✔
3453
                (void)snprintf(
16✔
3454
                    message,
1✔
3455
                    sizeof(message) - 1,
3456
                    "sixel_decoder_decode: failed to open input file: %s.",
3457
                    decoder->input);
1✔
3458
                sixel_helper_set_additional_message(message);
16✔
3459
                status = (SIXEL_LIBC_ERROR | (errno & 0xff));
16✔
3460
                goto end;
16✔
3461
            }
3462
        }
3463

3464
        raw_len = 0;
976✔
3465
        max = 64 * 1024;
976✔
3466

3467
        raw_data = (unsigned char *)sixel_allocator_malloc(
976✔
3468
            decoder->allocator,
62✔
3469
            (size_t)max);
62✔
3470
        if (raw_data == NULL) {
976!
UNCOV
3471
            sixel_helper_set_additional_message(
×
3472
                "sixel_decoder_decode: sixel_allocator_malloc() failed.");
UNCOV
3473
            status = SIXEL_BAD_ALLOCATION;
×
UNCOV
3474
            goto end;
×
3475
        }
3476

3477
        for (;;) {
3,110✔
3478
            if ((max - raw_len) < 4096) {
3,808✔
3479
                max *= 2;
72✔
3480
                raw_data = (unsigned char *)sixel_allocator_realloc(
72✔
3481
                    decoder->allocator,
6✔
3482
                    raw_data,
6✔
3483
                    (size_t)max);
6✔
3484
                if (raw_data == NULL) {
72!
UNCOV
3485
                    sixel_helper_set_additional_message(
×
3486
                        "sixel_decoder_decode: sixel_allocator_realloc() failed.");
UNCOV
3487
                    status = SIXEL_BAD_ALLOCATION;
×
UNCOV
3488
                    goto end;
×
3489
                }
3490
            }
6✔
3491
            if ((n = (int)fread(raw_data + raw_len, 1, 4096, input_fp)) <= 0) {
4,216!
3492
                break;
550✔
3493
            }
3494
            raw_len += n;
2,832✔
3495
        }
3496

3497
        if (input_fp != NULL && input_fp != stdin) {
976!
3498
            fclose(input_fp);
592✔
3499
        }
38✔
3500
    }
3501

3502
    ncolors = 0;
1,001✔
3503

3504
    if (decoder->gpu_policy == SIXEL_GPU_POLICY_FORCE &&
1,001✔
3505
            decoder->dequantize_method != SIXEL_DEQUANTIZE_NONE &&
16!
3506
            decoder->dequantize_method !=
9!
3507
                SIXEL_DEQUANTIZE_LSO_UNDITHER_VLIGHT) {
3508
        sixel_helper_set_additional_message(
16✔
3509
            "sixel_decoder_decode: GPU dequant supports fast4 only.");
3510
        status = SIXEL_BAD_ARGUMENT;
16✔
3511
        goto end;
16✔
3512
    }
3513

3514
    if (decoder->dequantize_method == SIXEL_DEQUANTIZE_LSO_UNDITHER_VLIGHT) {
985✔
3515
        /*
3516
         * The decoder GPU dequantizer consumes the direct RGBA image that the
3517
         * SIXEL parser already knows how to produce.  The legacy frame API can
3518
         * still ask for RGB fast4 output, so keep that case on the historical
3519
         * CPU path instead of silently handing an RGBA buffer to an RGB frame.
3520
         */
3521
        if (decoder->direct_color != 0 &&
56!
3522
                decoder->gpu_policy != SIXEL_GPU_POLICY_OFF) {
14!
UNCOV
3523
            if (logger_prepared) {
×
UNCOV
3524
                sixel_timeline_logger_logf(logger,
×
3525
                                  "decoder",
3526
                                  "undither_fast4_gpu",
3527
                                  "start",
3528
                                  0);
3529
            }
UNCOV
3530
            status = sixel_decoder_decode_pixels_gpu_fast4_try(
×
3531
                decoder,
3532
                raw_data,
3533
                (size_t)raw_len,
3534
                0U,
3535
                &fast4_pixels,
3536
                &sx,
3537
                &sy,
3538
                &gpu_result_flags);
UNCOV
3539
            if (logger_prepared && status != SIXEL_FALSE) {
×
UNCOV
3540
                sixel_timeline_logger_logf(
×
3541
                    logger,
3542
                    "decoder",
3543
                    "undither_fast4_gpu",
UNCOV
3544
                    SIXEL_FAILED(status) ? "abort" : "finish",
×
3545
                    0);
3546
            }
UNCOV
3547
            if (SIXEL_FAILED(status)) {
×
UNCOV
3548
                goto end;
×
3549
            }
3550
        }
3551
        if (fast4_pixels == NULL) {
56!
3552
            if (decoder->gpu_policy == SIXEL_GPU_POLICY_FORCE) {
56!
UNCOV
3553
                sixel_helper_set_additional_message(
×
3554
                    "sixel_decoder_decode: GPU fast4 requires direct RGBA "
3555
                    "output.");
UNCOV
3556
                status = SIXEL_BAD_ARGUMENT;
×
UNCOV
3557
                goto end;
×
3558
            }
3559
            if (logger_prepared) {
56!
UNCOV
3560
                sixel_timeline_logger_logf(logger,
×
3561
                                  "decoder",
3562
                                  "undither_fast4",
3563
                                  "start",
3564
                                  0);
3565
            }
3566
            status = sixel_decode_kundither_fast4_with_options(
80✔
3567
                raw_data,
4✔
3568
                raw_len,
4✔
3569
                decoder->direct_color != 0,
56✔
3570
                decoder->dequantize_similarity_bias,
4✔
3571
                0U,
3572
                NULL,
3573
                &fast4_pixels,
3574
                &sx,
3575
                &sy,
3576
                decoder->allocator);
4✔
3577
            if (logger_prepared) {
56!
UNCOV
3578
                sixel_timeline_logger_logf(logger,
×
3579
                                  "decoder",
3580
                                  "undither_fast4",
UNCOV
3581
                                  SIXEL_FAILED(status) ? "abort" : "finish",
×
3582
                                  0);
3583
            }
3584
        }
4✔
3585
    } else if (decoder->direct_color != 0 &&
933✔
3586
            decoder->dequantize_method == SIXEL_DEQUANTIZE_NONE) {
54✔
3587
        status = sixel_decode_direct(
80✔
3588
            raw_data,
5✔
3589
            raw_len,
5✔
3590
            &direct_pixels,
3591
            &sx,
3592
            &sy,
3593
            decoder->allocator);
5✔
3594
    } else {
5✔
3595
        status = sixel_decode_raw(
849✔
3596
            raw_data,
54✔
3597
            raw_len,
54✔
3598
            &indexed_pixels,
3599
            &sx,
3600
            &sy,
3601
            &palette,
3602
            &ncolors,
3603
            decoder->allocator);
54✔
3604
    }
3605
    if (SIXEL_FAILED(status)) {
985✔
3606
        goto end;
16✔
3607
    }
3608

3609
    if (sx > SIXEL_WIDTH_LIMIT || sy > SIXEL_HEIGHT_LIMIT) {
969!
UNCOV
3610
        status = SIXEL_BAD_INPUT;
×
UNCOV
3611
        goto end;
×
3612
    }
3613

3614
    if (decoder->dequantize_method == SIXEL_DEQUANTIZE_LSO_UNDITHER_VLIGHT) {
969✔
3615
        output_pixels = fast4_pixels;
56✔
3616
        output_palette = NULL;
56✔
3617
        output_pixelformat = decoder->direct_color != 0 ?
58✔
3618
            SIXEL_PIXELFORMAT_RGBA8888 : SIXEL_PIXELFORMAT_RGB888;
52✔
3619
        frame_ncolors = 0;
32✔
3620
    } else if (decoder->direct_color != 0 &&
917✔
3621
            decoder->dequantize_method == SIXEL_DEQUANTIZE_NONE) {
54✔
3622
        output_pixels = direct_pixels;
80✔
3623
        output_palette = NULL;
80✔
3624
        output_pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
80✔
3625
        frame_ncolors = 0;
80✔
3626
    } else {
5✔
3627
        output_pixels = indexed_pixels;
833✔
3628
        output_palette = palette;
833✔
3629
        output_pixelformat = SIXEL_PIXELFORMAT_PAL8;
833✔
3630

3631
        if (decoder->dequantize_method == SIXEL_DEQUANTIZE_K_UNDITHER) {
833✔
3632
            if (logger_prepared) {
96!
UNCOV
3633
                sixel_timeline_logger_logf(logger,
×
3634
                                  "decoder",
3635
                                  "undither",
3636
                                  "start",
3637
                                  0);
3638
            }
3639
            status = sixel_dequantize_k_undither(
96✔
3640
                indexed_pixels,
6✔
3641
                sx,
6✔
3642
                sy,
6✔
3643
                palette,
6✔
3644
                ncolors,
6✔
3645
                decoder->dequantize_similarity_bias,
6✔
3646
                decoder->dequantize_edge_strength,
6✔
3647
                decoder->allocator,
6✔
3648
                &rgb_pixels);
3649
            if (SIXEL_FAILED(status)) {
96!
UNCOV
3650
                if (logger_prepared) {
×
UNCOV
3651
                    sixel_timeline_logger_logf(
×
3652
                        logger,
3653
                        "decoder",
3654
                        "undither",
3655
                        "abort",
3656
                        0);
3657
                }
UNCOV
3658
                goto end;
×
3659
            }
3660
            if (decoder->direct_color != 0) {
96✔
3661
                status = sixel_decoder_promote_rgb888_to_rgba8888(
16✔
3662
                    &direct_pixels,
3663
                    rgb_pixels,
1✔
3664
                    sx,
1✔
3665
                    sy,
1✔
3666
                    decoder->allocator);
1✔
3667
                if (SIXEL_FAILED(status)) {
16!
UNCOV
3668
                    if (logger_prepared) {
×
UNCOV
3669
                        sixel_timeline_logger_logf(
×
3670
                            logger,
3671
                            "decoder",
3672
                            "undither",
3673
                            "abort",
3674
                            0);
3675
                    }
UNCOV
3676
                    goto end;
×
3677
                }
3678
            }
1✔
3679
            if (logger_prepared) {
96!
UNCOV
3680
                sixel_timeline_logger_logf(logger,
×
3681
                                  "decoder",
3682
                                  "undither",
3683
                                  "finish",
3684
                                  0);
3685
            }
3686
            if (decoder->direct_color != 0) {
96✔
3687
                output_pixels = direct_pixels;
16✔
3688
                output_palette = NULL;
16✔
3689
                output_pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
16✔
3690
            } else {
1✔
3691
                output_pixels = rgb_pixels;
80✔
3692
                output_palette = NULL;
80✔
3693
                output_pixelformat = SIXEL_PIXELFORMAT_RGB888;
80✔
3694
            }
3695
        } else if (decoder->dequantize_method ==
743✔
3696
                SIXEL_DEQUANTIZE_SELECTIVE_BLUR) {
3697
            if (logger_prepared) {
32!
UNCOV
3698
                sixel_timeline_logger_logf(logger,
×
3699
                                  "decoder",
3700
                                  "selective_blur",
3701
                                  "start",
3702
                                  0);
3703
            }
3704
            status = sixel_dequantize_selective_blur(
32✔
3705
                indexed_pixels,
2✔
3706
                sx,
2✔
3707
                sy,
2✔
3708
                palette,
2✔
3709
                ncolors,
2✔
3710
                decoder->dequantize_selective_blur_threshold,
2✔
3711
                decoder->allocator,
2✔
3712
                &rgb_pixels);
3713
            if (SIXEL_FAILED(status)) {
32!
UNCOV
3714
                if (logger_prepared) {
×
UNCOV
3715
                    sixel_timeline_logger_logf(
×
3716
                        logger,
3717
                        "decoder",
3718
                        "selective_blur",
3719
                        "abort",
3720
                        0);
3721
                }
UNCOV
3722
                goto end;
×
3723
            }
3724
            if (decoder->direct_color != 0) {
32!
UNCOV
3725
                status = sixel_decoder_promote_rgb888_to_rgba8888(
×
3726
                    &direct_pixels,
3727
                    rgb_pixels,
3728
                    sx,
3729
                    sy,
3730
                    decoder->allocator);
UNCOV
3731
                if (SIXEL_FAILED(status)) {
×
UNCOV
3732
                    if (logger_prepared) {
×
UNCOV
3733
                        sixel_timeline_logger_logf(
×
3734
                            logger,
3735
                            "decoder",
3736
                            "selective_blur",
3737
                            "abort",
3738
                            0);
3739
                    }
UNCOV
3740
                    goto end;
×
3741
                }
3742
            }
3743
            if (logger_prepared) {
32!
UNCOV
3744
                sixel_timeline_logger_logf(logger,
×
3745
                                  "decoder",
3746
                                  "selective_blur",
3747
                                  "finish",
3748
                                  0);
3749
            }
3750
            if (decoder->direct_color != 0) {
32!
UNCOV
3751
                output_pixels = direct_pixels;
×
UNCOV
3752
                output_palette = NULL;
×
UNCOV
3753
                output_pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
×
3754
            } else {
3755
                output_pixels = rgb_pixels;
32✔
3756
                output_palette = NULL;
32✔
3757
                output_pixelformat = SIXEL_PIXELFORMAT_RGB888;
32✔
3758
            }
3759
        }
2✔
3760

3761
        if (output_pixelformat == SIXEL_PIXELFORMAT_PAL8) {
525✔
3762
            frame_ncolors = ncolors;
705✔
3763
        } else {
45✔
3764
            frame_ncolors = 0;
72✔
3765
        }
3766
    }
3767

3768
    if (thumbnail_size > 0) {
969!
3769
        /*
3770
         * When the caller requests a thumbnail, compute the new geometry
3771
         * while preserving the original aspect ratio. We only allocate a
3772
         * frame when the dimensions actually change, so the fast path for
3773
         * matching sizes still avoids any additional allocations.
3774
         */
UNCOV
3775
        max_dimension = sx;
×
UNCOV
3776
        if (sy > max_dimension) {
×
3777
            max_dimension = sy;
3778
        }
UNCOV
3779
        if (max_dimension > 0) {
×
UNCOV
3780
            if (sx >= sy) {
×
UNCOV
3781
                new_width = thumbnail_size;
×
UNCOV
3782
                scaled_height = (double)sy * (double)thumbnail_size /
×
UNCOV
3783
                    (double)sx;
×
UNCOV
3784
                new_height = (int)(scaled_height + 0.5);
×
3785
            } else {
UNCOV
3786
                new_height = thumbnail_size;
×
UNCOV
3787
                scaled_width = (double)sx * (double)thumbnail_size /
×
UNCOV
3788
                    (double)sy;
×
UNCOV
3789
                new_width = (int)(scaled_width + 0.5);
×
3790
            }
UNCOV
3791
            if (new_width < 1) {
×
3792
                new_width = 1;
3793
            }
UNCOV
3794
            if (new_height < 1) {
×
3795
                new_height = 1;
3796
            }
UNCOV
3797
            if (new_width != sx || new_height != sy) {
×
3798
                /*
3799
                 * Wrap the decoded pixels in a frame so we can reuse the
3800
                 * central scaling helper. Ownership transfers to the frame,
3801
                 * which keeps the lifetime rules identical on both paths.
3802
                 */
UNCOV
3803
                status = sixel_frame_create_from_factory(
×
3804
                    &frame,
3805
                    decoder->allocator);
UNCOV
3806
                if (SIXEL_FAILED(status)) {
×
UNCOV
3807
                    goto end;
×
3808
                }
UNCOV
3809
                status = sixel_frame_init(
×
3810
                    frame,
3811
                    output_pixels,
3812
                    sx,
3813
                    sy,
3814
                    output_pixelformat,
3815
                    output_palette,
3816
                    frame_ncolors);
UNCOV
3817
                if (SIXEL_FAILED(status)) {
×
UNCOV
3818
                    goto end;
×
3819
                }
UNCOV
3820
                if (output_pixels == indexed_pixels) {
×
UNCOV
3821
                    indexed_pixels = NULL;
×
3822
                }
UNCOV
3823
                if (output_pixels == rgb_pixels) {
×
UNCOV
3824
                    rgb_pixels = NULL;
×
3825
                }
UNCOV
3826
                if (output_pixels == direct_pixels) {
×
UNCOV
3827
                    direct_pixels = NULL;
×
3828
                }
UNCOV
3829
                if (output_pixels == fast4_pixels) {
×
UNCOV
3830
                    fast4_pixels = NULL;
×
3831
                }
UNCOV
3832
                if (output_palette == palette) {
×
UNCOV
3833
                    palette = NULL;
×
3834
                }
UNCOV
3835
                status = sixel_frame_resize(
×
3836
                    frame,
3837
                    new_width,
3838
                    new_height,
3839
                    SIXEL_RES_BILINEAR);
UNCOV
3840
                if (SIXEL_FAILED(status)) {
×
UNCOV
3841
                    goto end;
×
3842
                }
3843
                /*
3844
                 * The resized frame already exposes a tightly packed RGB
3845
                 * buffer, so write the updated dimensions and references
3846
                 * back to the main encoder path.
3847
                 */
UNCOV
3848
                sx = sixel_frame_get_width(frame);
×
UNCOV
3849
                sy = sixel_frame_get_height(frame);
×
UNCOV
3850
                output_pixels = sixel_frame_get_pixels(frame);
×
UNCOV
3851
                output_palette = NULL;
×
UNCOV
3852
                output_pixelformat = sixel_frame_get_pixelformat(frame);
×
3853
            }
3854
        }
3855
    }
3856

3857
    if (decoder->clipboard_output_active) {
969✔
3858
        clipboard_output_status = decoder_clipboard_create_spool(
32✔
3859
            decoder->allocator,
2✔
3860
            "clipboard-out",
3861
            &clipboard_output_path);
3862
        if (SIXEL_FAILED(clipboard_output_status)) {
32!
UNCOV
3863
            status = clipboard_output_status;
×
UNCOV
3864
            goto end;
×
3865
        }
3866
    }
2✔
3867

3868
    if (logger_prepared) {
969✔
3869
        sixel_timeline_logger_logf(logger,
24✔
3870
                          "io",
3871
                          "png",
3872
                          "start",
3873
                          0);
3874
    }
2✔
3875
    status = sixel_helper_write_image_file(
969✔
3876
        output_pixels,
62✔
3877
        sx,
62✔
3878
        sy,
62✔
3879
        output_palette,
62✔
3880
        output_pixelformat,
62✔
3881
        decoder->clipboard_output_active
969✔
3882
            ? clipboard_output_path
2✔
3883
            : decoder->output,
60✔
3884
        SIXEL_FORMAT_PNG,
3885
        decoder->allocator);
62✔
3886
    if (SIXEL_FAILED(status)) {
969!
UNCOV
3887
        if (logger_prepared) {
×
UNCOV
3888
            sixel_timeline_logger_logf(logger,
×
3889
                              "io",
3890
                              "png",
3891
                              "abort",
3892
                              0);
3893
        }
UNCOV
3894
        goto end;
×
3895
    }
3896
    if (logger_prepared) {
969✔
3897
        sixel_timeline_logger_logf(logger,
24✔
3898
                          "io",
3899
                          "png",
3900
                          "finish",
3901
                          0);
3902
    }
2✔
3903

3904
    if (decoder->clipboard_output_active) {
971✔
3905
        clipboard_output_status = decoder_clipboard_read_file(
32✔
3906
            clipboard_output_path,
2✔
3907
            &clipboard_output_data,
3908
            &clipboard_output_size);
3909
        if (SIXEL_SUCCEEDED(clipboard_output_status)) {
32!
3910
            clipboard_output_status = sixel_clipboard_write(
32✔
3911
                decoder->clipboard_output_format,
32✔
3912
                clipboard_output_data,
2✔
3913
                clipboard_output_size);
2✔
3914
        }
2✔
3915
        if (clipboard_output_data != NULL) {
32!
3916
            free(clipboard_output_data);
32✔
3917
            clipboard_output_data = NULL;
32✔
3918
        }
2✔
3919
        if (SIXEL_FAILED(clipboard_output_status)) {
32✔
3920
            status = clipboard_output_status;
7✔
3921
            goto end;
7✔
3922
        }
3923
    }
2✔
3924

3925
end:
900✔
3926
    sixel_frame_unref(frame);
1,017✔
3927
    sixel_allocator_free(decoder->allocator, raw_data);
1,017✔
3928
    sixel_allocator_free(decoder->allocator, indexed_pixels);
1,017✔
3929
    sixel_allocator_free(decoder->allocator, palette);
1,017✔
3930
    sixel_allocator_free(decoder->allocator, direct_pixels);
1,017✔
3931
    sixel_allocator_free(decoder->allocator, rgb_pixels);
1,017✔
3932
    sixel_allocator_free(decoder->allocator, fast4_pixels);
1,017✔
3933
    if (clipboard_blob != NULL) {
1,017!
UNCOV
3934
        free(clipboard_blob);
×
3935
    }
3936
    if (clipboard_output_path != NULL) {
1,017✔
3937
        (void)sixel_compat_unlink(clipboard_output_path);
32✔
3938
        sixel_allocator_free(decoder->allocator, clipboard_output_path);
32✔
3939
    }
2✔
3940

3941
    sixel_decoder_unref(decoder);
1,017✔
3942
    if (logger_prepared) {
1,017✔
3943
        sixel_timeline_logger_unref(logger);
24✔
3944
    }
2✔
3945

3946
    return status;
1,017✔
3947
}
3948

3949

3950
/* Exercise legacy constructor and refcounting for the decoder. */
3951

3952
/* emacs Local Variables:      */
3953
/* emacs mode: c               */
3954
/* emacs tab-width: 4          */
3955
/* emacs indent-tabs-mode: nil */
3956
/* emacs c-basic-offset: 4     */
3957
/* emacs End:                  */
3958
/* vim: set expandtab ts=4 sts=4 sw=4 : */
3959
/* 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