• 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

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

25
#include "config.h"
26

27
/* STDC_HEADERS */
28
#include <stdio.h>
29
#include <stdlib.h>
30

31
#if HAVE_STRING_H
32
# include <string.h>
33
#endif  /* HAVE_STRING_H */
34
#if HAVE_SETJMP_H
35
# include <setjmp.h>
36
#endif  /* HAVE_SETJMP_H */
37
#if HAVE_ERRNO_H
38
# include <errno.h>
39
#endif  /* HAVE_ERRNO_H */
40
#if HAVE_LIBPNG
41
# include <png.h>
42
#else
43
# if !defined(HAVE_MEMCPY)
44
#  define memcpy(d, s, n) (bcopy ((s), (d), (n)))
45
# endif
46
# if !defined(HAVE_MEMMOVE)
47
#  define memmove(d, s, n) (bcopy ((s), (d), (n)))
48
# endif
49
/*
50
 * Silence warnings from the bundled stb_image_write header. The file is a
51
 * third-party dependency, so we prefer to guard the include site instead of
52
 * modifying upstream code.
53
 */
54
# if defined(__clang__)
55
#  pragma clang diagnostic push
56
#  pragma clang diagnostic ignored "-Wall"
57
#  pragma clang diagnostic ignored "-Wextra"
58
#  pragma clang diagnostic ignored "-Wpedantic"
59
# elif defined(__GNUC__)
60
#  pragma GCC diagnostic push
61
#  pragma GCC diagnostic ignored "-Wall"
62
#  pragma GCC diagnostic ignored "-Wextra"
63
#  pragma GCC diagnostic ignored "-Wpedantic"
64
# endif
65
# include "stb_image_write.h"
66
# if defined(__clang__)
67
#  pragma clang diagnostic pop
68
# elif defined(__GNUC__)
69
#  pragma GCC diagnostic pop
70
# endif
71
#endif  /* HAVE_LIBPNG */
72

73
#include <sixel.h>
74
#include "stdio_stub.h"
75
#include "compat_stub.h"
76

77
#if !defined(O_BINARY) && defined(_O_BINARY)
78
# define O_BINARY _O_BINARY
79
#endif  /* !defined(O_BINARY) && !defined(_O_BINARY) */
80

81

82
#if !HAVE_LIBPNG
83
unsigned char *
84
stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes,
85
                      int x, int y, int n, int *out_len);
86

87
/*
88
 * The stb helper lives in another translation unit, so we forward declare it
89
 * here to satisfy the compiler. The picture below shows the relationship.
90
 */
91
unsigned char *
92
stbi_zlib_compress(unsigned char *data,
93
                   int data_len,
94
                   int *out_len,
95
                   int quality);
96

97
/*
98
 * The CRC generator mirrors the PNG chunk layout so we can reuse the helper
99
 * from multiple writers.
100
 *
101
 *     +------+-------+
102
 *     | type | data  |
103
 *     +------+-------+
104
 */
105
static unsigned int
106
png_crc32_update(unsigned int crc, unsigned char const *data,
252✔
107
                 size_t length)
108
{
109
    static unsigned int table[256];
252✔
110
    static int initialized;
252✔
111
    unsigned int rem;
252✔
112
    unsigned char octet;
252✔
113
    size_t index;
252✔
114
    int table_index;
252✔
115

116
    if (!initialized) {
252✔
117
        for (table_index = 0; table_index < 256; ++table_index) {
9,252✔
118
            rem = (unsigned int)table_index;
9,216✔
119
            rem <<= 24;
9,216✔
120
            for (index = 0; index < 8; ++index) {
82,944✔
121
                if (rem & 0x80000000U) {
73,728✔
122
                    rem = (rem << 1) ^ 0x04c11db7U;
36,864✔
123
                } else {
124
                    rem <<= 1;
36,864✔
125
                }
126
            }
127
            table[table_index] = rem;
9,216✔
128
        }
129
        initialized = 1;
36✔
130
    }
131

132
    crc = ~crc;
252✔
133
    for (index = 0; index < length; ++index) {
12,957,306✔
134
        octet = data[index];
12,957,054✔
135
        crc = table[((crc >> 24) ^ octet) & 0xffU] ^ (crc << 8);
12,957,054✔
136
    }
137

138
    return ~crc;
252✔
139
}
140

141
static int
142
png_write_u32(FILE *output_fp, unsigned int value)
288✔
143
{
144
    unsigned char buffer[4];
288✔
145
    size_t written;
288✔
146

147
    buffer[0] = (unsigned char)((value >> 24) & 0xffU);
288✔
148
    buffer[1] = (unsigned char)((value >> 16) & 0xffU);
288✔
149
    buffer[2] = (unsigned char)((value >> 8) & 0xffU);
288✔
150
    buffer[3] = (unsigned char)(value & 0xffU);
288✔
151

152
    written = fwrite(buffer, 1, sizeof(buffer), output_fp);
288✔
153
    if (written != sizeof(buffer)) {
288!
154
        return 0;
×
155
    }
156

157
    return 1;
158
}
159

160
/*
161
 * Emit a single PNG chunk.
162
 *     +--------+---------+----------+--------+
163
 *     | length |  type   |  data    |  CRC   |
164
 *     +--------+---------+----------+--------+
165
 */
166
static int
167
png_write_chunk(FILE *output_fp, char const *tag,
144✔
168
                unsigned char const *payload, size_t length)
