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

saitoha / libsixel / 18743025293

23 Oct 2025 08:54AM UTC coverage: 53.951% (+0.003%) from 53.948%
18743025293

push

github

saitoha
test: fix images/ path in meson test session

5439 of 14556 branches covered (37.37%)

7933 of 14704 relevant lines covered (53.95%)

1069915.96 hits per line

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

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

22
#include "config.h"
23

24
/* STDC_HEADERS */
25
#include <stdio.h>
26
#include <stdlib.h>
27
#include <string.h>
28

29
#if HAVE_LIMITS_H
30
# include <limits.h>
31
#endif
32
#if HAVE_UNISTD_H
33
# include <unistd.h>
34
#elif HAVE_SYS_UNISTD_H
35
# include <sys/unistd.h>
36
#endif  /* HAVE_UNISTD_H */
37
#if HAVE_ERRNO_H
38
# include <errno.h>
39
#endif /* HAVE_ERRNO_H */
40

41
#include "decoder.h"
42
#include "frame.h"
43

44

45
/* original version of strdup(3) with allocator object */
46
static char *
47
strdup_with_allocator(
66✔
48
    char const          /* in */ *s,          /* source buffer */
49
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
50
                                                 destination buffer */
51
{
52
    char *p;
53

54
    p = (char *)sixel_allocator_malloc(allocator, (size_t)(strlen(s) + 1));
66✔
55
    if (p) {
66!
56
        strcpy(p, s);
66✔
57
    }
22✔
58
    return p;
66✔
59
}
60

61

62
/* create decoder object */
63
SIXELAPI SIXELSTATUS
64
sixel_decoder_new(
24✔
65
    sixel_decoder_t    /* out */ **ppdecoder,  /* decoder object to be created */
66
    sixel_allocator_t  /* in */  *allocator)   /* allocator, null if you use
67
                                                  default allocator */
68
{
69
    SIXELSTATUS status = SIXEL_FALSE;
24✔
70

71
    if (allocator == NULL) {
24!
72
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
24✔
73
        if (SIXEL_FAILED(status)) {
24!
74
            goto end;
×
75
        }
76
    } else {
8✔
77
        sixel_allocator_ref(allocator);
×
78
    }
79

80
    *ppdecoder = sixel_allocator_malloc(allocator, sizeof(sixel_decoder_t));
24✔
81
    if (*ppdecoder == NULL) {
24!
82
        sixel_allocator_unref(allocator);
×
83
        sixel_helper_set_additional_message(
×
84
            "sixel_decoder_new: sixel_allocator_malloc() failed.");
85
        status = SIXEL_BAD_ALLOCATION;
×
86
        goto end;
×
87
    }
88

89
    (*ppdecoder)->ref          = 1;
24✔
90
    (*ppdecoder)->output       = strdup_with_allocator("-", allocator);
24✔
91
    (*ppdecoder)->input        = strdup_with_allocator("-", allocator);
24✔
92
    (*ppdecoder)->allocator    = allocator;
24✔
93
    (*ppdecoder)->dequantize_method = SIXEL_DEQUANTIZE_NONE;
24✔
94
    (*ppdecoder)->dequantize_similarity_bias = 100;
24✔
95
    (*ppdecoder)->dequantize_edge_strength = 0;
24✔
96
    (*ppdecoder)->thumbnail_size = 0;
24✔
97

98
    if ((*ppdecoder)->output == NULL || (*ppdecoder)->input == NULL) {
24!
99
        sixel_decoder_unref(*ppdecoder);
×
100
        *ppdecoder = NULL;
×
101
        sixel_helper_set_additional_message(
×
102
            "sixel_decoder_new: strdup_with_allocator() failed.");
103
        status = SIXEL_BAD_ALLOCATION;
×
104
        sixel_allocator_unref(allocator);
×
105
        goto end;
×
106
    }
107

108
    status = SIXEL_OK;
24✔
109

110
end:
16✔
111
    return status;
24✔
112
}
113

114

115
/* deprecated version of sixel_decoder_new() */
116
SIXELAPI /* deprecated */ sixel_decoder_t *
117
sixel_decoder_create(void)
×
118
{
119
    SIXELSTATUS status = SIXEL_FALSE;
×
120
    sixel_decoder_t *decoder = NULL;
×
121

122
    status = sixel_decoder_new(&decoder, NULL);
×
123
    if (SIXEL_FAILED(status)) {
×
124
        goto end;
×
125
    }
126

127
end:
128
    return decoder;
×
129
}
130

131

132
/* destroy a decoder object */
133
static void
134
sixel_decoder_destroy(sixel_decoder_t *decoder)
24✔
135
{
136
    sixel_allocator_t *allocator;
137

138
    if (decoder) {
24!
139
        allocator = decoder->allocator;
24✔
140
        sixel_allocator_free(allocator, decoder->input);
24✔
141
        sixel_allocator_free(allocator, decoder->output);
24✔
142
        sixel_allocator_free(allocator, decoder);
24✔
143
        sixel_allocator_unref(allocator);
24✔
144
    }
8✔
145
}
24✔
146

147

148
/* increase reference count of decoder object (thread-unsafe) */
149
SIXELAPI void
150
sixel_decoder_ref(sixel_decoder_t *decoder)
36✔
151
{
152
    /* TODO: be thread safe */
153
    ++decoder->ref;
36✔
154
}
36✔
155

156

157
/* decrease reference count of decoder object (thread-unsafe) */
158
SIXELAPI void
159
sixel_decoder_unref(sixel_decoder_t *decoder)
60✔
160
{
161
    /* TODO: be thread safe */
162
    if (decoder != NULL && --decoder->ref == 0) {
60!
163
        sixel_decoder_destroy(decoder);
24✔
164
    }
8✔
165
}
60✔
166

167

168
typedef struct sixel_similarity {
169
    const unsigned char *palette;
170
    int ncolors;
171
    int stride;
172
    signed char *cache;
173
    int bias;
174
} sixel_similarity_t;
175

176
static SIXELSTATUS
177
sixel_similarity_init(sixel_similarity_t *similarity,
×
178
                      const unsigned char *palette,
179
                      int ncolors,
180
                      int bias,
181
                      sixel_allocator_t *allocator)
182
{
183
    size_t cache_size;
184
    int i;
185

186
    if (bias < 1) {
×
187
        bias = 1;
×
188
    }
189

190
    similarity->palette = palette;
×
191
    similarity->ncolors = ncolors;
×
192
    similarity->stride = ncolors;
×
193
    similarity->bias = bias;
×
194

195
    cache_size = (size_t)ncolors * (size_t)ncolors;
×
196
    if (cache_size == 0) {
×
197
        similarity->cache = NULL;
×
198
        return SIXEL_OK;
×
199
    }
200

201
    similarity->cache = (signed char *)sixel_allocator_malloc(
×
202
        allocator,
203
        cache_size);
204
    if (similarity->cache == NULL) {
×
205
        sixel_helper_set_additional_message(
×
206
            "sixel_similarity_init: sixel_allocator_malloc() failed.");
207
        return SIXEL_BAD_ALLOCATION;
×
208
    }
209
    memset(similarity->cache, -1, cache_size);
×
210
    for (i = 0; i < ncolors; ++i) {
×
211
        similarity->cache[i * similarity->stride + i] = 7;
×
212
    }
213

214
    return SIXEL_OK;
×
215
}
216

217
static void
218
sixel_similarity_destroy(sixel_similarity_t *similarity,
×
219
                         sixel_allocator_t *allocator)
220
{
221
    if (similarity->cache != NULL) {
×
222
        sixel_allocator_free(allocator, similarity->cache);
×
223
        similarity->cache = NULL;
×
224
    }
225
}
×
226

227
static inline unsigned int
228
sixel_similarity_diff(const unsigned char *a, const unsigned char *b)
×
229
{
230
    int dr = (int)a[0] - (int)b[0];
×
231
    int dg = (int)a[1] - (int)b[1];
×
232
    int db = (int)a[2] - (int)b[2];
×
233
    return (unsigned int)(dr * dr + dg * dg + db * db);
×
234
}
235

236
static unsigned int
237
sixel_similarity_compare(sixel_similarity_t *similarity,
×
238
                         int index1,
239
                         int index2,
240
                         int numerator,
241
                         int denominator)
242
{
243
    int min_index;
244
    int max_index;
245
    size_t cache_pos;
246
    signed char cached;
247
    const unsigned char *palette;
248
    const unsigned char *p1;
249
    const unsigned char *p2;
250
    unsigned char avg_color[3];
251
    unsigned int distance;
252
    unsigned int base_distance;
253
    unsigned long long scaled_distance;
254
    int bias;
255
    unsigned int min_diff = UINT_MAX;
×
256
    int i;
257
    unsigned int result;
258
    const unsigned char *pk;
259
    unsigned int diff;
260

261
    if (similarity->cache == NULL) {
×
262
        return 0;
×
263
    }
264

265
    if (index1 < 0 || index1 >= similarity->ncolors ||
×
266
        index2 < 0 || index2 >= similarity->ncolors) {
×
267
        return 0;
×
268
    }
269

270
    if (index1 <= index2) {
×
271
        min_index = index1;
×
272
        max_index = index2;
×
273
    } else {
274
        min_index = index2;
×
275
        max_index = index1;
×
276
    }
277

278
    cache_pos = (size_t)min_index * (size_t)similarity->stride
×
279
              + (size_t)max_index;
×
280
    cached = similarity->cache[cache_pos];
×
281
    if (cached >= 0) {
×
282
        return (unsigned int)cached;
×
283
    }
284

285
    palette = similarity->palette;
×
286
    p1 = palette + index1 * 3;
×
287
    p2 = palette + index2 * 3;
×
288

289
#if 0
290
   /*    original: n = (p1 + p2) / 2
291
    */
292
    avg_color[0] = (unsigned char)(((unsigned int)p1[0]
293
                                    + (unsigned int)p2[0]) >> 1);
294
    avg_color[1] = (unsigned char)(((unsigned int)p1[1]
295
                                    + (unsigned int)p2[1]) >> 1);
296
    avg_color[2] = (unsigned char)(((unsigned int)p1[2]
297
                                    + (unsigned int)p2[2]) >> 1);
298
#else
299
   /*
300
    *    diffuse(pos_a, n1) -> p1
301
    *    diffuse(pos_b, n2) -> p2
302
    *
303
    *    when n1 == n2 == n:
304
    *
305
    *    p2 = n + (n - p1) * numerator / denominator
306
    * => p2 * denominator = n * denominator + (n - p1) * numerator
307
    * => p2 * denominator = n * denominator + n * numerator - p1 * numerator
308
    * => n * (denominator + numerator) = p1 * numerator + p2 * denominator
309
    * => n = (p1 * numerator + p2 * denominator) / (denominator + numerator)
310
    *
311
    */
312
    avg_color[0] = (p1[0] * numerator + p2[0] * denominator)
×
313
                 / (numerator + denominator);
×
314
    avg_color[1] = (p1[1] * numerator + p2[1] * denominator)
×
315
                 / (numerator + denominator);
×
316
    avg_color[2] = (p1[2] * numerator + p2[2] * denominator)
×
317
                 / (numerator + denominator);
×
318
#endif
319

320
    distance = sixel_similarity_diff(avg_color, p1);
×
321
    bias = similarity->bias;
×
322
    if (bias < 1) {
×
323
        bias = 1;
×
324
    }
325
    scaled_distance = (unsigned long long)distance
×
326
                    * (unsigned long long)bias
×
327
                    + 50ULL;
328
    base_distance = (unsigned int)(scaled_distance / 100ULL);
×
329
    if (base_distance == 0U) {
×
330
        base_distance = 1U;
×
331
    }
332

333
    for (i = 0; i < similarity->ncolors; ++i) {
×
334
        if (i == index1 || i == index2) {
×
335
            continue;
×
336
        }
337
        pk = palette + i * 3;
×
338
        diff = sixel_similarity_diff(avg_color, pk);
×
339
        if (diff < min_diff) {
×
340
            min_diff = diff;
×
341
        }
342
    }
343

344
    if (min_diff == UINT_MAX) {
×
345
        min_diff = base_distance * 2U;
×
346
    }
347

348
    if (min_diff >= base_distance * 2U) {
×
349
        result = 12U;
×
350
    } else if (min_diff >= base_distance) {
×
351
        result = 8U;
×
352
    } else if ((unsigned long long)min_diff * 6ULL
×
353
               >= (unsigned long long)base_distance * 5ULL) {
×
354
        result = 8U;
×
355
    } else if ((unsigned long long)min_diff * 4ULL
×
356
               >= (unsigned long long)base_distance * 3ULL) {
×
357
        result = 7U;
×
358
    } else if ((unsigned long long)min_diff * 3ULL
×
359
               >= (unsigned long long)base_distance * 2ULL) {
×
360
        result = 5U;
×
361
    } else if ((unsigned long long)min_diff * 5ULL
×
362
               >= (unsigned long long)base_distance * 3ULL) {
×
363
        result = 4U;
×
364
    } else if ((unsigned long long)min_diff * 2ULL
×
365
               >= (unsigned long long)base_distance * 1ULL) {
×
366
        result = 3U;
×
367
    } else if ((unsigned long long)min_diff * 3ULL
×
368
               >= (unsigned long long)base_distance * 1ULL) {
×
369
        result = 2U;
×
370
    } else {
371
        result = 0U;
×
372
    }
373

374
    similarity->cache[cache_pos] = (signed char)result;
×
375

376
    return result;
×
377
}
378

379
static inline int
380
sixel_clamp(int value, int min_value, int max_value)
×
381
{
382
    if (value < min_value) {
×
383
        return min_value;
×
384
    }
385
    if (value > max_value) {
×
386
        return max_value;
×
387
    }
388
    return value;
×
389
}
390

391
static inline int
392
sixel_get_gray(const int *gray, int width, int height, int x, int y)
×
393
{
394
    int cx = sixel_clamp(x, 0, width - 1);
×
395
    int cy = sixel_clamp(y, 0, height - 1);
×
396
    return gray[cy * width + cx];
×
397
}
398

399
static unsigned short
400
sixel_prewitt_value(const int *gray, int width, int height, int x, int y)
×
401
{
402
    int top_prev = sixel_get_gray(gray, width, height, x - 1, y - 1);
×
403
    int top_curr = sixel_get_gray(gray, width, height, x, y - 1);
×
404
    int top_next = sixel_get_gray(gray, width, height, x + 1, y - 1);
×
405
    int mid_prev = sixel_get_gray(gray, width, height, x - 1, y);
×
406
    int mid_next = sixel_get_gray(gray, width, height, x + 1, y);
×
407
    int bot_prev = sixel_get_gray(gray, width, height, x - 1, y + 1);
×
408
    int bot_curr = sixel_get_gray(gray, width, height, x, y + 1);
×
409
    int bot_next = sixel_get_gray(gray, width, height, x + 1, y + 1);
×
410
    long gx = (long)top_next - (long)top_prev +
×
411
              (long)mid_next - (long)mid_prev +
×
412
              (long)bot_next - (long)bot_prev;
×
413
    long gy = (long)bot_prev + (long)bot_curr + (long)bot_next -
×
414
              (long)top_prev - (long)top_curr - (long)top_next;
×
415
    unsigned long long magnitude = (unsigned long long)gx
×
416
                                 * (unsigned long long)gx
×
417
                                 + (unsigned long long)gy
×
418
                                 * (unsigned long long)gy;
×
419
    magnitude /= 256ULL;
×
420
    if (magnitude > 65535ULL) {
×
421
        magnitude = 65535ULL;
×
422
    }
423
    return (unsigned short)magnitude;
×
424
}
425

426
static unsigned short
427
sixel_scale_threshold(unsigned short base, int percent)
×
428
{
429
    unsigned long long numerator;
430
    unsigned long long scaled;
431

432
    if (percent <= 0) {
×
433
        percent = 1;
×
434
    }
435

436
    numerator = (unsigned long long)base * 100ULL
×
437
              + (unsigned long long)percent / 2ULL;
×
438
    scaled = numerator / (unsigned long long)percent;
×
439
    if (scaled == 0ULL) {
×
440
        scaled = 1ULL;
×
441
    }
442
    if (scaled > USHRT_MAX) {
×
443
        scaled = USHRT_MAX;
×
444
    }
445

446
    return (unsigned short)scaled;
×
447
}
448

449
static SIXELSTATUS
450
sixel_dequantize_k_undither(unsigned char *indexed_pixels,
×
451
                            int width,
452
                            int height,
453
                            unsigned char *palette,
454
                            int ncolors,
455
                            int similarity_bias,
456
                            int edge_strength,
457
                            sixel_allocator_t *allocator,
458
                            unsigned char **output)
459
{
460
    SIXELSTATUS status = SIXEL_FALSE;
×
461
    unsigned char *rgb = NULL;
×
462
    int *gray = NULL;
×
463
    unsigned short *prewitt = NULL;
×
464
    sixel_similarity_t similarity;
465
    size_t num_pixels;
466
    int x;
467
    int y;
468
    unsigned short strong_threshold;
469
    unsigned short detail_threshold;
470
    static const int neighbor_offsets[8][4] = {
471
        {-1, -1,  2, 16}, {0, -1, 10, 16}, {1, -1,  6, 16},
472
        {-1,  0, 14, 16},                  {1,  0, 14, 16},
473
        {-1,  1,  6, 16}, {0,  1, 10, 16}, {1,  1,  2, 16}
474
    };
475
    const unsigned char *color;
476
    size_t out_index;
477
    int palette_index;
478
    unsigned int center_weight;
479
    unsigned int total_weight = 0;
×
480
    unsigned int accum_r;
481
    unsigned int accum_g;
482
    unsigned int accum_b;
483
    unsigned short gradient;
484
    int neighbor;
485
    int nx;
486
    int ny;
487
    int numerator;
488
    int denominator;
489
    unsigned int weight;
490
    const unsigned char *neighbor_color;
491
    int neighbor_index;
492

493
    if (width <= 0 || height <= 0 || palette == NULL || ncolors <= 0) {
×
494
        return SIXEL_BAD_INPUT;
×
495
    }
496

497
    num_pixels = (size_t)width * (size_t)height;
×
498

499
    memset(&similarity, 0, sizeof(sixel_similarity_t));
×
500

501
    strong_threshold = sixel_scale_threshold(256U, edge_strength);
×
502
    detail_threshold = sixel_scale_threshold(160U, edge_strength);
×
503
    if (strong_threshold < detail_threshold) {
×
504
        strong_threshold = detail_threshold;
×
505
    }
506

507
    /*
508
     * Build RGB and luminance buffers so we can reuse the similarity cache
509
     * and gradient analysis across the reconstructed image.
510
     */
511
    rgb = (unsigned char *)sixel_allocator_malloc(
×
512
        allocator,
513
        num_pixels * 3);
514
    if (rgb == NULL) {
×
515
        sixel_helper_set_additional_message(
×
516
            "sixel_dequantize_k_undither: "
517
            "sixel_allocator_malloc() failed.");
518
        status = SIXEL_BAD_ALLOCATION;
×
519
        goto end;
×
520
    }
521

522
    gray = (int *)sixel_allocator_malloc(
×
523
        allocator,
524
        num_pixels * sizeof(int));
525
    if (gray == NULL) {
×
526
        sixel_helper_set_additional_message(
×
527
            "sixel_dequantize_k_undither: "
528
            "sixel_allocator_malloc() failed.");
529
        status = SIXEL_BAD_ALLOCATION;
×
530
        goto end;
×
531
    }
532

533
    prewitt = (unsigned short *)sixel_allocator_malloc(
×
534
        allocator,
535
        num_pixels * sizeof(unsigned short));
536
    if (prewitt == NULL) {
×
537
        sixel_helper_set_additional_message(
×
538
            "sixel_dequantize_k_undither: "
539
            "sixel_allocator_malloc() failed.");
540
        status = SIXEL_BAD_ALLOCATION;
×
541
        goto end;
×
542
    }
543

544
    /*
545
     * Pre-compute palette distance heuristics so each neighbour lookup reuses
546
     * the k_undither-style similarity table.
547
     */
548
    status = sixel_similarity_init(
×
549
        &similarity,
550
        palette,
551
        ncolors,
552
        similarity_bias,
553
        allocator);
554
    if (SIXEL_FAILED(status)) {
×
555
        goto end;
×
556
    }
557

558
    for (y = 0; y < height; ++y) {
×
559
        for (x = 0; x < width; ++x) {
×
560
            palette_index = indexed_pixels[y * width + x];
×
561
            if (palette_index < 0 || palette_index >= ncolors) {
×
562
                palette_index = 0;
×
563
            }
564

565
            color = palette + palette_index * 3;
×
566
            out_index = (size_t)(y * width + x) * 3;
×
567
            rgb[out_index + 0] = color[0];
×
568
            rgb[out_index + 1] = color[1];
×
569
            rgb[out_index + 2] = color[2];
×
570

571
            if (edge_strength > 0) {
×
572
                gray[y * width + x] = (int)color[0]
×
573
                                    + (int)color[1] * 2
×
574
                                    + (int)color[2];
×
575
                /*
576
                 * Edge detection keeps high-frequency content intact while we
577
                 * smooth dithering noise in flatter regions.
578
                 */
579
                prewitt[y * width + x] = sixel_prewitt_value(
×
580
                    gray,
581
                    width,
582
                    height,
583
                    x,
584
                    y);
585

586
                gradient = prewitt[y * width + x];
×
587
                if (gradient > strong_threshold) {
×
588
                    continue;
×
589
                }
590

591
                if (gradient > detail_threshold) {
×
592
                    center_weight = 24U;
×
593
                } else {
594
                    center_weight = 8U;
×
595
                }
596
            } else {
597
                center_weight = 8U;
×
598
            }
599

600
            out_index = (size_t)(y * width + x) * 3;
×
601
            accum_r = (unsigned int)rgb[out_index + 0] * center_weight;
×
602
            accum_g = (unsigned int)rgb[out_index + 1] * center_weight;
×
603
            accum_b = (unsigned int)rgb[out_index + 2] * center_weight;
×
604
            total_weight = center_weight;
×
605

606
            /*
607
             * Blend neighbours that stay within the palette similarity
608
             * threshold so Floyd-Steinberg noise is averaged away without
609
             * bleeding across pronounced edges.
610
             */
611
            for (neighbor = 0; neighbor < 8; ++neighbor) {
×
612
                nx = x + neighbor_offsets[neighbor][0];
×
613
                ny = y + neighbor_offsets[neighbor][1];
×
614
                numerator = neighbor_offsets[neighbor][2];
×
615
                denominator = neighbor_offsets[neighbor][3];
×
616

617
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
×
618
                    continue;
×
619
                }
620

621
                neighbor_index = indexed_pixels[ny * width + nx];
×
622
                if (neighbor_index < 0 || neighbor_index >= ncolors) {
×
623
                    continue;
×
624
                }
625

626
                if (numerator) {
×
627
                    weight = sixel_similarity_compare(
×
628
                        &similarity,
629
                        palette_index,
630
                        neighbor_index,
631
                        numerator,
632
                        denominator);
633
                    if (weight == 0) {
×
634
                        continue;
×
635
                    }
636

637
                    neighbor_color = palette + neighbor_index * 3;
×
638
                    accum_r += (unsigned int)neighbor_color[0] * weight;
×
639
                    accum_g += (unsigned int)neighbor_color[1] * weight;
×
640
                    accum_b += (unsigned int)neighbor_color[2] * weight;
×
641
                    total_weight += weight;
×
642
                }
643
            }
644

645
            if (total_weight > 0U) {
×
646
                rgb[out_index + 0] = (unsigned char)(accum_r / total_weight);
×
647
                rgb[out_index + 1] = (unsigned char)(accum_g / total_weight);
×
648
                rgb[out_index + 2] = (unsigned char)(accum_b / total_weight);
×
649
            }
650
        }
651
    }
652

653

654
    *output = rgb;
×
655
    rgb = NULL;
×
656
    status = SIXEL_OK;
×
657

658
end:
659
    sixel_similarity_destroy(&similarity, allocator);
×
660
    sixel_allocator_free(allocator, rgb);
×
661
    sixel_allocator_free(allocator, gray);
×
662
    sixel_allocator_free(allocator, prewitt);
×
663
    return status;
×
664
}
665
/* set an option flag to decoder object */
666
SIXELAPI SIXELSTATUS
667
sixel_decoder_setopt(
18✔
668
    sixel_decoder_t /* in */ *decoder,
669
    int             /* in */ arg,
670
    char const      /* in */ *value
671
)
672
{
673
    SIXELSTATUS status = SIXEL_FALSE;
18✔
674

675
    sixel_decoder_ref(decoder);
18✔
676

677
    switch(arg) {
18!
678
    case SIXEL_OPTFLAG_INPUT:  /* i */
8✔
679
        free(decoder->input);
12✔
680
        decoder->input = strdup_with_allocator(value, decoder->allocator);
12✔
681
        if (decoder->input == NULL) {
12!
682
            sixel_helper_set_additional_message(
×
683
                "sixel_decoder_setopt: strdup_with_allocator() failed.");
684
            status = SIXEL_BAD_ALLOCATION;
×
685
            goto end;
×
686
        }
687
        break;
12✔
688
    case SIXEL_OPTFLAG_OUTPUT:  /* o */
4✔
689
        free(decoder->output);
6✔
690
        decoder->output = strdup_with_allocator(value, decoder->allocator);
6✔
691
        if (decoder->output == NULL) {
6!
692
            sixel_helper_set_additional_message(
×
693
                "sixel_decoder_setopt: strdup_with_allocator() failed.");
694
            status = SIXEL_BAD_ALLOCATION;
×
695
            goto end;
×
696
        }
697
        break;
6✔
698
    case SIXEL_OPTFLAG_DEQUANTIZE:  /* d */
699
        if (value == NULL) {
×
700
            sixel_helper_set_additional_message(
×
701
                "sixel_decoder_setopt: -d/--dequantize requires an argument.");
702
            status = SIXEL_BAD_ALLOCATION;
×
703
            goto end;
×
704
        }
705

706
        if (strcmp(value, "none") == 0) {
×
707
            decoder->dequantize_method = SIXEL_DEQUANTIZE_NONE;
×
708
        } else if (strcmp(value, "k_undither") == 0) {
×
709
            decoder->dequantize_method = SIXEL_DEQUANTIZE_K_UNDITHER;
×
710
        } else {
711
            sixel_helper_set_additional_message(
×
712
                "unsupported dequantize method.");
713
            status = SIXEL_BAD_ARGUMENT;
×
714
            goto end;
×
715
        }
716
        break;
×
717

718
    case SIXEL_OPTFLAG_SIMILARITY:  /* S */
719
        decoder->dequantize_similarity_bias = atoi(value);
×
720
        if (decoder->dequantize_similarity_bias < 0 ||
×
721
            decoder->dequantize_similarity_bias > 1000) {
×
722
            sixel_helper_set_additional_message(
×
723
                "similarity must be between 1 and 1000.");
724
            status = SIXEL_BAD_ARGUMENT;
×
725
            goto end;
×
726
        }
727
        break;
×
728

729
    case SIXEL_OPTFLAG_SIZE:  /* s */
730
        decoder->thumbnail_size = atoi(value);
×
731
        if (decoder->thumbnail_size <= 0) {
×
732
            sixel_helper_set_additional_message(
×
733
                "size must be greater than zero.");
734
            status = SIXEL_BAD_ARGUMENT;
×
735
            goto end;
×
736
        }
737
        break;
×
738

739
    case SIXEL_OPTFLAG_EDGE:  /* e */
740
        decoder->dequantize_edge_strength = atoi(value);
×
741
        if (decoder->dequantize_edge_strength < 0 ||
×
742
            decoder->dequantize_edge_strength > 1000) {
×
743
            sixel_helper_set_additional_message(
×
744
                "edge bias must be between 1 and 1000.");
745
            status = SIXEL_BAD_ARGUMENT;
×
746
            goto end;
×
747
        }
748
        break;
×
749

750
    case '?':
×
751
    default:
752
        status = SIXEL_BAD_ARGUMENT;
×
753
        goto end;
×
754
    }
755

756
    status = SIXEL_OK;
18✔
757

758
end:
12✔
759
    sixel_decoder_unref(decoder);
18✔
760

761
    return status;
18✔
762
}
763

