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

saitoha / libsixel / 19918707358

04 Dec 2025 05:12AM UTC coverage: 38.402% (-4.0%) from 42.395%
19918707358

push

github

saitoha
tests: fix meson msys dll lookup

9738 of 38220 branches covered (25.48%)

12841 of 33438 relevant lines covered (38.4%)

782420.02 hits per line

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

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

29
/*
30
 * Initial developer of this file is kmiya@culti.
31
 *
32
 * He distributes it under very permissive license which permits
33
 * useing, copying, modification, redistribution, and all other
34
 * public activities without any restrictions.
35
 *
36
 * He declares this is compatible with MIT/BSD/GPL.
37
 *
38
 * Hayaki Saito <saitoha@me.com> modified this and re-licensed
39
 * it to the MIT license.
40
 */
41
#include "config.h"
42

43
/* STDC_HEADERS */
44
#include <stdlib.h>
45
#include <stdio.h>
46

47
#if HAVE_CTYPE_H
48
# include <ctype.h>   /* isdigit */
49
#endif  /* HAVE_CTYPE_H */
50
#if HAVE_STRING_H
51
# include <string.h>  /* memcpy */
52
#endif  /* HAVE_STRING_H */
53
#if HAVE_LIMITS_H
54
# include <limits.h>
55
#endif  /* HAVE_LIMITS_H */
56
#if HAVE_INTTYPES_H
57
# include <inttypes.h>
58
#endif  /* HAVE_INTTYPES_H */
59
#if HAVE_ASSERT_H
60
# include <assert.h>
61
#endif  /* HAVE_ASSERT_H */
62

63
#include <sixel.h>
64
#include "output.h"
65
#include "decoder-image.h"
66
#include "decoder-parallel.h"
67
#include "logger.h"
68

69
#define SIXEL_RGB(r, g, b) (((r) << 16) + ((g) << 8) +  (b))
70

71
#define PALVAL(n,a,m) (((n) * (a) + ((m) / 2)) / (m))
72

73
#define SIXEL_XRGB(r,g,b) SIXEL_RGB(PALVAL(r, 255, 100), PALVAL(g, 255, 100), PALVAL(b, 255, 100))
74

75
#define DECSIXEL_PARAMS_MAX 16
76
#define SIXEL_PALETTE_MAX_DECODER 65536
77
typedef unsigned char sixel_decoder_index_t;
78

79
static int const sixel_default_color_table[] = {
80
    SIXEL_XRGB(0,  0,  0),   /*  0 Black    */
81
    SIXEL_XRGB(20, 20, 80),  /*  1 Blue     */
82
    SIXEL_XRGB(80, 13, 13),  /*  2 Red      */
83
    SIXEL_XRGB(20, 80, 20),  /*  3 Green    */
84
    SIXEL_XRGB(80, 20, 80),  /*  4 Magenta  */
85
    SIXEL_XRGB(20, 80, 80),  /*  5 Cyan     */
86
    SIXEL_XRGB(80, 80, 20),  /*  6 Yellow   */
87
    SIXEL_XRGB(53, 53, 53),  /*  7 Gray 50% */
88
    SIXEL_XRGB(26, 26, 26),  /*  8 Gray 25% */
89
    SIXEL_XRGB(33, 33, 60),  /*  9 Blue*    */
90
    SIXEL_XRGB(60, 26, 26),  /* 10 Red*     */
91
    SIXEL_XRGB(33, 60, 33),  /* 11 Green*   */
92
    SIXEL_XRGB(60, 33, 60),  /* 12 Magenta* */
93
    SIXEL_XRGB(33, 60, 60),  /* 13 Cyan*    */
94
    SIXEL_XRGB(60, 60, 33),  /* 14 Yellow*  */
95
    SIXEL_XRGB(80, 80, 80),  /* 15 Gray 75% */
96
};
97

98

99
/*
100
 * Store a single pixel in the image buffer. When the decoder is in direct
101
 * color mode the palette index is translated into an RGBA quadruplet with an
102
 * opaque alpha channel. Indexed mode keeps the original palette entry so the
103
 * caller can compose a palette later.
104
 */
105
static void
106
image_buffer_store_pixel(image_buffer_t *image, size_t pos, int color_index)
28,074,558✔
107
{
108
    unsigned char *bytes;
28,074,558✔
109
    int color;
28,074,558✔
110
    int depth;
28,074,558✔
111

112
    depth = image->depth;
28,074,558✔
113
    if (color_index < 0 || color_index >= SIXEL_PALETTE_MAX_DECODER) {
28,074,558!
114
        return;
115
    }
116

117
    if (depth == 1U) {
28,074,558✔
118
        image->pixels.in_bytes[pos] = (unsigned char)color_index;
27,243,822✔
119
    } else if (depth == 2U) {
830,736!
120
        image->pixels.in_shorts[pos] = (unsigned short)color_index;
×
121
    } else {  /* rgba */
122
        color = image->palette[color_index];
830,736✔
123
        bytes = image->pixels.in_bytes + pos * 4U;
830,736✔
124
        bytes[0] = (unsigned char)((color >> 16) & 0xff);
830,736✔
125
        bytes[1] = (unsigned char)((color >> 8) & 0xff);
830,736✔
126
        bytes[2] = (unsigned char)(color & 0xff);
830,736✔
127
        bytes[3] = 255u;
830,736✔
128
    }
129
}
1!
130

131
/*
132
 * Fill a horizontal run starting at (x, y) with the requested palette index.
133
 * Direct color output expands the span into RGBA bytes to keep the alpha
134
 * channel and the color components consistent with single pixel writes.
135
 */
136
static void
137
image_buffer_fill_span(image_buffer_t *image,
174,342✔
138
                       int y,
139
                       int x,
140
                       int repeat,
141
                       int color_index)
142
{
143
    size_t pos;
174,342✔
144
    int n;
174,342✔
145

146
    pos = (size_t)image->width * (size_t)y + (size_t)x;
174,342✔
147

148
    if (image->depth == 1U) {
174,342✔
149
        memset(image->pixels.in_bytes + pos, color_index, (size_t)repeat);
166,581✔
150
    } else {
151
        for (n = 0; n < repeat; ++n) {
57,159✔
152
            image_buffer_store_pixel(image, pos + (size_t)n, color_index);
49,398✔
153
        }
154
    }
155
}
174,342✔
156

157
typedef enum parse_state {
158
    PS_GROUND     = 0,
159
    PS_ESC        = 1,  /* ESC */
160
    PS_DCS        = 2,  /* DCS Device Control String Introducer \033P P...P I...I F */
161
    PS_DECSIXEL   = 3,  /* DECSIXEL body part ", $, -, ? ... ~ */
162
    PS_DECGRA     = 4,  /* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */
163
    PS_DECGRI     = 5,  /* DECGRI Graphics Repeat Introducer ! Pn Ch */
164
    PS_DECGCI     = 6   /* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */
165
} parse_state_t;
166

167
typedef struct parser_context {
168
    parse_state_t state;
169
    int pos_x;
170
    int pos_y;
171
    int max_x;
172
    int max_y;
173
    int attributed_pan;
174
    int attributed_pad;
175
    int attributed_ph;
176
    int attributed_pv;
177
    int repeat_count;
178
    int color_index;
179
    int bgindex;
180
    int param;
181
    int nparams;
182
    int params[DECSIXEL_PARAMS_MAX];
183
} parser_context_t;
184

185

186
/*
187
 * Primary color hues:
188
 *  blue:    0 degrees
189
 *  red:   120 degrees
190
 *  green: 240 degrees
191
 */