169
{
170
    unsigned char type[4];
144✔
171
    unsigned int crc;
144✔
172
    size_t written;
144✔
173

174
    type[0] = (unsigned char)tag[0];
144✔
175
    type[1] = (unsigned char)tag[1];
144✔
176
    type[2] = (unsigned char)tag[2];
144✔
177
    type[3] = (unsigned char)tag[3];
144✔
178

179
    if (!png_write_u32(output_fp, (unsigned int)length)) {
144!
180
        return 0;
181
    }
182

183
    written = fwrite(type, 1, sizeof(type), output_fp);
144✔
184
    if (written != sizeof(type)) {
144!
185
        return 0;
186
    }
187

188
    if (length > 0) {
144✔
189
        written = fwrite(payload, 1, length, output_fp);
108✔
190
        if (written != length) {
108!
191
            return 0;
192
        }
193
    }
194

195
    crc = png_crc32_update(0U, type, sizeof(type));
144✔
196
    if (length > 0) {
144✔
197
        crc = png_crc32_update(crc, payload, length);
108✔
198
    }
199

200
    if (!png_write_u32(output_fp, crc)) {
144!
201
        return 0;
202
    }
203

204
    return 1;
205
}
206
#endif  /* !HAVE_LIBPNG */
207

208
static SIXELSTATUS
209
sixel_writer_convert_to_rgba(
×
210
    unsigned char       *dst,
211
    unsigned char const *src,
212
    int                  src_pixelformat,
213
    int                  width,
214
    int                  height)
215
{
216
    SIXELSTATUS status = SIXEL_FALSE;
×
217
    size_t count = 0u;
×
218
    size_t index = 0u;
×
219
    unsigned char const *cursor = NULL;
×
220
    unsigned char *output = NULL;
×
221

222
    count = (size_t)width * (size_t)height;
×
223
    cursor = src;
×
224
    output = dst;
×
225

226
    for (index = 0u; index < count; ++index) {
×
227
        switch (src_pixelformat) {
×
228
        case SIXEL_PIXELFORMAT_RGBA8888:
×
229
            output[0] = cursor[0];
×
230
            output[1] = cursor[1];
×
231
            output[2] = cursor[2];
×
232
            output[3] = cursor[3];
×
233
            break;
×
234
        case SIXEL_PIXELFORMAT_ARGB8888:
×
235
            output[0] = cursor[1];
×
236
            output[1] = cursor[2];
×
237
            output[2] = cursor[3];
×
238
            output[3] = cursor[0];
×
239
            break;
×
240
        case SIXEL_PIXELFORMAT_BGRA8888:
×
241
            output[0] = cursor[2];
×
242
            output[1] = cursor[1];
×
243
            output[2] = cursor[0];
×
244
            output[3] = cursor[3];
×
245
            break;
×
246
        case SIXEL_PIXELFORMAT_ABGR8888:
×
247
            output[0] = cursor[3];
×
248
            output[1] = cursor[2];
×
249
            output[2] = cursor[1];
×
250
            output[3] = cursor[0];
×
251
            break;
×
252
        default:
×
253
            status = SIXEL_BAD_ARGUMENT;
×
254
            goto end;
×
255
        }
256
        cursor += 4;
×
257
        output += 4;
×
258
    }
259

260
    status = SIXEL_OK;
261

262
end:
×
263
    return status;
×
264
}
265

266

267
static SIXELSTATUS
268
write_png_to_file(
51✔
269
    unsigned char       /* in */ *data,         /* source pixel data */
270
    int                 /* in */ width,         /* source data width */
271
    int                 /* in */ height,        /* source data height */
272
    unsigned char       /* in */ *palette,      /* palette of source data */
273
    int                 /* in */ pixelformat,   /* source pixelFormat */
274
    char const          /* in */ *filename,     /* destination filename */
275
    sixel_allocator_t   /* in */ *allocator)
