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

saitoha / libsixel / 18762699075

23 Oct 2025 09:36PM UTC coverage: 53.085% (+0.2%) from 52.906%
18762699075

push

github

saitoha
dequant: arrange parameters

5439 of 14848 branches covered (36.63%)

0 of 7 new or added lines in 1 file covered. (0.0%)

7 existing lines in 3 files now uncovered.

7933 of 14944 relevant lines covered (53.08%)

1052730.89 hits per line

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

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

22
#include "config.h"
23

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

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

44
#include "decoder.h"
45
#include "frame.h"
46

47

48
static float sixel_srgb_to_linear_lut[256];
49
static unsigned char sixel_linear_to_srgb_lut[256];
50
static int sixel_color_lut_initialized = 0;
51

52
static const float sixel_gaussian3x3_kernel[9] = {
53
    0.0625f, 0.1250f, 0.0625f,
54
    0.1250f, 0.2500f, 0.1250f,
55
    0.0625f, 0.1250f, 0.0625f
56
};
57

58
static const float sixel_weak_sharpen3x3_kernel[9] = {
59
    -0.0625f, -0.0625f, -0.0625f,
60
    -0.0625f,  1.5000f, -0.0625f,
61
    -0.0625f, -0.0625f, -0.0625f
62
};
63

64
static const float sixel_sobel_gx_kernel[9] = {
65
    -1.0f, 0.0f, 1.0f,
66
    -2.0f, 0.0f, 2.0f,
67
    -1.0f, 0.0f, 1.0f
68
};
69

70
static const float sixel_sobel_gy_kernel[9] = {
71
    -1.0f, -2.0f, -1.0f,
72
     0.0f,  0.0f,  0.0f,
73
     1.0f,  2.0f,  1.0f
74
};
75

76
static void
77
sixel_color_lut_init(void)
×
78
{
79
    int i;
80
    float srgb;
81
    float linear;
82

83
    if (sixel_color_lut_initialized) {
×
84
        return;
×
85
    }
86

87
    for (i = 0; i < 256; ++i) {
×
88
        srgb = (float)i / 255.0f;
×
89
        if (srgb <= 0.04045f) {
×
90
            linear = srgb / 12.92f;
×
91
        } else {
92
            linear = powf((srgb + 0.055f) / 1.055f, 2.4f);
×
93
        }
94
        sixel_srgb_to_linear_lut[i] = linear * 255.0f;
×
95
    }
96

97
    for (i = 0; i < 256; ++i) {
×
98
        linear = (float)i / 255.0f;
×
99
        if (linear <= 0.0031308f) {
×
100
            srgb = linear * 12.92f;
×
101
        } else {
102
            srgb = 1.055f * powf(linear, 1.0f / 2.4f) - 0.055f;
×
103
        }
104
        srgb *= 255.0f;
×
105
        if (srgb < 0.0f) {
×
106
            srgb = 0.0f;
×
107
        } else if (srgb > 255.0f) {
×
108
            srgb = 255.0f;
×
109
        }
110
        sixel_linear_to_srgb_lut[i] = (unsigned char)(srgb + 0.5f);
×
111
    }
112

113
    sixel_color_lut_initialized = 1;
×
114
}
115

116
static void
117
sixel_convolve3x3(const float *kernel,
×
118
                  float *dst,
119
                  const float *src,
120
                  int width,
121
                  int height)
122
{
123
    int x;
124
    int y;
125
    int kx;
126
    int ky;
127
    int sx;
128
    int sy;
129
    int idx;
130
    int src_index;
131
    int kernel_index;
132
    float sum;
133
    float weight;
134

135
    if (kernel == NULL || dst == NULL || src == NULL) {
×
136
        return;
×
137
    }
138

139
    for (y = 0; y < height; ++y) {
×
140
        for (x = 0; x < width; ++x) {
×
141
            sum = 0.0f;
×
142
            for (ky = -1; ky <= 1; ++ky) {
×
143
                sy = y + ky;
×
144
                if (sy < 0) {
×
145
                    sy = 0;
×
146
                } else if (sy >= height) {
×
147
                    sy = height - 1;
×
148
                }
149
                for (kx = -1; kx <= 1; ++kx) {
×
150
                    sx = x + kx;
×
151
                    if (sx < 0) {
×
152
                        sx = 0;
×
153
                    } else if (sx >= width) {
×
154
                        sx = width - 1;
×
155
                    }
156
                    kernel_index = (ky + 1) * 3 + (kx + 1);
×
157
                    weight = kernel[kernel_index];
×
158
                    src_index = sy * width + sx;
×
159
                    sum += src[src_index] * weight;
×
160
                }
161
            }
162
            idx = y * width + x;
×
163
            dst[idx] = sum;
×
164
        }
165
    }
166
}
167

168
static void
169
sixel_apply_relu(float *buffer, size_t count)
×
170
{
171
    size_t i;
172

173
    if (buffer == NULL) {
×
174
        return;
×
175
    }
176

177
    for (i = 0; i < count; ++i) {
×
178
        if (buffer[i] < 0.0f) {
×
179
            buffer[i] = 0.0f;
×
180
        }
181
    }
182
}
183

184
static unsigned char
185
sixel_linear_to_srgb_value(float value)
×
186
{
187
    int index;
188

189
    if (value < 0.0f) {
×
190
        value = 0.0f;
×
191
    } else if (value > 255.0f) {
×
192
        value = 255.0f;
×
193
    }
194

195
    index = (int)(value + 0.5f);
×
196
    if (index < 0) {
×
197
        index = 0;
×
198
    } else if (index > 255) {
×
199
        index = 255;
×
200
    }
201

202
    return sixel_linear_to_srgb_lut[index];
×
203
}
204

205
static void
206
sixel_post_undither_refine(unsigned char *rgb,
×
207
                           int width,
208
                           int height,
209
                           const float *mask)