764

765
/* load source data from stdin or the file specified with
766
   SIXEL_OPTFLAG_INPUT flag, and decode it */
767
SIXELAPI SIXELSTATUS
768
sixel_decoder_decode(
18✔
769
    sixel_decoder_t /* in */ *decoder)
770
{
771
    SIXELSTATUS status = SIXEL_FALSE;
18✔
772
    unsigned char *raw_data = NULL;
18✔
773
    int sx;
774
    int sy;
775
    int raw_len;
776
    int max;
777
    int n;
778
    FILE *input_fp = NULL;
18✔
779
    unsigned char *indexed_pixels = NULL;
18✔
780
    unsigned char *palette = NULL;
18✔
781
    unsigned char *rgb_pixels = NULL;
18✔
782
    unsigned char *output_pixels;
783
    unsigned char *output_palette;
784
    int output_pixelformat;
785
    int ncolors;
786
    sixel_frame_t *frame;
787
    int new_width;
788
    int new_height;
789
    double scaled_width;
790
    double scaled_height;
791
    int max_dimension;
792
    int thumbnail_size;
793
    int frame_ncolors;
794

795
    sixel_decoder_ref(decoder);
18✔
796

797
    frame = NULL;
18✔
798
    new_width = 0;
18✔
799
    new_height = 0;
18✔
800
    scaled_width = 0.0;
18✔
801
    scaled_height = 0.0;
18✔
802
    max_dimension = 0;
18✔
803
    thumbnail_size = decoder->thumbnail_size;
18✔
804
    frame_ncolors = -1;
18✔
805

806
    if (strcmp(decoder->input, "-") == 0) {
18✔
807
        /* for windows */
808
#if defined(O_BINARY)
809
# if HAVE__SETMODE
810
        _setmode(STDIN_FILENO, O_BINARY);
811
# elif HAVE_SETMODE
812
        setmode(STDIN_FILENO, O_BINARY);
813
# endif  /* HAVE_SETMODE */
814
#endif  /* defined(O_BINARY) */
815
        input_fp = stdin;
9✔
816
    } else {
3✔
817
        input_fp = fopen(decoder->input, "rb");
9✔
818
        if (!input_fp) {
9✔
819
            sixel_helper_set_additional_message(
6✔
820
                "sixel_decoder_decode: fopen() failed.");
821
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
6✔
822
            goto end;
6✔
823
        }
824
    }
825

826
    raw_len = 0;
12✔
827
    max = 64 * 1024;
12✔
828

829
    raw_data = (unsigned char *)sixel_allocator_malloc(
12✔
830
        decoder->allocator,
4✔
831
        (size_t)max);
4✔
832
    if (raw_data == NULL) {
12!
833
        sixel_helper_set_additional_message(
×
834
            "sixel_decoder_decode: sixel_allocator_malloc() failed.");
835
        status = SIXEL_BAD_ALLOCATION;
×
836
        goto end;
×
837
    }
838

839
    for (;;) {
136✔
840
        if ((max - raw_len) < 4096) {
408✔
841
            max *= 2;
18✔
842
            raw_data = (unsigned char *)sixel_allocator_realloc(
18✔
843
                decoder->allocator,
6✔
844
                raw_data,
6✔
845
                (size_t)max);
6✔
846
            if (raw_data == NULL) {
18!
847
                sixel_helper_set_additional_message(
×
848
                    "sixel_decoder_decode: sixel_allocator_realloc() failed.");
849
                status = SIXEL_BAD_ALLOCATION;
×
850
                goto end;
×
851
            }
852
        }
6✔
853
        if ((n = (int)fread(raw_data + raw_len, 1, 4096, input_fp)) <= 0) {
408✔
854
            break;
12✔
855
        }
856
        raw_len += n;
396✔
857
    }
858

859
    if (input_fp != stdout) {
12!
860
        fclose(input_fp);
12✔
861
    }
4✔
862

863
    status = sixel_decode_raw(
12✔
864
        raw_data,
4✔
865
        raw_len,
4✔
866
        &indexed_pixels,
867
        &sx,
868
        &sy,
869
        &palette,
870
        &ncolors,
871
        decoder->allocator);
4✔
872
    if (SIXEL_FAILED(status)) {
12!
873
        goto end;
×
874
    }
875

876
    if (sx > SIXEL_WIDTH_LIMIT || sy > SIXEL_HEIGHT_LIMIT) {
12!
877
        status = SIXEL_BAD_INPUT;
×
878
        goto end;
×
879
    }
880

881
    output_pixels = indexed_pixels;
12✔
882
    output_palette = palette;
12✔
883
    output_pixelformat = SIXEL_PIXELFORMAT_PAL8;
12✔
884

885
    if (decoder->dequantize_method == SIXEL_DEQUANTIZE_K_UNDITHER) {
12!
886
        status = sixel_dequantize_k_undither(
×
887
            indexed_pixels,
888
            sx,
889
            sy,
890
            palette,
891
            ncolors,
892
            decoder->dequantize_similarity_bias,
893
            decoder->dequantize_edge_strength,
894
            decoder->allocator,
895
            &rgb_pixels);
896
        if (SIXEL_FAILED(status)) {
×
897
            goto end;
×
898
        }
899
        output_pixels = rgb_pixels;
×
900
        output_palette = NULL;
×
901
        output_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
902
    }
903

904
    if (output_pixelformat == SIXEL_PIXELFORMAT_PAL8) {
12!
905
        frame_ncolors = ncolors;
12✔
906
    }
4✔
907

908
    if (thumbnail_size > 0) {
12!
909
        /*
910
         * When the caller requests a thumbnail, compute the new geometry
911
         * while preserving the original aspect ratio. We only allocate a
912
         * frame when the dimensions actually change, so the fast path for
913
         * matching sizes still avoids any additional allocations.
914
         */
915
        max_dimension = sx;
×
916
        if (sy > max_dimension) {
×
917
            max_dimension = sy;
×
918
        }
919
        if (max_dimension > 0) {
×
920
            if (sx >= sy) {
×
921
                new_width = thumbnail_size;
×
922
                scaled_height = (double)sy * (double)thumbnail_size /
×
923
                    (double)sx;
×
924
                new_height = (int)(scaled_height + 0.5);
×
925
            } else {
926
                new_height = thumbnail_size;
×
927
                scaled_width = (double)sx * (double)thumbnail_size /
×
928
                    (double)sy;
×
929
                new_width = (int)(scaled_width + 0.5);
×
930
            }
931
            if (new_width < 1) {
×
932
                new_width = 1;
×
933
            }
934
            if (new_height < 1) {
×
935
                new_height = 1;
×
936
            }
937
            if (new_width != sx || new_height != sy) {
×
938
                /*
939
                 * Wrap the decoded pixels in a frame so we can reuse the
940
                 * central scaling helper. Ownership transfers to the frame,
941
                 * which keeps the lifetime rules identical on both paths.
942
                 */
943
                status = sixel_frame_new(&frame, decoder->allocator);
×
944
                if (SIXEL_FAILED(status)) {
×
945
                    goto end;
×
946
                }
947
                status = sixel_frame_init(
×
948
                    frame,
949
                    output_pixels,
950
                    sx,
951
                    sy,
952
                    output_pixelformat,
953
                    output_palette,
954
                    frame_ncolors);
955
                if (SIXEL_FAILED(status)) {
×
956
                    goto end;
×
957
                }
958
                if (output_pixels == indexed_pixels) {
×
959
                    indexed_pixels = NULL;
×
960
                }
961
                if (output_pixels == rgb_pixels) {
×
962
                    rgb_pixels = NULL;
×
963
                }
964
                if (output_palette == palette) {
×
965
                    palette = NULL;
×
966
                }
967
                status = sixel_frame_resize(
×
968
                    frame,
969
                    new_width,
970
                    new_height,
971
                    SIXEL_RES_BILINEAR);
972
                if (SIXEL_FAILED(status)) {
×
973
                    goto end;
×
974
                }
975
                /*
976
                 * The resized frame already exposes a tightly packed RGB
977
                 * buffer, so write the updated dimensions and references
978
                 * back to the main encoder path.
979
                 */
980
                sx = sixel_frame_get_width(frame);
×
981
                sy = sixel_frame_get_height(frame);
×
982
                output_pixels = sixel_frame_get_pixels(frame);
×
983
                output_palette = NULL;
×
984
                output_pixelformat = sixel_frame_get_pixelformat(frame);
×
985
            }
986
        }
987
    }
988

989
    status = sixel_helper_write_image_file(
12✔
990
        output_pixels,
4✔
991
        sx,
4✔
992
        sy,
4✔
993
        output_palette,
4✔
994
        output_pixelformat,
4✔
995
        decoder->output,
12✔
996
        SIXEL_FORMAT_PNG,
997
        decoder->allocator);
4✔
998

999
    if (SIXEL_FAILED(status)) {
12!
1000
        goto end;
×
1001
    }
1002

1003
end:
8✔
1004
    sixel_frame_unref(frame);
18✔
1005
    sixel_allocator_free(decoder->allocator, raw_data);
18✔
1006
    sixel_allocator_free(decoder->allocator, indexed_pixels);
18✔
1007
    sixel_allocator_free(decoder->allocator, palette);
18✔
1008
    sixel_allocator_free(decoder->allocator, rgb_pixels);
18✔
1009

1010
    sixel_decoder_unref(decoder);
18✔
1011

1012
    return status;
18✔
1013
}
1014