276
{
277
    SIXELSTATUS status = SIXEL_FALSE;
51✔
278
    FILE *output_fp = NULL;
51✔
279
    unsigned char *pixels = NULL;
51✔
280
    unsigned char *new_pixels = NULL;
51✔
281
    int uses_palette = 0;
51✔
282
    int palette_entries = 0;
51✔
283
    int total_pixels = 0;
51✔
284
    int max_index = 0;
51✔
285
    int i = 0;
51✔
286
    unsigned char *src = NULL;
51✔
287
    unsigned char *dst = NULL;
51✔
288
    int bytes_per_pixel = 3;
51✔
289
#if HAVE_LIBPNG
290
    int y = 0;
291
    png_structp png_ptr = NULL;
292
    png_infop info_ptr = NULL;
293
    unsigned char **rows = NULL;
294
    png_color *png_palette = NULL;
295
#else
296
    unsigned char *filtered = NULL;
51✔
297
    unsigned char *compressed = NULL;
51✔
298
    unsigned char *png_data = NULL;
51✔
299
    unsigned char signature[8];
51✔
300
    unsigned char ihdr[13];
51✔
301
    unsigned char *plte = NULL;
51✔
302
    int stride = 0;
51✔
303
    int png_len = 0;
51✔
304
    int write_len = 0;
51✔
305
    size_t payload_size = 0;
51✔
306
#endif  /* HAVE_LIBPNG */
307

308
    switch (pixelformat) {
51!
309
    case SIXEL_PIXELFORMAT_PAL1:
×
310
    case SIXEL_PIXELFORMAT_PAL2:
311
    case SIXEL_PIXELFORMAT_PAL4:
312
        if (palette == NULL) {
×
313
            status = SIXEL_BAD_ARGUMENT;
×
314
            sixel_helper_set_additional_message(
×
315
                "write_png_to_file: no palette is given");
316
            goto end;
×
317
        }
318
        new_pixels = sixel_allocator_malloc(allocator,
×
319
                                            (size_t)(width * height));
×
320
        if (new_pixels == NULL) {
×
321
            status = SIXEL_BAD_ALLOCATION;
×
322
            sixel_helper_set_additional_message(
×
323
                "write_png_to_file: sixel_allocator_malloc() failed");
324
            goto end;
×
325
        }
326
        pixels = new_pixels;
×
327
        status = sixel_helper_normalize_pixelformat(pixels,
×
328
                                                    &pixelformat,
329
                                                    data,
330
                                                    pixelformat,
331
                                                    width,
332
                                                    height);
333
        if (SIXEL_FAILED(status)) {
×
334
            goto end;
×
335
        }
336
        break;
337
    case SIXEL_PIXELFORMAT_PAL8:
36✔
338
        if (palette == NULL) {
36!
339
            status = SIXEL_BAD_ARGUMENT;
×
340
            sixel_helper_set_additional_message(
×
341
                "write_png_to_file: no palette is given");
342
            goto end;
×
343
        }
344
        pixels = data;
345
        break;
346
    case SIXEL_PIXELFORMAT_RGB888:
347
        pixels = data;
348
        break;
349
    case SIXEL_PIXELFORMAT_G8:
×
350
        src = data;
×
351
        dst = pixels = new_pixels
×
352
            = sixel_allocator_malloc(allocator, (size_t)(width * height * 3));
×
353
        if (new_pixels == NULL) {
×
354
            status = SIXEL_BAD_ALLOCATION;
×
355
            sixel_helper_set_additional_message(
×
356
                "write_png_to_file: sixel_allocator_malloc() failed");
357
            goto end;
×
358
        }
359
        if (palette) {
×
360
            for (i = 0; i < width * height; ++i, ++src) {
×
361
                *dst++ = *(palette + *src * 3 + 0);
×
362
                *dst++ = *(palette + *src * 3 + 1);
×
363
                *dst++ = *(palette + *src * 3 + 2);
×
364
            }
365
        } else {
366
            for (i = 0; i < width * height; ++i, ++src) {
×
367
                *dst++ = *src;
×
368
                *dst++ = *src;
×
369
                *dst++ = *src;
×
370
            }
371
        }
372
        break;
373
    case SIXEL_PIXELFORMAT_RGB565:
×
374
    case SIXEL_PIXELFORMAT_RGB555:
375
    case SIXEL_PIXELFORMAT_BGR565:
376
    case SIXEL_PIXELFORMAT_BGR555:
377
    case SIXEL_PIXELFORMAT_GA88:
378
    case SIXEL_PIXELFORMAT_AG88:
379
    case SIXEL_PIXELFORMAT_BGR888:
380
    case SIXEL_PIXELFORMAT_RGBFLOAT32:
381
    case SIXEL_PIXELFORMAT_LINEARRGBFLOAT32:
382
    case SIXEL_PIXELFORMAT_OKLABFLOAT32:
383
    case SIXEL_PIXELFORMAT_CIELABFLOAT32:
384
    case SIXEL_PIXELFORMAT_DIN99DFLOAT32:
385
        pixels = new_pixels = sixel_allocator_malloc(allocator,
×
386
                                                    (size_t)(width
387
                                                             * height
×
388
                                                             * 3));
×
389
        if (new_pixels == NULL) {
×
390
            status = SIXEL_BAD_ALLOCATION;
×
391
            sixel_helper_set_additional_message(
×
392
                "write_png_to_file: sixel_allocator_malloc() failed");
393
            goto end;
×
394
        }
395
        status = sixel_helper_normalize_pixelformat(pixels,
×
396
                                                    &pixelformat,
397
                                                    data,
398
                                                    pixelformat,
399
                                                    width, height);
400
        if (SIXEL_FAILED(status)) {
×
401
            goto end;
×
402
        }
403
        break;
404
    case SIXEL_PIXELFORMAT_RGBA8888:
12✔
405
        pixels = data;
12✔
406
        pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
12✔
407
        bytes_per_pixel = 4;
12✔
408
        break;
12✔
409
    case SIXEL_PIXELFORMAT_ARGB8888:
×
410
    case SIXEL_PIXELFORMAT_BGRA8888:
411
    case SIXEL_PIXELFORMAT_ABGR8888:
412
        new_pixels = sixel_allocator_malloc(allocator,
×
413
                                            (size_t)width
×
414
                                            * (size_t)height
×
415
                                            * 4u);
416
        if (new_pixels == NULL) {
×
417
            status = SIXEL_BAD_ALLOCATION;
×
418
            sixel_helper_set_additional_message(
×
419
                "write_png_to_file: sixel_allocator_malloc() failed");
420
            goto end;
×
421
        }
422
        status = sixel_writer_convert_to_rgba(new_pixels,
×
423
                                              data,
424
                                              pixelformat,
425
                                              width,
426
                                              height);
427
        if (SIXEL_FAILED(status)) {
×
428
            goto end;
×
429
        }
430
        pixels = new_pixels;
×
431
        pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
×
432
        bytes_per_pixel = 4;
×
433
        break;
×
434
    default:
×
435
        status = SIXEL_BAD_ARGUMENT;
×
436
        sixel_helper_set_additional_message(
×
437
            "write_png_to_file: unkown pixelformat is specified");
438
        goto end;
×
439
    }
440

441
    uses_palette = (pixelformat == SIXEL_PIXELFORMAT_PAL8 && palette != NULL);
51!
442
    if (uses_palette) {
36✔
443
        total_pixels = width * height;
36✔
444
        max_index = 0;
36✔
445
        for (i = 0; i < total_pixels; ++i) {
18,484,182✔
446
            if (pixels[i] > max_index) {
18,484,146✔
447
                max_index = pixels[i];
448
            }
449
        }
450
        palette_entries = max_index + 1;
36✔
451
        if (palette_entries < 1) {
36!
452
            palette_entries = 1;
453
        }
454
        if (palette_entries > SIXEL_PALETTE_MAX) {
36!
455
            palette_entries = SIXEL_PALETTE_MAX;
456
        }
457
    }
458

459
    if (strcmp(filename, "-") == 0) {
51✔
460
#if defined(O_BINARY)
461
# if HAVE__SETMODE
462
        _setmode(STDOUT_FILENO, O_BINARY);
463
# elif HAVE_SETMODE
464
        setmode(STDOUT_FILENO, O_BINARY);
465
# endif  /* HAVE_SETMODE */
466
#endif  /* defined(O_BINARY) */
467
        output_fp = stdout;
33✔
468
    } else {
469
        output_fp = sixel_compat_fopen(filename, "wb");
18✔
470
        if (!output_fp) {
18!
471
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
472
            sixel_helper_set_additional_message("fopen() failed.");
×
473
            goto end;
×
474
        }
475
    }
476

477
    /*
478
     * Palette and RGB output branches fan out here.
479
     *
480
     *     +---------------+
481
     *     | use palette? |
482
     *     +---------------+
483
     *        | yes              no |
484
     *        v                  v
485
     *   indexed pipeline   RGB pipeline
486
     */
487
#if HAVE_LIBPNG
488
    if (uses_palette) {
2✔
489
        rows = sixel_allocator_malloc(allocator,
490
                                      (size_t)height
491
                                      * sizeof(unsigned char *));
492
        if (rows == NULL) {
1!
493
            status = SIXEL_BAD_ALLOCATION;
494
            sixel_helper_set_additional_message(
495
                "write_png_to_file: sixel_allocator_malloc() failed");
496
            goto end;
497
        }
498
        for (y = 0; y < height; ++y) {
2✔
499
            rows[y] = pixels + width * y;
500
        }
501
    } else {
502
        rows = sixel_allocator_malloc(allocator,
503
                                      (size_t)height
504
                                      * sizeof(unsigned char *));
505
        if (rows == NULL) {
1!
506
            status = SIXEL_BAD_ALLOCATION;
507
            sixel_helper_set_additional_message(
508
                "write_png_to_file: sixel_allocator_malloc() failed");
509
            goto end;
510
        }
511
        for (y = 0; y < height; ++y) {
2✔
512
            rows[y] = pixels
513
                + (size_t)width * (size_t)bytes_per_pixel * (size_t)y;
514
        }
515
    }
516

517
    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
518
                                      NULL,
519
                                      NULL,
520
                                      NULL);