192
static int
193
hls_to_rgb(int hue, int lum, int sat)
1,152✔
194
{
195
    double min, max;
1,152✔
196
    int r, g, b;
1,152✔
197

198
    if (sat == 0) {
1,152✔
199
        r = g = b = lum;
1,152✔
200
    }
201

202
    /* https://wikimedia.org/api/rest_v1/media/math/render/svg/17e876f7e3260ea7fed73f69e19c71eb715dd09d */
203
    max = lum + sat * (1.0 - (lum > 50 ? (2 * (lum / 100.0) - 1.0): - (2 * (lum / 100.0) - 1.0))) / 2.0;
1,152✔
204

205
    /* https://wikimedia.org/api/rest_v1/media/math/render/svg/f6721b57985ad83db3d5b800dc38c9980eedde1d */
206
    min = lum - sat * (1.0 - (lum > 50 ? (2 * (lum / 100.0) - 1.0): - (2 * (lum / 100.0) - 1.0))) / 2.0;
1,152✔
207

208
    /* sixel hue color ring is roteted -120 degree from nowdays general one. */
209
    hue = (hue + 240) % 360;
1,152✔
210

211
    /* https://wikimedia.org/api/rest_v1/media/math/render/svg/937e8abdab308a22ff99de24d645ec9e70f1e384 */
212
    switch (hue / 60) {
1,152!
213
    case 0:  /* 0 <= hue < 60 */
180✔
214
        r = max;
180✔
215
        g = (min + (max - min) * (hue / 60.0));
180✔
216
        b = min;
180✔
217
        break;
180✔
218
    case 1:  /* 60 <= hue < 120 */
180✔
219
        r = min + (max - min) * ((120 - hue) / 60.0);
180✔
220
        g = max;
180✔
221
        b = min;
180✔
222
        break;
180✔
223
    case 2:  /* 120 <= hue < 180 */
180✔
224
        r = min;
180✔
225
        g = max;
180✔
226
        b = (min + (max - min) * ((hue - 120) / 60.0));
180✔
227
        break;
180✔
228
    case 3:  /* 180 <= hue < 240 */
180✔
229
        r = min;
180✔
230
        g = (min + (max - min) * ((240 - hue) / 60.0));
180✔
231
        b = max;
180✔
232
        break;
180✔
233
    case 4:  /* 240 <= hue < 300 */
252✔
234
        r = (min + (max - min) * ((hue - 240) / 60.0));
252✔
235
        g = min;
252✔
236
        b = max;
252✔
237
        break;
252✔
238
    case 5:  /* 300 <= hue < 360 */
180✔
239
        r = max;
180✔
240
        g = min;
180✔
241
        b = (min + (max - min) * ((360 - hue) / 60.0));
180✔
242
        break;
180✔
243
    default:
×
244
#if HAVE___BUILTIN_UNREACHABLE
245
        __builtin_unreachable();
×
246
#endif
247
        break;
1,152✔
248
    }
249

250
    return SIXEL_XRGB(r, g, b);
1,152✔
251
}
252

253

254
SIXELSTATUS
255
image_buffer_init(
207✔
256
    image_buffer_t        *image,
257
    int                    width,
258
    int                    height,
259
    int                    bgindex,
260
    int                    depth,
261
    sixel_allocator_t     *allocator)
262
{
263
    SIXELSTATUS status = SIXEL_FALSE;
207✔
264
    size_t size;
207✔
265
    size_t stride;
207✔
266
    int i;
207✔
267
    int n;
207✔
268
    int r;
207✔
269
    int g;
207✔
270
    int b;
207✔
271

272
    /* check parameters */
273
    if (width <= 0) {
207!
274
        sixel_helper_set_additional_message(
×
275
            "image_buffer_init: an invalid width parameter detected.");
276
        status = SIXEL_BAD_INPUT;
×
277
        goto end;
×
278
    }
279
    if (height <= 0) {
207!
280
        sixel_helper_set_additional_message(
×
281
            "image_buffer_init: an invalid width parameter detected.");
282
        status = SIXEL_BAD_INPUT;
×
283
        goto end;
×
284
    }
285
    if (width > SIXEL_WIDTH_LIMIT) {
207!
286
        sixel_helper_set_additional_message(
×
287
            "image_buffer_init: given width parameter is too huge.");
288
        status = SIXEL_BAD_INPUT;
×
289
        goto end;
×
290
    }
291
    if (height > SIXEL_HEIGHT_LIMIT) {
207!
292
        sixel_helper_set_additional_message(
×
293
            "image_buffer_init: given height parameter is too huge.");
294
        status = SIXEL_BAD_INPUT;
×
295
        goto end;
×
296
    }
297

298
    image->depth = depth;
207✔
299
    stride = (size_t)width * depth;
207✔
300
    size = stride * (size_t)height;
207✔
301
    image->width = width;
207✔
302
    image->height = height;
207✔
303
    image->pixels.p = (unsigned char *)sixel_allocator_malloc(allocator, size);
207✔
304
    if (depth == 4U) {
207✔
305
        image->ncolors = (-1);
12✔
306
    } else {
307
        image->ncolors = 2;
195✔
308
    }
309

310
    if (image->pixels.p == NULL) {
207!
311
        sixel_helper_set_additional_message(
×
312
            "sixel_deocde_raw: sixel_allocator_malloc() failed.");
313
        status = SIXEL_BAD_ALLOCATION;
×
314
        goto end;
×
315
    }
316
    if (depth == 4U) {
207✔
317
        memset(image->pixels.p, 0U, size);
12✔
318
    } else {
319
        if (depth == 1U) {
195!
320
            memset(image->pixels.p, bgindex, size);
195✔
321
        } else {  /* 2U */
322
            for (n = 0; n <= width * height; ++n) {
×
323
                image_buffer_store_pixel(image, n, bgindex);
×
324
            }
325
        }
326

327
        /* palette initialization */
328
        for (n = 0; n < 16; n++) {
3,315✔
329
            image->palette[n] = sixel_default_color_table[n];
3,120✔
330
        }
331

332
        /* colors 16-231 are a 6x6x6 color cube */
333
        for (r = 0; r < 6; r++) {
1,365✔
334
            for (g = 0; g < 6; g++) {
8,190✔
335
                for (b = 0; b < 6; b++) {
49,140✔
336
                    image->palette[n++] = SIXEL_RGB(r * 51, g * 51, b * 51);
42,120✔
337
                }
338
            }
339
        }
340

341
        /* colors 232-255 are a grayscale ramp, intentionally leaving out */
342
        for (i = 0; i < 24; i++) {
4,875✔
343
            image->palette[n++] = SIXEL_RGB(i * 11, i * 11, i * 11);
4,680✔
344
        }
345

346
#if HAVE_ASSERT
347
        assert(n == 256);
348
#endif  /* HAVE_ASSERT */
349

350
    for (n = 256; n < SIXEL_PALETTE_MAX_DECODER; n++) {
12,729,795✔
351
        image->palette[n] = SIXEL_RGB(255, 255, 255);
12,729,600✔
352
    }
353
    }
354
    status = SIXEL_OK;
355

356
end:
207✔
357
    return status;
207✔
358
}
359

360

361
SIXELSTATUS
362
image_buffer_resize(
498✔
363
    image_buffer_t        *image,
364
    int                    width,
365
    int                    height,
366
    int                    bgindex,
367
    sixel_allocator_t     *allocator)