210
{
211
    size_t num_pixels;
212
    float *Y;
213
    float *Cb;
214
    float *Cr;
215
    float *work0;
216
    float *work1;
217
    float *gate;
218
    float *gradient;
219
    int x;
220
    int y;
221
    int kx;
222
    int ky;
223
    int sx;
224
    int sy;
225
    int idx;
226
    int src_index;
227
    int kernel_index;
228
    size_t i;
229
    size_t base;
230
    float sigma_r;
231
    float beta;
232
    float alpha1;
233
    float alpha2;
234
    float smooth_gate_scale;
235
    float inv_sigma_r2;
236
    float center;
237
    float neighbor;
238
    float sum;
239
    float weight;
240
    float weight_sum;
241
    float diff;
242
    float range_weight;
243
    float gate_value;
244
    float gaussian_weight;
245
    float max_grad;
246
    float scale;
247
    float gx;
248
    float gy;
249
    float value;
250
    float y_value;
251
    float cb_value;
252
    float cr_value;
253
    float r_lin;
254
    float g_lin;
255
    float b_lin;
256
    float magnitude;
257

258
    if (rgb == NULL) {
×
259
        return;
×
260
    }
261

262
    if (width <= 0 || height <= 0) {
×
263
        return;
×
264
    }
265

266
    sixel_color_lut_init();
×
267

268
    num_pixels = (size_t)width * (size_t)height;
×
269
    if (num_pixels == 0) {
×
270
        return;
×
271
    }
272

273
    Y = NULL;
×
274
    Cb = NULL;
×
275
    Cr = NULL;
×
276
    work0 = NULL;
×
277
    work1 = NULL;
×
278
    gate = NULL;
×
279
    gradient = NULL;
×
280

NEW
281
    sigma_r = 10.0f;
×
NEW
282
    beta = 0.25f;
×
283
    alpha1 = 0.60f;
×
284
    alpha2 = 0.40f;
×
285
    inv_sigma_r2 = 1.0f / (2.0f * sigma_r * sigma_r);
×
NEW
286
    smooth_gate_scale = 0.96f;
×
287

288
    Y = (float *)malloc(num_pixels * sizeof(float));
×
289
    Cb = (float *)malloc(num_pixels * sizeof(float));
×
290
    Cr = (float *)malloc(num_pixels * sizeof(float));
×
291
    work0 = (float *)malloc(num_pixels * sizeof(float));
×
292
    work1 = (float *)malloc(num_pixels * sizeof(float));
×
293
    gate = (float *)malloc(num_pixels * sizeof(float));
×
294

295
    if (Y == NULL || Cb == NULL || Cr == NULL ||
×
296
        work0 == NULL || work1 == NULL || gate == NULL) {
×
297
        goto cleanup;
×
298
    }
299

300
    for (i = 0; i < num_pixels; ++i) {
×
301
        base = i * 3;
×
302
        r_lin = sixel_srgb_to_linear_lut[rgb[base + 0]];
×
303
        g_lin = sixel_srgb_to_linear_lut[rgb[base + 1]];
×
304
        b_lin = sixel_srgb_to_linear_lut[rgb[base + 2]];
×
305

306
        y_value = 0.2990f * r_lin + 0.5870f * g_lin + 0.1140f * b_lin;
×
307
        cb_value = (b_lin - y_value) * 0.564383f;
×
308
        cr_value = (r_lin - y_value) * 0.713272f;
×
309

310
        Y[i] = y_value;
×
311
        Cb[i] = cb_value;
×
312
        Cr[i] = cr_value;
×
313
    }
314

315
    if (mask != NULL) {
×
316
        for (i = 0; i < num_pixels; ++i) {
×
317
            value = mask[i];
×
318
            if (value < 0.0f) {
×
319
                value = 0.0f;
×
320
            } else if (value > 1.0f) {
×
321
                value = 1.0f;
×
322
            }
323
            gate[i] = 1.0f - value;
×
324
            if (gate[i] < 0.0f) {
×
325
                gate[i] = 0.0f;
×
326
            }
327
        }
328
    } else {
329
        gradient = (float *)malloc(num_pixels * sizeof(float));
×
330
        if (gradient == NULL) {
×
331
            goto cleanup;
×
332
        }
333

334
        max_grad = 0.0f;
×
335
        for (y = 0; y < height; ++y) {
×
336
            for (x = 0; x < width; ++x) {
×
337
                gx = 0.0f;
×
338
                gy = 0.0f;
×
339
                for (ky = -1; ky <= 1; ++ky) {
×
340
                    sy = y + ky;
×
341
                    if (sy < 0) {
×
342
                        sy = 0;
×
343
                    } else if (sy >= height) {
×
344
                        sy = height - 1;
×
345
                    }
346
                    for (kx = -1; kx <= 1; ++kx) {
×
347
                        sx = x + kx;
×
348
                        if (sx < 0) {
×
349
                            sx = 0;
×
350
                        } else if (sx >= width) {
×
351
                            sx = width - 1;
×
352
                        }
353
                        kernel_index = (ky + 1) * 3 + (kx + 1);
×
354
                        src_index = sy * width + sx;
×
355
                        neighbor = Y[src_index];
×
356
                        gx += neighbor *
×
357
                              sixel_sobel_gx_kernel[kernel_index];
×
358
                        gy += neighbor *
×
359
                              sixel_sobel_gy_kernel[kernel_index];
×
360
                    }
361
                }
362
                idx = y * width + x;
×
363
                magnitude = sqrtf(gx * gx + gy * gy);
×
364
                gradient[idx] = magnitude;
×
365
                if (magnitude > max_grad) {
×
366
                    max_grad = magnitude;
×
367
                }
368
            }
369
        }
370

371
        if (max_grad <= 0.0f) {
×
372
            max_grad = 1.0f;
×
373
        }
374
        scale = 1.0f / max_grad;
×
375

376
        for (i = 0; i < num_pixels; ++i) {
×
377
            value = gradient[i] * scale;
×
378
            if (value < 0.0f) {
×
379
                value = 0.0f;
×
380
            } else if (value > 1.0f) {
×
381
                value = 1.0f;
×
382
            }
383
            gate[i] = 1.0f - value;
×
384
            if (gate[i] < 0.0f) {
×
385
                gate[i] = 0.0f;
×
386
            }
387
        }
388
    }
389

390
    for (y = 0; y < height; ++y) {
×
391
        for (x = 0; x < width; ++x) {
×
392
            idx = y * width + x;
×
393
            center = Y[idx];
×
394
            gate_value = gate[idx];
×
395
            sum = 0.0f;
×
396
            weight_sum = 0.0f;
×
397
            for (ky = -1; ky <= 1; ++ky) {
×
398
                sy = y + ky;
×
399
                if (sy < 0) {
×
400
                    sy = 0;
×
401
                } else if (sy >= height) {
×
402
                    sy = height - 1;
×
403
                }
404
                for (kx = -1; kx <= 1; ++kx) {
×
405
                    sx = x + kx;
×
406
                    if (sx < 0) {
×
407
                        sx = 0;
×
408
                    } else if (sx >= width) {
×
409
                        sx = width - 1;
×
410
                    }
411
                    kernel_index = (ky + 1) * 3 + (kx + 1);
×
412
                    gaussian_weight =
×
413
                        sixel_gaussian3x3_kernel[kernel_index];
414
                    src_index = sy * width + sx;
×
415
                    neighbor = Y[src_index];
×
416
                    if (kx == 0 && ky == 0) {
×
417
                        weight = gaussian_weight;
×
418
                    } else {
419
                        diff = neighbor - center;
×
420
                        range_weight = expf(-(diff * diff) * inv_sigma_r2);
×
421
                        weight = gaussian_weight * gate_value * range_weight;
×
422
                    }
423
                    sum += neighbor * weight;
×
424
                    weight_sum += weight;
×
425
                }
426
            }
427
            if (weight_sum <= 0.0f) {
×
428
                work0[idx] = center;
×
429
            } else {
430
                work0[idx] = sum / weight_sum;
×
431
            }
432
        }
433
    }
434

435
    for (i = 0; i < num_pixels; ++i) {
×
436
        center = Y[i];
×
437
        value = work0[i];
×
438
        Y[i] = (1.0f - beta) * center + beta * value;
×
439
    }
440

441
    sixel_convolve3x3(sixel_gaussian3x3_kernel,
×
442
                      work0,
443
                      Y,
444
                      width,
445
                      height);
446
    for (i = 0; i < num_pixels; ++i) {
×
447
        gate_value = gate[i] * smooth_gate_scale;
×
448
        center = Y[i];
×
449
        value = work0[i];
×
450
        work0[i] = gate_value * value
×
451
                 + (1.0f - gate_value) * center;
×
452
    }
453
    sixel_apply_relu(work0, num_pixels);
×
454
    sixel_convolve3x3(sixel_weak_sharpen3x3_kernel,
×
455
                      work1,
456
                      work0,
457
                      width,
458
                      height);
459

460
    for (i = 0; i < num_pixels; ++i) {
×
461
        center = Y[i];
×
462
        value = work1[i];
×
463
        Y[i] = center + alpha1 * (value - center);
×
464
    }
465

466
    sixel_convolve3x3(sixel_gaussian3x3_kernel,
×
467
                      work0,
468
                      Y,
469
                      width,
470
                      height);
471
    for (i = 0; i < num_pixels; ++i) {
×
472
        gate_value = gate[i] * smooth_gate_scale;
×
473
        center = Y[i];
×
474
        value = work0[i];
×
475
        work0[i] = gate_value * value
×
476
                 + (1.0f - gate_value) * center;
×
477
    }
478
    sixel_apply_relu(work0, num_pixels);
×
479
    sixel_convolve3x3(sixel_weak_sharpen3x3_kernel,
×
480
                      work1,
481
                      work0,
482
                      width,
483
                      height);
484

485
    for (i = 0; i < num_pixels; ++i) {
×
486
        center = Y[i];
×
487
        value = work1[i];
×
488
        Y[i] = center + alpha2 * (value - center);
×
489
    }
490

491
    for (i = 0; i < num_pixels; ++i) {
×
492
        base = i * 3;
×
493
        y_value = Y[i];
×
494
        cb_value = Cb[i];
×
495
        cr_value = Cr[i];
×
496

497
        r_lin = y_value + 1.402000f * cr_value;
×
498
        b_lin = y_value + 1.772000f * cb_value;
×
499
        g_lin = y_value - 0.344136f * cb_value - 0.714136f * cr_value;
×
500

501
        rgb[base + 0] = sixel_linear_to_srgb_value(r_lin);
×
502
        rgb[base + 1] = sixel_linear_to_srgb_value(g_lin);
×
503
        rgb[base + 2] = sixel_linear_to_srgb_value(b_lin);
×
504
    }
505

506
cleanup:
507
    free(Y);
×
508
    free(Cb);
×
509
    free(Cr);
×
510
    free(work0);
×
511
    free(work1);
×
512
    free(gate);
×
513
    free(gradient);
×
514
}
515