521
    if (!png_ptr) {
1!
522
        status = SIXEL_PNG_ERROR;
523
        goto end;
524
    }
525
    info_ptr = png_create_info_struct(png_ptr);
526
    if (!info_ptr) {
1!
527
        status = SIXEL_PNG_ERROR;
528
        goto end;
529
    }
530
# if USE_SETJMP && HAVE_SETJMP
531
    if (setjmp(png_jmpbuf(png_ptr))) {
532
        status = SIXEL_PNG_ERROR;
533
        goto end;
534
    }
535
# endif
536
    png_init_io(png_ptr, output_fp);
537
    if (uses_palette) {
2✔
538
        png_set_IHDR(png_ptr,
539
                     info_ptr,
540
                     (png_uint_32)width,
541
                     (png_uint_32)height,
542
                     8,
543
                     PNG_COLOR_TYPE_PALETTE,
544
                     PNG_INTERLACE_NONE,
545
                     PNG_COMPRESSION_TYPE_BASE,
546
                     PNG_FILTER_TYPE_BASE);
547
        png_palette = sixel_allocator_malloc(allocator,
548
                                             (size_t)palette_entries
549
                                             * sizeof(png_color));
550
        if (png_palette == NULL) {
1!
551
            status = SIXEL_BAD_ALLOCATION;
552
            sixel_helper_set_additional_message(
553
                "write_png_to_file: sixel_allocator_malloc() failed");
554
            goto end;
555
        }
556
        for (i = 0; i < palette_entries; ++i) {
2✔
557
            png_palette[i].red = palette[i * 3 + 0];
558
            png_palette[i].green = palette[i * 3 + 1];
559
            png_palette[i].blue = palette[i * 3 + 2];
560
        }
561
        png_set_PLTE(png_ptr, info_ptr, png_palette, palette_entries);
562
    } else {
563
        int color_type;
564

565
        color_type = PNG_COLOR_TYPE_RGB;
566
        if (pixelformat == SIXEL_PIXELFORMAT_RGBA8888) {
2✔
567
            color_type = PNG_COLOR_TYPE_RGBA;
568
        }
569
        png_set_IHDR(png_ptr,
570
                     info_ptr,
571
                     (png_uint_32)width,
572
                     (png_uint_32)height,
573
                     8,
574
                     color_type,
575
                     PNG_INTERLACE_NONE,
576
                     PNG_COMPRESSION_TYPE_BASE,
577
                     PNG_FILTER_TYPE_BASE);
578
    }