368
{
369
    SIXELSTATUS status = SIXEL_FALSE;
498✔
370
    size_t size;
498✔
371
    unsigned char *alt_buffer;
498✔
372
    int n;
498✔
373
    int min_height;
498✔
374
    size_t stride;
498✔
375
    size_t old_stride;
498✔
376
    size_t copy_stride;
498✔
377
    size_t depth = image->depth;
498✔
378

379
    /* check parameters */
380
    if (width <= 0) {
498!
381
        sixel_helper_set_additional_message(
×
382
            "image_buffer_init: an invalid width parameter detected.");
383
        status = SIXEL_BAD_INPUT;
×
384
        goto end;
×
385
    }
386
    if (height <= 0) {
498!
387
        sixel_helper_set_additional_message(
×
388
            "image_buffer_init: an invalid width parameter detected.");
389
        status = SIXEL_BAD_INPUT;
×
390
        goto end;
×
391
    }
392
    if (height > SIXEL_HEIGHT_LIMIT) {
498!
393
        sixel_helper_set_additional_message(
×
394
            "image_buffer_init: given height parameter is too huge.");
395
        status = SIXEL_BAD_INPUT;
×
396
        goto end;
×
397
    }
398
    if (width > SIXEL_WIDTH_LIMIT) {
498!
399
        sixel_helper_set_additional_message(
×
400
            "image_buffer_init: given width parameter is too huge.");
401
        status = SIXEL_BAD_INPUT;
×
402
        goto end;
×
403
    }
404
    if (height > SIXEL_HEIGHT_LIMIT) {
498!
405
        sixel_helper_set_additional_message(
406
            "image_buffer_init: given height parameter is too huge.");
407
        status = SIXEL_BAD_INPUT;
408
        goto end;
409
    }
410

411
    stride = (size_t)width * depth;
498✔
412
    size = stride * (size_t)height;
498✔
413
    alt_buffer = (sixel_decoder_index_t *)sixel_allocator_malloc(allocator, size);
498✔
414
    if (alt_buffer == NULL || size == 0) {
498!
415
        /* free source image */
416
        sixel_allocator_free(allocator, image->pixels.p);
×
417
        image->pixels.p = NULL;
×
418
        sixel_helper_set_additional_message(
×
419
            "image_buffer_resize: sixel_allocator_malloc() failed.");
420
        status = SIXEL_BAD_ALLOCATION;
×
421
        goto end;
×
422
    }
423

424
    min_height = height > image->height ? image->height: height;
498✔
425
    old_stride = (size_t)image->width * depth;
498✔
426
    if (width > image->width) {
498✔
427
        copy_stride = old_stride;
428
        for (n = 0; n < min_height; ++n) {
867✔
429
            memcpy(alt_buffer + stride * (size_t)n,
600✔
430
                   image->pixels.in_bytes + old_stride * (size_t)n,
600!
431
                   copy_stride);
432
            if (stride > copy_stride) {
600!
433
                if (depth == 4U) {  /* rgba */
600✔
434
                    memset(alt_buffer + stride * (size_t)n + copy_stride,
174✔
435
                           0,
436
                           stride - copy_stride);
437
                } else {
438
                    memset(alt_buffer + stride * (size_t)n + copy_stride,
426✔
439
                           bgindex,
440
                           stride - copy_stride);
441
                }
442
            }
443
        }
444
    } else {
445
        copy_stride = stride;
446
        for (n = 0; n < min_height; ++n) {
3,282✔
447
            memcpy(alt_buffer + stride * (size_t)n,
3,051✔
448
                   image->pixels.in_bytes + old_stride * (size_t)n,
3,051✔
449
                   copy_stride);
450
        }
451
    }
452

453
    if (height > image->height) {
498✔
454
        if (depth == 4u) {  /* rgba */
312✔
455
            memset(alt_buffer + stride * (size_t)image->height,
12✔
456
                   0,
457
                   stride * (size_t)(height - image->height));
12✔
458
        } else {
459
            memset(alt_buffer + stride * (size_t)image->height,
300✔
460
                   bgindex,
461
                   stride * (size_t)(height - image->height));
300✔
462
        }
463
    }
464

465
    /* free source image */
466
    sixel_allocator_free(allocator, image->pixels.p);
498✔
467

468
    image->pixels.in_bytes = alt_buffer;
498✔
469
    image->width = width;
498✔
470
    image->height = height;
498✔
471

472
    status = SIXEL_OK;
498✔
473

474
end:
498✔
475
    return status;
498✔
476
}
477

478

479
static SIXELSTATUS
480
parser_context_init(parser_context_t *context)
207✔
481
{
482
    SIXELSTATUS status = SIXEL_FALSE;
207✔
483

484
    context->state = PS_GROUND;
207✔
485
    context->pos_x = 0;
207✔
486
    context->pos_y = 0;
207✔
487
    context->max_x = 0;
207✔
488
    context->max_y = 0;
207✔
489
    context->attributed_pan = 2;
207✔
490
    context->attributed_pad = 1;
207✔
491
    context->attributed_ph = 0;
207✔
492
    context->attributed_pv = 0;
207✔
493
    context->repeat_count = 1;
207✔
494
    context->color_index = 15;
207✔
495
    context->bgindex = (-1);
207✔
496
    context->nparams = 0;
207✔
497
    context->param = 0;
207✔
498

499
    status = SIXEL_OK;
207✔
500

501
    return status;
207✔
502
}
503

504

505
static SIXELSTATUS
506
safe_addition_for_params(parser_context_t *context, unsigned char *p)
7,906,146✔
507
{
508
    SIXELSTATUS status = SIXEL_FALSE;
7,906,146✔
509
    int x;
7,906,146✔
510

511
    x = *p - '0'; /* 0 <= x <= 9 */
7,906,146✔
512
    if ((context->param > INT_MAX / 10) || (x > INT_MAX - context->param * 10)) {
7,906,146!
513
        status = SIXEL_BAD_INTEGER_OVERFLOW;
×
514
        sixel_helper_set_additional_message(
×
515
            "safe_addition_for_params: ingeger overflow detected.");
516
        goto end;
×
517
    }
518
    context->param = context->param * 10 + x;
7,906,146✔
519
    status = SIXEL_OK;
7,906,146✔
520

521
end:
7,906,146✔
522
    return status;
7,906,146✔
523
}
524

525

526
/* convert sixel data into indexed pixel bytes and palette data */
527
SIXELAPI SIXELSTATUS
528
sixel_decode_raw_impl(
207✔
529
    unsigned char     *p,         /* sixel bytes */
530
    int                len,       /* size of sixel bytes */
531
    image_buffer_t    *image,
532
    parser_context_t  *context,
533
    sixel_allocator_t *allocator, /* allocator object */
534
    sixel_logger_t    *logger,
535
    int logger_prepared)