1015

1016
#if HAVE_TESTS
1017
static int
1018
test1(void)
×
1019
{
1020
    int nret = EXIT_FAILURE;
×
1021
    sixel_decoder_t *decoder = NULL;
×
1022

1023
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1024
#  pragma GCC diagnostic push
1025
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1026
#endif
1027
    decoder = sixel_decoder_create();
×
1028
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1029
#  pragma GCC diagnostic pop
1030
#endif
1031
    if (decoder == NULL) {
×
1032
        goto error;
×
1033
    }
1034
    sixel_decoder_ref(decoder);
×
1035
    sixel_decoder_unref(decoder);
×
1036
    nret = EXIT_SUCCESS;
×
1037

1038
error:
1039
    sixel_decoder_unref(decoder);
×
1040
    return nret;
×
1041
}
1042

1043

1044
static int
1045
test2(void)
×
1046
{
1047
    int nret = EXIT_FAILURE;
×
1048
    sixel_decoder_t *decoder = NULL;
×
1049
    SIXELSTATUS status;
1050

1051
    status = sixel_decoder_new(&decoder, NULL);
×
1052
    if (SIXEL_FAILED(status)) {
×
1053
        goto error;
×
1054
    }
1055

1056
    sixel_decoder_ref(decoder);
×
1057
    sixel_decoder_unref(decoder);
×
1058
    nret = EXIT_SUCCESS;
×
1059

1060
error:
1061
    sixel_decoder_unref(decoder);
×
1062
    return nret;
×
1063
}
1064