579
    png_write_info(png_ptr, info_ptr);
580
    png_write_image(png_ptr, rows);
581
    png_write_end(png_ptr, NULL);
582
#else
583
    if (uses_palette) {
51✔
584
        stride = width + 1;
36✔
585
        payload_size = (size_t)stride * (size_t)height;
36✔
586
        filtered = sixel_allocator_malloc(allocator, payload_size);
36✔
587
        if (filtered == NULL) {
36!
588
            status = SIXEL_BAD_ALLOCATION;
×
589
            sixel_helper_set_additional_message(
×
590
                "write_png_to_file: sixel_allocator_malloc() failed");
591
            goto end;
×
592
        }
593
        for (i = 0; i < height; ++i) {
13,974✔
594
            filtered[i * stride] = 0;
13,938✔
595
            memcpy(filtered + i * stride + 1,
13,938✔
596
                   pixels + i * width,
13,938✔
597
                   (size_t)width);
598
        }
599
        compressed = stbi_zlib_compress(filtered,
36✔
600
                                        (int)payload_size,
601
                                        &png_len,
602
                                        stbi_write_png_compression_level);
603
        if (compressed == NULL) {
36!
604
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
605
            sixel_helper_set_additional_message(
×
606
                "stbi_zlib_compress() failed.");
607
            goto end;
×
608
        }
609
        signature[0] = 0x89;
36✔
610
        signature[1] = 0x50;
36✔
611
        signature[2] = 0x4e;
36✔
612
        signature[3] = 0x47;
36✔
613
        signature[4] = 0x0d;
36✔
614
        signature[5] = 0x0a;
36✔
615
        signature[6] = 0x1a;
36✔
616
        signature[7] = 0x0a;
36✔
617
        write_len = (int)fwrite(signature, 1, sizeof(signature), output_fp);
36✔
618
        if (write_len != (int)sizeof(signature)) {
36!
619
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
620
            sixel_helper_set_additional_message("fwrite() failed.");
×
621
            goto end;
×
622
        }
623
        memset(ihdr, 0, sizeof(ihdr));
36✔
624
        ihdr[0] = (unsigned char)((width >> 24) & 0xff);
36✔
625
        ihdr[1] = (unsigned char)((width >> 16) & 0xff);
36✔
626
        ihdr[2] = (unsigned char)((width >> 8) & 0xff);
36✔
627
        ihdr[3] = (unsigned char)(width & 0xff);
36✔
628
        ihdr[4] = (unsigned char)((height >> 24) & 0xff);
36✔
629
        ihdr[5] = (unsigned char)((height >> 16) & 0xff);
36✔
630
        ihdr[6] = (unsigned char)((height >> 8) & 0xff);
36✔
631
        ihdr[7] = (unsigned char)(height & 0xff);
36✔
632
        ihdr[8] = 8;
36✔
633
        ihdr[9] = 3;
36✔
634
        ihdr[10] = 0;
36✔
635
        ihdr[11] = 0;
36✔
636
        ihdr[12] = 0;
36✔
637
        if (!png_write_chunk(output_fp, "IHDR", ihdr, sizeof(ihdr))) {
36!
638
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
639
            sixel_helper_set_additional_message("fwrite() failed.");
×
640
            goto end;
×
641
        }
642
        plte = sixel_allocator_malloc(allocator,
72✔
643
                                      (size_t)palette_entries * 3U);
36✔
644
        if (plte == NULL) {
36!
645
            status = SIXEL_BAD_ALLOCATION;
×
646
            sixel_helper_set_additional_message(
×
647
                "write_png_to_file: sixel_allocator_malloc() failed");
648
            goto end;
×
649
        }
650
        for (i = 0; i < palette_entries; ++i) {
5,706✔
651
            plte[i * 3 + 0] = palette[i * 3 + 0];
5,670✔
652
            plte[i * 3 + 1] = palette[i * 3 + 1];
5,670✔
653
            plte[i * 3 + 2] = palette[i * 3 + 2];
5,670✔
654
        }
655
        if (!png_write_chunk(output_fp,
36!
656
                             "PLTE",
657
                             plte,
658
                             (size_t)palette_entries * 3U)) {
659
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
660
            sixel_helper_set_additional_message("fwrite() failed.");
×
661
            goto end;
×
662
        }
663
        if (!png_write_chunk(output_fp,
36!
664
                             "IDAT",
665
                             compressed,
666
                             (size_t)png_len)) {
667
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
668
            sixel_helper_set_additional_message("fwrite() failed.");
×
669
            goto end;
×
670
        }
671
        if (!png_write_chunk(output_fp, "IEND", NULL, 0)) {
36!
672
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
673
            sixel_helper_set_additional_message("fwrite() failed.");
×
674
            goto end;
×
675
        }
676
    } else {
677
        png_data = stbi_write_png_to_mem(pixels,
15✔
678
                                         width * bytes_per_pixel,
679
                                         width,
680
                                         height,
681
                                         bytes_per_pixel,
682
                                         &png_len);
683
        if (png_data == NULL) {
15!
684
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
685
            sixel_helper_set_additional_message(
×
686
                "stbi_write_png_to_mem() failed.");
687
            goto end;
×
688
        }
689
        write_len = (int)fwrite(png_data,
15✔
690
                                 1,
691
                                 (size_t)png_len,
692
                                 output_fp);
693
        if (write_len <= 0) {
15!
694
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
695
            sixel_helper_set_additional_message("fwrite() failed.");
×
696
            goto end;
×
697
        }
698
    }