536
{
537
    SIXELSTATUS status = SIXEL_FALSE;
207✔
538
    int n;
207✔
539
    int i;
207✔
540
    int y;
207✔
541
    int bits;
207✔
542
    int sixel_vertical_mask;
207✔
543
    int sx;
207✔
544
    int sy;
207✔
545
    int c;
207✔
546
    size_t pos;
207✔
547
    unsigned char *p0 = p;
207✔
548
#if SIXEL_ENABLE_THREADS
549
    int parallel_started = 0;
138✔
550
    int raster_ready = 0;
138✔
551
    int palette_ready = 0;
138✔
552
    unsigned char *parallel_anchor = p;
138✔
553
#else
554
    (void) logger;
69✔
555
    (void) logger_prepared;
69✔
556
#endif  /* SIXEL_ENABLE_THREADS */
557

558
    while (p < p0 + len) {
38,026,722✔
559
        switch (context->state) {
38,026,512!
560
        case PS_GROUND:
207✔
561
            switch (*p) {
207!
562
            case 0x1b:
204✔
563
                context->state = PS_ESC;
204✔
564
                p++;
204✔
565
                break;
204✔
566
            case 0x90:
3✔
567
                context->state = PS_DCS;
3✔
568
                p++;
3✔
569
                break;
3✔
570
            case 0x9c:
×
571
                p++;
×
572
                goto finalize;
×
573
            default:
×
574
                p++;
×
575
                break;
×
576
            }
577
            break;
578

579
        case PS_ESC:
408✔
580
            switch (*p) {
408!
581
            case '\\':
204✔
582
            case 0x9c:
583
                p++;
204✔
584
                goto finalize;
204✔
585
            case 'P':
204✔
586
                context->param = -1;
204✔
587
                context->state = PS_DCS;
204✔
588
                p++;
204✔
589
                break;
204✔
590
            default:
×
591
                p++;
×
592
                break;
×
593
            }
594
            break;
595

596
        case PS_DCS:
624✔
597
            switch (*p) {
624!
598
            case 0x1b:
×
599
                context->state = PS_ESC;
×
600
                p++;
×
601
                break;
×
602
            case '0':
207✔
603
            case '1':
604
            case '2':
605
            case '3':
606
            case '4':
607
            case '5':
608
            case '6':
609
            case '7':
610
            case '8':
611
            case '9':
612
                if (context->param < 0) {
207✔
613
                    context->param = 0;
99✔
614
                }
615
                status = safe_addition_for_params(context, p);
207✔
616
                if (SIXEL_FAILED(status)) {
207!
617
                    goto end;
×
618
                }
619
                p++;
207✔
620
                break;
207✔
621
            case ';':
198✔
622
                if (context->param < 0) {
198!
623
                    context->param = 0;
×
624
                }
625
                if (context->nparams < DECSIXEL_PARAMS_MAX) {
198!
626
                    context->params[context->nparams++] = context->param;
198✔
627
                }
628
                context->param = 0;
198✔
629
                p++;
198✔
630
                break;
198✔
631
            case 'q':
207✔
632
                if (context->param >= 0 && context->nparams < DECSIXEL_PARAMS_MAX) {
207!
633
                    context->params[context->nparams++] = context->param;
102✔
634
                }
635
                if (context->nparams > 0) {
207✔
636
                    /* Pn1 */
637
                    switch (context->params[0]) {
102✔
638
                    case 0:
21✔
639
                    case 1:
640
                        context->attributed_pad = 2;
21✔
641
                        break;
21✔
642
                    case 2:
9✔
643
                        context->attributed_pad = 5;
9✔
644
                        break;
9✔
645
                    case 3:
18✔
646
                    case 4:
647
                        context->attributed_pad = 4;
18✔
648
                        break;
18✔
649
                    case 5:
18✔
650
                    case 6:
651
                        context->attributed_pad = 3;
18✔
652
                        break;
18✔
653
                    case 7:
18✔
654
                    case 8:
655
                        context->attributed_pad = 2;
18✔
656
                        break;
18✔
657
                    case 9:
9✔
658
                        context->attributed_pad = 1;
9✔
659
                        break;
9✔
660
                    default:
9✔
661
                        context->attributed_pad = 2;
9✔
662
                        break;
9✔
663
                    }
664
                }
665

666
                if (context->nparams > 2) {
207✔
667
                    /* Pn3 */
668
                    if (context->params[2] == 0) {
99✔
669
                        context->params[2] = 10;
33✔
670
                    }
671
                    context->attributed_pan = context->attributed_pan * context->params[2] / 10;
99✔
672
                    context->attributed_pad = context->attributed_pad * context->params[2] / 10;
99✔
673
                    if (context->attributed_pan <= 0) {
99✔
674
                        context->attributed_pan = 1;
66✔
675
                    }
676
                    if (context->attributed_pad <= 0) {
99✔
677
                        context->attributed_pad = 1;
63✔
678
                    }
679
                }
680
                context->nparams = 0;
207✔
681
                context->state = PS_DECSIXEL;
207✔
682
                p++;
207✔
683
                break;
207✔
684
            default:
12✔
685
                p++;
12✔
686
                break;
12✔
687
            }
688
            break;
689

690
        case PS_DECSIXEL:
26,221,230✔
691
            switch (*p) {
26,221,230✔
692
            case '\x1b':
204✔
693
                context->state = PS_ESC;
204✔
694
                p++;
204✔
695
                break;
204✔
696
            case '"':
204✔
697
                context->param = 0;
204✔
698
                context->nparams = 0;
204✔
699
                context->state = PS_DECGRA;
204✔
700
                p++;
204✔
701
                break;
204✔
702
            case '!':
1,506,300✔
703
                context->param = 0;
1,506,300✔
704
                context->nparams = 0;
1,506,300✔
705
                context->state = PS_DECGRI;
1,506,300✔
706
                p++;
1,506,300✔
707
                break;
1,506,300✔
708
            case '#':
2,333,454✔
709
                context->param = 0;
2,333,454✔
710
                context->nparams = 0;
2,333,454✔
711
#if SIXEL_ENABLE_THREADS
712
                if (!palette_ready) {
1,555,636✔
713
                    palette_ready = 1;
1,555,636✔
714
                }
715
#endif  /* SIXEL_ENABLE_THREADS */
716
                context->state = PS_DECGCI;
2,333,454✔
717
                p++;
2,333,454✔
718
                break;
2,333,454✔
719
            case '$':
61,746✔
720
                /* DECGCR Graphics Carriage Return */
721
                context->pos_x = 0;
61,746✔
722
#if SIXEL_ENABLE_THREADS
723
                if (!palette_ready) {
41,164!
724
                    palette_ready = 1;
41,164✔
725
                }
726
                if (!parallel_started && raster_ready && palette_ready) {
41,164!
727
                    status = sixel_decoder_parallel_request_start(
×
728
                        image->depth == 4U ? 1: 0,
729
                        p0,
730
                        len,
731
                        parallel_anchor,
732
                        image,
733
                        image->palette,
734
                        logger_prepared ? logger : NULL);
×
735
                    parallel_started = 1;
736
                    if (status == SIXEL_FALSE) {
×
737
                        /* Parallel decode aborted; continue serially. */
738
                    } else {
739
                        goto end;
740
                    }
741
                }
742
#endif  /* SIXEL_ENABLE_THREADS */
743
                p++;
61,746✔
744
                break;
61,746✔
745
            case '-':
5,496✔
746
                /* DECGNL Graphics Next Line */
747
                context->pos_x = 0;
5,496✔
748
                context->pos_y += 6;
5,496✔
749
#if SIXEL_ENABLE_THREADS
750
                if (!palette_ready) {
3,664!
751
                    palette_ready = 1;
3,664✔
752
                }
753
                if (!parallel_started && raster_ready && palette_ready) {
3,664!
754
                    status = sixel_decoder_parallel_request_start(
×
755
                        image->depth == 4U ? 1: 0,
756
                        p0,
757
                        len,
758
                        parallel_anchor,
759
                        image,
760
                        image->palette,
761
                        logger_prepared ? logger : NULL);
×
762
                    parallel_started = 1;
763
                    if (status == SIXEL_FALSE) {
×
764
                        /* Parallel decode aborted; continue serially. */
765
                    } else {
766
                        goto end;
767
                    }
768
                }
769
#endif  /* SIXEL_ENABLE_THREADS */
770
                p++;
5,496✔
771
                break;
5,496✔
772
            default:
22,313,826✔
773
                if (*p >= '?' && *p <= '~') {  /* sixel characters */
22,313,826!
774
#if SIXEL_ENABLE_THREADS
775
                    if (!palette_ready) {
14,849,130✔
776
                        palette_ready = 1;
14,849,130✔
777
                    }
778
                    if (!parallel_started && raster_ready && palette_ready) {
14,849,130!
779
                        status = sixel_decoder_parallel_request_start(
136!
780
                            image->depth == 4U ? 1: 0,
136✔
781
                            p0,
782
                            len,
783
                            parallel_anchor,
784
                            image,
785
                            image->palette,
136✔
786
                            logger_prepared ? logger : NULL);
1!
787
                        parallel_started = 1;
136✔
788
                        if (status == SIXEL_FALSE) {
136!
789
                            /* Parallel decode aborted; continue serially. */
790
                        } else {
791
                            goto end;
792
                        }
793
                    }
794
#endif  /* SIXEL_ENABLE_THREADS */
795

796
                    sx = image->width;
22,273,695✔
797
                    while (sx < context->pos_x + context->repeat_count) {
22,273,803✔
798
                        sx *= 2;
108✔
799
                    }
800

801
                    sy = image->height;
22,273,695✔
802
                    while (sy < context->pos_y + 6) {
22,273,854✔
803
                        sy *= 2;
159✔
804
                    }
805

806
                    if (sx > image->width || sy > image->height) {
22,273,695✔
807
                        status = image_buffer_resize(image, sx, sy, context->bgindex, allocator);
186✔
808
                        if (SIXEL_FAILED(status)) {
186!
809
                            goto end;
×
810
                        }
811
                    }
812

813
                    if (image->depth != 4U &&
22,273,695✔
814
                            context->color_index > image->ncolors) {
21,723,657✔
815
                        image->ncolors = context->color_index;
18✔
816
                    }
817

818
                    if (context->pos_x < 0 || context->pos_y < 0) {
22,273,695!
819
                        status = SIXEL_BAD_INPUT;
×
820
                        goto end;
×
821
                    }
822
                    bits = *p - '?';
22,273,695✔
823

824
                    if (bits == 0) {
22,273,695✔
825
                        context->pos_x += context->repeat_count;
6,548,022✔
826
                    } else {
827
                        sixel_vertical_mask = 0x01;
15,725,673✔
828
                        if (context->repeat_count <= 1) {
15,725,673✔
829
                            for (i = 0; i < 6; i++) {
109,602,675✔
830
                                if ((bits & sixel_vertical_mask) != 0) {
93,945,150✔
831
                                    pos =
28,025,160✔
832
                                        (size_t)image->width *
28,025,160✔
833
                                        (size_t)(context->pos_y + i) +
28,025,160✔
834
                                        (size_t)context->pos_x;
28,025,160✔
835
                                    image_buffer_store_pixel(
28,025,160✔
836
                                        image,
837
                                        pos,
838
                                        context->color_index);
839
                                    if (context->max_x < context->pos_x) {
28,025,160✔
840
                                        context->max_x = context->pos_x;
28,092✔
841
                                    }
842
                                    if (context->max_y < (context->pos_y + i)) {
28,025,160✔
843
                                        context->max_y = context->pos_y + i;
20,322✔
844
                                    }
845
                                }
846
                                sixel_vertical_mask <<= 1;
93,945,150✔
847
                            }
848
                            context->pos_x += 1;
15,657,525✔
849
                        } else {
850
                            /* context->repeat_count > 1 */
851
                            for (i = 0; i < 6; i++) {
372,075✔
852
                                if ((bits & sixel_vertical_mask) != 0) {
303,927✔
853
                                    c = sixel_vertical_mask << 1;
69,381✔
854
                                    for (n = 1; (i + n) < 6; n++) {
174,342✔
855
                                        if ((bits & c) == 0) {
141,498✔
856
                                            break;
857
                                        }
858
                                        c <<= 1;
104,961✔
859
                                    }
860
                                    for (y = context->pos_y + i; y < context->pos_y + i + n; ++y) {
243,723✔
861
                                        image_buffer_fill_span(
174,342✔
862
                                            image,
863
                                            y,
864
                                            context->pos_x,
865
                                            context->repeat_count,
866
                                            context->color_index);
867
                                    }
868
                                    if (context->max_x < (context->pos_x + context->repeat_count - 1)) {
69,381✔
869
                                        context->max_x = context->pos_x + context->repeat_count - 1;
2,436✔
870
                                    }
871
                                    if (context->max_y < (context->pos_y + i + n - 1)) {
69,381✔
872
                                        context->max_y = context->pos_y + i + n - 1;
603✔
873
                                    }
874
                                    i += (n - 1);
69,381✔
875
                                    sixel_vertical_mask <<= (n - 1);
69,381✔
876
                                }
877
                                sixel_vertical_mask <<= 1;
303,927✔
878
                            }
879
                            context->pos_x += context->repeat_count;
68,148✔
880
                        }
881
                    }
882
                    context->repeat_count = 1;
22,273,695✔
883
                }
884
                p++;
22,313,826✔
885
                break;
22,313,826✔
886
            }
887
            break;
888

889
        case PS_DECGRA:
2,130✔
890
            /* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */
891
            switch (*p) {
2,130!
892
            case '\x1b':
×
893
                context->state = PS_ESC;
×
894
                p++;
×
895
                break;
×
896
            case '0':
1,314✔
897
            case '1':
898
            case '2':
899
            case '3':
900
            case '4':
901
            case '5':
902
            case '6':
903
            case '7':
904
            case '8':
905
            case '9':
906
                status = safe_addition_for_params(context, p);
1,314✔
907
                if (SIXEL_FAILED(status)) {
1,314!
908
                    goto end;
×
909
                }
910
                p++;
1,314✔
911
                break;
1,314✔
912
            case ';':
612✔
913
                if (context->nparams < DECSIXEL_PARAMS_MAX) {
612!
914
                    context->params[context->nparams++] = context->param;
612✔
915
                }
916
                context->param = 0;
612✔
917
                p++;
612✔
918
                break;
612✔
919
            default:
204✔
920
                if (context->nparams < DECSIXEL_PARAMS_MAX) {
204!
921
                    context->params[context->nparams++] = context->param;
204✔
922
                }
923
                if (context->nparams > 0) {
204!
924
                    context->attributed_pad = context->params[0];
204✔
925
                }
926
                if (context->nparams > 1) {
204!
927
                    context->attributed_pan = context->params[1];
204✔
928
                }
929
                if (context->nparams > 2 && context->params[2] > 0) {
204!
930
                    context->attributed_ph = context->params[2];
204✔
931
                }
932
                if (context->nparams > 3 && context->params[3] > 0) {
204!
933
                    context->attributed_pv = context->params[3];
204✔
934
                }
935

936
                if (context->attributed_pan <= 0) {
204!
937
                    context->attributed_pan = 1;
×
938
                }
939
                if (context->attributed_pad <= 0) {
204!
940
                    context->attributed_pad = 1;
×
941
                }
942

943
                if (image->width < context->attributed_ph ||
204!
944
                        image->height < context->attributed_pv) {
15!
945
                    sx = context->attributed_ph;
189✔
946
                    if (image->width > context->attributed_ph) {
189!
947
                        sx = image->width;
948
                    }
949

950
                    sy = context->attributed_pv;
189✔
951
                    if (image->height > context->attributed_pv) {
189!
952
                        sy = image->height;
953
                    }
954

955
                    status = image_buffer_resize(image, sx, sy, context->bgindex, allocator);
189✔
956
                    if (SIXEL_FAILED(status)) {
189!
957
                        goto end;
×
958
                    }
959
                }
960
#if SIXEL_ENABLE_THREADS
961
                if (!raster_ready && context->attributed_ph > 0 &&
136!
962
                        context->attributed_pv > 0) {
136!
963
                    raster_ready = 1;
136✔
964
                }
965
                parallel_anchor = p;
136✔
966
#endif  /* SIXEL_ENABLE_THREADS */
967
                context->state = PS_DECSIXEL;
204✔
968
                context->param = 0;
204✔
969
                context->nparams = 0;
204✔
970
            }
971
            break;
972

973
        case PS_DECGRI:
3,538,680✔
974
            /* DECGRI Graphics Repeat Introducer ! Pn Ch */
975
            switch (*p) {
3,538,680!
976
            case '\x1b':
×
977
                context->state = PS_ESC;
×
978
                p++;
×
979
                break;
×
980
            case '0':
2,032,380✔
981
            case '1':
982
            case '2':
983
            case '3':
984
            case '4':
985
            case '5':
986
            case '6':
987
            case '7':
988
            case '8':
989
            case '9':
990
                status = safe_addition_for_params(context, p);
2,032,380✔
991
                if (SIXEL_FAILED(status)) {
2,032,380!
992
                    goto end;
×
993
                }
994
                p++;
2,032,380✔
995
                break;
2,032,380✔
996
            default:
1,506,300✔
997
                context->repeat_count = context->param;
1,506,300✔
998
                if (context->repeat_count == 0) {
1,506,300!
999
                    context->repeat_count = 1;
×
1000
                }
1001
                if (context->repeat_count > 0xffff) {  /* check too huge number */
1,506,300!
1002
                    status = SIXEL_BAD_INPUT;
×
1003
                    sixel_helper_set_additional_message(
×
1004
                        "sixel_decode_raw_impl: detected too huge repeat parameter.");
1005
                    goto end;
×
1006
                }
1007
                context->state = PS_DECSIXEL;
1,506,300✔
1008
                context->param = 0;
1,506,300✔
1009
                context->nparams = 0;
1,506,300✔
1010
                break;
1,506,300✔
1011
            }
1012
            break;
1013

1014
        case PS_DECGCI:
8,263,233✔
1015
            /* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */
1016
            switch (*p) {
8,263,233!
1017
            case '\x1b':
×
1018
                context->state = PS_ESC;
×
1019
                p++;
×
1020
                break;
×
1021
            case '0':
5,872,245✔
1022
            case '1':
1023
            case '2':
1024
            case '3':
1025
            case '4':
1026
            case '5':
1027
            case '6':
1028
            case '7':
1029
            case '8':
1030
            case '9':
1031
                status = safe_addition_for_params(context, p);
5,872,245✔
1032
                if (SIXEL_FAILED(status)) {
5,872,245!
1033
                    goto end;
×
1034
                }
1035
                p++;
5,872,245✔
1036
                break;
5,872,245✔
1037
            case ';':
57,534✔
1038
                if (context->nparams < DECSIXEL_PARAMS_MAX) {
57,534!
1039
                    context->params[context->nparams++] = context->param;
57,534✔
1040
                }
1041
                context->param = 0;
57,534✔
1042
                p++;
57,534✔
1043
                break;
57,534✔
1044
            default:
2,333,454✔
1045
                context->state = PS_DECSIXEL;
2,333,454✔
1046
                if (context->nparams < DECSIXEL_PARAMS_MAX) {
2,333,454!
1047
                    context->params[context->nparams++] = context->param;
2,333,454✔
1048
                }
1049
                context->param = 0;
2,333,454✔
1050

1051
                if (context->nparams > 0) {
2,333,454!
1052
                    context->color_index = context->params[0];
2,333,454✔
1053
                    if (context->color_index < 0) {
2,333,454!
1054
                        context->color_index = 0;
×
1055
                    } else if (context->color_index >= SIXEL_PALETTE_MAX_DECODER) {
2,333,454!
1056
                        context->color_index = SIXEL_PALETTE_MAX_DECODER - 1;
×
1057
                    }
1058
                }
1059

1060
                if (context->color_index + 1 > image->ncolors) {
2,333,454✔
1061
                    image->ncolors = context->color_index + 1;
14,025✔
1062
                    if (image->ncolors > SIXEL_PALETTE_MAX_DECODER) {
14,025!
1063
                        image->ncolors = SIXEL_PALETTE_MAX_DECODER;
×
1064
                    }
1065
                }
1066

1067
                if (context->nparams > 4) {
2,333,454✔
1068
                    if (context->params[1] == 1) {
14,379✔
1069
                        /* HLS */
1070
                        if (context->params[2] > 360) {
1,152!
1071
                            context->params[2] = 360;
×
1072
                        }
1073
                        if (context->params[3] > 100) {
1,152!
1074
                            context->params[3] = 100;
×
1075
                        }
1076
                        if (context->params[4] > 100) {
1,152!
1077
                            context->params[4] = 100;
×
1078
                        }
1079
                        image->palette[context->color_index]
1,152✔
1080
                            = hls_to_rgb(context->params[2], context->params[3], context->params[4]);
1,152✔
1081
                    } else if (context->params[1] == 2) {
13,227!
1082
                        /* RGB */
1083
                        if (context->params[2] > 100) {
13,227!
1084
                            context->params[2] = 100;
×
1085
                        }
1086
                        if (context->params[3] > 100) {
13,227!
1087
                            context->params[3] = 100;
×
1088
                        }
1089
                        if (context->params[4] > 100) {
13,227!
1090
                            context->params[4] = 100;
×
1091
                        }
1092
                        image->palette[context->color_index]
13,227✔
1093
                            = SIXEL_XRGB(context->params[2], context->params[3], context->params[4]);
13,227✔
1094
                    }
1095
#if SIXEL_ENABLE_THREADS
1096
                    parallel_anchor = p;
25,351,010✔
1097
#endif  /* SIXEL_ENABLE_THREADS */
1098
                }
1099
                break;
1100
            }
1101
            break;
1102
        default:
1103
            break;
1104
        }
1105
    }
1106

1107
finalize:
3✔
1108
    if (++context->max_x < context->attributed_ph) {
207✔
1109
        context->max_x = context->attributed_ph;
3✔
1110
    }
1111

1112
    if (++context->max_y < context->attributed_pv) {
207✔
1113
        context->max_y = context->attributed_pv;
3✔
1114
    }
1115

1116
    if (image->width > context->max_x || image->height > context->max_y) {
207✔
1117
        status = image_buffer_resize(image, context->max_x, context->max_y, context->bgindex, allocator);
123✔
1118
        if (SIXEL_FAILED(status)) {
123!
1119
            goto end;
×
1120
        }
1121
    }
1122

1123
    status = SIXEL_OK;
1124

1125
end:
207✔
1126
    return status;
207✔
1127
}
1128