1065

1066
static int
1067
test3(void)
×
1068
{
1069
    int nret = EXIT_FAILURE;
×
1070
    sixel_decoder_t *decoder = NULL;
×
1071
    sixel_allocator_t *allocator = NULL;
×
1072
    SIXELSTATUS status;
1073

1074
    sixel_debug_malloc_counter = 1;
×
1075

1076
    status = sixel_allocator_new(
×
1077
        &allocator,
1078
        sixel_bad_malloc,
1079
        NULL,
1080
        NULL,
1081
        NULL);
1082
    if (SIXEL_FAILED(status)) {
×
1083
        goto error;
×
1084
    }
1085

1086
    status = sixel_decoder_new(&decoder, allocator);
×
1087
    if (status != SIXEL_BAD_ALLOCATION) {
×
1088
        goto error;
×
1089
    }
1090

1091
    nret = EXIT_SUCCESS;
×
1092

1093
error:
1094
    return nret;
×
1095
}
1096

1097

1098
static int
1099
test4(void)
×
1100
{
1101
    int nret = EXIT_FAILURE;
×
1102
    sixel_decoder_t *decoder = NULL;
×
1103
    sixel_allocator_t *allocator = NULL;
×
1104
    SIXELSTATUS status;
1105

1106
    sixel_debug_malloc_counter = 2;
×
1107

1108
    status = sixel_allocator_new(
×
1109
        &allocator,
1110
        sixel_bad_malloc,
1111
        NULL,
1112
        NULL,
1113
        NULL);
1114
    if (SIXEL_FAILED(status)) {
×
1115
        goto error;
×
1116
    }
1117

1118
    status = sixel_decoder_new(&decoder, allocator);
×
1119
    if (status != SIXEL_BAD_ALLOCATION) {
×
1120
        goto error;
×
1121
    }
1122

1123
    nret = EXIT_SUCCESS;
×
1124

1125
error:
1126
    return nret;
×
1127
}
1128