516

517
/* original version of strdup(3) with allocator object */
518
static char *
519
strdup_with_allocator(
66✔
520
    char const          /* in */ *s,          /* source buffer */
521
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
522
                                                 destination buffer */
523
{
524
    char *p;
525

526
    p = (char *)sixel_allocator_malloc(allocator, (size_t)(strlen(s) + 1));
66✔
527
    if (p) {
66!
528
        strcpy(p, s);
66✔
529
    }
22✔
530
    return p;
66✔
531
}
532

533

534
/* create decoder object */
535
SIXELAPI SIXELSTATUS
536
sixel_decoder_new(
24✔
537
    sixel_decoder_t    /* out */ **ppdecoder,  /* decoder object to be created */
538
    sixel_allocator_t  /* in */  *allocator)   /* allocator, null if you use
539
                                                  default allocator */
540
{
541
    SIXELSTATUS status = SIXEL_FALSE;
24✔
542

543
    if (allocator == NULL) {
24!
544
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
24✔
545
        if (SIXEL_FAILED(status)) {
24!
546
            goto end;
×
547
        }
548
    } else {
8✔
549
        sixel_allocator_ref(allocator);
×
550
    }
551

552
    *ppdecoder = sixel_allocator_malloc(allocator, sizeof(sixel_decoder_t));
24✔
553
    if (*ppdecoder == NULL) {
24!
554
        sixel_allocator_unref(allocator);
×
555
        sixel_helper_set_additional_message(
×
556
            "sixel_decoder_new: sixel_allocator_malloc() failed.");
557
        status = SIXEL_BAD_ALLOCATION;
×
558
        goto end;
×
559
    }
560

561
    (*ppdecoder)->ref          = 1;
24✔
562
    (*ppdecoder)->output       = strdup_with_allocator("-", allocator);
24✔
563
    (*ppdecoder)->input        = strdup_with_allocator("-", allocator);
24✔
564
    (*ppdecoder)->allocator    = allocator;
24✔
565
    (*ppdecoder)->dequantize_method = SIXEL_DEQUANTIZE_NONE;
24✔
566
    (*ppdecoder)->dequantize_similarity_bias = 100;
24✔
567
    (*ppdecoder)->dequantize_edge_strength = 0;
24✔
568
    (*ppdecoder)->thumbnail_size = 0;
24✔
569

570
    if ((*ppdecoder)->output == NULL || (*ppdecoder)->input == NULL) {
24!
571
        sixel_decoder_unref(*ppdecoder);
×
572
        *ppdecoder = NULL;
×
573
        sixel_helper_set_additional_message(
×
574
            "sixel_decoder_new: strdup_with_allocator() failed.");
575
        status = SIXEL_BAD_ALLOCATION;
×
576
        sixel_allocator_unref(allocator);
×
577
        goto end;
×
578
    }
579

580
    status = SIXEL_OK;
24✔
581

582
end:
16✔
583
    return status;
24✔
584
}
585

586

587
/* deprecated version of sixel_decoder_new() */
588
SIXELAPI /* deprecated */ sixel_decoder_t *
589
sixel_decoder_create(void)
×
590
{
591
    SIXELSTATUS status = SIXEL_FALSE;
×
592
    sixel_decoder_t *decoder = NULL;
×
593

594
    status = sixel_decoder_new(&decoder, NULL);
×
595
    if (SIXEL_FAILED(status)) {
×
596
        goto end;
×
597
    }
598

599
end:
600
    return decoder;
×
601
}
602

603

604
/* destroy a decoder object */
605
static void
606
sixel_decoder_destroy(sixel_decoder_t *decoder)
24✔
607
{
608
    sixel_allocator_t *allocator;
609

610
    if (decoder) {
24!
611
        allocator = decoder->allocator;
24✔
612
        sixel_allocator_free(allocator, decoder->input);
24✔
613
        sixel_allocator_free(allocator, decoder->output);
24✔
614
        sixel_allocator_free(allocator, decoder);
24✔
615
        sixel_allocator_unref(allocator);
24✔
616
    }
8✔
617
}
24✔
618

619

620
/* increase reference count of decoder object (thread-unsafe) */
621
SIXELAPI void
622
sixel_decoder_ref(sixel_decoder_t *decoder)
36✔
623
{
624
    /* TODO: be thread safe */
625
    ++decoder->ref;
36✔
626
}
36✔
627

628

629
/* decrease reference count of decoder object (thread-unsafe) */
630
SIXELAPI void
631
sixel_decoder_unref(sixel_decoder_t *decoder)
60✔
632
{
633
    /* TODO: be thread safe */
634
    if (decoder != NULL && --decoder->ref == 0) {
60!
635
        sixel_decoder_destroy(decoder);
24✔
636
    }
8✔
637
}
60✔
638

639

640
typedef struct sixel_similarity {
641
    const unsigned char *palette;
642
    int ncolors;
643
    int stride;
644
    signed char *cache;
645
    int bias;
646
} sixel_similarity_t;
647

648
static SIXELSTATUS
649
sixel_similarity_init(sixel_similarity_t *similarity,
×
650
                      const unsigned char *palette,
651
                      int ncolors,
652
                      int bias,
653
                      sixel_allocator_t *allocator)
654
{
655
    size_t cache_size;
656
    int i;
657

658
    if (bias < 1) {
×
659
        bias = 1;
×
660
    }
661

662
    similarity->palette = palette;
×
663
    similarity->ncolors = ncolors;
×
664
    similarity->stride = ncolors;
×
665
    similarity->bias = bias;
×
666

667
    cache_size = (size_t)ncolors * (size_t)ncolors;
×
668
    if (cache_size == 0) {
×
669
        similarity->cache = NULL;
×
670
        return SIXEL_OK;
×
671
    }
672

673
    similarity->cache = (signed char *)sixel_allocator_malloc(
×
674
        allocator,
675
        cache_size);
676
    if (similarity->cache == NULL) {
×
677
        sixel_helper_set_additional_message(
×
678
            "sixel_similarity_init: sixel_allocator_malloc() failed.");
679
        return SIXEL_BAD_ALLOCATION;
×
680
    }
681
    memset(similarity->cache, -1, cache_size);
×
682
    for (i = 0; i < ncolors; ++i) {
×
683
        similarity->cache[i * similarity->stride + i] = 7;
×
684
    }
685

686
    return SIXEL_OK;
×
687
}
688

689
static void
690
sixel_similarity_destroy(sixel_similarity_t *similarity,
×
691
                         sixel_allocator_t *allocator)
692
{
693
    if (similarity->cache != NULL) {
×
694
        sixel_allocator_free(allocator, similarity->cache);
×
695
        similarity->cache = NULL;
×
696
    }
697
}
×
698

699
static inline unsigned int
700
sixel_similarity_diff(const unsigned char *a, const unsigned char *b)
×
701
{
702
    int dr = (int)a[0] - (int)b[0];
×
703
    int dg = (int)a[1] - (int)b[1];
×
704
    int db = (int)a[2] - (int)b[2];
×
705
    return (unsigned int)(dr * dr + dg * dg + db * db);
×
706
}
707

708
static unsigned int
709
sixel_similarity_compare(sixel_similarity_t *similarity,
×
710
                         int index1,
711
                         int index2,
712
                         int numerator,
713
                         int denominator)
714
{
715
    int min_index;
716
    int max_index;
717
    size_t cache_pos;
718
    signed char cached;
719
    const unsigned char *palette;
720
    const unsigned char *p1;
721
    const unsigned char *p2;
722
    unsigned char avg_color[3];
723
    unsigned int distance;
724
    unsigned int base_distance;
725
    unsigned long long scaled_distance;
726
    int bias;
727
    unsigned int min_diff = UINT_MAX;
×
728
    int i;
729
    unsigned int result;
730
    const unsigned char *pk;
731
    unsigned int diff;
732

733
    if (similarity->cache == NULL) {
×
734
        return 0;
×
735
    }
736

737
    if (index1 < 0 || index1 >= similarity->ncolors ||
×
738
        index2 < 0 || index2 >= similarity->ncolors) {
×
739
        return 0;
×
740
    }
741

742
    if (index1 <= index2) {
×
743
        min_index = index1;
×
744
        max_index = index2;
×
745
    } else {
746
        min_index = index2;
×
747
        max_index = index1;
×
748
    }
749

750
    cache_pos = (size_t)min_index * (size_t)similarity->stride
×
751
              + (size_t)max_index;
×
752
    cached = similarity->cache[cache_pos];
×
753
    if (cached >= 0) {
×
754
        return (unsigned int)cached;
×
755
    }
756

757
    palette = similarity->palette;
×
758
    p1 = palette + index1 * 3;
×
759
    p2 = palette + index2 * 3;
×
760

761
#if 0
762
   /*    original: n = (p1 + p2) / 2
763
    */
764
    avg_color[0] = (unsigned char)(((unsigned int)p1[0]
765
                                    + (unsigned int)p2[0]) >> 1);
766
    avg_color[1] = (unsigned char)(((unsigned int)p1[1]
767
                                    + (unsigned int)p2[1]) >> 1);
768
    avg_color[2] = (unsigned char)(((unsigned int)p1[2]
769
                                    + (unsigned int)p2[2]) >> 1);
770
#else
771
   /*
772
    *    diffuse(pos_a, n1) -> p1
773
    *    diffuse(pos_b, n2) -> p2
774
    *
775
    *    when n1 == n2 == n:
776
    *
777
    *    p2 = n + (n - p1) * numerator / denominator
778
    * => p2 * denominator = n * denominator + (n - p1) * numerator
779
    * => p2 * denominator = n * denominator + n * numerator - p1 * numerator
780
    * => n * (denominator + numerator) = p1 * numerator + p2 * denominator
781
    * => n = (p1 * numerator + p2 * denominator) / (denominator + numerator)
782
    *
783
    */
784
    avg_color[0] = (p1[0] * numerator + p2[0] * denominator)
×
785
                 / (numerator + denominator);
×
786
    avg_color[1] = (p1[1] * numerator + p2[1] * denominator)
×
787
                 / (numerator + denominator);
×
788
    avg_color[2] = (p1[2] * numerator + p2[2] * denominator)
×
789
                 / (numerator + denominator);
×
790
#endif
791

792
    distance = sixel_similarity_diff(avg_color, p1);
×
793
    bias = similarity->bias;
×
794
    if (bias < 1) {
×
795
        bias = 1;
×
796
    }
797
    scaled_distance = (unsigned long long)distance
×
798
                    * (unsigned long long)bias
×
799
                    + 50ULL;
800
    base_distance = (unsigned int)(scaled_distance / 100ULL);
×
801
    if (base_distance == 0U) {
×
802
        base_distance = 1U;
×
803
    }
804

805
    for (i = 0; i < similarity->ncolors; ++i) {
×
806
        if (i == index1 || i == index2) {
×
807
            continue;
×
808
        }
809
        pk = palette + i * 3;
×
810
        diff = sixel_similarity_diff(avg_color, pk);
×
811
        if (diff < min_diff) {
×
812
            min_diff = diff;
×
813
        }
814
    }
815

816
    if (min_diff == UINT_MAX) {
×
817
        min_diff = base_distance * 2U;
×
818
    }
819

820
    if (min_diff >= base_distance * 2U) {
×
NEW
821
        result = 5U;
×
822
    } else if (min_diff >= base_distance) {
×
823
        result = 8U;
×
824
    } else if ((unsigned long long)min_diff * 6ULL
×
825
               >= (unsigned long long)base_distance * 5ULL) {
×
NEW
826
        result = 7U;
×
827
    } else if ((unsigned long long)min_diff * 4ULL
×
828
               >= (unsigned long long)base_distance * 3ULL) {
×
829
        result = 7U;
×
830
    } else if ((unsigned long long)min_diff * 3ULL
×
831
               >= (unsigned long long)base_distance * 2ULL) {
×
832
        result = 5U;
×
833
    } else if ((unsigned long long)min_diff * 5ULL
×
834
               >= (unsigned long long)base_distance * 3ULL) {
×
NEW
835
        result = 7U;
×
836
    } else if ((unsigned long long)min_diff * 2ULL
×
837
               >= (unsigned long long)base_distance * 1ULL) {
×
NEW
838
        result = 4U;
×
839
    } else if ((unsigned long long)min_diff * 3ULL
×
840
               >= (unsigned long long)base_distance * 1ULL) {
×
841
        result = 2U;
×
842
    } else {
843
        result = 0U;
×
844
    }
845

846
    similarity->cache[cache_pos] = (signed char)result;
×
847

848
    return result;
×
849
}
850

851
static inline int
852
sixel_clamp(int value, int min_value, int max_value)
×
853
{
854
    if (value < min_value) {
×
855
        return min_value;
×
856
    }
857
    if (value > max_value) {
×
858
        return max_value;
×
859
    }
860
    return value;
×
861
}
862

863
static inline int
864
sixel_get_gray(const int *gray, int width, int height, int x, int y)
×
865
{
866
    int cx = sixel_clamp(x, 0, width - 1);
×
867
    int cy = sixel_clamp(y, 0, height - 1);
×
868
    return gray[cy * width + cx];
×
869
}
870

871
static unsigned short
872
sixel_prewitt_value(const int *gray, int width, int height, int x, int y)
×
873
{
874
    int top_prev = sixel_get_gray(gray, width, height, x - 1, y - 1);
×
875
    int top_curr = sixel_get_gray(gray, width, height, x, y - 1);
×
876
    int top_next = sixel_get_gray(gray, width, height, x + 1, y - 1);
×
877
    int mid_prev = sixel_get_gray(gray, width, height, x - 1, y);
×
878
    int mid_next = sixel_get_gray(gray, width, height, x + 1, y);
×
879
    int bot_prev = sixel_get_gray(gray, width, height, x - 1, y + 1);
×
880
    int bot_curr = sixel_get_gray(gray, width, height, x, y + 1);
×
881
    int bot_next = sixel_get_gray(gray, width, height, x + 1, y + 1);
×
882
    long gx = (long)top_next - (long)top_prev +
×
883
              (long)mid_next - (long)mid_prev +
×
884
              (long)bot_next - (long)bot_prev;
×
885
    long gy = (long)bot_prev + (long)bot_curr + (long)bot_next -
×
886
              (long)top_prev - (long)top_curr - (long)top_next;
×
887
    unsigned long long magnitude = (unsigned long long)gx
×
888
                                 * (unsigned long long)gx
×
889
                                 + (unsigned long long)gy
×
890
                                 * (unsigned long long)gy;
×
891
    magnitude /= 256ULL;
×
892
    if (magnitude > 65535ULL) {
×
893
        magnitude = 65535ULL;
×
894
    }
895
    return (unsigned short)magnitude;
×
896
}
897

898
static unsigned short
899
sixel_scale_threshold(unsigned short base, int percent)
×
900
{
901
    unsigned long long numerator;
902
    unsigned long long scaled;
903

904
    if (percent <= 0) {
×
905
        percent = 1;
×
906
    }
907

908
    numerator = (unsigned long long)base * 100ULL
×
909
              + (unsigned long long)percent / 2ULL;
×
910
    scaled = numerator / (unsigned long long)percent;
×
911
    if (scaled == 0ULL) {
×
912
        scaled = 1ULL;
×
913
    }
914
    if (scaled > USHRT_MAX) {
×
915
        scaled = USHRT_MAX;
×
916
    }
917

918
    return (unsigned short)scaled;
×
919
}
920

921
static SIXELSTATUS
922
sixel_dequantize_k_undither(unsigned char *indexed_pixels,
×
923
                            int width,
924
                            int height,
925
                            unsigned char *palette,
926
                            int ncolors,
927
                            int similarity_bias,
928
                            int edge_strength,
929
                            sixel_allocator_t *allocator,
930
                            unsigned char **output)
931
{
932
    SIXELSTATUS status = SIXEL_FALSE;
×
933
    unsigned char *rgb = NULL;
×
934
    int *gray = NULL;
×
935
    unsigned short *prewitt = NULL;
×
936
    sixel_similarity_t similarity;
937
    size_t num_pixels;
938
    int x;
939
    int y;
940
    unsigned short strong_threshold;
941
    unsigned short detail_threshold;
942
    static const int neighbor_offsets[8][4] = {
943
        {-1, -1,  10, 16}, {0, -1, 16, 16}, {1, -1,   6, 16},
944
        {-1,  0,  11, 16},                  {1,  0,  11, 16},
945
        {-1,  1,   6, 16}, {0,  1, 16, 16}, {1,  1,  10, 16}
946
    };
947
    const unsigned char *color;
948
    size_t out_index;
949
    int palette_index;
950
    unsigned int center_weight;
951
    unsigned int total_weight = 0;
×
952
    unsigned int accum_r;
953
    unsigned int accum_g;
954
    unsigned int accum_b;
955
    unsigned short gradient;
956
    int neighbor;
957
    int nx;
958
    int ny;
959
    int numerator;
960
    int denominator;
961
    unsigned int weight;
962
    const unsigned char *neighbor_color;
963
    int neighbor_index;
964

965
    if (width <= 0 || height <= 0 || palette == NULL || ncolors <= 0) {
×
966
        return SIXEL_BAD_INPUT;
×
967
    }
968

969
    num_pixels = (size_t)width * (size_t)height;
×
970

971
    memset(&similarity, 0, sizeof(sixel_similarity_t));
×
972

973
    strong_threshold = sixel_scale_threshold(256U, edge_strength);
×
974
    detail_threshold = sixel_scale_threshold(160U, edge_strength);
×
975
    if (strong_threshold < detail_threshold) {
×
976
        strong_threshold = detail_threshold;
×
977
    }
978

979
    /*
980
     * Build RGB and luminance buffers so we can reuse the similarity cache
981
     * and gradient analysis across the reconstructed image.
982
     */
983
    rgb = (unsigned char *)sixel_allocator_malloc(
×
984
        allocator,
985
        num_pixels * 3);
986
    if (rgb == NULL) {
×
987
        sixel_helper_set_additional_message(
×
988
            "sixel_dequantize_k_undither: "
989
            "sixel_allocator_malloc() failed.");
990
        status = SIXEL_BAD_ALLOCATION;
×
991
        goto end;
×
992
    }
993

994
    gray = (int *)sixel_allocator_malloc(
×
995
        allocator,
996
        num_pixels * sizeof(int));
997
    if (gray == NULL) {
×
998
        sixel_helper_set_additional_message(
×
999
            "sixel_dequantize_k_undither: "
1000
            "sixel_allocator_malloc() failed.");
1001
        status = SIXEL_BAD_ALLOCATION;
×
1002
        goto end;
×
1003
    }
1004

1005
    prewitt = (unsigned short *)sixel_allocator_malloc(
×
1006
        allocator,
1007
        num_pixels * sizeof(unsigned short));
1008
    if (prewitt == NULL) {
×
1009
        sixel_helper_set_additional_message(
×
1010
            "sixel_dequantize_k_undither: "
1011
            "sixel_allocator_malloc() failed.");
1012
        status = SIXEL_BAD_ALLOCATION;
×
1013
        goto end;
×
1014
    }
1015

1016
    /*
1017
     * Pre-compute palette distance heuristics so each neighbour lookup reuses
1018
     * the k_undither-style similarity table.
1019
     */
1020
    status = sixel_similarity_init(
×
1021
        &similarity,
1022
        palette,
1023
        ncolors,
1024
        similarity_bias,
1025
        allocator);
1026
    if (SIXEL_FAILED(status)) {
×
1027
        goto end;
×
1028
    }
1029

1030
    for (y = 0; y < height; ++y) {
×
1031
        for (x = 0; x < width; ++x) {
×
1032
            palette_index = indexed_pixels[y * width + x];
×
1033
            if (palette_index < 0 || palette_index >= ncolors) {
×
1034
                palette_index = 0;
×
1035
            }
1036

1037
            color = palette + palette_index * 3;
×
1038
            out_index = (size_t)(y * width + x) * 3;
×
1039
            rgb[out_index + 0] = color[0];
×
1040
            rgb[out_index + 1] = color[1];
×
1041
            rgb[out_index + 2] = color[2];
×
1042

1043
            if (edge_strength > 0) {
×
1044
                gray[y * width + x] = (int)color[0]
×
1045
                                    + (int)color[1] * 2
×
1046
                                    + (int)color[2];
×
1047
                /*
1048
                 * Edge detection keeps high-frequency content intact while we
1049
                 * smooth dithering noise in flatter regions.
1050
                 */
1051
                prewitt[y * width + x] = sixel_prewitt_value(
×
1052
                    gray,
1053
                    width,
1054
                    height,
1055
                    x,
1056
                    y);
1057

1058
                gradient = prewitt[y * width + x];
×
1059
                if (gradient > strong_threshold) {
×
1060
                    continue;
×
1061
                }
1062

1063
                if (gradient > detail_threshold) {
×
1064
                    center_weight = 24U;
×
1065
                } else {
1066
                    center_weight = 8U;
×
1067
                }
1068
            } else {
1069
                center_weight = 8U;
×
1070
            }
1071

1072
            out_index = (size_t)(y * width + x) * 3;
×
1073
            accum_r = (unsigned int)rgb[out_index + 0] * center_weight;
×
1074
            accum_g = (unsigned int)rgb[out_index + 1] * center_weight;
×
1075
            accum_b = (unsigned int)rgb[out_index + 2] * center_weight;
×
1076
            total_weight = center_weight;
×
1077

1078
            /*
1079
             * Blend neighbours that stay within the palette similarity
1080
             * threshold so Floyd-Steinberg noise is averaged away without
1081
             * bleeding across pronounced edges.
1082
             */
1083
            for (neighbor = 0; neighbor < 8; ++neighbor) {
×
1084
                nx = x + neighbor_offsets[neighbor][0];
×
1085
                ny = y + neighbor_offsets[neighbor][1];
×
1086
                numerator = neighbor_offsets[neighbor][2];
×
1087
                denominator = neighbor_offsets[neighbor][3];
×
1088

1089
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
×
1090
                    continue;
×
1091
                }
1092

1093
                neighbor_index = indexed_pixels[ny * width + nx];
×
1094
                if (neighbor_index < 0 || neighbor_index >= ncolors) {
×
1095
                    continue;
×
1096
                }
1097

1098
                if (numerator) {
×
1099
                    weight = sixel_similarity_compare(
×
1100
                        &similarity,
1101
                        palette_index,
1102
                        neighbor_index,
1103
                        numerator,
1104
                        denominator);
1105
                    if (weight == 0) {
×
1106
                        continue;
×
1107
                    }
1108

1109
                    neighbor_color = palette + neighbor_index * 3;
×
1110
                    accum_r += (unsigned int)neighbor_color[0] * weight;
×
1111
                    accum_g += (unsigned int)neighbor_color[1] * weight;
×
1112
                    accum_b += (unsigned int)neighbor_color[2] * weight;
×
1113
                    total_weight += weight;
×
1114
                }
1115
            }
1116

1117
            if (total_weight > 0U) {
×
1118
                rgb[out_index + 0] = (unsigned char)(accum_r / total_weight);
×
1119
                rgb[out_index + 1] = (unsigned char)(accum_g / total_weight);
×
1120
                rgb[out_index + 2] = (unsigned char)(accum_b / total_weight);
×
1121
            }
1122
        }
1123
    }
1124

1125

1126
    *output = rgb;
×
1127
    rgb = NULL;
×
1128
    sixel_post_undither_refine(*output, width, height, NULL);
×
1129
    status = SIXEL_OK;
×
1130

1131
end:
1132
    sixel_similarity_destroy(&similarity, allocator);
×
1133
    sixel_allocator_free(allocator, rgb);
×
1134
    sixel_allocator_free(allocator, gray);
×
1135
    sixel_allocator_free(allocator, prewitt);
×
1136
    return status;
×
1137
}
1138
/* set an option flag to decoder object */
1139
SIXELAPI SIXELSTATUS
1140
sixel_decoder_setopt(
18✔
1141
    sixel_decoder_t /* in */ *decoder,
1142
    int             /* in */ arg,
1143
    char const      /* in */ *value
1144
)
1145
{
1146
    SIXELSTATUS status = SIXEL_FALSE;
18✔
1147

1148
    sixel_decoder_ref(decoder);
18✔
1149

1150
    switch(arg) {
18!
1151
    case SIXEL_OPTFLAG_INPUT:  /* i */
8✔
1152
        free(decoder->input);
12✔
1153
        decoder->input = strdup_with_allocator(value, decoder->allocator);
12✔
1154
        if (decoder->input == NULL) {
12!
1155
            sixel_helper_set_additional_message(
×
1156
                "sixel_decoder_setopt: strdup_with_allocator() failed.");
1157
            status = SIXEL_BAD_ALLOCATION;
×
1158
            goto end;
×
1159
        }
1160
        break;
12✔
1161
    case SIXEL_OPTFLAG_OUTPUT:  /* o */
4✔
1162
        free(decoder->output);
6✔
1163
        decoder->output = strdup_with_allocator(value, decoder->allocator);
6✔
1164
        if (decoder->output == NULL) {
6!
1165
            sixel_helper_set_additional_message(
×
1166
                "sixel_decoder_setopt: strdup_with_allocator() failed.");
1167
            status = SIXEL_BAD_ALLOCATION;
×
1168
            goto end;
×
1169
        }
1170
        break;
6✔
1171
    case SIXEL_OPTFLAG_DEQUANTIZE:  /* d */
1172
        if (value == NULL) {
×
1173
            sixel_helper_set_additional_message(
×
1174
                "sixel_decoder_setopt: -d/--dequantize requires an argument.");
1175
            status = SIXEL_BAD_ALLOCATION;
×
1176
            goto end;
×
1177
        }
1178

1179
        if (strcmp(value, "none") == 0) {
×
1180
            decoder->dequantize_method = SIXEL_DEQUANTIZE_NONE;
×
1181
        } else if (strcmp(value, "k_undither") == 0) {
×
1182
            decoder->dequantize_method = SIXEL_DEQUANTIZE_K_UNDITHER;
×
1183
        } else {
1184
            sixel_helper_set_additional_message(
×
1185
                "unsupported dequantize method.");
1186
            status = SIXEL_BAD_ARGUMENT;
×
1187
            goto end;
×
1188
        }
1189
        break;
×
1190

1191
    case SIXEL_OPTFLAG_SIMILARITY:  /* S */
1192
        decoder->dequantize_similarity_bias = atoi(value);
×
1193
        if (decoder->dequantize_similarity_bias < 0 ||
×
1194
            decoder->dequantize_similarity_bias > 1000) {
×
1195
            sixel_helper_set_additional_message(
×
1196
                "similarity must be between 1 and 1000.");
1197
            status = SIXEL_BAD_ARGUMENT;
×
1198
            goto end;
×
1199
        }
1200
        break;
×
1201

1202
    case SIXEL_OPTFLAG_SIZE:  /* s */
1203
        decoder->thumbnail_size = atoi(value);
×
1204
        if (decoder->thumbnail_size <= 0) {
×
1205
            sixel_helper_set_additional_message(
×
1206
                "size must be greater than zero.");
1207
            status = SIXEL_BAD_ARGUMENT;
×
1208
            goto end;
×
1209
        }
1210
        break;
×
1211

1212
    case SIXEL_OPTFLAG_EDGE:  /* e */
1213
        decoder->dequantize_edge_strength = atoi(value);
×
1214
        if (decoder->dequantize_edge_strength < 0 ||
×
1215
            decoder->dequantize_edge_strength > 1000) {
×
1216
            sixel_helper_set_additional_message(
×
1217
                "edge bias must be between 1 and 1000.");
1218
            status = SIXEL_BAD_ARGUMENT;
×
1219
            goto end;
×
1220
        }
1221
        break;
×
1222

UNCOV
1223
    case '?':
×
1224
    default:
UNCOV
1225
        status = SIXEL_BAD_ARGUMENT;
×
UNCOV
1226
        goto end;
×
1227
    }
1228

1229
    status = SIXEL_OK;
18✔
1230

1231
end:
12✔
1232
    sixel_decoder_unref(decoder);
18✔
1233

1234
    return status;
18✔
1235
}
1236