1129

1130
static SIXELSTATUS
1131
sixel_decode_image(
207✔
1132
    unsigned char     *p,
1133
    int                len,
1134
    int                initial_width,
1135
    int                initial_height,
1136
    int                depth,
1137
    image_buffer_t    *image,
1138
    parser_context_t  *context,
1139
    sixel_allocator_t *allocator)
1140
{
1141
    SIXELSTATUS status = SIXEL_FALSE;
207✔
1142
    sixel_logger_t logger;
207✔
1143
    int logger_prepared;
207✔
1144

1145
    image->pixels.p = NULL;
207✔
1146

1147
    sixel_logger_init(&logger);
207✔
1148
    logger_prepared = 0;
207✔
1149
    (void)sixel_logger_prepare_env(&logger);
207✔
1150
    logger_prepared = logger.active;
207✔
1151
    if (logger_prepared) {
207!
1152
        /*
1153
         * File I/O window for timeline visualization. The buffer is already
1154
         * populated, but logging the bounds keeps decode timing aligned with
1155
         * encoder logs.
1156
         */
1157
        sixel_logger_logf(&logger,
×
1158
                          "decoder",
1159
                          "io",
1160
                          "start",
1161
                          0,
1162
                          0,
1163
                          0,
1164
                          len,
1165
                          0,
1166
                          len,
1167
                          "reading sixel payload");
1168
    }
1169

1170
    status = parser_context_init(context);
207✔
1171
    if (SIXEL_FAILED(status)) {
207!
1172
        goto end;
1173
    }
1174

1175
    /*
1176
     * The serial parser always runs first. When palette and raster
1177
     * attributes become available, the parser may request a parallel worker
1178
     * via sixel_decoder_parallel_request_start(). This guarantees bounds
1179
     * checks and logging are consistent before any background decode starts.
1180
     */
1181

1182
    if (logger_prepared) {
207!
1183
        /* Mark when the serial parser begins scanning tokens. */
1184
        sixel_logger_logf(&logger,
×
1185
                          "decoder",
1186
                          "controller",
1187
                          "start",
1188
                          0,
1189
                          0,
1190
                          0,
1191
                          len,
1192
                          0,
1193
                          len,
1194
                          "serial parser begin depth=%u",
1195
                          depth);
1196
    }
1197

1198
    status = image_buffer_init(image,
207✔
1199
                               initial_width,
1200
                               initial_height,
1201
                               context->bgindex,
1202
                               depth,
1203
                               allocator);
1204
    if (SIXEL_FAILED(status)) {
207!
1205
        goto end;
×
1206
    }
1207

1208
    status = sixel_decode_raw_impl(p,
207✔
1209
                                   len,
1210
                                   image,
1211
                                   context,
1212
                                   allocator,
1213
                                   &logger,
1214
                                   logger_prepared);
1215
    if (SIXEL_FAILED(status)) {
207!
1216
        sixel_allocator_free(allocator, image->pixels.p);
×
1217
        image->pixels.p = NULL;
×
1218
        goto end;
×
1219
    }
1220

1221
    status = SIXEL_OK;
1222

1223
end:
207✔
1224
    if (logger_prepared) {
207!
1225
        sixel_logger_logf(&logger,
×
1226
                          "decoder",
1227
                          "parser",
1228
                          "finish",
1229
                          0,
1230
                          0,
1231
                          0,
1232
                          len,
1233
                          0,
1234
                          len,
1235
                          "parser status=%d",
1236
                          status);
1237
        sixel_logger_logf(&logger,
×
1238
                          "decoder",
1239
                          "io",
1240
                          "finish",
1241
                          0,
1242
                          0,
1243
                          0,
1244
                          len,
1245
                          0,
1246
                          len,
1247
                          "input processed status=%d",
1248
                          status);
1249
        sixel_logger_close(&logger);
×
1250
    }
1251
    return status;
207✔
1252
}
1253