699
#endif  /* HAVE_LIBPNG */
700

701
    status = SIXEL_OK;
702

703
end:
36✔
704
    if (output_fp && output_fp != stdout) {
51!
705
        fclose(output_fp);
18✔
706
    }
707
#if HAVE_LIBPNG
708
    sixel_allocator_free(allocator, rows);
709
    if (png_ptr) {
1!
710
        png_destroy_write_struct(&png_ptr, &info_ptr);
711
    }
712
    sixel_allocator_free(allocator, png_palette);
713
#else
714
    sixel_allocator_free(allocator, compressed);
51✔
715
    sixel_allocator_free(allocator, filtered);
51✔
716
    sixel_allocator_free(allocator, plte);
51✔
717
    sixel_allocator_free(allocator, png_data);
51✔
718
#endif  /* HAVE_LIBPNG */
719
    sixel_allocator_free(allocator, new_pixels);
51✔
720

721
    return status;
51✔
722
}
723

724

725
SIXELAPI SIXELSTATUS
726
sixel_helper_write_image_file(
51✔
727
    unsigned char       /* in */ *data,        /* source pixel data */
728
    int                 /* in */ width,        /* source data width */
729
    int                 /* in */ height,       /* source data height */
730
    unsigned char       /* in */ *palette,     /* palette of source data */
731
    int                 /* in */ pixelformat,  /* source pixelFormat */
732
    char const          /* in */ *filename,    /* destination filename */
733
    int                 /* in */ imageformat,  /* destination imageformat */
734
    sixel_allocator_t   /* in */ *allocator)   /* allocator object */
735
{
736
    SIXELSTATUS status = SIXEL_FALSE;
51✔
737

738
    if (allocator == NULL) {
51!
739
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
740
        if (SIXEL_FAILED(status)) {
×
741
            goto end;
×
742
        }
743
    } else {
744
        sixel_allocator_ref(allocator);
51✔
745
    }
746

747
    if (width > SIXEL_WIDTH_LIMIT) {
51!
748
        sixel_helper_set_additional_message(
×
749
            "sixel_encode: bad width parameter."
750
            " (width > SIXEL_WIDTH_LIMIT)");
751
        status = SIXEL_BAD_INPUT;
×
752
        goto end;
×
753
    }
754

755
    if (width > SIXEL_HEIGHT_LIMIT) {
51!
756
        sixel_helper_set_additional_message(
757
            "sixel_encode: bad width parameter."
758
            " (width > SIXEL_HEIGHT_LIMIT)");
759
        status = SIXEL_BAD_INPUT;
760
        goto end;
761
    }
762

763
    if (height < 1) {
51!
764
        sixel_helper_set_additional_message(
×
765
            "sixel_encode: bad height parameter."
766
            " (height < 1)");
767
        status = SIXEL_BAD_INPUT;
×
768
        goto end;
×
769
    }
770

771
    if (width < 1) {
51!
772
        sixel_helper_set_additional_message(
×
773
            "sixel_encode: bad width parameter."
774
            " (width < 1)");
775
        status = SIXEL_BAD_INPUT;
×
776
        goto end;
×
777
    }
778

779
    if (height < 1) {
51!
780
        sixel_helper_set_additional_message(
781
            "sixel_encode: bad height parameter."
782
            " (height < 1)");
783
        status = SIXEL_BAD_INPUT;
784
        goto end;
785
    }
786

787
    switch (imageformat) {
51!
788
    case SIXEL_FORMAT_PNG:
51✔
789
        status = write_png_to_file(data, width, height, palette,
51✔
790
                                   pixelformat, filename, allocator);
791
        break;
51✔
792
    case SIXEL_FORMAT_GIF:
×
793
    case SIXEL_FORMAT_BMP:
794
    case SIXEL_FORMAT_JPG:
795
    case SIXEL_FORMAT_TGA:
796
    case SIXEL_FORMAT_WBMP:
797
    case SIXEL_FORMAT_TIFF:
798
    case SIXEL_FORMAT_SIXEL:
799
    case SIXEL_FORMAT_PNM:
800
    case SIXEL_FORMAT_GD2:
801
    case SIXEL_FORMAT_PSD:
802
    case SIXEL_FORMAT_HDR:
803
    default:
804
        status = SIXEL_NOT_IMPLEMENTED;
×
805
        goto end;
×
806
        break;
51✔
807
    }
808

809
end:
51✔
810
    sixel_allocator_unref(allocator);
51✔
811
    return status;
51✔
812
}
813

814