1237

1238
/* load source data from stdin or the file specified with
1239
   SIXEL_OPTFLAG_INPUT flag, and decode it */
1240
SIXELAPI SIXELSTATUS
1241
sixel_decoder_decode(
18✔
1242
    sixel_decoder_t /* in */ *decoder)
1243
{
1244
    SIXELSTATUS status = SIXEL_FALSE;
18✔
1245
    unsigned char *raw_data = NULL;
18✔
1246
    int sx;
1247
    int sy;
1248
    int raw_len;
1249
    int max;
1250
    int n;
1251
    FILE *input_fp = NULL;
18✔
1252
    unsigned char *indexed_pixels = NULL;
18✔
1253
    unsigned char *palette = NULL;
18✔
1254
    unsigned char *rgb_pixels = NULL;
18✔
1255
    unsigned char *output_pixels;
1256
    unsigned char *output_palette;
1257
    int output_pixelformat;
1258
    int ncolors;
1259
    sixel_frame_t *frame;
1260
    int new_width;
1261
    int new_height;
1262
    double scaled_width;
1263
    double scaled_height;
1264
    int max_dimension;
1265
    int thumbnail_size;
1266
    int frame_ncolors;
1267

1268
    sixel_decoder_ref(decoder);
18✔
1269

1270
    frame = NULL;
18✔
1271
    new_width = 0;
18✔
1272
    new_height = 0;
18✔
1273
    scaled_width = 0.0;
18✔
1274
    scaled_height = 0.0;
18✔
1275
    max_dimension = 0;
18✔
1276
    thumbnail_size = decoder->thumbnail_size;
18✔
1277
    frame_ncolors = -1;
18✔
1278

1279
    if (strcmp(decoder->input, "-") == 0) {
18✔
1280
        /* for windows */
1281
#if defined(O_BINARY)
1282
# if HAVE__SETMODE
1283
        _setmode(STDIN_FILENO, O_BINARY);
1284
# elif HAVE_SETMODE
1285
        setmode(STDIN_FILENO, O_BINARY);
1286
# endif  /* HAVE_SETMODE */
1287
#endif  /* defined(O_BINARY) */
1288
        input_fp = stdin;
9✔
1289
    } else {
3✔
1290
        input_fp = fopen(decoder->input, "rb");
9✔
1291
        if (!input_fp) {
9✔
1292
            sixel_helper_set_additional_message(
6✔
1293
                "sixel_decoder_decode: fopen() failed.");
1294
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
6✔
1295
            goto end;
6✔
1296
        }
1297
    }
1298

1299
    raw_len = 0;
12✔
1300
    max = 64 * 1024;
12✔
1301

1302
    raw_data = (unsigned char *)sixel_allocator_malloc(
12✔
1303
        decoder->allocator,
4✔
1304
        (size_t)max);
4✔
1305
    if (raw_data == NULL) {
12!
1306
        sixel_helper_set_additional_message(
×
1307
            "sixel_decoder_decode: sixel_allocator_malloc() failed.");
1308
        status = SIXEL_BAD_ALLOCATION;
×
1309
        goto end;
×
1310
    }
1311

1312
    for (;;) {
136✔
1313
        if ((max - raw_len) < 4096) {
408✔
1314
            max *= 2;
18✔
1315
            raw_data = (unsigned char *)sixel_allocator_realloc(
18✔
1316
                decoder->allocator,
6✔
1317
                raw_data,
6✔
1318
                (size_t)max);
6✔
1319
            if (raw_data == NULL) {
18!
1320
                sixel_helper_set_additional_message(
×
1321
                    "sixel_decoder_decode: sixel_allocator_realloc() failed.");
1322
                status = SIXEL_BAD_ALLOCATION;
×
1323
                goto end;
×
1324
            }
1325
        }
6✔
1326
        if ((n = (int)fread(raw_data + raw_len, 1, 4096, input_fp)) <= 0) {
408✔
1327
            break;
12✔
1328
        }
1329
        raw_len += n;
396✔
1330
    }
1331

1332
    if (input_fp != stdout) {
12!
1333
        fclose(input_fp);
12✔
1334
    }
4✔
1335

1336
    status = sixel_decode_raw(
12✔
1337
        raw_data,
4✔
1338
        raw_len,
4✔
1339
        &indexed_pixels,
1340
        &sx,
1341
        &sy,
1342
        &palette,
1343
        &ncolors,
1344
        decoder->allocator);
4✔
1345
    if (SIXEL_FAILED(status)) {
12!
1346
        goto end;
×
1347
    }
1348

1349
    if (sx > SIXEL_WIDTH_LIMIT || sy > SIXEL_HEIGHT_LIMIT) {
12!
1350
        status = SIXEL_BAD_INPUT;
×
1351
        goto end;
×
1352
    }
1353

1354
    output_pixels = indexed_pixels;
12✔
1355
    output_palette = palette;
12✔
1356
    output_pixelformat = SIXEL_PIXELFORMAT_PAL8;
12✔
1357

1358
    if (decoder->dequantize_method == SIXEL_DEQUANTIZE_K_UNDITHER) {
12!
1359
        status = sixel_dequantize_k_undither(
×
1360
            indexed_pixels,
1361
            sx,
1362
            sy,
1363
            palette,
1364
            ncolors,
1365
            decoder->dequantize_similarity_bias,
1366
            decoder->dequantize_edge_strength,
1367
            decoder->allocator,
1368
            &rgb_pixels);
1369
        if (SIXEL_FAILED(status)) {
×
1370
            goto end;
×
1371
        }
1372
        output_pixels = rgb_pixels;
×
1373
        output_palette = NULL;
×
1374
        output_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1375
    }
1376

1377
    if (output_pixelformat == SIXEL_PIXELFORMAT_PAL8) {
12!
1378
        frame_ncolors = ncolors;
12✔
1379
    }
4✔
1380

1381
    if (thumbnail_size > 0) {
12!
1382
        /*
1383
         * When the caller requests a thumbnail, compute the new geometry
1384
         * while preserving the original aspect ratio. We only allocate a
1385
         * frame when the dimensions actually change, so the fast path for
1386
         * matching sizes still avoids any additional allocations.
1387
         */
1388
        max_dimension = sx;
×
1389
        if (sy > max_dimension) {
×
1390
            max_dimension = sy;
×
1391
        }
1392
        if (max_dimension > 0) {
×
1393
            if (sx >= sy) {
×
1394
                new_width = thumbnail_size;
×
1395
                scaled_height = (double)sy * (double)thumbnail_size /
×
1396
                    (double)sx;
×
1397
                new_height = (int)(scaled_height + 0.5);
×
1398
            } else {
1399
                new_height = thumbnail_size;
×
1400
                scaled_width = (double)sx * (double)thumbnail_size /
×
1401
                    (double)sy;
×
1402
                new_width = (int)(scaled_width + 0.5);
×
1403
            }
1404
            if (new_width < 1) {
×
1405
                new_width = 1;
×
1406
            }
1407
            if (new_height < 1) {
×
1408
                new_height = 1;
×
1409
            }
1410
            if (new_width != sx || new_height != sy) {
×
1411
                /*
1412
                 * Wrap the decoded pixels in a frame so we can reuse the
1413
                 * central scaling helper. Ownership transfers to the frame,
1414
                 * which keeps the lifetime rules identical on both paths.
1415
                 */
1416
                status = sixel_frame_new(&frame, decoder->allocator);
×
1417
                if (SIXEL_FAILED(status)) {
×
1418
                    goto end;
×
1419
                }
1420
                status = sixel_frame_init(
×
1421
                    frame,
1422
                    output_pixels,
1423
                    sx,
1424
                    sy,
1425
                    output_pixelformat,
1426
                    output_palette,
1427
                    frame_ncolors);
1428
                if (SIXEL_FAILED(status)) {
×
1429
                    goto end;
×
1430
                }
1431
                if (output_pixels == indexed_pixels) {
×
1432
                    indexed_pixels = NULL;
×
1433
                }
1434
                if (output_pixels == rgb_pixels) {
×
1435
                    rgb_pixels = NULL;
×
1436
                }
1437
                if (output_palette == palette) {
×
1438
                    palette = NULL;
×
1439
                }
1440
                status = sixel_frame_resize(
×
1441
                    frame,
1442
                    new_width,
1443
                    new_height,
1444
                    SIXEL_RES_BILINEAR);
1445
                if (SIXEL_FAILED(status)) {
×
1446
                    goto end;
×
1447
                }
1448
                /*
1449
                 * The resized frame already exposes a tightly packed RGB
1450
                 * buffer, so write the updated dimensions and references
1451
                 * back to the main encoder path.
1452
                 */
1453
                sx = sixel_frame_get_width(frame);
×
1454
                sy = sixel_frame_get_height(frame);
×
1455
                output_pixels = sixel_frame_get_pixels(frame);
×
1456
                output_palette = NULL;
×
1457
                output_pixelformat = sixel_frame_get_pixelformat(frame);
×
1458
            }
1459
        }
1460
    }
1461

1462
    status = sixel_helper_write_image_file(
12✔
1463
        output_pixels,
4✔
1464
        sx,
4✔
1465
        sy,
4✔
1466
        output_palette,
4✔
1467
        output_pixelformat,
4✔
1468
        decoder->output,
12✔
1469
        SIXEL_FORMAT_PNG,
1470
        decoder->allocator);
4✔
1471

1472
    if (SIXEL_FAILED(status)) {
12!
1473
        goto end;
×
1474
    }
1475

1476
end:
8✔
1477
    sixel_frame_unref(frame);
18✔
1478
    sixel_allocator_free(decoder->allocator, raw_data);
18✔
1479
    sixel_allocator_free(decoder->allocator, indexed_pixels);
18✔
1480
    sixel_allocator_free(decoder->allocator, palette);
18✔
1481
    sixel_allocator_free(decoder->allocator, rgb_pixels);
18✔
1482

1483
    sixel_decoder_unref(decoder);
18✔
1484

1485
    return status;
18✔
1486
}
1487