1254

1255
/* convert sixel data into indexed pixel bytes and palette data */
1256
SIXELAPI SIXELSTATUS
1257
sixel_decode_raw(
195✔
1258
    unsigned char       /* in */  *p,           /* sixel bytes */
1259
    int                 /* in */  len,          /* size of sixel bytes */
1260
    unsigned char       /* out */ **pixels,     /* decoded pixels */
1261
    int                 /* out */ *pwidth,      /* image width */
1262
    int                 /* out */ *pheight,     /* image height */
1263
    unsigned char       /* out */ **palette,    /* RGB palette */
1264
    int                 /* out */ *ncolors,     /* palette size (<= 256) */
1265
    sixel_allocator_t   /* in */  *allocator)   /* allocator object or null */
1266
{
1267
    SIXELSTATUS status = SIXEL_FALSE;
195✔
1268
    parser_context_t context;
195✔
1269
    image_buffer_t image;
195✔
1270
    int n;
195✔
1271
    int alloc_size;
195✔
1272

1273
    image.pixels.p = NULL;
195✔
1274

1275
    if (allocator) {
195!
1276
        sixel_allocator_ref(allocator);
195✔
1277
    } else {
1278
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
1279
        if (SIXEL_FAILED(status)) {
×
1280
            allocator = NULL;
×
1281
            goto error;
×
1282
        }
1283
    }
1284

1285
    status = sixel_decode_image(p,
195✔
1286
                                len,
1287
                                1,  /* initial_width */
1288
                                1,  /* initial_height */
1289
                                1,  /* depth */
1290
                                &image,
1291
                                &context,
1292
                                allocator);
1293
    if (SIXEL_FAILED(status)) {
195!
1294
        goto error;
×
1295
    }
1296

1297
    *ncolors = alloc_size = image.ncolors;
195✔
1298
    if (alloc_size < SIXEL_PALETTE_MAX_DECODER) {
195!
1299
        /* memory access range should be 0 <= 255 */
1300
        alloc_size = SIXEL_PALETTE_MAX_DECODER;
1301
    }
1302
    *palette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(alloc_size * 3));