815
#if HAVE_TESTS
816
static int
817
test1(void)
×
818
{
819
    int nret = EXIT_FAILURE;
×
820
    SIXELSTATUS status;
×
821
    unsigned char pixels[] = {0xff, 0xff, 0xff};
×
822

823
    status = sixel_helper_write_image_file(
×
824
        pixels,
825
        1,
826
        1,
827
        NULL,
828
        SIXEL_PIXELFORMAT_RGB888,
829
        "output.gif",
830
        SIXEL_FORMAT_GIF,
831
        NULL);
832

833
    if (!SIXEL_FAILED(status)) {
×
834
        goto error;
×
835
    }
836
    nret = EXIT_SUCCESS;
837

838
error:
×
839
    return nret;
×
840
}
841

842

843
static int
844
test2(void)
×
845
{
846
    int nret = EXIT_FAILURE;
×
847
    SIXELSTATUS status;
×
848
    unsigned char pixels[] = {0xff, 0xff, 0xff};
×
849

850
    status = sixel_helper_write_image_file(
×
851
        pixels,
852
        1,
853
        1,
854
        NULL,
855
        SIXEL_PIXELFORMAT_RGB888,
856
        "test-output.png",
857
        SIXEL_FORMAT_PNG,
858
        NULL);
859

860
    if (SIXEL_FAILED(status)) {
×
861
        goto error;
×
862
    }
863
    nret = EXIT_SUCCESS;
864

865
error:
×
866
    return nret;
×
867
}
868

869

870
static unsigned char *
871
writer_tests_duplicate_palette(
×
872
    sixel_dither_t *dither,
873
    sixel_allocator_t **ppallocator)
874
{
875
    sixel_palette_t *palette_obj;
×
876
    unsigned char *copy;
×
877
    size_t count;
×
878
    sixel_allocator_t *allocator;
×
879

880
    if (dither == NULL || ppallocator == NULL) {
×
881
        return NULL;
882
    }
883

884
    palette_obj = NULL;
×
885
    copy = NULL;
×
886
    count = 0U;
×
887
    allocator = *ppallocator;
×
888
    if (allocator == NULL) {
×
889
        if (SIXEL_FAILED(sixel_allocator_new(
×
890
                &allocator,
891
                NULL,
892
                NULL,
893
                NULL,
894
                NULL))) {
895
            return NULL;
896
        }
897
        *ppallocator = allocator;
×
898
    }
899
    if (SIXEL_FAILED(
×
900
            sixel_dither_get_quantized_palette(dither, &palette_obj))
901
            || palette_obj == NULL) {
×
902
        return NULL;
903
    }
904
    if (SIXEL_FAILED(sixel_palette_copy_entries_8bit(
×
905
            palette_obj,
906
            &copy,
907
            &count,
908
            SIXEL_PIXELFORMAT_RGB888,
909
            allocator))) {
910
        sixel_palette_unref(palette_obj);
×
911
        return NULL;
×
912
    }
913
    sixel_palette_unref(palette_obj);
×
914
    return copy;
×
915
}
916

917
static int
918
test3(void)
×
919
{
920
    int nret = EXIT_FAILURE;
×
921
    SIXELSTATUS status;
×
922
    unsigned char pixels[] = {0x00, 0x7f, 0xff};
×
923
    sixel_dither_t *dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
924
    unsigned char *palette_copy = NULL;
×
925
    sixel_allocator_t *allocator = NULL;
×
926

927
    status = sixel_helper_write_image_file(
×
928
        pixels,
929
        1,
930
        1,
931
        NULL,
932
        SIXEL_PIXELFORMAT_G8,
933
        "test-output.png",
934
        SIXEL_FORMAT_PNG,
935
        NULL);
936

937
    if (SIXEL_FAILED(status)) {
×
938
        goto error;
×
939
    }
940

941
    palette_copy = writer_tests_duplicate_palette(dither, &allocator);
×
942
    status = sixel_helper_write_image_file(
×
943
        pixels,
944
        1,
945
        1,
946
        palette_copy,
947
        SIXEL_PIXELFORMAT_G8,
948
        "test-output.png",
949
        SIXEL_FORMAT_PNG,
950
        NULL);
951

952
    if (SIXEL_FAILED(status)) {
×
953
        goto error;
×
954
    }
955
    nret = EXIT_SUCCESS;
956

957
error:
×
958
    if (palette_copy != NULL && allocator != NULL) {
×
959
        sixel_allocator_free(allocator, palette_copy);
×
960
    }
961
    if (allocator != NULL) {
×
962
        sixel_allocator_unref(allocator);
×
963
    }
964
    return nret;
×
965
}
966

967

968
static int
969
test4(void)
×
970
{
971
    int nret = EXIT_FAILURE;
×
972
    SIXELSTATUS status;
×
973
    unsigned char pixels[] = {0xa0};
×
974
    sixel_dither_t *dither = sixel_dither_get(SIXEL_BUILTIN_XTERM256);
×
975
    unsigned char *palette_copy = NULL;
×
976
    sixel_allocator_t *allocator = NULL;
×
977

978
    palette_copy = writer_tests_duplicate_palette(dither, &allocator);
×
979
    status = sixel_helper_write_image_file(
×
980
        pixels,
981
        1,
982
        1,
983
        palette_copy,
984
        SIXEL_PIXELFORMAT_PAL1,
985
        "test-output.png",
986
        SIXEL_FORMAT_PNG,
987
        NULL);
988
    if (SIXEL_FAILED(status)) {
×
989
        goto error;
×
990
    }
991

992
    status = sixel_helper_write_image_file(
×
993
        pixels,
994
        1,
995
        1,
996
        NULL,
997
        SIXEL_PIXELFORMAT_PAL1,
998
        "test-output.png",
999
        SIXEL_FORMAT_PNG,
1000
        NULL);
1001
    if (status != SIXEL_BAD_ARGUMENT) {
×
1002
        goto error;
×
1003
    }
1004

1005
    nret = EXIT_SUCCESS;
1006

1007
error:
×
1008
    if (palette_copy != NULL && allocator != NULL) {
×
1009
        sixel_allocator_free(allocator, palette_copy);
×
1010
    }
1011
    if (allocator != NULL) {
×
1012
        sixel_allocator_unref(allocator);
×
1013
    }
1014
    return nret;
×
1015
}
1016