1488

1489
#if HAVE_TESTS
1490
static int
1491
test1(void)
×
1492
{
1493
    int nret = EXIT_FAILURE;
×
1494
    sixel_decoder_t *decoder = NULL;
×
1495

1496
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1497
#  pragma GCC diagnostic push
1498
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1499
#endif
1500
    decoder = sixel_decoder_create();
×
1501
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1502
#  pragma GCC diagnostic pop
1503
#endif
1504
    if (decoder == NULL) {
×
1505
        goto error;
×
1506
    }
1507
    sixel_decoder_ref(decoder);
×
1508
    sixel_decoder_unref(decoder);
×
1509
    nret = EXIT_SUCCESS;
×
1510

1511
error:
1512
    sixel_decoder_unref(decoder);
×
1513
    return nret;
×
1514
}
1515

1516

1517
static int
1518
test2(void)
×
1519
{
1520
    int nret = EXIT_FAILURE;
×
1521
    sixel_decoder_t *decoder = NULL;
×
1522
    SIXELSTATUS status;
1523

1524
    status = sixel_decoder_new(&decoder, NULL);
×
1525
    if (SIXEL_FAILED(status)) {
×
1526
        goto error;
×
1527
    }
1528

1529
    sixel_decoder_ref(decoder);
×
1530
    sixel_decoder_unref(decoder);
×
1531
    nret = EXIT_SUCCESS;
×
1532

1533
error:
1534
    sixel_decoder_unref(decoder);
×
1535
    return nret;
×
1536
}
1537