195✔
1303
    if (palette == NULL) {
195!
1304
        sixel_allocator_free(allocator, image.pixels.p);
1305
        sixel_helper_set_additional_message(
1306
            "sixel_deocde_raw: sixel_allocator_malloc() failed.");
1307
        status = SIXEL_BAD_ALLOCATION;
1308
        goto error;
1309
    }
1310
    for (n = 0; n < *ncolors; ++n) {
13,767✔
1311
        (*palette)[n * 3 + 0] = image.palette[n] >> 16 & 0xff;
13,572✔
1312
        (*palette)[n * 3 + 1] = image.palette[n] >> 8 & 0xff;
13,572✔
1313
        (*palette)[n * 3 + 2] = image.palette[n] & 0xff;
13,572✔
1314
    }
1315

1316
    *pwidth = image.width;
195✔
1317
    *pheight = image.height;
195✔
1318
    *pixels = image.pixels.p;
195✔
1319

1320
    status = SIXEL_OK;
195✔
1321
    goto end;
195✔
1322

1323
error:
×
1324
    if (image.pixels.p != NULL) {
×
1325
        if (allocator != NULL) {
×
1326
            sixel_allocator_free(allocator, image.pixels.p);
×
1327
        } else {
1328
            free(image.pixels.p);
×
1329
        }
1330
        image.pixels.p = NULL;
×
1331
    }
1332

1333
end:
×
1334
    sixel_allocator_unref(allocator);
195✔
1335
    return status;
195✔
1336
}
1337

1338

1339
/* convert sixel data into wide-indexed(16bit) pixels and palette data */
1340
SIXELAPI SIXELSTATUS
1341
sixel_decode_wide(
×
1342
    unsigned char       /* in */  *p,           /* sixel bytes */
1343
    int                 /* in */  len,          /* size of sixel bytes */
1344
    unsigned short      /* out */ **pixels,     /* decoded wide indexes */
1345
    int                 /* out */ *pwidth,      /* image width */
1346
    int                 /* out */ *pheight,     /* image height */
1347
    unsigned char       /* out */ **palette,    /* RGB palette */
1348
    int                 /* out */ *ncolors,     /* palette size (<= 256) */
1349
    sixel_allocator_t   /* in */  *allocator)   /* allocator object or null */