1017

1018
static int
1019
test5(void)
×
1020
{
1021
    int nret = EXIT_FAILURE;
×
1022
    SIXELSTATUS status;
×
1023
    unsigned char pixels[] = {0x00};
×
1024
    sixel_dither_t *dither = sixel_dither_get(SIXEL_BUILTIN_XTERM256);
×
1025
    unsigned char *palette_copy = NULL;
×
1026
    sixel_allocator_t *allocator = NULL;
×
1027

1028
    palette_copy = writer_tests_duplicate_palette(dither, &allocator);
×
1029
    status = sixel_helper_write_image_file(
×
1030
        pixels,
1031
        1,
1032
        1,
1033
        palette_copy,
1034
        SIXEL_PIXELFORMAT_PAL8,
1035
        "test-output.png",
1036
        SIXEL_FORMAT_PNG,
1037
        NULL);
1038
    if (SIXEL_FAILED(status)) {
×
1039
        goto error;
×
1040
    }
1041

1042
    status = sixel_helper_write_image_file(
×
1043
        pixels,
1044
        1,
1045
        1,
1046
        NULL,
1047
        SIXEL_PIXELFORMAT_PAL8,
1048
        "test-output.png",
1049
        SIXEL_FORMAT_PNG,
1050
        NULL);
1051
    if (status != SIXEL_BAD_ARGUMENT) {
×
1052
        goto error;
×
1053
    }
1054

1055
    nret = EXIT_SUCCESS;
1056

1057
error:
×
1058
    if (palette_copy != NULL && allocator != NULL) {
×
1059
        sixel_allocator_free(allocator, palette_copy);
×
1060
    }
1061
    if (allocator != NULL) {
×
1062
        sixel_allocator_unref(allocator);
×
1063
    }
1064
    return nret;
×
1065
}
1066

1067

1068
static int
1069
test6(void)
×
1070
{
1071
    int nret = EXIT_FAILURE;
×
1072
    SIXELSTATUS status;
×
1073
    unsigned char pixels[] = {0x00, 0x7f, 0xff};
×
1074

1075
    status = sixel_helper_write_image_file(
×
1076
        pixels,
1077
        1,
1078
        1,
1079
        NULL,
1080
        SIXEL_PIXELFORMAT_BGR888,
1081
        "test-output.png",
1082
        SIXEL_FORMAT_PNG,
1083
        NULL);
1084

1085
    if (SIXEL_FAILED(status)) {
×
1086
        goto error;
×
1087
    }
1088
    nret = EXIT_SUCCESS;
1089

1090
error:
×
1091
    return nret;
×
1092
}
1093

1094

1095
static int
1096
test7(void)
×
1097
{
1098
    int nret = EXIT_FAILURE;
×
1099
    SIXELSTATUS status;
×
1100
    float pixels[] = { 0.0f, 0.5f, 1.0f };
×
1101

1102
    status = sixel_helper_write_image_file(
×
1103
        (unsigned char *)pixels,
1104
        1,
1105
        1,
1106
        NULL,
1107
        SIXEL_PIXELFORMAT_RGBFLOAT32,
1108
        "test-output.png",
1109
        SIXEL_FORMAT_PNG,
1110
        NULL);
1111

1112
    if (SIXEL_FAILED(status)) {
×
1113
        goto error;
×
1114
    }
1115
    nret = EXIT_SUCCESS;
1116

1117
error:
×
1118
    return nret;
×
1119
}
1120

1121

1122
SIXELAPI int
1123
sixel_writer_tests_main(void)
×
1124
{
1125
    int nret = EXIT_FAILURE;
×
1126
    size_t i;
×
1127
    typedef int (* testcase)(void);
×
1128

1129
    static testcase const testcases[] = {
×
1130
        test1,
1131
        test2,
1132
        test3,
1133
        test4,
1134
        test5,
1135
        test6,
1136
        test7,
1137
    };
1138

1139
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
1140
        nret = testcases[i]();
×
1141
        if (nret != EXIT_SUCCESS) {
×
1142
            goto error;
×
1143
        }
1144
    }
1145

1146
    nret = EXIT_SUCCESS;
1147

1148
error:
×
1149
    return nret;
×
1150
}
1151
#endif  /* HAVE_TESTS */
1152

1153
/* emacs Local Variables:      */
1154
/* emacs mode: c               */
1155
/* emacs tab-width: 4          */
1156
/* emacs indent-tabs-mode: nil */
1157
/* emacs c-basic-offset: 4     */
1158
/* emacs End:                  */
1159
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1160
/* 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