1538

1539
static int
1540
test3(void)
×
1541
{
1542
    int nret = EXIT_FAILURE;
×
1543
    sixel_decoder_t *decoder = NULL;
×
1544
    sixel_allocator_t *allocator = NULL;
×
1545
    SIXELSTATUS status;
1546

1547
    sixel_debug_malloc_counter = 1;
×
1548

1549
    status = sixel_allocator_new(
×
1550
        &allocator,
1551
        sixel_bad_malloc,
1552
        NULL,
1553
        NULL,
1554
        NULL);
1555
    if (SIXEL_FAILED(status)) {
×
1556
        goto error;
×
1557
    }
1558

1559
    status = sixel_decoder_new(&decoder, allocator);
×
1560
    if (status != SIXEL_BAD_ALLOCATION) {
×
1561
        goto error;
×
1562
    }
1563

1564
    nret = EXIT_SUCCESS;
×
1565

1566
error:
1567
    return nret;
×
1568
}
1569

1570

1571
static int
1572
test4(void)
×
1573
{
1574
    int nret = EXIT_FAILURE;
×
1575
    sixel_decoder_t *decoder = NULL;
×
1576
    sixel_allocator_t *allocator = NULL;
×
1577
    SIXELSTATUS status;
1578

1579
    sixel_debug_malloc_counter = 2;
×
1580

1581
    status = sixel_allocator_new(
×
1582
        &allocator,
1583
        sixel_bad_malloc,
1584
        NULL,
1585
        NULL,
1586
        NULL);
1587
    if (SIXEL_FAILED(status)) {
×
1588
        goto error;
×
1589
    }
1590

1591
    status = sixel_decoder_new(&decoder, allocator);
×
1592
    if (status != SIXEL_BAD_ALLOCATION) {
×
1593
        goto error;
×
1594
    }
1595

1596
    nret = EXIT_SUCCESS;
×
1597

1598
error:
1599
    return nret;
×
1600
}
1601