1350
{
1351
    SIXELSTATUS status = SIXEL_FALSE;
×
1352
    parser_context_t context;
×
1353
    image_buffer_t image;
×
1354
    int n;
×
1355
    int alloc_size;
×
1356

1357
    image.pixels.p = NULL;
×
1358

1359
    if (allocator) {
×
1360
        sixel_allocator_ref(allocator);
×
1361
    } else {
1362
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
1363
        if (SIXEL_FAILED(status)) {
×
1364
            allocator = NULL;
×
1365
            goto error;
×
1366
        }
1367
    }
1368

1369
    status = sixel_decode_image(p,
×
1370
                                len,
1371
                                1,  /* initial width */
1372
                                1,  /* initial height */
1373
                                2,  /* depth */
1374
                                &image,
1375
                                &context,
1376
                                allocator);
1377
    if (SIXEL_FAILED(status)) {
×
1378
        goto error;
×
1379
    }
1380

1381
    *ncolors = alloc_size = image.ncolors;
×
1382
    if (alloc_size < SIXEL_PALETTE_MAX_DECODER) {
×
1383
        /* memory access range should be 0 <= 255 */
1384
        alloc_size = SIXEL_PALETTE_MAX_DECODER;
1385
    }
1386
    *palette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(alloc_size * 3));
×
1387
    if (palette == NULL) {
×
1388
        sixel_allocator_free(allocator, image.pixels.p);
1389
        sixel_helper_set_additional_message(
1390
            "sixel_deocde_raw: sixel_allocator_malloc() failed.");
1391
        status = SIXEL_BAD_ALLOCATION;
1392
        goto error;
1393
    }
1394
    for (n = 0; n < *ncolors; ++n) {
×
1395
        (*palette)[n * 3 + 0] = image.palette[n] >> 16 & 0xff;
×
1396
        (*palette)[n * 3 + 1] = image.palette[n] >> 8 & 0xff;
×
1397
        (*palette)[n * 3 + 2] = image.palette[n] & 0xff;
×
1398
    }
1399

1400
    *pwidth = image.width;
×
1401
    *pheight = image.height;
×
1402
    *pixels = image.pixels.in_shorts;
×
1403

1404
    status = SIXEL_OK;
×
1405
    goto end;
×
1406

1407
error:
×
1408
    if (image.pixels.p != NULL) {
×
1409
        if (allocator != NULL) {
×
1410
            sixel_allocator_free(allocator, image.pixels.p);
×
1411
        } else {
1412
            free(image.pixels.p);
×
1413
        }
1414
        image.pixels.p = NULL;
×
1415
    }
1416

1417
end:
×
1418
    sixel_allocator_unref(allocator);
×
1419
    return status;
×
1420
}
1421

1422

1423
SIXELAPI SIXELSTATUS
1424
sixel_decode_direct(
12✔
1425
    unsigned char       *p,
1426
    int                  len,
1427
    unsigned char      **pixels,
1428
    int                 *pwidth,
1429
    int                 *pheight,
1430
    sixel_allocator_t   *allocator)
1431
{
1432
    SIXELSTATUS status = SIXEL_FALSE;
12✔
1433
    parser_context_t context;
12✔
1434
    image_buffer_t image;
12✔
1435

1436
    image.pixels.p = NULL;
12✔
1437

1438
    if (allocator) {
12!
1439
        sixel_allocator_ref(allocator);
12✔
1440
    } else {
1441
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
1442
        if (SIXEL_FAILED(status)) {
×
1443
            allocator = NULL;
×
1444
            goto error;
×
1445
        }
1446
    }
1447

1448
    status = sixel_decode_image(p,
12✔
1449
                                len,
1450
                                1,
1451
                                1,
1452
                                4U,
1453
                                &image,
1454
                                &context,
1455
                                allocator);
1456
    if (SIXEL_FAILED(status)) {
12!
1457
        goto error;
×
1458
    }
1459

1460
    *pwidth = image.width;
12✔
1461
    *pheight = image.height;
12✔
1462
    *pixels = image.pixels.in_bytes;
12✔
1463

1464
    status = SIXEL_OK;
12✔
1465
    goto end;
12✔
1466

1467
error:
×
1468
    if (image.pixels.p != NULL) {
×
1469
        if (allocator != NULL) {
×
1470
            sixel_allocator_free(allocator, image.pixels.p);
×
1471
        } else {
1472
            free(image.pixels.p);
×
1473
        }
1474
        image.pixels.p = NULL;
×
1475
    }
1476

1477
end:
×
1478
    sixel_allocator_unref(allocator);
12✔
1479
    return status;
12✔
1480
}
1481

1482

1483
/* deprecated */
1484
SIXELAPI SIXELSTATUS
1485
sixel_decode(unsigned char              /* in */   *p,        /* sixel bytes */
×
1486
             int                        /* in */    len,      /* size of sixel bytes */
1487
             unsigned char              /* out */ **pixels,   /* decoded pixels */
1488
             int                        /* out */  *pwidth,   /* image width */
1489
             int                        /* out */  *pheight,  /* image height */
1490
             unsigned char              /* out */ **palette,  /* ARGB palette */
1491
             int                        /* out */  *ncolors,  /* palette size (<= 256) */
1492
    sixel_allocator_function   /* in */  fn_malloc)  /* malloc function */
1493
{
1494
    SIXELSTATUS status = SIXEL_FALSE;
×
1495
    sixel_allocator_t *allocator = NULL;
×
1496
    parser_context_t context;
×
1497
    image_buffer_t image;
×
1498
    int n;
×
1499

1500
    status = sixel_allocator_new(&allocator, fn_malloc, NULL, NULL, NULL);
×
1501
    if (SIXEL_FAILED(status)) {
×
1502
        allocator = NULL;
×
1503
        goto end;
×
1504
    }
1505

1506
    status = sixel_decode_image(p,
×
1507
                                len,
1508
                                2048,
1509
                                2048,
1510
                                0,
1511
                                &image,
1512
                                &context,
1513
                                allocator);
1514
    if (SIXEL_FAILED(status)) {
×
1515
        goto end;
×
1516
    }
1517

1518
    *ncolors = image.ncolors;
×
1519
    *palette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(*ncolors * 3));
×
1520
    if (palette == NULL) {
×
1521
        sixel_allocator_free(allocator, image.pixels.p);
1522
        sixel_helper_set_additional_message(
1523
            "sixel_deocde_raw: sixel_allocator_malloc() failed.");
1524
        status = SIXEL_BAD_ALLOCATION;
1525
        goto end;
1526
    }
1527
    for (n = 0; n < *ncolors; ++n) {
×
1528
        (*palette)[n * 3 + 0] = image.palette[n] >> 16 & 0xff;
×
1529
        (*palette)[n * 3 + 1] = image.palette[n] >> 8 & 0xff;
×
1530
        (*palette)[n * 3 + 2] = image.palette[n] & 0xff;
×
1531
    }
1532

1533
    *pwidth = image.width;
×
1534
    *pheight = image.height;
×
1535
    *pixels = image.pixels.p;
×
1536

1537
    status = SIXEL_OK;
×
1538

1539
end:
×
1540
    sixel_allocator_unref(allocator);
×
1541
    return status;
×
1542
}
1543

1544
/* emacs Local Variables:      */
1545
/* emacs mode: c               */
1546
/* emacs tab-width: 4          */
1547
/* emacs indent-tabs-mode: nil */
1548
/* emacs c-basic-offset: 4     */
1549
/* emacs End:                  */
1550
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1551
/* 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