1129

1130
static int
1131
test5(void)
×
1132
{
1133
    int nret = EXIT_FAILURE;
×
1134
    sixel_decoder_t *decoder = NULL;
×
1135
    sixel_allocator_t *allocator = NULL;
×
1136
    SIXELSTATUS status;
1137

1138
    sixel_debug_malloc_counter = 4;
×
1139

1140
    status = sixel_allocator_new(
×
1141
        &allocator,
1142
        sixel_bad_malloc,
1143
        NULL,
1144
        NULL,
1145
        NULL);
1146
    if (SIXEL_FAILED(status)) {
×
1147
        goto error;
×
1148
    }
1149
    status = sixel_decoder_new(&decoder, allocator);
×
1150
    if (SIXEL_FAILED(status)) {
×
1151
        goto error;
×
1152
    }
1153

1154
    status = sixel_decoder_setopt(
×
1155
        decoder,
1156
        SIXEL_OPTFLAG_INPUT,
1157
        "/");
1158
    if (status != SIXEL_BAD_ALLOCATION) {
×
1159
        goto error;
×
1160
    }
1161

1162
    nret = EXIT_SUCCESS;
×
1163

1164
error:
1165
    return nret;
×
1166
}
1167

1168

1169
static int
1170
test6(void)
×
1171
{
1172
    int nret = EXIT_FAILURE;
×
1173
    sixel_decoder_t *decoder = NULL;
×
1174
    sixel_allocator_t *allocator = NULL;
×
1175
    SIXELSTATUS status;
1176

1177
    sixel_debug_malloc_counter = 4;
×
1178

1179
    status = sixel_allocator_new(
×
1180
        &allocator,
1181
        sixel_bad_malloc,
1182
        NULL,
1183
        NULL,
1184
        NULL);
1185
    if (SIXEL_FAILED(status)) {
×
1186
        goto error;
×
1187
    }
1188

1189
    status = sixel_decoder_new(&decoder, allocator);
×
1190
    if (SIXEL_FAILED(status)) {
×
1191
        goto error;
×
1192
    }
1193

1194
    status = sixel_decoder_setopt(
×
1195
        decoder,
1196
        SIXEL_OPTFLAG_OUTPUT,
1197
        "/");
1198
    if (status != SIXEL_BAD_ALLOCATION) {
×
1199
        goto error;
×
1200
    }
1201

1202
    nret = EXIT_SUCCESS;
×
1203

1204
error:
1205
    return nret;
×
1206
}
1207