1602

1603
static int
1604
test5(void)
×
1605
{
1606
    int nret = EXIT_FAILURE;
×
1607
    sixel_decoder_t *decoder = NULL;
×
1608
    sixel_allocator_t *allocator = NULL;
×
1609
    SIXELSTATUS status;
1610

1611
    sixel_debug_malloc_counter = 4;
×
1612

1613
    status = sixel_allocator_new(
×
1614
        &allocator,
1615
        sixel_bad_malloc,
1616
        NULL,
1617
        NULL,
1618
        NULL);
1619
    if (SIXEL_FAILED(status)) {
×
1620
        goto error;
×
1621
    }
1622
    status = sixel_decoder_new(&decoder, allocator);
×
1623
    if (SIXEL_FAILED(status)) {
×
1624
        goto error;
×
1625
    }
1626

1627
    status = sixel_decoder_setopt(
×
1628
        decoder,
1629
        SIXEL_OPTFLAG_INPUT,
1630
        "/");
1631
    if (status != SIXEL_BAD_ALLOCATION) {
×
1632
        goto error;
×
1633
    }
1634

1635
    nret = EXIT_SUCCESS;
×
1636

1637
error:
1638
    return nret;
×
1639
}
1640

1641

1642
static int
1643
test6(void)
×
1644
{
1645
    int nret = EXIT_FAILURE;
×
1646
    sixel_decoder_t *decoder = NULL;
×
1647
    sixel_allocator_t *allocator = NULL;
×
1648
    SIXELSTATUS status;
1649

1650
    sixel_debug_malloc_counter = 4;
×
1651

1652
    status = sixel_allocator_new(
×
1653
        &allocator,
1654
        sixel_bad_malloc,
1655
        NULL,
1656
        NULL,
1657
        NULL);
1658
    if (SIXEL_FAILED(status)) {
×
1659
        goto error;
×
1660
    }
1661

1662
    status = sixel_decoder_new(&decoder, allocator);
×
1663
    if (SIXEL_FAILED(status)) {
×
1664
        goto error;
×
1665
    }
1666

1667
    status = sixel_decoder_setopt(
×
1668
        decoder,
1669
        SIXEL_OPTFLAG_OUTPUT,
1670
        "/");
1671
    if (status != SIXEL_BAD_ALLOCATION) {
×
1672
        goto error;
×
1673
    }
1674

1675
    nret = EXIT_SUCCESS;
×
1676

1677
error:
1678
    return nret;
×
1679
}
1680

1681

1682
static int
1683
test7(void)
×
1684
{
1685
    int nret = EXIT_FAILURE;
×
1686
    sixel_decoder_t *decoder = NULL;
×
1687
    sixel_allocator_t *allocator = NULL;
×
1688
    SIXELSTATUS status;
1689

1690
    status = sixel_allocator_new(
×
1691
        &allocator,
1692
        NULL,
1693
        NULL,
1694
        NULL,
1695
        NULL);
1696
    if (SIXEL_FAILED(status)) {
×
1697
        goto error;
×
1698
    }
1699

1700
    status = sixel_decoder_new(&decoder, allocator);
×
1701
    if (SIXEL_FAILED(status)) {
×
1702
        goto error;
×
1703
    }
1704

1705
    status = sixel_decoder_setopt(
×
1706
        decoder,
1707
        SIXEL_OPTFLAG_INPUT,
1708
        "../images/file");
1709
    if (SIXEL_FAILED(status)) {
×
1710
        goto error;
×
1711
    }
1712

1713
    status = sixel_decoder_decode(decoder);
×
1714
    if ((status >> 8) != (SIXEL_LIBC_ERROR >> 8)) {
×
1715
        goto error;
×
1716
    }
1717

1718
    nret = EXIT_SUCCESS;
×
1719

1720
error:
1721
    return nret;
×
1722
}
1723

1724

1725
static int
1726
test8(void)
×
1727
{
1728
    int nret = EXIT_FAILURE;
×
1729
    sixel_decoder_t *decoder = NULL;
×
1730
    sixel_allocator_t *allocator = NULL;
×
1731
    SIXELSTATUS status;
1732

1733
    sixel_debug_malloc_counter = 5;
×
1734

1735
    status = sixel_allocator_new(
×
1736
        &allocator,
1737
        sixel_bad_malloc,
1738
        NULL,
1739
        NULL,
1740
        NULL);
1741
    if (SIXEL_FAILED(status)) {
×
1742
        goto error;
×
1743
    }
1744

1745
    status = sixel_decoder_new(&decoder, allocator);
×
1746
    if (SIXEL_FAILED(status)) {
×
1747
        goto error;
×
1748
    }
1749

1750
    status = sixel_decoder_setopt(
×
1751
        decoder,
1752
        SIXEL_OPTFLAG_INPUT,
1753
        "../images/map8.six");
1754
    if (SIXEL_FAILED(status)) {
×
1755
        goto error;
×
1756
    }
1757

1758
    status = sixel_decoder_decode(decoder);
×
1759
    if (status != SIXEL_BAD_ALLOCATION) {
×
1760
        goto error;
×
1761
    }
1762

1763
    nret = EXIT_SUCCESS;
×
1764

1765
error:
1766
    return nret;
×
1767
}
1768

1769

1770
SIXELAPI int
1771
sixel_decoder_tests_main(void)
×
1772
{
1773
    int nret = EXIT_FAILURE;
×
1774
    size_t i;
1775
    typedef int (* testcase)(void);
1776

1777
    static testcase const testcases[] = {
1778
        test1,
1779
        test2,
1780
        test3,
1781
        test4,
1782
        test5,
1783
        test6,
1784
        test7,
1785
        test8
1786
    };
1787

1788
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
1789
        nret = testcases[i]();
×
1790
        if (nret != EXIT_SUCCESS) {
×
1791
            goto error;
×
1792
        }
1793
    }
1794

1795
    nret = EXIT_SUCCESS;
×
1796

1797
error:
1798
    return nret;
×
1799
}
1800
#endif  /* HAVE_TESTS */
1801

1802
/* emacs Local Variables:      */
1803
/* emacs mode: c               */
1804
/* emacs tab-width: 4          */
1805
/* emacs indent-tabs-mode: nil */
1806
/* emacs c-basic-offset: 4     */
1807
/* emacs End:                  */
1808
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1809
/* 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