1208

1209
static int
1210
test7(void)
×
1211
{
1212
    int nret = EXIT_FAILURE;
×
1213
    sixel_decoder_t *decoder = NULL;
×
1214
    sixel_allocator_t *allocator = NULL;
×
1215
    SIXELSTATUS status;
1216

1217
    status = sixel_allocator_new(
×
1218
        &allocator,
1219
        NULL,
1220
        NULL,
1221
        NULL,
1222
        NULL);
1223
    if (SIXEL_FAILED(status)) {
×
1224
        goto error;
×
1225
    }
1226

1227
    status = sixel_decoder_new(&decoder, allocator);
×
1228
    if (SIXEL_FAILED(status)) {
×
1229
        goto error;
×
1230
    }
1231

1232
    status = sixel_decoder_setopt(
×
1233
        decoder,
1234
        SIXEL_OPTFLAG_INPUT,
1235
        "../images/file");
1236
    if (SIXEL_FAILED(status)) {
×
1237
        goto error;
×
1238
    }
1239

1240
    status = sixel_decoder_decode(decoder);
×
1241
    if ((status >> 8) != (SIXEL_LIBC_ERROR >> 8)) {
×
1242
        goto error;
×
1243
    }
1244

1245
    nret = EXIT_SUCCESS;
×
1246

1247
error:
1248
    return nret;
×
1249
}
1250

1251

1252
static int
1253
test8(void)
×
1254
{
1255
    int nret = EXIT_FAILURE;
×
1256
    sixel_decoder_t *decoder = NULL;
×
1257
    sixel_allocator_t *allocator = NULL;
×
1258
    SIXELSTATUS status;
1259

1260
    sixel_debug_malloc_counter = 5;
×
1261

1262
    status = sixel_allocator_new(
×
1263
        &allocator,
1264
        sixel_bad_malloc,
1265
        NULL,
1266
        NULL,
1267
        NULL);
1268
    if (SIXEL_FAILED(status)) {
×
1269
        goto error;
×
1270
    }
1271

1272
    status = sixel_decoder_new(&decoder, allocator);
×
1273
    if (SIXEL_FAILED(status)) {
×
1274
        goto error;
×
1275
    }
1276

1277
    status = sixel_decoder_setopt(
×
1278
        decoder,
1279
        SIXEL_OPTFLAG_INPUT,
1280
        "../images/map8.six");
1281
    if (SIXEL_FAILED(status)) {
×
1282
        goto error;
×
1283
    }
1284

1285
    status = sixel_decoder_decode(decoder);
×
1286
    if (status != SIXEL_BAD_ALLOCATION) {
×
1287
        goto error;
×
1288
    }
1289

1290
    nret = EXIT_SUCCESS;
×
1291

1292
error:
1293
    return nret;
×
1294
}
1295

1296

1297
SIXELAPI int
1298
sixel_decoder_tests_main(void)
×
1299
{
1300
    int nret = EXIT_FAILURE;
×
1301
    size_t i;
1302
    typedef int (* testcase)(void);
1303

1304
    static testcase const testcases[] = {
1305
        test1,
1306
        test2,
1307
        test3,
1308
        test4,
1309
        test5,
1310
        test6,
1311
        test7,
1312
        test8
1313
    };
1314

1315
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
1316
        nret = testcases[i]();
×
1317
        if (nret != EXIT_SUCCESS) {
×
1318
            goto error;
×
1319
        }
1320
    }
1321

1322
    nret = EXIT_SUCCESS;
×
1323

1324
error:
1325
    return nret;
×
1326
}
1327
#endif  /* HAVE_TESTS */
1328

1329
/* emacs Local Variables:      */
1330
/* emacs mode: c               */
1331
/* emacs tab-width: 4          */
1332
/* emacs indent-tabs-mode: nil */
1333
/* emacs c-basic-offset: 4     */
1334
/* emacs End:                  */
1335
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1336
/* 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