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

saitoha / libsixel / 18977394527

31 Oct 2025 03:18PM UTC coverage: 41.681% (-1.1%) from 42.816%
18977394527

push

github

saitoha
cli: add -M,--mapfile-output option

6279 of 22484 branches covered (27.93%)

119 of 838 new or added lines in 1 file covered. (14.2%)

2 existing lines in 1 file now uncovered.

9003 of 21600 relevant lines covered (41.68%)

971172.22 hits per line

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

42.96
/src/encoder.c
1
/* SPDX-License-Identifier: MIT AND BSD-3-Clause
2
 *
3
 * Copyright (c) 2014-2019 Hayaki Saito
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
6
 * this software and associated documentation files (the "Software"), to deal in
7
 * the Software without restriction, including without limitation the rights to
8
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
 * the Software, and to permit persons to whom the Software is furnished to do so,
10
 * subject to the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be included in all
13
 * copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
 *
22
 * -------------------------------------------------------------------------------
23
 * Portions of this file(sixel_encoder_emit_drcsmmv2_chars) are derived from
24
 * mlterm's drcssixel.c.
25
 *
26
 * Copyright (c) Araki Ken(arakiken@users.sourceforge.net)
27
 *
28
 * Redistribution and use in source and binary forms, with or without
29
 * modification, are permitted provided that the following conditions
30
 * are met:
31
 * 1. Redistributions of source code must retain the above copyright
32
 *    notice, this list of conditions and the following disclaimer.
33
 * 2. Redistributions in binary form must reproduce the above copyright
34
 *    notice, this list of conditions and the following disclaimer in the
35
 *    documentation and/or other materials provided with the distribution.
36
 * 3. The name of any author may not be used to endorse or promote
37
 *    products derived from this software without their specific prior
38
 *    written permission.
39
 *
40
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
41
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
42
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
43
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
44
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
45
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
46
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
47
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
48
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
49
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
50
 * SUCH DAMAGE.
51
 *
52
 */
53

54
#include "config.h"
55
#if !defined(_POSIX_C_SOURCE)
56
# define _POSIX_C_SOURCE 200809L
57
#endif
58

59
/* STDC_HEADERS */
60
#include <stdio.h>
61
#include <stdlib.h>
62
#include <stdarg.h>
63

64
# if HAVE_STRING_H
65
#include <string.h>
66
#endif  /* HAVE_STRING_H */
67
#if HAVE_UNISTD_H
68
# include <unistd.h>
69
#elif HAVE_SYS_UNISTD_H
70
# include <sys/unistd.h>
71
#endif  /* HAVE_SYS_UNISTD_H */
72
#if HAVE_SYS_TYPES_H
73
# include <sys/types.h>
74
#endif  /* HAVE_SYS_TYPES_H */
75
#if HAVE_INTTYPES_H
76
# include <inttypes.h>
77
#endif  /* HAVE_INTTYPES_H */
78
#if HAVE_ERRNO_H
79
# include <errno.h>
80
#endif  /* HAVE_ERRNO_H */
81
#if HAVE_SYS_STAT_H
82
# include <sys/stat.h>
83
#endif  /* HAVE_SYS_STAT_H */
84
#if HAVE_SYS_TIME_H
85
# include <sys/time.h>
86
#elif HAVE_TIME_H
87
# include <time.h>
88
#endif  /* HAVE_SYS_TIME_H HAVE_TIME_H */
89
#if HAVE_SYS_IOCTL_H
90
# include <sys/ioctl.h>
91
#endif  /* HAVE_SYS_IOCTL_H */
92
#if HAVE_FCNTL_H
93
# include <fcntl.h>
94
#endif  /* HAVE_FCNTL_H */
95
#if HAVE_ERRNO_H
96
# include <errno.h>
97
#endif  /* HAVE_ERRNO_H */
98
#if HAVE_CTYPE_H
99
# include <ctype.h>
100
#endif  /* HAVE_CTYPE_H */
101
#if HAVE_LIMITS_H
102
# include <limits.h>
103
#endif  /* HAVE_LIMITS_H */
104

105
#include <sixel.h>
106
#include "loader.h"
107
#include "assessment.h"
108
#include "tty.h"
109
#include "encoder.h"
110
#include "output.h"
111
#include "dither.h"
112
#include "frame.h"
113
#include "rgblookup.h"
114

115
#if defined(_WIN32)
116

117
# include <windows.h>
118
# if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
119
#  include <io.h>
120
# endif
121
# if defined(_MSC_VER)
122
#   include <time.h>
123
# endif
124

125
/* for msvc */
126
# ifndef STDIN_FILENO
127
#  define STDIN_FILENO 0
128
# endif
129
# ifndef STDOUT_FILENO
130
#  define STDOUT_FILENO 1
131
# endif
132
# ifndef STDERR_FILENO
133
#  define STDERR_FILENO 2
134
# endif
135
# ifndef S_IRUSR
136
#  define S_IRUSR _S_IREAD
137
# endif
138
# ifndef S_IWUSR
139
#  define S_IWUSR _S_IWRITE
140
# endif
141

142
# if defined(CLOCKS_PER_SEC)
143
#  undef CLOCKS_PER_SEC
144
# endif
145
# define CLOCKS_PER_SEC 1000
146

147
# if !defined(HAVE_NANOSLEEP)
148
# define HAVE_NANOSLEEP_WIN 1
149
static int
150
nanosleep_win(
151
    struct timespec const *req,
152
    struct timespec *rem)
153
{
154
    LONGLONG nanoseconds;
155
    LARGE_INTEGER dueTime;
156
    HANDLE timer;
157

158
    if (req == NULL || req->tv_sec < 0 || req->tv_nsec < 0 ||
159
        req->tv_nsec >= 1000000000L) {
160
        errno = EINVAL;
161
        return (-1);
162
    }
163

164
    /* Convert to 100-nanosecond intervals (Windows FILETIME units) */
165
    nanoseconds = req->tv_sec * 1000000000LL + req->tv_nsec;
166
    dueTime.QuadPart = -(nanoseconds / 100); /* Negative for relative time */
167

168
    timer = CreateWaitableTimer(NULL, TRUE, NULL);
169
    if (timer == NULL) {
170
        errno = EFAULT;  /* Approximate error */
171
        return (-1);
172
    }
173

174
    if (! SetWaitableTimer(timer, &dueTime, 0, NULL, NULL, FALSE)) {
175
        (void) CloseHandle(timer);
176
        errno = EFAULT;
177
        return (-1);
178
    }
179

180
    (void) WaitForSingleObject(timer, INFINITE);
181
    (void) CloseHandle(timer);
182

183
    /* No interruption handling, so rem is unchanged */
184
    if (rem != NULL) {
185
        rem->tv_sec = 0;
186
        rem->tv_nsec = 0;
187
    }
188

189
    return (0);
190
}
191
# endif  /* HAVE_NANOSLEEP */
192

193
# if !defined(HAVE_CLOCK)
194
# define HAVE_CLOCK_WIN 1
195
static sixel_clock_t
196
clock_win(void)
197
{
198
    FILETIME ct, et, kt, ut;
199
    ULARGE_INTEGER u, k;
200

201
    if (! GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) {
202
        return (sixel_clock_t)(-1);
203
    }
204
    u.LowPart = ut.dwLowDateTime; u.HighPart = ut.dwHighDateTime;
205
    k.LowPart = kt.dwLowDateTime; k.HighPart = kt.dwHighDateTime;
206
    /* 100ns -> ms */
207
    return (sixel_clock_t)((u.QuadPart + k.QuadPart) / 10000ULL);
208
}
209
# endif  /* HAVE_CLOCK */
210

211
#endif /* _WIN32 */
212

213

214
static char *
215
arg_strdup(
60✔
216
    char const          /* in */ *s,          /* source buffer */
217
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
218
                                                 destination buffer */
219
{
220
    char *p;
221
    size_t len;
222

223
    len = strlen(s);
60✔
224

225
    p = (char *)sixel_allocator_malloc(allocator, len + 1);
60✔
226
    if (p) {
60!
227
#if HAVE_STRCPY_S
228
        (void) strcpy_s(p, (rsize_t)len, s);
229
#else
230
        (void) strcpy(p, s);
60✔
231
#endif  /* HAVE_STRCPY_S */
232
    }
20✔
233
    return p;
60✔
234
}
235

236

237
/* An clone function of XColorSpec() of xlib */
238
static SIXELSTATUS
239
sixel_parse_x_colorspec(
45✔
240
    unsigned char       /* out */ **bgcolor,     /* destination buffer */
241
    char const          /* in */  *s,            /* source buffer */
242
    sixel_allocator_t   /* in */  *allocator)    /* allocator object for
243
                                                    destination buffer */
244
{
245
    SIXELSTATUS status = SIXEL_FALSE;
45✔
246
    char *p;
247
    unsigned char components[3];
248
    int component_index = 0;
45✔
249
    unsigned long v;
250
    char *endptr;
251
    char *buf = NULL;
45✔
252
    struct color const *pcolor;
253

254
    /* from rgb_lookup.h generated by gpref */
255
    pcolor = lookup_rgb(s, strlen(s));
45✔
256
    if (pcolor) {
45✔
257
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
3✔
258
        if (*bgcolor == NULL) {
3!
259
            sixel_helper_set_additional_message(
×
260
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
261
            status = SIXEL_BAD_ALLOCATION;
×
262
            goto end;
×
263
        }
264
        (*bgcolor)[0] = pcolor->r;
3✔
265
        (*bgcolor)[1] = pcolor->g;
3✔
266
        (*bgcolor)[2] = pcolor->b;
3✔
267
    } else if (s[0] == 'r' && s[1] == 'g' && s[2] == 'b' && s[3] == ':') {
43!
268
        p = buf = arg_strdup(s + 4, allocator);
6✔
269
        if (buf == NULL) {
6!
270
            sixel_helper_set_additional_message(
×
271
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
272
            status = SIXEL_BAD_ALLOCATION;
×
273
            goto end;
×
274
        }
275
        while (*p) {
15!
276
            v = 0;
15✔
277
            for (endptr = p; endptr - p <= 12; ++endptr) {
36!
278
                if (*endptr >= '0' && *endptr <= '9') {
36✔
279
                    v = (v << 4) | (unsigned long)(*endptr - '0');
15✔
280
                } else if (*endptr >= 'a' && *endptr <= 'f') {
26!
281
                    v = (v << 4) | (unsigned long)(*endptr - 'a' + 10);
3✔
282
                } else if (*endptr >= 'A' && *endptr <= 'F') {
19!
283
                    v = (v << 4) | (unsigned long)(*endptr - 'A' + 10);
3✔
284
                } else {
1✔
285
                    break;
5✔
286
                }
287
            }
7✔
288
            if (endptr - p == 0) {
15!
289
                break;
×
290
            }
291
            if (endptr - p > 4) {
15!
292
                break;
×
293
            }
294
            v = v << ((4 - (endptr - p)) * 4) >> 8;
15✔
295
            components[component_index++] = (unsigned char)v;
15✔
296
            p = endptr;
15✔
297
            if (component_index == 3) {
15✔
298
                break;
3✔
299
            }
300
            if (*p == '\0') {
12✔
301
                break;
3✔
302
            }
303
            if (*p != '/') {
9!
304
                break;
×
305
            }
306
            ++p;
9✔
307
        }
308
        if (component_index != 3 || *p != '\0' || *p == '/') {
6!
309
            status = SIXEL_BAD_ARGUMENT;
3✔
310
            goto end;
3✔
311
        }
312
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
3✔
313
        if (*bgcolor == NULL) {
3!
314
            sixel_helper_set_additional_message(
×
315
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
316
            status = SIXEL_BAD_ALLOCATION;
×
317
            goto end;
×
318
        }
319
        (*bgcolor)[0] = components[0];
3✔
320
        (*bgcolor)[1] = components[1];
3✔
321
        (*bgcolor)[2] = components[2];
3✔
322
    } else if (*s == '#') {
37✔
323
        buf = arg_strdup(s + 1, allocator);
27✔
324
        if (buf == NULL) {
27!
325
            sixel_helper_set_additional_message(
×
326
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
327
            status = SIXEL_BAD_ALLOCATION;
×
328
            goto end;
×
329
        }
330
        for (p = endptr = buf; endptr - p <= 12; ++endptr) {
192✔
331
            if (*endptr >= '0' && *endptr <= '9') {
189✔
332
                *endptr -= '0';
99✔
333
            } else if (*endptr >= 'a' && *endptr <= 'f') {
123!
334
                *endptr -= 'a' - 10;
57✔
335
            } else if (*endptr >= 'A' && *endptr <= 'F') {
52✔
336
                *endptr -= 'A' - 10;
9✔
337
            } else if (*endptr == '\0') {
27✔
338
                break;
21✔
339
            } else {
340
                status = SIXEL_BAD_ARGUMENT;
3✔
341
                goto end;
3✔
342
            }
343
        }
55✔
344
        if (endptr - p > 12) {
24✔
345
            status = SIXEL_BAD_ARGUMENT;
3✔
346
            goto end;
3✔
347
        }
348
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
21✔
349
        if (*bgcolor == NULL) {
21!
350
            sixel_helper_set_additional_message(
×
351
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
352
            status = SIXEL_BAD_ALLOCATION;
×
353
            goto end;
×
354
        }
355
        switch (endptr - p) {
21✔
356
        case 3:
6✔
357
            (*bgcolor)[0] = (unsigned char)(p[0] << 4);
9✔
358
            (*bgcolor)[1] = (unsigned char)(p[1] << 4);
9✔
359
            (*bgcolor)[2] = (unsigned char)(p[2] << 4);
9✔
360
            break;
9✔
361
        case 6:
2✔
362
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
363
            (*bgcolor)[1] = (unsigned char)(p[2] << 4 | p[3]);
3✔
364
            (*bgcolor)[2] = (unsigned char)(p[4] << 4 | p[4]);
3✔
365
            break;
3✔
366
        case 9:
2✔
367
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
368
            (*bgcolor)[1] = (unsigned char)(p[3] << 4 | p[4]);
3✔
369
            (*bgcolor)[2] = (unsigned char)(p[6] << 4 | p[7]);
3✔
370
            break;
3✔
371
        case 12:
2✔
372
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
373
            (*bgcolor)[1] = (unsigned char)(p[4] << 4 | p[5]);
3✔
374
            (*bgcolor)[2] = (unsigned char)(p[8] << 4 | p[9]);
3✔
375
            break;
3✔
376
        default:
2✔
377
            status = SIXEL_BAD_ARGUMENT;
3✔
378
            goto end;
3✔
379
        }
380
    } else {
6✔
381
        status = SIXEL_BAD_ARGUMENT;
9✔
382
        goto end;
9✔
383
    }
384

385
    status = SIXEL_OK;
24✔
386

387
end:
30✔
388
    sixel_allocator_free(allocator, buf);
45✔
389

390
    return status;
45✔
391
}
392

393

394
/* generic writer function for passing to sixel_output_new() */
395
static int
396
sixel_write_callback(char *data, int size, void *priv)
4,298✔
397
{
398
    int result;
399

400
#if HAVE__WRITE
401
    result = _write(*(int *)priv, data, (size_t)size);
402
#elif defined(__MINGW64__)
403
    result = write(*(int *)priv, data, (unsigned int)size);
404
#else
405
    result = write(*(int *)priv, data, (size_t)size);
4,298✔
406
#endif
407

408
    return result;
4,298✔
409
}
410

411

412
/* the writer function with hex-encoding for passing to sixel_output_new() */
413
static int
414
sixel_hex_write_callback(
73✔
415
    char    /* in */ *data,
416
    int     /* in */ size,
417
    void    /* in */ *priv)
418
{
419
    char hex[SIXEL_OUTPUT_PACKET_SIZE * 2];
420
    int i;
421
    int j;
422
    int result;
423

424
    for (i = j = 0; i < size; ++i, ++j) {
635,609✔
425
        hex[j] = (data[i] >> 4) & 0xf;
635,536✔
426
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
635,536!
427
        hex[++j] = data[i] & 0xf;
635,536✔
428
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
635,536✔
429
    }
168,020✔
430

431
#if HAVE__WRITE
432
    result = _write(*(int *)priv, hex, (unsigned int)(size * 2));
433
#elif defined(__MINGW64__)
434
    result = write(*(int *)priv, hex, (unsigned int)(size * 2));
435
#else
436
    result = write(*(int *)priv, hex, (size_t)(size * 2));
73✔
437
#endif
438

439
    return result;
73✔
440
}
441

442
typedef struct sixel_encoder_output_probe {
443
    sixel_encoder_t *encoder;
444
    sixel_write_function base_write;
445
    void *base_priv;
446
} sixel_encoder_output_probe_t;
447

448
static int
449
sixel_write_with_probe(char *data, int size, void *priv)
×
450
{
451
    sixel_encoder_output_probe_t *probe;
452
    int written;
453
    double started_at;
454
    double finished_at;
455
    double duration;
456

457
    probe = (sixel_encoder_output_probe_t *)priv;
×
458
    if (probe == NULL || probe->base_write == NULL) {
×
459
        return 0;
×
460
    }
461
    started_at = 0.0;
×
462
    finished_at = 0.0;
×
463
    duration = 0.0;
×
464
    if (probe->encoder != NULL &&
×
465
            probe->encoder->assessment_observer != NULL) {
×
466
        started_at = sixel_assessment_timer_now();
×
467
    }
468
    written = probe->base_write(data, size, probe->base_priv);
×
469
    if (probe->encoder != NULL &&
×
470
            probe->encoder->assessment_observer != NULL) {
×
471
        finished_at = sixel_assessment_timer_now();
×
472
        duration = finished_at - started_at;
×
473
        if (duration < 0.0) {
×
474
            duration = 0.0;
×
475
        }
476
    }
477
    if (written > 0 && probe->encoder != NULL &&
×
478
            probe->encoder->assessment_observer != NULL) {
×
479
        sixel_assessment_record_output_write(
×
480
            probe->encoder->assessment_observer,
×
481
            (size_t)written,
482
            duration);
483
    }
484
    return written;
×
485
}
486

487
/*
488
 * Reuse the fn_write probe for raw escape writes so that every
489
 * assessment bucket receives the same accounting.
490
 *
491
 *     encoder        probe wrapper       write(2)
492
 *     +------+    +----------------+    +---------+
493
 *     | data | -> | sixel_write_*  | -> | target  |
494
 *     +------+    +----------------+    +---------+
495
 */
496
static int
497
sixel_encoder_probe_fd_write(sixel_encoder_t *encoder,
×
498
                             char *data,
499
                             int size,
500
                             int fd)
501
{
502
    sixel_encoder_output_probe_t probe;
503
    int written;
504

505
    probe.encoder = encoder;
×
506
    probe.base_write = sixel_write_callback;
×
507
    probe.base_priv = &fd;
×
508
    written = sixel_write_with_probe(data, size, &probe);
×
509

510
    return written;
×
511
}
512

513
static SIXELSTATUS
514
sixel_encoder_ensure_cell_size(sixel_encoder_t *encoder)
×
515
{
516
#if defined(TIOCGWINSZ)
517
    struct winsize ws;
518
    int result;
519
    int fd = 0;
×
520

521
    if (encoder->cell_width > 0 && encoder->cell_height > 0) {
×
522
        return SIXEL_OK;
×
523
    }
524

525
#if HAVE__OPEN
526
    fd = _open("/dev/tty", O_RDONLY);
527
#else
528
    fd = open("/dev/tty", O_RDONLY);
×
529
#endif  /* #if HAVE__OPEN */
530
    if (fd >= 0) {
×
531
        result = ioctl(fd, TIOCGWINSZ, &ws);
×
532
        close(fd);
×
533
    } else {
534
        sixel_helper_set_additional_message(
×
535
            "failed to open /dev/tty");
536
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
537
    }
538
    if (result != 0) {
×
539
        sixel_helper_set_additional_message(
×
540
            "failed to query terminal geometry with ioctl().");
541
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
542
    }
543

544
    if (ws.ws_col <= 0 || ws.ws_row <= 0 ||
×
545
        ws.ws_xpixel <= ws.ws_col || ws.ws_ypixel <= ws.ws_row) {
×
546
        sixel_helper_set_additional_message(
×
547
            "terminal does not report pixel cell size for drcs option.");
548
        return SIXEL_BAD_ARGUMENT;
×
549
    }
550

551
    encoder->cell_width = ws.ws_xpixel / ws.ws_col;
×
552
    encoder->cell_height = ws.ws_ypixel / ws.ws_row;
×
553
    if (encoder->cell_width <= 0 || encoder->cell_height <= 0) {
×
554
        sixel_helper_set_additional_message(
×
555
            "terminal cell size reported zero via ioctl().");
556
        return SIXEL_BAD_ARGUMENT;
×
557
    }
558

559
    return SIXEL_OK;
×
560
#else
561
    (void) encoder;
562
    sixel_helper_set_additional_message(
563
        "drcs option is not supported on this platform.");
564
    return SIXEL_NOT_IMPLEMENTED;
565
#endif
566
}
567

568

569
/* returns monochrome dithering context object */
570
static SIXELSTATUS
571
sixel_prepare_monochrome_palette(
12✔
572
    sixel_dither_t  /* out */ **dither,
573
     int            /* in */  finvert)
574
{
575
    SIXELSTATUS status = SIXEL_FALSE;
12✔
576

577
    if (finvert) {
12✔
578
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_LIGHT);
3✔
579
    } else {
1✔
580
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_DARK);
9✔
581
    }
582
    if (*dither == NULL) {
12!
583
        sixel_helper_set_additional_message(
×
584
            "sixel_prepare_monochrome_palette: sixel_dither_get() failed.");
585
        status = SIXEL_RUNTIME_ERROR;
×
586
        goto end;
×
587
    }
588

589
    status = SIXEL_OK;
12✔
590

591
end:
8✔
592
    return status;
12✔
593
}
594

595

596
/* returns dithering context object with specified builtin palette */
597
typedef struct palette_conversion {
598
    unsigned char *original;
599
    unsigned char *copy;
600
    size_t size;
601
    int convert_inplace;
602
    int converted;
603
    int frame_colorspace;
604
} palette_conversion_t;
605

606
static SIXELSTATUS
607
sixel_encoder_convert_palette(sixel_encoder_t *encoder,
513✔
608
                              sixel_output_t *output,
609
                              sixel_dither_t *dither,
610
                              int frame_colorspace,
611
                              int pixelformat,
612
                              palette_conversion_t *ctx)
613
{
614
    SIXELSTATUS status = SIXEL_OK;
513✔
615
    unsigned char *palette;
616
    int palette_colors;
617

618
    ctx->original = NULL;
513✔
619
    ctx->copy = NULL;
513✔
620
    ctx->size = 0;
513✔
621
    ctx->convert_inplace = 0;
513✔
622
    ctx->converted = 0;
513✔
623
    ctx->frame_colorspace = frame_colorspace;
513✔
624

625
    palette = sixel_dither_get_palette(dither);
513✔
626
    palette_colors = sixel_dither_get_num_of_palette_colors(dither);
513✔
627
    ctx->original = palette;
513✔
628

629
    if (palette == NULL || palette_colors <= 0 ||
513!
630
            frame_colorspace == output->colorspace) {
513!
631
        return SIXEL_OK;
513✔
632
    }
633

634
    ctx->size = (size_t)palette_colors * 3;
×
635

636
    output->pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
637
    output->source_colorspace = frame_colorspace;
×
638

639
    if (palette != (unsigned char *)(dither + 1)) {
×
640
        ctx->copy = (unsigned char *)sixel_allocator_malloc(
×
641
            encoder->allocator, ctx->size);
642
        if (ctx->copy == NULL) {
×
643
            sixel_helper_set_additional_message(
×
644
                "sixel_encoder_convert_palette: "
645
                "sixel_allocator_malloc() failed.");
646
            status = SIXEL_BAD_ALLOCATION;
×
647
            goto end;
×
648
        }
649
        memcpy(ctx->copy, palette, ctx->size);
×
650
        dither->palette = ctx->copy;
×
651
    } else {
652
        ctx->convert_inplace = 1;
×
653
    }
654

655
    status = sixel_output_convert_colorspace(output,
×
656
                                             dither->palette,
657
                                             ctx->size);
658
    if (SIXEL_FAILED(status)) {
×
659
        goto end;
×
660
    }
661
    ctx->converted = 1;
×
662

663
end:
664
    output->pixelformat = pixelformat;
×
665
    output->source_colorspace = frame_colorspace;
×
666

667
    return status;
×
668
}
177✔
669

670
static void
671
sixel_encoder_restore_palette(sixel_encoder_t *encoder,
513✔
672
                              sixel_dither_t *dither,
673
                              palette_conversion_t *ctx)
674
{
675
    if (ctx->copy) {
513!
676
        dither->palette = ctx->original;
×
677
        sixel_allocator_free(encoder->allocator, ctx->copy);
×
678
        ctx->copy = NULL;
×
679
    } else if (ctx->convert_inplace && ctx->converted &&
513!
680
               ctx->original && ctx->size > 0) {
×
681
        (void)sixel_helper_convert_colorspace(ctx->original,
×
682
                                              ctx->size,
683
                                              SIXEL_PIXELFORMAT_RGB888,
684
                                              SIXEL_COLORSPACE_GAMMA,
685
                                              ctx->frame_colorspace);
686
    }
687
}
513✔
688

689
static SIXELSTATUS
690
sixel_encoder_capture_quantized(sixel_encoder_t *encoder,
×
691
                                sixel_dither_t *dither,
692
                                unsigned char const *pixels,
693
                                size_t size,
694
                                int width,
695
                                int height,
696
                                int pixelformat,
697
                                int colorspace)
698
{
699
    SIXELSTATUS status;
700
    unsigned char *palette;
701
    int ncolors;
702
    size_t palette_bytes;
703
    unsigned char *new_pixels;
704
    unsigned char *new_palette;
705
    size_t capture_bytes;
706
    unsigned char const *capture_source;
707
    sixel_index_t *paletted_pixels;
708
    size_t quantized_pixels;
709
    sixel_allocator_t *dither_allocator;
710
    int saved_pixelformat;
711
    int restore_pixelformat;
712

713
    /*
714
     * Preserve the quantized frame for assessment observers.
715
     *
716
     *     +-----------------+     +---------------------+
717
     *     | quantized bytes | --> | encoder->capture_*  |
718
     *     +-----------------+     +---------------------+
719
     */
720

721
    status = SIXEL_OK;
×
722
    palette = NULL;
×
723
    ncolors = 0;
×
724
    palette_bytes = 0;
×
725
    new_pixels = NULL;
×
726
    new_palette = NULL;
×
727
    capture_bytes = size;
×
728
    capture_source = pixels;
×
729
    paletted_pixels = NULL;
×
730
    quantized_pixels = 0;
×
731
    dither_allocator = NULL;
×
732

733
    if (encoder == NULL || pixels == NULL ||
×
734
            (dither == NULL && size == 0)) {
×
735
        sixel_helper_set_additional_message(
×
736
            "sixel_encoder_capture_quantized: invalid capture request.");
737
        return SIXEL_BAD_ARGUMENT;
×
738
    }
739

740
    if (!encoder->capture_quantized) {
×
741
        return SIXEL_OK;
×
742
    }
743

744
    saved_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
745
    restore_pixelformat = 0;
×
746
    if (dither != NULL) {
×
747
        dither_allocator = dither->allocator;
×
748
        saved_pixelformat = dither->pixelformat;
×
749
        restore_pixelformat = 1;
×
750
        if (width <= 0 || height <= 0) {
×
751
            sixel_helper_set_additional_message(
×
752
                "sixel_encoder_capture_quantized: invalid dimensions.");
753
            status = SIXEL_BAD_ARGUMENT;
×
754
            goto cleanup;
×
755
        }
756
        quantized_pixels = (size_t)width * (size_t)height;
×
757
        if (height != 0 &&
×
758
                quantized_pixels / (size_t)height != (size_t)width) {
×
759
            sixel_helper_set_additional_message(
×
760
                "sixel_encoder_capture_quantized: image too large.");
761
            status = SIXEL_RUNTIME_ERROR;
×
762
            goto cleanup;
×
763
        }
764
        paletted_pixels = sixel_dither_apply_palette(
×
765
            dither, (unsigned char *)pixels, width, height);
766
        if (paletted_pixels == NULL) {
×
767
            sixel_helper_set_additional_message(
×
768
                "sixel_encoder_capture_quantized: palette conversion failed.");
769
            status = SIXEL_RUNTIME_ERROR;
×
770
            goto cleanup;
×
771
        }
772
        capture_source = (unsigned char const *)paletted_pixels;
×
773
        capture_bytes = quantized_pixels;
×
774
    }
775

776
    if (capture_bytes > 0) {
×
777
        if (encoder->capture_pixels == NULL ||
×
778
                encoder->capture_pixels_size < capture_bytes) {
×
779
            new_pixels = (unsigned char *)sixel_allocator_malloc(
×
780
                encoder->allocator, capture_bytes);
781
            if (new_pixels == NULL) {
×
782
                sixel_helper_set_additional_message(
×
783
                    "sixel_encoder_capture_quantized: "
784
                    "sixel_allocator_malloc() failed.");
785
                status = SIXEL_BAD_ALLOCATION;
×
786
                goto cleanup;
×
787
            }
788
            sixel_allocator_free(encoder->allocator, encoder->capture_pixels);
×
789
            encoder->capture_pixels = new_pixels;
×
790
            encoder->capture_pixels_size = capture_bytes;
×
791
        }
792
        memcpy(encoder->capture_pixels, capture_source, capture_bytes);
×
793
    }
794
    encoder->capture_pixel_bytes = capture_bytes;
×
795

796
    palette = NULL;
×
797
    ncolors = 0;
×
798
    palette_bytes = 0;
×
799
    if (dither != NULL) {
×
800
        palette = sixel_dither_get_palette(dither);
×
801
        ncolors = sixel_dither_get_num_of_palette_colors(dither);
×
802
    }
803
    if (palette != NULL && ncolors > 0) {
×
804
        palette_bytes = (size_t)ncolors * 3;
×
805
        if (encoder->capture_palette == NULL ||
×
806
                encoder->capture_palette_size < palette_bytes) {
×
807
            new_palette = (unsigned char *)sixel_allocator_malloc(
×
808
                encoder->allocator, palette_bytes);
809
            if (new_palette == NULL) {
×
810
                sixel_helper_set_additional_message(
×
811
                    "sixel_encoder_capture_quantized: "
812
                    "sixel_allocator_malloc() failed.");
813
                status = SIXEL_BAD_ALLOCATION;
×
814
                goto cleanup;
×
815
            }
816
            sixel_allocator_free(encoder->allocator,
×
817
                                 encoder->capture_palette);
×
818
            encoder->capture_palette = new_palette;
×
819
            encoder->capture_palette_size = palette_bytes;
×
820
        }
821
        memcpy(encoder->capture_palette, palette, palette_bytes);
×
822
    }
823

824
    encoder->capture_width = width;
×
825
    encoder->capture_height = height;
×
826
    if (dither != NULL) {
×
827
        encoder->capture_pixelformat = SIXEL_PIXELFORMAT_PAL8;
×
828
    } else {
829
        encoder->capture_pixelformat = pixelformat;
×
830
    }
831
    encoder->capture_colorspace = colorspace;
×
832
    encoder->capture_palette_size = palette_bytes;
×
833
    encoder->capture_ncolors = ncolors;
×
834
    encoder->capture_valid = 1;
×
835

836
cleanup:
837
    if (restore_pixelformat && dither != NULL) {
×
838
        /*
839
         * Undo the normalization performed by sixel_dither_apply_palette().
840
         *
841
         *     RGBA8888 --capture--> RGB888 (temporary)
842
         *          \______________________________/
843
         *                          |
844
         *                 restore original state for
845
         *                 the real encoder execution.
846
         */
847
        sixel_dither_set_pixelformat(dither, saved_pixelformat);
×
848
    }
849
    if (paletted_pixels != NULL && dither_allocator != NULL) {
×
850
        sixel_allocator_free(dither_allocator, paletted_pixels);
×
851
    }
852

853
    return status;
×
854
}
855

856
static SIXELSTATUS
857
sixel_prepare_builtin_palette(
27✔
858
    sixel_dither_t /* out */ **dither,
859
    int            /* in */  builtin_palette)
860
{
861
    SIXELSTATUS status = SIXEL_FALSE;
27✔
862

863
    *dither = sixel_dither_get(builtin_palette);
27✔
864
    if (*dither == NULL) {
27!
865
        sixel_helper_set_additional_message(
×
866
            "sixel_prepare_builtin_palette: sixel_dither_get() failed.");
867
        status = SIXEL_RUNTIME_ERROR;
×
868
        goto end;
×
869
    }
870

871
    status = SIXEL_OK;
27✔
872

873
end:
18✔
874
    return status;
27✔
875
}
876

877
static int
878
sixel_encoder_thumbnail_hint(sixel_encoder_t *encoder)
447✔
879
{
880
    int width_hint;
881
    int height_hint;
882
    long base;
883
    long size;
884

885
    width_hint = 0;
447✔
886
    height_hint = 0;
447✔
887
    base = 0;
447✔
888
    size = 0;
447✔
889

890
    if (encoder == NULL) {
447!
891
        return 0;
×
892
    }
893

894
    width_hint = encoder->pixelwidth;
447✔
895
    height_hint = encoder->pixelheight;
447✔
896

897
    /* Request extra resolution for downscaling to preserve detail. */
898
    if (width_hint > 0 && height_hint > 0) {
447✔
899
        /* Follow the CLI rule: double the larger axis before doubling
900
         * again for the final request size. */
901
        if (width_hint >= height_hint) {
9!
902
            base = (long)width_hint;
9✔
903
        } else {
3✔
904
            base = (long)height_hint;
×
905
        }
906
        base *= 2L;
9✔
907
    } else if (width_hint > 0) {
441✔
908
        base = (long)width_hint;
48✔
909
    } else if (height_hint > 0) {
406✔
910
        base = (long)height_hint;
36✔
911
    } else {
12✔
912
        return 0;
354✔
913
    }
914

915
    size = base * 2L;
93✔
916
    if (size > (long)INT_MAX) {
93!
917
        size = (long)INT_MAX;
×
918
    }
919
    if (size < 1L) {
93!
920
        size = 1L;
×
921
    }
922

923
    return (int)size;
93✔
924
}
149✔
925

926

927
typedef struct sixel_callback_context_for_mapfile {
928
    int reqcolors;
929
    sixel_dither_t *dither;
930
    sixel_allocator_t *allocator;
931
    int working_colorspace;
932
    int lut_policy;
933
} sixel_callback_context_for_mapfile_t;
934

935

936
/* callback function for sixel_helper_load_image_file() */
937
static SIXELSTATUS
938
load_image_callback_for_palette(
21✔
939
    sixel_frame_t   /* in */    *frame, /* frame object from image loader */
940
    void            /* in */    *data)  /* private data */
941
{
942
    SIXELSTATUS status = SIXEL_FALSE;
21✔
943
    sixel_callback_context_for_mapfile_t *callback_context;
944

945
    /* get callback context object from the private data */
946
    callback_context = (sixel_callback_context_for_mapfile_t *)data;
21✔
947

948
    status = sixel_frame_ensure_colorspace(frame,
28✔
949
                                           callback_context->working_colorspace);
7✔
950
    if (SIXEL_FAILED(status)) {
21!
951
        goto end;
×
952
    }
953

954
    switch (sixel_frame_get_pixelformat(frame)) {
21!
955
    case SIXEL_PIXELFORMAT_PAL1:
2✔
956
    case SIXEL_PIXELFORMAT_PAL2:
957
    case SIXEL_PIXELFORMAT_PAL4:
958
    case SIXEL_PIXELFORMAT_PAL8:
959
        if (sixel_frame_get_palette(frame) == NULL) {
3!
960
            status = SIXEL_LOGIC_ERROR;
×
961
            goto end;
×
962
        }
963
        /* create new dither object */
964
        status = sixel_dither_new(
3✔
965
            &callback_context->dither,
1✔
966
            sixel_frame_get_ncolors(frame),
1✔
967
            callback_context->allocator);
1✔
968
        if (SIXEL_FAILED(status)) {
3!
969
            goto end;
×
970
        }
971

972
        sixel_dither_set_lut_policy(callback_context->dither,
4✔
973
                                    callback_context->lut_policy);
1✔
974

975
        /* use palette which is extracted from the image */
976
        sixel_dither_set_palette(callback_context->dither,
4✔
977
                                 sixel_frame_get_palette(frame));
1✔
978
        /* success */
979
        status = SIXEL_OK;
3✔
980
        break;
3✔
981
    case SIXEL_PIXELFORMAT_G1:
982
        /* use 1bpp grayscale builtin palette */
983
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
984
        /* success */
985
        status = SIXEL_OK;
×
986
        break;
×
987
    case SIXEL_PIXELFORMAT_G2:
988
        /* use 2bpp grayscale builtin palette */
989
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
990
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
991
        /* success */
992
        status = SIXEL_OK;
×
993
        break;
×
994
    case SIXEL_PIXELFORMAT_G4:
995
        /* use 4bpp grayscale builtin palette */
996
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
997
        /* success */
998
        status = SIXEL_OK;
×
999
        break;
×
1000
    case SIXEL_PIXELFORMAT_G8:
1001
        /* use 8bpp grayscale builtin palette */
1002
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
1003
        /* success */
1004
        status = SIXEL_OK;
×
1005
        break;
×
1006
    default:
12✔
1007
        /* create new dither object */
1008
        status = sixel_dither_new(
18✔
1009
            &callback_context->dither,
6✔
1010
            callback_context->reqcolors,
6✔
1011
            callback_context->allocator);
6✔
1012
        if (SIXEL_FAILED(status)) {
18!
1013
            goto end;
×
1014
        }
1015

1016
        sixel_dither_set_lut_policy(callback_context->dither,
24✔
1017
                                    callback_context->lut_policy);
6✔
1018

1019
        /* create adaptive palette from given frame object */
1020
        status = sixel_dither_initialize(callback_context->dither,
24✔
1021
                                         sixel_frame_get_pixels(frame),
6✔
1022
                                         sixel_frame_get_width(frame),
6✔
1023
                                         sixel_frame_get_height(frame),
6✔
1024
                                         sixel_frame_get_pixelformat(frame),
6✔
1025
                                         SIXEL_LARGE_NORM,
1026
                                         SIXEL_REP_CENTER_BOX,
1027
                                         SIXEL_QUALITY_HIGH);
1028
        if (SIXEL_FAILED(status)) {
18!
1029
            sixel_dither_unref(callback_context->dither);
×
1030
            goto end;
×
1031
        }
1032

1033
        /* success */
1034
        status = SIXEL_OK;
18✔
1035

1036
        break;
18✔
1037
    }
7✔
1038

1039
end:
14✔
1040
    return status;
21✔
1041
}
1042

1043

1044
static SIXELSTATUS
1045
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder);
1046

1047

1048
static int
1049
sixel_path_has_extension(char const *path, char const *extension)
78✔
1050
{
1051
    size_t path_len;
1052
    size_t ext_len;
1053
    size_t index;
1054

1055
    path_len = 0u;
78✔
1056
    ext_len = 0u;
78✔
1057
    index = 0u;
78✔
1058

1059
    if (path == NULL || extension == NULL) {
78!
NEW
1060
        return 0;
×
1061
    }
1062

1063
    path_len = strlen(path);
78✔
1064
    ext_len = strlen(extension);
78✔
1065
    if (ext_len == 0u || path_len < ext_len) {
78!
NEW
1066
        return 0;
×
1067
    }
1068

1069
    for (index = 0u; index < ext_len; ++index) {
159!
1070
        unsigned char path_ch;
1071
        unsigned char ext_ch;
1072

1073
        path_ch = (unsigned char)path[path_len - ext_len + index];
159✔
1074
        ext_ch = (unsigned char)extension[index];
159✔
1075
        if (tolower(path_ch) != tolower(ext_ch)) {
159✔
1076
            return 0;
78✔
1077
        }
1078
    }
27✔
1079

NEW
1080
    return 1;
×
1081
}
30✔
1082

1083
typedef enum sixel_palette_format {
1084
    SIXEL_PALETTE_FORMAT_NONE = 0,
1085
    SIXEL_PALETTE_FORMAT_ACT,
1086
    SIXEL_PALETTE_FORMAT_PAL_JASC,
1087
    SIXEL_PALETTE_FORMAT_PAL_RIFF,
1088
    SIXEL_PALETTE_FORMAT_PAL_AUTO,
1089
    SIXEL_PALETTE_FORMAT_GPL
1090
} sixel_palette_format_t;
1091

1092
/*
1093
 * Palette specification parser
1094
 *
1095
 *   TYPE:PATH  -> explicit format prefix
1096
 *   PATH       -> rely on extension or heuristics
1097
 *
1098
 * The ASCII diagram below shows how the prefix is peeled:
1099
 *
1100
 *   [type] : [path]
1101
 *    ^-- left part selects decoder/encoder when present.
1102
 */
1103
static char const *
1104
sixel_palette_strip_prefix(char const *spec,
26✔
1105
                           sixel_palette_format_t *format_hint)
1106
{
1107
    char const *colon;
1108
    size_t type_len;
1109
    size_t index;
1110
    char lowered[16];
1111

1112
    colon = NULL;
26✔
1113
    type_len = 0u;
26✔
1114
    index = 0u;
26✔
1115

1116
    if (format_hint != NULL) {
26!
1117
        *format_hint = SIXEL_PALETTE_FORMAT_NONE;
26✔
1118
    }
10✔
1119
    if (spec == NULL) {
26!
NEW
1120
        return NULL;
×
1121
    }
1122

1123
    colon = strchr(spec, ':');
26✔
1124
    if (colon == NULL) {
26!
1125
        return spec;
26✔
1126
    }
1127

NEW
1128
    type_len = (size_t)(colon - spec);
×
NEW
1129
    if (type_len == 0u || type_len >= sizeof(lowered)) {
×
NEW
1130
        return spec;
×
1131
    }
1132

NEW
1133
    for (index = 0u; index < type_len; ++index) {
×
NEW
1134
        lowered[index] = (char)tolower((unsigned char)spec[index]);
×
1135
    }
NEW
1136
    lowered[type_len] = '\0';
×
1137

NEW
1138
    if (strcmp(lowered, "act") == 0) {
×
NEW
1139
        if (format_hint != NULL) {
×
NEW
1140
            *format_hint = SIXEL_PALETTE_FORMAT_ACT;
×
1141
        }
NEW
1142
        return colon + 1;
×
1143
    }
NEW
1144
    if (strcmp(lowered, "pal") == 0) {
×
NEW
1145
        if (format_hint != NULL) {
×
NEW
1146
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1147
        }
NEW
1148
        return colon + 1;
×
1149
    }
NEW
1150
    if (strcmp(lowered, "pal-jasc") == 0) {
×
NEW
1151
        if (format_hint != NULL) {
×
NEW
1152
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1153
        }
NEW
1154
        return colon + 1;
×
1155
    }
NEW
1156
    if (strcmp(lowered, "pal-riff") == 0) {
×
NEW
1157
        if (format_hint != NULL) {
×
NEW
1158
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1159
        }
NEW
1160
        return colon + 1;
×
1161
    }
NEW
1162
    if (strcmp(lowered, "gpl") == 0) {
×
NEW
1163
        if (format_hint != NULL) {
×
NEW
1164
            *format_hint = SIXEL_PALETTE_FORMAT_GPL;
×
1165
        }
NEW
1166
        return colon + 1;
×
1167
    }
1168

NEW
1169
    return spec;
×
1170
}
10✔
1171

1172
static sixel_palette_format_t
1173
sixel_palette_format_from_extension(char const *path)
26✔
1174
{
1175
    if (path == NULL) {
26!
NEW
1176
        return SIXEL_PALETTE_FORMAT_NONE;
×
1177
    }
1178

1179
    if (sixel_path_has_extension(path, ".act")) {
26!
NEW
1180
        return SIXEL_PALETTE_FORMAT_ACT;
×
1181
    }
1182
    if (sixel_path_has_extension(path, ".pal")) {
26!
NEW
1183
        return SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1184
    }
1185
    if (sixel_path_has_extension(path, ".gpl")) {
26!
NEW
1186
        return SIXEL_PALETTE_FORMAT_GPL;
×
1187
    }
1188

1189
    return SIXEL_PALETTE_FORMAT_NONE;
26✔
1190
}
10✔
1191

1192
static int
1193
sixel_path_has_any_extension(char const *path)
26✔
1194
{
1195
    char const *slash_forward;
1196
#if defined(_WIN32)
1197
    char const *slash_backward;
1198
#endif
1199
    char const *start;
1200
    char const *dot;
1201

1202
    slash_forward = NULL;
26✔
1203
#if defined(_WIN32)
1204
    slash_backward = NULL;
1205
#endif
1206
    start = path;
26✔
1207
    dot = NULL;
26✔
1208

1209
    if (path == NULL) {
26!
NEW
1210
        return 0;
×
1211
    }
1212

1213
    slash_forward = strrchr(path, '/');
26✔
1214
#if defined(_WIN32)
1215
    slash_backward = strrchr(path, '\\');
1216
    if (slash_backward != NULL &&
1217
            (slash_forward == NULL || slash_backward > slash_forward)) {
1218
        slash_forward = slash_backward;
1219
    }
1220
#endif
1221
    if (slash_forward == NULL) {
26!
NEW
1222
        start = path;
×
1223
    } else {
1224
        start = slash_forward + 1;
26✔
1225
    }
1226

1227
    dot = strrchr(start, '.');
26✔
1228
    if (dot == NULL) {
26✔
1229
        return 0;
5✔
1230
    }
1231

1232
    if (dot[1] == '\0') {
21!
NEW
1233
        return 0;
×
1234
    }
1235

1236
    return 1;
21✔
1237
}
10✔
1238

1239
static int
NEW
1240
sixel_palette_has_utf8_bom(unsigned char const *data, size_t size)
×
1241
{
NEW
1242
    if (data == NULL || size < 3u) {
×
NEW
1243
        return 0;
×
1244
    }
NEW
1245
    if (data[0] == 0xefu && data[1] == 0xbbu && data[2] == 0xbfu) {
×
NEW
1246
        return 1;
×
1247
    }
NEW
1248
    return 0;
×
1249
}
1250

1251

1252
/*
1253
 * Materialize palette bytes from a stream.
1254
 *
1255
 * The flow looks like:
1256
 *
1257
 *   stream --> [scratch buffer] --> [resizable heap buffer]
1258
 *                  ^ looped read        ^ returned payload
1259
 */
1260
static SIXELSTATUS
NEW
1261
sixel_palette_read_stream(FILE *stream,
×
1262
                          sixel_allocator_t *allocator,
1263
                          unsigned char **pdata,
1264
                          size_t *psize)
1265
{
1266
    SIXELSTATUS status;
1267
    unsigned char *buffer;
1268
    unsigned char *grown;
1269
    size_t capacity;
1270
    size_t used;
1271
    size_t read_bytes;
1272
    size_t needed;
1273
    size_t new_capacity;
1274
    unsigned char scratch[4096];
1275

NEW
1276
    status = SIXEL_FALSE;
×
NEW
1277
    buffer = NULL;
×
NEW
1278
    grown = NULL;
×
NEW
1279
    capacity = 0u;
×
NEW
1280
    used = 0u;
×
NEW
1281
    read_bytes = 0u;
×
NEW
1282
    needed = 0u;
×
NEW
1283
    new_capacity = 0u;
×
1284

NEW
1285
    if (pdata == NULL || psize == NULL || stream == NULL || allocator == NULL) {
×
NEW
1286
        sixel_helper_set_additional_message(
×
1287
            "sixel_palette_read_stream: invalid argument.");
NEW
1288
        return SIXEL_BAD_ARGUMENT;
×
1289
    }
1290

NEW
1291
    *pdata = NULL;
×
NEW
1292
    *psize = 0u;
×
1293

1294
    while (1) {
NEW
1295
        read_bytes = fread(scratch, 1, sizeof(scratch), stream);
×
NEW
1296
        if (read_bytes == 0u) {
×
NEW
1297
            if (ferror(stream)) {
×
NEW
1298
                sixel_helper_set_additional_message(
×
1299
                    "sixel_palette_read_stream: fread() failed.");
NEW
1300
                status = SIXEL_LIBC_ERROR;
×
NEW
1301
                goto cleanup;
×
1302
            }
NEW
1303
            break;
×
1304
        }
1305

NEW
1306
        if (used > SIZE_MAX - read_bytes) {
×
NEW
1307
            sixel_helper_set_additional_message(
×
1308
                "sixel_palette_read_stream: size overflow.");
NEW
1309
            status = SIXEL_BAD_ALLOCATION;
×
NEW
1310
            goto cleanup;
×
1311
        }
NEW
1312
        needed = used + read_bytes;
×
1313

NEW
1314
        if (needed > capacity) {
×
NEW
1315
            new_capacity = capacity;
×
NEW
1316
            if (new_capacity == 0u) {
×
NEW
1317
                new_capacity = 4096u;
×
1318
            }
NEW
1319
            while (needed > new_capacity) {
×
NEW
1320
                if (new_capacity > SIZE_MAX / 2u) {
×
NEW
1321
                    sixel_helper_set_additional_message(
×
1322
                        "sixel_palette_read_stream: size overflow.");
NEW
1323
                    status = SIXEL_BAD_ALLOCATION;
×
NEW
1324
                    goto cleanup;
×
1325
                }
NEW
1326
                new_capacity *= 2u;
×
1327
            }
1328

NEW
1329
            grown = (unsigned char *)sixel_allocator_malloc(allocator,
×
1330
                                                             new_capacity);
NEW
1331
            if (grown == NULL) {
×
NEW
1332
                sixel_helper_set_additional_message(
×
1333
                    "sixel_palette_read_stream: allocation failed.");
NEW
1334
                status = SIXEL_BAD_ALLOCATION;
×
NEW
1335
                goto cleanup;
×
1336
            }
1337

NEW
1338
            if (buffer != NULL) {
×
NEW
1339
                memcpy(grown, buffer, used);
×
NEW
1340
                sixel_allocator_free(allocator, buffer);
×
1341
            }
1342

NEW
1343
            buffer = grown;
×
NEW
1344
            grown = NULL;
×
NEW
1345
            capacity = new_capacity;
×
1346
        }
1347

NEW
1348
        memcpy(buffer + used, scratch, read_bytes);
×
NEW
1349
        used += read_bytes;
×
1350
    }
1351

NEW
1352
    *pdata = buffer;
×
NEW
1353
    *psize = used;
×
NEW
1354
    status = SIXEL_OK;
×
NEW
1355
    return status;
×
1356

1357
cleanup:
NEW
1358
    if (grown != NULL) {
×
NEW
1359
        sixel_allocator_free(allocator, grown);
×
1360
    }
NEW
1361
    if (buffer != NULL) {
×
NEW
1362
        sixel_allocator_free(allocator, buffer);
×
1363
    }
NEW
1364
    return status;
×
1365
}
1366

1367

1368
static SIXELSTATUS
1369
sixel_palette_open_read(char const *path, FILE **pstream, int *pclose)
5✔
1370
{
1371
    if (pstream == NULL || pclose == NULL || path == NULL) {
5!
NEW
1372
        sixel_helper_set_additional_message(
×
1373
            "sixel_palette_open_read: invalid argument.");
NEW
1374
        return SIXEL_BAD_ARGUMENT;
×
1375
    }
1376

1377
    if (strcmp(path, "-") == 0) {
5!
NEW
1378
        *pstream = stdin;
×
NEW
1379
        *pclose = 0;
×
NEW
1380
        return SIXEL_OK;
×
1381
    }
1382

1383
    *pstream = fopen(path, "rb");
5✔
1384
    if (*pstream == NULL) {
5!
1385
        sixel_helper_set_additional_message(
5✔
1386
            "sixel_palette_open_read: failed to open file.");
1387
        return SIXEL_LIBC_ERROR;
5✔
1388
    }
1389

NEW
1390
    *pclose = 1;
×
NEW
1391
    return SIXEL_OK;
×
1392
}
3✔
1393

1394

1395
static void
NEW
1396
sixel_palette_close_stream(FILE *stream, int close_stream)
×
1397
{
NEW
1398
    if (close_stream && stream != NULL) {
×
NEW
1399
        (void) fclose(stream);
×
1400
    }
NEW
1401
}
×
1402

1403

1404
static sixel_palette_format_t
NEW
1405
sixel_palette_guess_format(unsigned char const *data, size_t size)
×
1406
{
1407
    size_t offset;
1408
    size_t data_size;
1409

NEW
1410
    offset = 0u;
×
NEW
1411
    data_size = size;
×
1412

NEW
1413
    if (data == NULL || size == 0u) {
×
NEW
1414
        return SIXEL_PALETTE_FORMAT_NONE;
×
1415
    }
1416

NEW
1417
    if (size == 256u * 3u || size == 256u * 3u + 4u) {
×
NEW
1418
        return SIXEL_PALETTE_FORMAT_ACT;
×
1419
    }
1420

NEW
1421
    if (size >= 12u && memcmp(data, "RIFF", 4) == 0
×
NEW
1422
            && memcmp(data + 8, "PAL ", 4) == 0) {
×
NEW
1423
        return SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1424
    }
1425

NEW
1426
    if (sixel_palette_has_utf8_bom(data, size)) {
×
NEW
1427
        offset = 3u;
×
NEW
1428
        data_size = size - 3u;
×
1429
    }
1430

NEW
1431
    if (data_size >= 8u && memcmp(data + offset, "JASC-PAL", 8) == 0) {
×
NEW
1432
        return SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1433
    }
NEW
1434
    if (data_size >= 12u && memcmp(data + offset, "GIMP Palette", 12) == 0) {
×
NEW
1435
        return SIXEL_PALETTE_FORMAT_GPL;
×
1436
    }
1437

NEW
1438
    return SIXEL_PALETTE_FORMAT_NONE;
×
1439
}
1440

1441

1442
static unsigned int
NEW
1443
sixel_palette_read_le16(unsigned char const *ptr)
×
1444
{
NEW
1445
    if (ptr == NULL) {
×
NEW
1446
        return 0u;
×
1447
    }
NEW
1448
    return (unsigned int)ptr[0] | ((unsigned int)ptr[1] << 8);
×
1449
}
1450

1451

1452
static unsigned int
NEW
1453
sixel_palette_read_le32(unsigned char const *ptr)
×
1454
{
NEW
1455
    if (ptr == NULL) {
×
NEW
1456
        return 0u;
×
1457
    }
NEW
1458
    return ((unsigned int)ptr[0])
×
NEW
1459
        | ((unsigned int)ptr[1] << 8)
×
NEW
1460
        | ((unsigned int)ptr[2] << 16)
×
NEW
1461
        | ((unsigned int)ptr[3] << 24);
×
1462
}
1463

1464

1465
/*
1466
 * Adobe Color Table (*.act) reader
1467
 *
1468
 *   +-----------+---------------------------+
1469
 *   | section   | bytes                     |
1470
 *   +-----------+---------------------------+
1471
 *   | palette   | 256 entries * 3 RGB bytes |
1472
 *   | trailer   | optional count/start pair |
1473
 *   +-----------+---------------------------+
1474
 */
1475
static SIXELSTATUS
NEW
1476
sixel_palette_parse_act(unsigned char const *data,
×
1477
                        size_t size,
1478
                        sixel_encoder_t *encoder,
1479
                        sixel_dither_t **dither)
1480
{
1481
    SIXELSTATUS status;
1482
    sixel_dither_t *local;
1483
    unsigned char const *palette_start;
1484
    unsigned char const *trailer;
1485
    unsigned char *target;
1486
    size_t copy_bytes;
1487
    int exported_colors;
1488
    int start_index;
1489

NEW
1490
    status = SIXEL_FALSE;
×
NEW
1491
    local = NULL;
×
NEW
1492
    palette_start = data;
×
NEW
1493
    trailer = NULL;
×
NEW
1494
    target = NULL;
×
NEW
1495
    copy_bytes = 0u;
×
NEW
1496
    exported_colors = 0;
×
NEW
1497
    start_index = 0;
×
1498

NEW
1499
    if (encoder == NULL || dither == NULL) {
×
NEW
1500
        sixel_helper_set_additional_message(
×
1501
            "sixel_palette_parse_act: invalid argument.");
NEW
1502
        return SIXEL_BAD_ARGUMENT;
×
1503
    }
NEW
1504
    if (data == NULL || size < 256u * 3u) {
×
NEW
1505
        sixel_helper_set_additional_message(
×
1506
            "sixel_palette_parse_act: truncated ACT palette.");
NEW
1507
        return SIXEL_BAD_INPUT;
×
1508
    }
1509

NEW
1510
    if (size == 256u * 3u) {
×
NEW
1511
        exported_colors = 256;
×
NEW
1512
        start_index = 0;
×
NEW
1513
    } else if (size == 256u * 3u + 4u) {
×
NEW
1514
        trailer = data + 256u * 3u;
×
NEW
1515
        exported_colors = (int)(((unsigned int)trailer[0] << 8)
×
NEW
1516
                                | (unsigned int)trailer[1]);
×
NEW
1517
        start_index = (int)(((unsigned int)trailer[2] << 8)
×
NEW
1518
                            | (unsigned int)trailer[3]);
×
1519
    } else {
NEW
1520
        sixel_helper_set_additional_message(
×
1521
            "sixel_palette_parse_act: invalid ACT length.");
NEW
1522
        return SIXEL_BAD_INPUT;
×
1523
    }
1524

NEW
1525
    if (start_index < 0 || start_index >= 256) {
×
NEW
1526
        sixel_helper_set_additional_message(
×
1527
            "sixel_palette_parse_act: ACT start index out of range.");
NEW
1528
        return SIXEL_BAD_INPUT;
×
1529
    }
NEW
1530
    if (exported_colors <= 0 || exported_colors > 256) {
×
NEW
1531
        exported_colors = 256;
×
1532
    }
NEW
1533
    if (start_index + exported_colors > 256) {
×
NEW
1534
        sixel_helper_set_additional_message(
×
1535
            "sixel_palette_parse_act: ACT palette exceeds 256 slots.");
NEW
1536
        return SIXEL_BAD_INPUT;
×
1537
    }
1538

NEW
1539
    status = sixel_dither_new(&local, exported_colors, encoder->allocator);
×
NEW
1540
    if (SIXEL_FAILED(status)) {
×
NEW
1541
        return status;
×
1542
    }
1543

NEW
1544
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1545

NEW
1546
    target = sixel_dither_get_palette(local);
×
NEW
1547
    copy_bytes = (size_t)exported_colors * 3u;
×
NEW
1548
    memcpy(target, palette_start + (size_t)start_index * 3u, copy_bytes);
×
1549

NEW
1550
    *dither = local;
×
NEW
1551
    return SIXEL_OK;
×
1552
}
1553

1554

1555
static SIXELSTATUS
NEW
1556
sixel_palette_parse_pal_jasc(unsigned char const *data,
×
1557
                             size_t size,
1558
                             sixel_encoder_t *encoder,
1559
                             sixel_dither_t **dither)
1560
{
1561
    SIXELSTATUS status;
1562
    char *text;
1563
    size_t index;
1564
    size_t offset;
1565
    char *cursor;
1566
    char *line;
1567
    char *line_end;
1568
    int stage;
1569
    int exported_colors;
1570
    int parsed_colors;
1571
    sixel_dither_t *local;
1572
    unsigned char *target;
1573
    long component;
1574
    char *parse_end;
1575
    int value_index;
1576
    int values[3];
1577
    char tail;
1578

NEW
1579
    status = SIXEL_FALSE;
×
NEW
1580
    text = NULL;
×
NEW
1581
    index = 0u;
×
NEW
1582
    offset = 0u;
×
NEW
1583
    cursor = NULL;
×
NEW
1584
    line = NULL;
×
NEW
1585
    line_end = NULL;
×
NEW
1586
    stage = 0;
×
NEW
1587
    exported_colors = 0;
×
NEW
1588
    parsed_colors = 0;
×
NEW
1589
    local = NULL;
×
NEW
1590
    target = NULL;
×
NEW
1591
    component = 0;
×
NEW
1592
    parse_end = NULL;
×
NEW
1593
    value_index = 0;
×
NEW
1594
    values[0] = 0;
×
NEW
1595
    values[1] = 0;
×
NEW
1596
    values[2] = 0;
×
1597

NEW
1598
    if (encoder == NULL || dither == NULL) {
×
NEW
1599
        sixel_helper_set_additional_message(
×
1600
            "sixel_palette_parse_pal_jasc: invalid argument.");
NEW
1601
        return SIXEL_BAD_ARGUMENT;
×
1602
    }
NEW
1603
    if (data == NULL || size == 0u) {
×
NEW
1604
        sixel_helper_set_additional_message(
×
1605
            "sixel_palette_parse_pal_jasc: empty palette.");
NEW
1606
        return SIXEL_BAD_INPUT;
×
1607
    }
1608

NEW
1609
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
NEW
1610
    if (text == NULL) {
×
NEW
1611
        sixel_helper_set_additional_message(
×
1612
            "sixel_palette_parse_pal_jasc: allocation failed.");
NEW
1613
        return SIXEL_BAD_ALLOCATION;
×
1614
    }
NEW
1615
    memcpy(text, data, size);
×
NEW
1616
    text[size] = '\0';
×
1617

NEW
1618
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
NEW
1619
        offset = 3u;
×
1620
    }
NEW
1621
    cursor = text + offset;
×
1622

NEW
1623
    while (*cursor != '\0') {
×
NEW
1624
        line = cursor;
×
NEW
1625
        line_end = cursor;
×
NEW
1626
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
NEW
1627
            ++line_end;
×
1628
        }
NEW
1629
        if (*line_end != '\0') {
×
NEW
1630
            *line_end = '\0';
×
NEW
1631
            cursor = line_end + 1;
×
1632
        } else {
NEW
1633
            cursor = line_end;
×
1634
        }
NEW
1635
        while (*cursor == '\n' || *cursor == '\r') {
×
NEW
1636
            ++cursor;
×
1637
        }
1638

NEW
1639
        while (*line == ' ' || *line == '\t') {
×
NEW
1640
            ++line;
×
1641
        }
NEW
1642
        index = strlen(line);
×
NEW
1643
        while (index > 0u) {
×
NEW
1644
            tail = line[index - 1];
×
NEW
1645
            if (tail != ' ' && tail != '\t') {
×
NEW
1646
                break;
×
1647
            }
NEW
1648
            line[index - 1] = '\0';
×
NEW
1649
            --index;
×
1650
        }
NEW
1651
        if (*line == '\0') {
×
NEW
1652
            continue;
×
1653
        }
NEW
1654
        if (*line == '#') {
×
NEW
1655
            continue;
×
1656
        }
1657

NEW
1658
        if (stage == 0) {
×
NEW
1659
            if (strcmp(line, "JASC-PAL") != 0) {
×
NEW
1660
                sixel_helper_set_additional_message(
×
1661
                    "sixel_palette_parse_pal_jasc: missing header.");
NEW
1662
                status = SIXEL_BAD_INPUT;
×
NEW
1663
                goto cleanup;
×
1664
            }
NEW
1665
            stage = 1;
×
NEW
1666
            continue;
×
1667
        }
NEW
1668
        if (stage == 1) {
×
NEW
1669
            stage = 2;
×
NEW
1670
            continue;
×
1671
        }
NEW
1672
        if (stage == 2) {
×
NEW
1673
            component = strtol(line, &parse_end, 10);
×
NEW
1674
            if (parse_end == line || component <= 0L || component > 256L) {
×
NEW
1675
                sixel_helper_set_additional_message(
×
1676
                    "sixel_palette_parse_pal_jasc: invalid color count.");
NEW
1677
                status = SIXEL_BAD_INPUT;
×
NEW
1678
                goto cleanup;
×
1679
            }
NEW
1680
            exported_colors = (int)component;
×
NEW
1681
            status = sixel_dither_new(&local, exported_colors,
×
1682
                                      encoder->allocator);
NEW
1683
            if (SIXEL_FAILED(status)) {
×
NEW
1684
                goto cleanup;
×
1685
            }
NEW
1686
            sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
NEW
1687
            target = sixel_dither_get_palette(local);
×
NEW
1688
            stage = 3;
×
NEW
1689
            continue;
×
1690
        }
1691

NEW
1692
        value_index = 0;
×
NEW
1693
        while (value_index < 3) {
×
NEW
1694
            component = strtol(line, &parse_end, 10);
×
NEW
1695
            if (parse_end == line || component < 0L || component > 255L) {
×
NEW
1696
                sixel_helper_set_additional_message(
×
1697
                    "sixel_palette_parse_pal_jasc: invalid component.");
NEW
1698
                status = SIXEL_BAD_INPUT;
×
NEW
1699
                goto cleanup;
×
1700
            }
NEW
1701
            values[value_index] = (int)component;
×
NEW
1702
            ++value_index;
×
NEW
1703
            line = parse_end;
×
NEW
1704
            while (*line == ' ' || *line == '\t') {
×
NEW
1705
                ++line;
×
1706
            }
1707
        }
1708

NEW
1709
        if (parsed_colors >= exported_colors) {
×
NEW
1710
            sixel_helper_set_additional_message(
×
1711
                "sixel_palette_parse_pal_jasc: excess entries.");
NEW
1712
            status = SIXEL_BAD_INPUT;
×
NEW
1713
            goto cleanup;
×
1714
        }
1715

NEW
1716
        target[parsed_colors * 3 + 0] =
×
NEW
1717
            (unsigned char)values[0];
×
NEW
1718
        target[parsed_colors * 3 + 1] =
×
NEW
1719
            (unsigned char)values[1];
×
NEW
1720
        target[parsed_colors * 3 + 2] =
×
NEW
1721
            (unsigned char)values[2];
×
NEW
1722
        ++parsed_colors;
×
1723
    }
1724

NEW
1725
    if (stage < 3) {
×
NEW
1726
        sixel_helper_set_additional_message(
×
1727
            "sixel_palette_parse_pal_jasc: incomplete header.");
NEW
1728
        status = SIXEL_BAD_INPUT;
×
NEW
1729
        goto cleanup;
×
1730
    }
NEW
1731
    if (parsed_colors != exported_colors) {
×
NEW
1732
        sixel_helper_set_additional_message(
×
1733
            "sixel_palette_parse_pal_jasc: color count mismatch.");
NEW
1734
        status = SIXEL_BAD_INPUT;
×
NEW
1735
        goto cleanup;
×
1736
    }
1737

NEW
1738
    *dither = local;
×
NEW
1739
    status = SIXEL_OK;
×
1740

1741
cleanup:
NEW
1742
    if (SIXEL_FAILED(status) && local != NULL) {
×
NEW
1743
        sixel_dither_unref(local);
×
1744
    }
NEW
1745
    if (text != NULL) {
×
NEW
1746
        sixel_allocator_free(encoder->allocator, text);
×
1747
    }
NEW
1748
    return status;
×
1749
}
1750

1751

1752
static SIXELSTATUS
NEW
1753
sixel_palette_parse_pal_riff(unsigned char const *data,
×
1754
                             size_t size,
1755
                             sixel_encoder_t *encoder,
1756
                             sixel_dither_t **dither)
1757
{
1758
    SIXELSTATUS status;
1759
    size_t offset;
1760
    size_t chunk_size;
1761
    sixel_dither_t *local;
1762
    unsigned char const *chunk;
1763
    unsigned char *target;
1764
    unsigned int entry_count;
1765
    unsigned int version;
1766
    unsigned int index;
1767
    size_t palette_offset;
1768

NEW
1769
    status = SIXEL_FALSE;
×
NEW
1770
    offset = 0u;
×
NEW
1771
    chunk_size = 0u;
×
NEW
1772
    local = NULL;
×
NEW
1773
    chunk = NULL;
×
NEW
1774
    target = NULL;
×
NEW
1775
    entry_count = 0u;
×
NEW
1776
    version = 0u;
×
NEW
1777
    index = 0u;
×
NEW
1778
    palette_offset = 0u;
×
1779

NEW
1780
    if (encoder == NULL || dither == NULL) {
×
NEW
1781
        sixel_helper_set_additional_message(
×
1782
            "sixel_palette_parse_pal_riff: invalid argument.");
NEW
1783
        return SIXEL_BAD_ARGUMENT;
×
1784
    }
NEW
1785
    if (data == NULL || size < 12u) {
×
NEW
1786
        sixel_helper_set_additional_message(
×
1787
            "sixel_palette_parse_pal_riff: truncated palette.");
NEW
1788
        return SIXEL_BAD_INPUT;
×
1789
    }
NEW
1790
    if (memcmp(data, "RIFF", 4) != 0 || memcmp(data + 8, "PAL ", 4) != 0) {
×
NEW
1791
        sixel_helper_set_additional_message(
×
1792
            "sixel_palette_parse_pal_riff: missing RIFF header.");
NEW
1793
        return SIXEL_BAD_INPUT;
×
1794
    }
1795

NEW
1796
    offset = 12u;
×
NEW
1797
    while (offset + 8u <= size) {
×
NEW
1798
        chunk = data + offset;
×
NEW
1799
        chunk_size = (size_t)sixel_palette_read_le32(chunk + 4);
×
NEW
1800
        if (offset + 8u + chunk_size > size) {
×
NEW
1801
            sixel_helper_set_additional_message(
×
1802
                "sixel_palette_parse_pal_riff: chunk extends past end.");
NEW
1803
            return SIXEL_BAD_INPUT;
×
1804
        }
NEW
1805
        if (memcmp(chunk, "data", 4) == 0) {
×
NEW
1806
            break;
×
1807
        }
NEW
1808
        offset += 8u + ((chunk_size + 1u) & ~1u);
×
1809
    }
1810

NEW
1811
    if (offset + 8u > size || memcmp(chunk, "data", 4) != 0) {
×
NEW
1812
        sixel_helper_set_additional_message(
×
1813
            "sixel_palette_parse_pal_riff: missing data chunk.");
NEW
1814
        return SIXEL_BAD_INPUT;
×
1815
    }
1816

NEW
1817
    if (chunk_size < 4u) {
×
NEW
1818
        sixel_helper_set_additional_message(
×
1819
            "sixel_palette_parse_pal_riff: data chunk too small.");
NEW
1820
        return SIXEL_BAD_INPUT;
×
1821
    }
NEW
1822
    version = sixel_palette_read_le16(chunk + 8);
×
1823
    (void)version;
NEW
1824
    entry_count = sixel_palette_read_le16(chunk + 10);
×
NEW
1825
    if (entry_count == 0u || entry_count > 256u) {
×
NEW
1826
        sixel_helper_set_additional_message(
×
1827
            "sixel_palette_parse_pal_riff: invalid entry count.");
NEW
1828
        return SIXEL_BAD_INPUT;
×
1829
    }
NEW
1830
    if (chunk_size != 4u + (size_t)entry_count * 4u) {
×
NEW
1831
        sixel_helper_set_additional_message(
×
1832
            "sixel_palette_parse_pal_riff: unexpected chunk size.");
NEW
1833
        return SIXEL_BAD_INPUT;
×
1834
    }
1835

NEW
1836
    status = sixel_dither_new(&local, (int)entry_count, encoder->allocator);
×
NEW
1837
    if (SIXEL_FAILED(status)) {
×
NEW
1838
        return status;
×
1839
    }
NEW
1840
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
NEW
1841
    target = sixel_dither_get_palette(local);
×
NEW
1842
    palette_offset = 12u;
×
NEW
1843
    for (index = 0u; index < entry_count; ++index) {
×
NEW
1844
        target[index * 3u + 0u] =
×
NEW
1845
            chunk[palette_offset + index * 4u + 0u];
×
NEW
1846
        target[index * 3u + 1u] =
×
NEW
1847
            chunk[palette_offset + index * 4u + 1u];
×
NEW
1848
        target[index * 3u + 2u] =
×
NEW
1849
            chunk[palette_offset + index * 4u + 2u];
×
1850
    }
1851

NEW
1852
    *dither = local;
×
NEW
1853
    return SIXEL_OK;
×
1854
}
1855

1856

1857
static SIXELSTATUS
NEW
1858
sixel_palette_parse_gpl(unsigned char const *data,
×
1859
                        size_t size,
1860
                        sixel_encoder_t *encoder,
1861
                        sixel_dither_t **dither)
1862
{
1863
    SIXELSTATUS status;
1864
    char *text;
1865
    size_t offset;
1866
    char *cursor;
1867
    char *line;
1868
    char *line_end;
1869
    size_t index;
1870
    int header_seen;
1871
    int parsed_colors;
1872
    unsigned char palette_bytes[256 * 3];
1873
    long component;
1874
    char *parse_end;
1875
    int value_index;
1876
    int values[3];
1877
    sixel_dither_t *local;
1878
    unsigned char *target;
1879
    char tail;
1880

NEW
1881
    status = SIXEL_FALSE;
×
NEW
1882
    text = NULL;
×
NEW
1883
    offset = 0u;
×
NEW
1884
    cursor = NULL;
×
NEW
1885
    line = NULL;
×
NEW
1886
    line_end = NULL;
×
NEW
1887
    index = 0u;
×
NEW
1888
    header_seen = 0;
×
NEW
1889
    parsed_colors = 0;
×
NEW
1890
    component = 0;
×
NEW
1891
    parse_end = NULL;
×
NEW
1892
    value_index = 0;
×
NEW
1893
    values[0] = 0;
×
NEW
1894
    values[1] = 0;
×
NEW
1895
    values[2] = 0;
×
NEW
1896
    local = NULL;
×
NEW
1897
    target = NULL;
×
1898

NEW
1899
    if (encoder == NULL || dither == NULL) {
×
NEW
1900
        sixel_helper_set_additional_message(
×
1901
            "sixel_palette_parse_gpl: invalid argument.");
NEW
1902
        return SIXEL_BAD_ARGUMENT;
×
1903
    }
NEW
1904
    if (data == NULL || size == 0u) {
×
NEW
1905
        sixel_helper_set_additional_message(
×
1906
            "sixel_palette_parse_gpl: empty palette.");
NEW
1907
        return SIXEL_BAD_INPUT;
×
1908
    }
1909

NEW
1910
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
NEW
1911
    if (text == NULL) {
×
NEW
1912
        sixel_helper_set_additional_message(
×
1913
            "sixel_palette_parse_gpl: allocation failed.");
NEW
1914
        return SIXEL_BAD_ALLOCATION;
×
1915
    }
NEW
1916
    memcpy(text, data, size);
×
NEW
1917
    text[size] = '\0';
×
1918

NEW
1919
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
NEW
1920
        offset = 3u;
×
1921
    }
NEW
1922
    cursor = text + offset;
×
1923

NEW
1924
    while (*cursor != '\0') {
×
NEW
1925
        line = cursor;
×
NEW
1926
        line_end = cursor;
×
NEW
1927
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
NEW
1928
            ++line_end;
×
1929
        }
NEW
1930
        if (*line_end != '\0') {
×
NEW
1931
            *line_end = '\0';
×
NEW
1932
            cursor = line_end + 1;
×
1933
        } else {
NEW
1934
            cursor = line_end;
×
1935
        }
NEW
1936
        while (*cursor == '\n' || *cursor == '\r') {
×
NEW
1937
            ++cursor;
×
1938
        }
1939

NEW
1940
        while (*line == ' ' || *line == '\t') {
×
NEW
1941
            ++line;
×
1942
        }
NEW
1943
        index = strlen(line);
×
NEW
1944
        while (index > 0u) {
×
NEW
1945
            tail = line[index - 1];
×
NEW
1946
            if (tail != ' ' && tail != '\t') {
×
NEW
1947
                break;
×
1948
            }
NEW
1949
            line[index - 1] = '\0';
×
NEW
1950
            --index;
×
1951
        }
NEW
1952
        if (*line == '\0') {
×
NEW
1953
            continue;
×
1954
        }
NEW
1955
        if (*line == '#') {
×
NEW
1956
            continue;
×
1957
        }
NEW
1958
        if (strncmp(line, "Name:", 5) == 0) {
×
NEW
1959
            continue;
×
1960
        }
NEW
1961
        if (strncmp(line, "Columns:", 8) == 0) {
×
NEW
1962
            continue;
×
1963
        }
1964

NEW
1965
        if (!header_seen) {
×
NEW
1966
            if (strcmp(line, "GIMP Palette") != 0) {
×
NEW
1967
                sixel_helper_set_additional_message(
×
1968
                    "sixel_palette_parse_gpl: missing header.");
NEW
1969
                status = SIXEL_BAD_INPUT;
×
NEW
1970
                goto cleanup;
×
1971
            }
NEW
1972
            header_seen = 1;
×
NEW
1973
            continue;
×
1974
        }
1975

NEW
1976
        if (parsed_colors >= 256) {
×
NEW
1977
            sixel_helper_set_additional_message(
×
1978
                "sixel_palette_parse_gpl: too many colors.");
NEW
1979
            status = SIXEL_BAD_INPUT;
×
NEW
1980
            goto cleanup;
×
1981
        }
1982

NEW
1983
        value_index = 0;
×
NEW
1984
        while (value_index < 3) {
×
NEW
1985
            component = strtol(line, &parse_end, 10);
×
NEW
1986
            if (parse_end == line || component < 0L || component > 255L) {
×
NEW
1987
                sixel_helper_set_additional_message(
×
1988
                    "sixel_palette_parse_gpl: invalid component.");
NEW
1989
                status = SIXEL_BAD_INPUT;
×
NEW
1990
                goto cleanup;
×
1991
            }
NEW
1992
            values[value_index] = (int)component;
×
NEW
1993
            ++value_index;
×
NEW
1994
            line = parse_end;
×
NEW
1995
            while (*line == ' ' || *line == '\t') {
×
NEW
1996
                ++line;
×
1997
            }
1998
        }
1999

NEW
2000
        palette_bytes[parsed_colors * 3 + 0] =
×
NEW
2001
            (unsigned char)values[0];
×
NEW
2002
        palette_bytes[parsed_colors * 3 + 1] =
×
NEW
2003
            (unsigned char)values[1];
×
NEW
2004
        palette_bytes[parsed_colors * 3 + 2] =
×
NEW
2005
            (unsigned char)values[2];
×
NEW
2006
        ++parsed_colors;
×
2007
    }
2008

NEW
2009
    if (!header_seen) {
×
NEW
2010
        sixel_helper_set_additional_message(
×
2011
            "sixel_palette_parse_gpl: header missing.");
NEW
2012
        status = SIXEL_BAD_INPUT;
×
NEW
2013
        goto cleanup;
×
2014
    }
NEW
2015
    if (parsed_colors <= 0) {
×
NEW
2016
        sixel_helper_set_additional_message(
×
2017
            "sixel_palette_parse_gpl: no colors parsed.");
NEW
2018
        status = SIXEL_BAD_INPUT;
×
NEW
2019
        goto cleanup;
×
2020
    }
2021

NEW
2022
    status = sixel_dither_new(&local, parsed_colors, encoder->allocator);
×
NEW
2023
    if (SIXEL_FAILED(status)) {
×
NEW
2024
        goto cleanup;
×
2025
    }
NEW
2026
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
NEW
2027
    target = sixel_dither_get_palette(local);
×
NEW
2028
    memcpy(target, palette_bytes, (size_t)parsed_colors * 3u);
×
2029

NEW
2030
    *dither = local;
×
NEW
2031
    status = SIXEL_OK;
×
2032

2033
cleanup:
NEW
2034
    if (SIXEL_FAILED(status) && local != NULL) {
×
NEW
2035
        sixel_dither_unref(local);
×
2036
    }
NEW
2037
    if (text != NULL) {
×
NEW
2038
        sixel_allocator_free(encoder->allocator, text);
×
2039
    }
NEW
2040
    return status;
×
2041
}
2042

2043

2044
/*
2045
 * Palette exporters
2046
 *
2047
 *   +----------+-------------------------+
2048
 *   | format   | emission strategy       |
2049
 *   +----------+-------------------------+
2050
 *   | ACT      | fixed 256 entries + EOF |
2051
 *   | PAL JASC | textual lines           |
2052
 *   | PAL RIFF | RIFF container          |
2053
 *   | GPL      | textual lines           |
2054
 *   +----------+-------------------------+
2055
 */
2056
static SIXELSTATUS
NEW
2057
sixel_palette_write_act(FILE *stream,
×
2058
                        unsigned char const *palette,
2059
                        int exported_colors)
2060
{
2061
    SIXELSTATUS status;
2062
    unsigned char act_table[256 * 3];
2063
    unsigned char trailer[4];
2064
    size_t exported_bytes;
2065

NEW
2066
    status = SIXEL_FALSE;
×
NEW
2067
    exported_bytes = 0u;
×
2068

NEW
2069
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
NEW
2070
        return SIXEL_BAD_ARGUMENT;
×
2071
    }
NEW
2072
    if (exported_colors > 256) {
×
NEW
2073
        exported_colors = 256;
×
2074
    }
2075

NEW
2076
    memset(act_table, 0, sizeof(act_table));
×
NEW
2077
    exported_bytes = (size_t)exported_colors * 3u;
×
NEW
2078
    memcpy(act_table, palette, exported_bytes);
×
2079

NEW
2080
    trailer[0] = (unsigned char)(((unsigned int)exported_colors >> 8)
×
2081
                                 & 0xffu);
NEW
2082
    trailer[1] = (unsigned char)((unsigned int)exported_colors & 0xffu);
×
NEW
2083
    trailer[2] = 0u;
×
NEW
2084
    trailer[3] = 0u;
×
2085

NEW
2086
    if (fwrite(act_table, 1, sizeof(act_table), stream)
×
2087
            != sizeof(act_table)) {
NEW
2088
        status = SIXEL_LIBC_ERROR;
×
NEW
2089
        return status;
×
2090
    }
NEW
2091
    if (fwrite(trailer, 1, sizeof(trailer), stream)
×
2092
            != sizeof(trailer)) {
NEW
2093
        status = SIXEL_LIBC_ERROR;
×
NEW
2094
        return status;
×
2095
    }
2096

NEW
2097
    return SIXEL_OK;
×
2098
}
2099

2100

2101
static SIXELSTATUS
NEW
2102
sixel_palette_write_pal_jasc(FILE *stream,
×
2103
                             unsigned char const *palette,
2104
                             int exported_colors)
2105
{
2106
    int index;
2107

NEW
2108
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
NEW
2109
        return SIXEL_BAD_ARGUMENT;
×
2110
    }
NEW
2111
    if (fprintf(stream, "JASC-PAL\n0100\n%d\n", exported_colors) < 0) {
×
NEW
2112
        return SIXEL_LIBC_ERROR;
×
2113
    }
NEW
2114
    for (index = 0; index < exported_colors; ++index) {
×
NEW
2115
        if (fprintf(stream, "%d %d %d\n",
×
NEW
2116
                    (int)palette[index * 3 + 0],
×
NEW
2117
                    (int)palette[index * 3 + 1],
×
NEW
2118
                    (int)palette[index * 3 + 2]) < 0) {
×
NEW
2119
            return SIXEL_LIBC_ERROR;
×
2120
        }
2121
    }
NEW
2122
    return SIXEL_OK;
×
2123
}
2124

2125

2126
static SIXELSTATUS
NEW
2127
sixel_palette_write_pal_riff(FILE *stream,
×
2128
                             unsigned char const *palette,
2129
                             int exported_colors)
2130
{
2131
    unsigned char header[12];
2132
    unsigned char chunk[8];
2133
    unsigned char log_palette[4 + 256 * 4];
2134
    unsigned int data_size;
2135
    unsigned int riff_size;
2136
    int index;
2137

NEW
2138
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
NEW
2139
        return SIXEL_BAD_ARGUMENT;
×
2140
    }
NEW
2141
    if (exported_colors > 256) {
×
NEW
2142
        exported_colors = 256;
×
2143
    }
2144

NEW
2145
    data_size = 4u + (unsigned int)exported_colors * 4u;
×
NEW
2146
    riff_size = 4u + 8u + data_size;
×
2147

NEW
2148
    memcpy(header, "RIFF", 4);
×
NEW
2149
    header[4] = (unsigned char)(riff_size & 0xffu);
×
NEW
2150
    header[5] = (unsigned char)((riff_size >> 8) & 0xffu);
×
NEW
2151
    header[6] = (unsigned char)((riff_size >> 16) & 0xffu);
×
NEW
2152
    header[7] = (unsigned char)((riff_size >> 24) & 0xffu);
×
NEW
2153
    memcpy(header + 8, "PAL ", 4);
×
2154

NEW
2155
    memcpy(chunk, "data", 4);
×
NEW
2156
    chunk[4] = (unsigned char)(data_size & 0xffu);
×
NEW
2157
    chunk[5] = (unsigned char)((data_size >> 8) & 0xffu);
×
NEW
2158
    chunk[6] = (unsigned char)((data_size >> 16) & 0xffu);
×
NEW
2159
    chunk[7] = (unsigned char)((data_size >> 24) & 0xffu);
×
2160

NEW
2161
    memset(log_palette, 0, sizeof(log_palette));
×
NEW
2162
    log_palette[0] = 0x00;
×
NEW
2163
    log_palette[1] = 0x03;
×
NEW
2164
    log_palette[2] = (unsigned char)(exported_colors & 0xff);
×
NEW
2165
    log_palette[3] = (unsigned char)((exported_colors >> 8) & 0xff);
×
NEW
2166
    for (index = 0; index < exported_colors; ++index) {
×
NEW
2167
        log_palette[4 + index * 4 + 0] = palette[index * 3 + 0];
×
NEW
2168
        log_palette[4 + index * 4 + 1] = palette[index * 3 + 1];
×
NEW
2169
        log_palette[4 + index * 4 + 2] = palette[index * 3 + 2];
×
NEW
2170
        log_palette[4 + index * 4 + 3] = 0u;
×
2171
    }
2172

NEW
2173
    if (fwrite(header, 1, sizeof(header), stream)
×
2174
            != sizeof(header)) {
NEW
2175
        return SIXEL_LIBC_ERROR;
×
2176
    }
NEW
2177
    if (fwrite(chunk, 1, sizeof(chunk), stream) != sizeof(chunk)) {
×
NEW
2178
        return SIXEL_LIBC_ERROR;
×
2179
    }
NEW
2180
    if (fwrite(log_palette, 1, (size_t)data_size, stream)
×
NEW
2181
            != (size_t)data_size) {
×
NEW
2182
        return SIXEL_LIBC_ERROR;
×
2183
    }
NEW
2184
    return SIXEL_OK;
×
2185
}
2186

2187

2188
static SIXELSTATUS
NEW
2189
sixel_palette_write_gpl(FILE *stream,
×
2190
                        unsigned char const *palette,
2191
                        int exported_colors)
2192
{
2193
    int index;
2194

NEW
2195
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
NEW
2196
        return SIXEL_BAD_ARGUMENT;
×
2197
    }
NEW
2198
    if (fprintf(stream, "GIMP Palette\n") < 0) {
×
NEW
2199
        return SIXEL_LIBC_ERROR;
×
2200
    }
NEW
2201
    if (fprintf(stream, "Name: libsixel export\n") < 0) {
×
NEW
2202
        return SIXEL_LIBC_ERROR;
×
2203
    }
NEW
2204
    if (fprintf(stream, "Columns: 16\n") < 0) {
×
NEW
2205
        return SIXEL_LIBC_ERROR;
×
2206
    }
NEW
2207
    if (fprintf(stream, "# Exported by libsixel\n") < 0) {
×
NEW
2208
        return SIXEL_LIBC_ERROR;
×
2209
    }
NEW
2210
    for (index = 0; index < exported_colors; ++index) {
×
NEW
2211
        if (fprintf(stream, "%3d %3d %3d\tIndex %d\n",
×
NEW
2212
                    (int)palette[index * 3 + 0],
×
NEW
2213
                    (int)palette[index * 3 + 1],
×
NEW
2214
                    (int)palette[index * 3 + 2],
×
2215
                    index) < 0) {
NEW
2216
            return SIXEL_LIBC_ERROR;
×
2217
        }
2218
    }
NEW
2219
    return SIXEL_OK;
×
2220
}
2221

2222

2223
/* create palette from specified map file */
2224
static SIXELSTATUS
2225
sixel_prepare_specified_palette(
26✔
2226
    sixel_dither_t  /* out */   **dither,
2227
    sixel_encoder_t /* in */    *encoder)
2228
{
2229
    SIXELSTATUS status;
2230
    sixel_callback_context_for_mapfile_t callback_context;
2231
    sixel_loader_t *loader;
2232
    int fstatic;
2233
    int fuse_palette;
2234
    int reqcolors;
2235
    int loop_override;
2236
    char const *path;
2237
    sixel_palette_format_t format_hint;
2238
    sixel_palette_format_t format_ext;
2239
    sixel_palette_format_t format_final;
2240
    sixel_palette_format_t format_detected;
2241
    FILE *stream;
2242
    int close_stream;
2243
    unsigned char *buffer;
2244
    size_t buffer_size;
2245
    int palette_request;
2246
    int need_detection;
2247
    int treat_as_image;
2248
    int path_has_extension;
2249

2250
    status = SIXEL_FALSE;
26✔
2251
    loader = NULL;
26✔
2252
    fstatic = 1;
26✔
2253
    fuse_palette = 1;
26✔
2254
    reqcolors = SIXEL_PALETTE_MAX;
26✔
2255
    loop_override = SIXEL_LOOP_DISABLE;
26✔
2256
    path = NULL;
26✔
2257
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
26✔
2258
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
26✔
2259
    format_final = SIXEL_PALETTE_FORMAT_NONE;
26✔
2260
    format_detected = SIXEL_PALETTE_FORMAT_NONE;
26✔
2261
    stream = NULL;
26✔
2262
    close_stream = 0;
26✔
2263
    buffer = NULL;
26✔
2264
    buffer_size = 0u;
26✔
2265
    palette_request = 0;
26✔
2266
    need_detection = 0;
26✔
2267
    treat_as_image = 0;
26✔
2268
    path_has_extension = 0;
26✔
2269

2270
    if (dither == NULL || encoder == NULL || encoder->mapfile == NULL) {
26!
NEW
2271
        sixel_helper_set_additional_message(
×
2272
            "sixel_prepare_specified_palette: invalid mapfile path.");
NEW
2273
        return SIXEL_BAD_ARGUMENT;
×
2274
    }
2275

2276
    path = sixel_palette_strip_prefix(encoder->mapfile, &format_hint);
26✔
2277
    if (path == NULL || *path == '\0') {
26!
NEW
2278
        sixel_helper_set_additional_message(
×
2279
            "sixel_prepare_specified_palette: empty mapfile path.");
NEW
2280
        return SIXEL_BAD_ARGUMENT;
×
2281
    }
2282

2283
    format_ext = sixel_palette_format_from_extension(path);
26✔
2284
    path_has_extension = sixel_path_has_any_extension(path);
26✔
2285

2286
    if (format_hint != SIXEL_PALETTE_FORMAT_NONE) {
26!
NEW
2287
        palette_request = 1;
×
NEW
2288
        format_final = format_hint;
×
2289
    } else if (format_ext != SIXEL_PALETTE_FORMAT_NONE) {
26!
NEW
2290
        palette_request = 1;
×
NEW
2291
        format_final = format_ext;
×
2292
    } else if (!path_has_extension) {
26✔
2293
        palette_request = 1;
5✔
2294
        need_detection = 1;
5✔
2295
    } else {
3✔
2296
        treat_as_image = 1;
21✔
2297
    }
2298

2299
    if (palette_request) {
26✔
2300
        status = sixel_palette_open_read(path, &stream, &close_stream);
5✔
2301
        if (SIXEL_FAILED(status)) {
5!
2302
            goto palette_cleanup;
5✔
2303
        }
NEW
2304
        status = sixel_palette_read_stream(stream,
×
2305
                                           encoder->allocator,
2306
                                           &buffer,
2307
                                           &buffer_size);
NEW
2308
        if (close_stream) {
×
NEW
2309
            sixel_palette_close_stream(stream, close_stream);
×
NEW
2310
            stream = NULL;
×
NEW
2311
            close_stream = 0;
×
2312
        }
NEW
2313
        if (SIXEL_FAILED(status)) {
×
NEW
2314
            goto palette_cleanup;
×
2315
        }
NEW
2316
        if (buffer_size == 0u) {
×
NEW
2317
            sixel_helper_set_additional_message(
×
2318
                "sixel_prepare_specified_palette: mapfile is empty.");
NEW
2319
            status = SIXEL_BAD_INPUT;
×
NEW
2320
            goto palette_cleanup;
×
2321
        }
2322

NEW
2323
        if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
NEW
2324
            format_detected = sixel_palette_guess_format(buffer,
×
2325
                                                         buffer_size);
NEW
2326
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
NEW
2327
                sixel_helper_set_additional_message(
×
2328
                    "sixel_prepare_specified_palette: "
2329
                    "unable to detect palette format.");
NEW
2330
                status = SIXEL_BAD_INPUT;
×
NEW
2331
                goto palette_cleanup;
×
2332
            }
NEW
2333
            format_final = format_detected;
×
NEW
2334
        } else if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
NEW
2335
            format_detected = sixel_palette_guess_format(buffer,
×
2336
                                                         buffer_size);
NEW
2337
            if (format_detected == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
2338
                    format_detected == SIXEL_PALETTE_FORMAT_PAL_RIFF) {
NEW
2339
                format_final = format_detected;
×
2340
            } else {
NEW
2341
                sixel_helper_set_additional_message(
×
2342
                    "sixel_prepare_specified_palette: "
2343
                    "ambiguous .pal content.");
NEW
2344
                status = SIXEL_BAD_INPUT;
×
NEW
2345
                goto palette_cleanup;
×
2346
            }
NEW
2347
        } else if (need_detection) {
×
NEW
2348
            format_detected = sixel_palette_guess_format(buffer,
×
2349
                                                         buffer_size);
NEW
2350
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
NEW
2351
                sixel_helper_set_additional_message(
×
2352
                    "sixel_prepare_specified_palette: "
2353
                    "unable to detect palette format.");
NEW
2354
                status = SIXEL_BAD_INPUT;
×
NEW
2355
                goto palette_cleanup;
×
2356
            }
NEW
2357
            format_final = format_detected;
×
2358
        }
2359

NEW
2360
        switch (format_final) {
×
2361
        case SIXEL_PALETTE_FORMAT_ACT:
NEW
2362
            status = sixel_palette_parse_act(buffer,
×
2363
                                             buffer_size,
2364
                                             encoder,
2365
                                             dither);
NEW
2366
            break;
×
2367
        case SIXEL_PALETTE_FORMAT_PAL_JASC:
NEW
2368
            status = sixel_palette_parse_pal_jasc(buffer,
×
2369
                                                  buffer_size,
2370
                                                  encoder,
2371
                                                  dither);
NEW
2372
            break;
×
2373
        case SIXEL_PALETTE_FORMAT_PAL_RIFF:
NEW
2374
            status = sixel_palette_parse_pal_riff(buffer,
×
2375
                                                  buffer_size,
2376
                                                  encoder,
2377
                                                  dither);
NEW
2378
            break;
×
2379
        case SIXEL_PALETTE_FORMAT_GPL:
NEW
2380
            status = sixel_palette_parse_gpl(buffer,
×
2381
                                             buffer_size,
2382
                                             encoder,
2383
                                             dither);
NEW
2384
            break;
×
2385
        default:
NEW
2386
            sixel_helper_set_additional_message(
×
2387
                "sixel_prepare_specified_palette: "
2388
                "unsupported palette format.");
NEW
2389
            status = SIXEL_BAD_INPUT;
×
NEW
2390
            break;
×
2391
        }
2392

2393
palette_cleanup:
2✔
2394
        if (buffer != NULL) {
5!
NEW
2395
            sixel_allocator_free(encoder->allocator, buffer);
×
NEW
2396
            buffer = NULL;
×
2397
        }
2398
        if (stream != NULL) {
5!
NEW
2399
            sixel_palette_close_stream(stream, close_stream);
×
NEW
2400
            stream = NULL;
×
2401
        }
2402
        if (SIXEL_SUCCEEDED(status)) {
5!
NEW
2403
            return status;
×
2404
        }
2405
        if (!treat_as_image) {
5!
2406
            return status;
5✔
2407
        }
2408
    }
2409

2410
    callback_context.reqcolors = encoder->reqcolors;
21✔
2411
    callback_context.dither = NULL;
21✔
2412
    callback_context.allocator = encoder->allocator;
21✔
2413
    callback_context.working_colorspace = encoder->working_colorspace;
21✔
2414
    callback_context.lut_policy = encoder->lut_policy;
21✔
2415

2416
    sixel_helper_set_loader_trace(encoder->verbose);
21✔
2417
    sixel_helper_set_thumbnail_size_hint(
21✔
2418
        sixel_encoder_thumbnail_hint(encoder));
7✔
2419
    status = sixel_loader_new(&loader, encoder->allocator);
21✔
2420
    if (SIXEL_FAILED(status)) {
21!
2421
        goto end_loader;
×
2422
    }
2423

2424
    status = sixel_loader_setopt(loader,
21✔
2425
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
2426
                                 &fstatic);
2427
    if (SIXEL_FAILED(status)) {
21!
2428
        goto end_loader;
×
2429
    }
2430

2431
    status = sixel_loader_setopt(loader,
21✔
2432
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
2433
                                 &fuse_palette);
2434
    if (SIXEL_FAILED(status)) {
21!
2435
        goto end_loader;
×
2436
    }
2437

2438
    status = sixel_loader_setopt(loader,
21✔
2439
                                 SIXEL_LOADER_OPTION_REQCOLORS,
2440
                                 &reqcolors);
2441
    if (SIXEL_FAILED(status)) {
21!
2442
        goto end_loader;
×
2443
    }
2444

2445
    status = sixel_loader_setopt(loader,
28✔
2446
                                 SIXEL_LOADER_OPTION_BGCOLOR,
2447
                                 encoder->bgcolor);
21✔
2448
    if (SIXEL_FAILED(status)) {
21!
2449
        goto end_loader;
×
2450
    }
2451

2452
    status = sixel_loader_setopt(loader,
21✔
2453
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
2454
                                 &loop_override);
2455
    if (SIXEL_FAILED(status)) {
21!
2456
        goto end_loader;
×
2457
    }
2458

2459
    status = sixel_loader_setopt(loader,
28✔
2460
                                 SIXEL_LOADER_OPTION_INSECURE,
2461
                                 &encoder->finsecure);
21✔
2462
    if (SIXEL_FAILED(status)) {
21!
2463
        goto end_loader;
×
2464
    }
2465

2466
    status = sixel_loader_setopt(loader,
28✔
2467
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
2468
                                 encoder->cancel_flag);
21✔
2469
    if (SIXEL_FAILED(status)) {
21!
2470
        goto end_loader;
×
2471
    }
2472

2473
    status = sixel_loader_setopt(loader,
28✔
2474
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
2475
                                 encoder->loader_order);
21✔
2476
    if (SIXEL_FAILED(status)) {
21!
2477
        goto end_loader;
×
2478
    }
2479

2480
    status = sixel_loader_setopt(loader,
21✔
2481
                                 SIXEL_LOADER_OPTION_CONTEXT,
2482
                                 &callback_context);
2483
    if (SIXEL_FAILED(status)) {
21!
2484
        goto end_loader;
×
2485
    }
2486

2487
    status = sixel_loader_load_file(loader,
28✔
2488
                                    encoder->mapfile,
21✔
2489
                                    load_image_callback_for_palette);
2490
    if (status != SIXEL_OK) {
21!
UNCOV
2491
        goto end_loader;
×
2492
    }
2493

2494
end_loader:
14✔
2495
    sixel_loader_unref(loader);
21✔
2496

2497
    if (status != SIXEL_OK) {
21!
UNCOV
2498
        return status;
×
2499
    }
2500

2501
    if (! callback_context.dither) {
21!
2502
        sixel_helper_set_additional_message(
×
2503
            "sixel_prepare_specified_palette() failed.\n"
2504
            "reason: mapfile is empty.");
2505
        return SIXEL_BAD_INPUT;
×
2506
    }
2507

2508
    *dither = callback_context.dither;
21✔
2509

2510
    return status;
21✔
2511
}
10✔
2512

2513

2514
/* create dither object from a frame */
2515
static SIXELSTATUS
2516
sixel_encoder_prepare_palette(
518✔
2517
    sixel_encoder_t *encoder,  /* encoder object */
2518
    sixel_frame_t   *frame,    /* input frame object */
2519
    sixel_dither_t  **dither)  /* dither object to be created from the frame */
2520
{
2521
    SIXELSTATUS status = SIXEL_FALSE;
518✔
2522
    int histogram_colors;
2523
    sixel_assessment_t *assessment;
2524
    int promoted_stage;
2525

2526
    assessment = NULL;
518✔
2527
    promoted_stage = 0;
518✔
2528
    if (encoder != NULL) {
518!
2529
        assessment = encoder->assessment_observer;
518✔
2530
    }
180✔
2531

2532
    switch (encoder->color_option) {
518!
2533
    case SIXEL_COLOR_OPTION_HIGHCOLOR:
24✔
2534
        if (encoder->dither_cache) {
36!
2535
            *dither = encoder->dither_cache;
×
2536
            status = SIXEL_OK;
×
2537
        } else {
2538
            status = sixel_dither_new(dither, (-1), encoder->allocator);
36✔
2539
            sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
36✔
2540
        }
2541
        goto end;
36✔
2542
    case SIXEL_COLOR_OPTION_MONOCHROME:
8✔
2543
        if (encoder->dither_cache) {
12!
2544
            *dither = encoder->dither_cache;
×
2545
            status = SIXEL_OK;
×
2546
        } else {
2547
            status = sixel_prepare_monochrome_palette(dither, encoder->finvert);
12✔
2548
        }
2549
        goto end;
12✔
2550
    case SIXEL_COLOR_OPTION_MAPFILE:
16✔
2551
        if (encoder->dither_cache) {
26!
2552
            *dither = encoder->dither_cache;
×
2553
            status = SIXEL_OK;
×
2554
        } else {
2555
            status = sixel_prepare_specified_palette(dither, encoder);
26✔
2556
        }
2557
        goto end;
26✔
2558
    case SIXEL_COLOR_OPTION_BUILTIN:
18✔
2559
        if (encoder->dither_cache) {
27!
2560
            *dither = encoder->dither_cache;
×
2561
            status = SIXEL_OK;
×
2562
        } else {
2563
            status = sixel_prepare_builtin_palette(dither, encoder->builtin_palette);
27✔
2564
        }
2565
        goto end;
27✔
2566
    case SIXEL_COLOR_OPTION_DEFAULT:
417✔
2567
    default:
2568
        break;
417✔
2569
    }
2570

2571
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE) {
417✔
2572
        if (!sixel_frame_get_palette(frame)) {
191!
2573
            status = SIXEL_LOGIC_ERROR;
×
2574
            goto end;
×
2575
        }
2576
        status = sixel_dither_new(dither, sixel_frame_get_ncolors(frame),
230✔
2577
                                  encoder->allocator);
39✔
2578
        if (SIXEL_FAILED(status)) {
191!
2579
            goto end;
×
2580
        }
2581
        sixel_dither_set_palette(*dither, sixel_frame_get_palette(frame));
191✔
2582
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
191✔
2583
        if (sixel_frame_get_transparent(frame) != (-1)) {
191!
2584
            sixel_dither_set_transparent(*dither, sixel_frame_get_transparent(frame));
×
2585
        }
2586
        if (*dither && encoder->dither_cache) {
191!
2587
            sixel_dither_unref(encoder->dither_cache);
×
2588
        }
2589
        goto end;
191✔
2590
    }
2591

2592
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_GRAYSCALE) {
226!
2593
        switch (sixel_frame_get_pixelformat(frame)) {
×
2594
        case SIXEL_PIXELFORMAT_G1:
2595
            *dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
2596
            break;
×
2597
        case SIXEL_PIXELFORMAT_G2:
2598
            *dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
2599
            break;
×
2600
        case SIXEL_PIXELFORMAT_G4:
2601
            *dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
2602
            break;
×
2603
        case SIXEL_PIXELFORMAT_G8:
2604
            *dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
2605
            break;
×
2606
        default:
2607
            *dither = NULL;
×
2608
            status = SIXEL_LOGIC_ERROR;
×
2609
            goto end;
×
2610
        }
2611
        if (*dither && encoder->dither_cache) {
×
2612
            sixel_dither_unref(encoder->dither_cache);
×
2613
        }
2614
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
×
2615
        status = SIXEL_OK;
×
2616
        goto end;
×
2617
    }
2618

2619
    if (encoder->dither_cache) {
226!
2620
        sixel_dither_unref(encoder->dither_cache);
×
2621
    }
2622
    status = sixel_dither_new(dither, encoder->reqcolors, encoder->allocator);
226✔
2623
    if (SIXEL_FAILED(status)) {
226!
2624
        goto end;
×
2625
    }
2626

2627
    sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
226✔
2628

2629
    status = sixel_dither_initialize(*dither,
332✔
2630
                                     sixel_frame_get_pixels(frame),
106✔
2631
                                     sixel_frame_get_width(frame),
106✔
2632
                                     sixel_frame_get_height(frame),
106✔
2633
                                     sixel_frame_get_pixelformat(frame),
106✔
2634
                                     encoder->method_for_largest,
106✔
2635
                                     encoder->method_for_rep,
106✔
2636
                                     encoder->quality_mode);
106✔
2637
    if (SIXEL_FAILED(status)) {
226!
2638
        sixel_dither_unref(*dither);
×
2639
        goto end;
×
2640
    }
2641

2642
    if (assessment != NULL && promoted_stage == 0) {
226!
2643
        sixel_assessment_stage_transition(
×
2644
            assessment,
2645
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2646
        promoted_stage = 1;
×
2647
    }
2648

2649
    histogram_colors = sixel_dither_get_num_of_histogram_colors(*dither);
226✔
2650
    if (histogram_colors <= encoder->reqcolors) {
226✔
2651
        encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
172✔
2652
    }
88✔
2653
    sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
226✔
2654

2655
    status = SIXEL_OK;
226✔
2656

2657
end:
338✔
2658
    if (assessment != NULL && promoted_stage == 0) {
518!
2659
        sixel_assessment_stage_transition(
×
2660
            assessment,
2661
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2662
        promoted_stage = 1;
×
2663
    }
2664
    if (SIXEL_SUCCEEDED(status) && dither != NULL && *dither != NULL) {
518!
2665
        sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
513✔
2666
        /* pass down the user's demand for an exact palette size */
2667
        (*dither)->force_palette = encoder->force_palette;
513✔
2668
    }
177✔
2669
    return status;
518✔
2670
}
2671

2672

2673
/* resize a frame with settings of specified encoder object */
2674
static SIXELSTATUS
2675
sixel_encoder_do_resize(
525✔
2676
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
2677
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
2678
{
2679
    SIXELSTATUS status = SIXEL_FALSE;
525✔
2680
    int src_width;
2681
    int src_height;
2682
    int dst_width;
2683
    int dst_height;
2684

2685
    /* get frame width and height */
2686
    src_width = sixel_frame_get_width(frame);
525✔
2687
    src_height = sixel_frame_get_height(frame);
525✔
2688

2689
    if (src_width < 1) {
525✔
2690
         sixel_helper_set_additional_message(
6✔
2691
             "sixel_encoder_do_resize: "
2692
             "detected a frame with a non-positive width.");
2693
        return SIXEL_BAD_ARGUMENT;
6✔
2694
    }
2695

2696
    if (src_height < 1) {
519!
2697
         sixel_helper_set_additional_message(
×
2698
             "sixel_encoder_do_resize: "
2699
             "detected a frame with a non-positive height.");
2700
        return SIXEL_BAD_ARGUMENT;
×
2701
    }
2702

2703
    /* settings around scaling */
2704
    dst_width = encoder->pixelwidth;    /* may be -1 (default) */
519✔
2705
    dst_height = encoder->pixelheight;  /* may be -1 (default) */
519✔
2706

2707
    /* if the encoder has percentwidth or percentheight property,
2708
       convert them to pixelwidth / pixelheight */
2709
    if (encoder->percentwidth > 0) {
519✔
2710
        dst_width = src_width * encoder->percentwidth / 100;
12✔
2711
    }
4✔
2712
    if (encoder->percentheight > 0) {
519✔
2713
        dst_height = src_height * encoder->percentheight / 100;
9✔
2714
    }
3✔
2715

2716
    /* if only either width or height is set, set also the other
2717
       to retain frame aspect ratio */
2718
    if (dst_width > 0 && dst_height <= 0) {
519✔
2719
        dst_height = src_height * dst_width / src_width;
42✔
2720
    }
14✔
2721
    if (dst_height > 0 && dst_width <= 0) {
519✔
2722
        dst_width = src_width * dst_height / src_height;
33✔
2723
    }
11✔
2724

2725
    /* do resize */
2726
    if (dst_width > 0 && dst_height > 0) {
519!
2727
        status = sixel_frame_resize(frame, dst_width, dst_height,
124✔
2728
                                    encoder->method_for_resampling);
31✔
2729
        if (SIXEL_FAILED(status)) {
93!
2730
            goto end;
×
2731
        }
2732
    }
31✔
2733

2734
    /* success */
2735
    status = SIXEL_OK;
519✔
2736

2737
end:
338✔
2738
    return status;
519✔
2739
}
183✔
2740

2741

2742
/* clip a frame with settings of specified encoder object */
2743
static SIXELSTATUS
2744
sixel_encoder_do_clip(
521✔
2745
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
2746
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
2747
{
2748
    SIXELSTATUS status = SIXEL_FALSE;
521✔
2749
    int src_width;
2750
    int src_height;
2751
    int clip_x;
2752
    int clip_y;
2753
    int clip_w;
2754
    int clip_h;
2755

2756
    /* get frame width and height */
2757
    src_width = sixel_frame_get_width(frame);
521✔
2758
    src_height = sixel_frame_get_height(frame);
521✔
2759

2760
    /* settings around clipping */
2761
    clip_x = encoder->clipx;
521✔
2762
    clip_y = encoder->clipy;
521✔
2763
    clip_w = encoder->clipwidth;
521✔
2764
    clip_h = encoder->clipheight;
521✔
2765

2766
    /* adjust clipping width with comparing it to frame width */
2767
    if (clip_w + clip_x > src_width) {
521✔
2768
        if (clip_x > src_width) {
7✔
2769
            clip_w = 0;
3✔
2770
        } else {
1✔
2771
            clip_w = src_width - clip_x;
4✔
2772
        }
2773
    }
3✔
2774

2775
    /* adjust clipping height with comparing it to frame height */
2776
    if (clip_h + clip_y > src_height) {
521✔
2777
        if (clip_y > src_height) {
6✔
2778
            clip_h = 0;
3✔
2779
        } else {
1✔
2780
            clip_h = src_height - clip_y;
3✔
2781
        }
2782
    }
2✔
2783

2784
    /* do clipping */
2785
    if (clip_w > 0 && clip_h > 0) {
521!
2786
        status = sixel_frame_clip(frame, clip_x, clip_y, clip_w, clip_h);
15✔
2787
        if (SIXEL_FAILED(status)) {
15!
2788
            goto end;
3✔
2789
        }
2790
    }
4✔
2791

2792
    /* success */
2793
    status = SIXEL_OK;
518✔
2794

2795
end:
338✔
2796
    return status;
521✔
2797
}
2798

2799

2800
static void
2801
sixel_debug_print_palette(
3✔
2802
    sixel_dither_t /* in */ *dither /* dithering object */
2803
)
2804
{
2805
    unsigned char *palette;
2806
    int i;
2807

2808
    palette = sixel_dither_get_palette(dither);
3✔
2809
    fprintf(stderr, "palette:\n");
3✔
2810
    for (i = 0; i < sixel_dither_get_num_of_palette_colors(dither); ++i) {
51✔
2811
        fprintf(stderr, "%d: #%02x%02x%02x\n", i,
64✔
2812
                palette[i * 3 + 0],
48✔
2813
                palette[i * 3 + 1],
48✔
2814
                palette[i * 3 + 2]);
48✔
2815
    }
16✔
2816
}
3✔
2817

2818

2819
static SIXELSTATUS
2820
sixel_encoder_output_without_macro(
420✔
2821
    sixel_frame_t       /* in */ *frame,
2822
    sixel_dither_t      /* in */ *dither,
2823
    sixel_output_t      /* in */ *output,
2824
    sixel_encoder_t     /* in */ *encoder)
2825
{
2826
    SIXELSTATUS status = SIXEL_OK;
420✔
2827
    static unsigned char *p;
2828
    int depth;
2829
    enum { message_buffer_size = 256 };
2830
    char message[message_buffer_size];
2831
    int nwrite;
2832
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
2833
    int dulation;
2834
    int delay;
2835
    struct timespec tv;
2836
#endif
2837
    unsigned char *pixbuf;
2838
    int width;
2839
    int height;
2840
    int pixelformat = 0;
420✔
2841
    size_t size;
2842
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
420✔
2843
    palette_conversion_t palette_ctx;
2844

2845
    memset(&palette_ctx, 0, sizeof(palette_ctx));
420✔
2846
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
2847
    sixel_clock_t last_clock;
2848
#endif
2849

2850
    if (encoder == NULL) {
420!
2851
        sixel_helper_set_additional_message(
×
2852
            "sixel_encoder_output_without_macro: encoder object is null.");
2853
        status = SIXEL_BAD_ARGUMENT;
×
2854
        goto end;
×
2855
    }
2856

2857
    if (encoder->assessment_observer != NULL) {
420!
2858
        sixel_assessment_stage_transition(
×
2859
            encoder->assessment_observer,
×
2860
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
2861
    }
2862

2863
    if (encoder->color_option == SIXEL_COLOR_OPTION_DEFAULT) {
420✔
2864
        if (encoder->force_palette) {
324!
2865
            /* keep every slot when the user forced the palette size */
2866
            sixel_dither_set_optimize_palette(dither, 0);
×
2867
        } else {
2868
            sixel_dither_set_optimize_palette(dither, 1);
324✔
2869
        }
2870
    }
108✔
2871

2872
    pixelformat = sixel_frame_get_pixelformat(frame);
420✔
2873
    frame_colorspace = sixel_frame_get_colorspace(frame);
420✔
2874
    output->pixelformat = pixelformat;
420✔
2875
    output->source_colorspace = frame_colorspace;
420✔
2876
    output->colorspace = encoder->output_colorspace;
420✔
2877
    sixel_dither_set_pixelformat(dither, pixelformat);
420✔
2878
    depth = sixel_helper_compute_depth(pixelformat);
420✔
2879
    if (depth < 0) {
420!
2880
        status = SIXEL_LOGIC_ERROR;
×
2881
        nwrite = sprintf(message,
×
2882
                         "sixel_encoder_output_without_macro: "
2883
                         "sixel_helper_compute_depth(%08x) failed.",
2884
                         pixelformat);
2885
        if (nwrite > 0) {
×
2886
            sixel_helper_set_additional_message(message);
×
2887
        }
2888
        goto end;
×
2889
    }
2890

2891
    width = sixel_frame_get_width(frame);
420✔
2892
    height = sixel_frame_get_height(frame);
420✔
2893
    size = (size_t)(width * height * depth);
420✔
2894
    p = (unsigned char *)sixel_allocator_malloc(encoder->allocator, size);
420✔
2895
    if (p == NULL) {
420!
2896
        sixel_helper_set_additional_message(
×
2897
            "sixel_encoder_output_without_macro: sixel_allocator_malloc() failed.");
2898
        status = SIXEL_BAD_ALLOCATION;
×
2899
        goto end;
×
2900
    }
2901
#if defined(HAVE_CLOCK)
2902
    if (output->last_clock == 0) {
420!
2903
        output->last_clock = clock();
420✔
2904
    }
140✔
2905
#elif defined(HAVE_CLOCK_WIN)
2906
    if (output->last_clock == 0) {
2907
        output->last_clock = clock_win();
2908
    }
2909
#endif
2910
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
2911
    delay = sixel_frame_get_delay(frame);
420✔
2912
    if (delay > 0 && !encoder->fignore_delay) {
420✔
2913
# if defined(HAVE_CLOCK)
2914
        last_clock = clock();
79✔
2915
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
2916
#  if defined(__APPLE__)
2917
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
154✔
2918
                          / 100000);
77✔
2919
#  else
2920
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
2✔
2921
                          / CLOCKS_PER_SEC);
2922
#  endif
2923
        output->last_clock = last_clock;
79✔
2924
# elif defined(HAVE_CLOCK_WIN)
2925
        last_clock = clock_win();
2926
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
2927
                          / CLOCKS_PER_SEC);
2928
        output->last_clock = last_clock;
2929
# else
2930
        dulation = 0;
2931
# endif
2932
        if (dulation < 1000 * 10 * delay) {
79!
2933
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
2934
            tv.tv_sec = 0;
79✔
2935
            tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
79✔
2936
#  if defined(HAVE_NANOSLEEP)
2937
            nanosleep(&tv, NULL);
79✔
2938
#  else
2939
            nanosleep_win(&tv, NULL);
2940
#  endif
2941
# endif
2942
        }
77✔
2943
    }
77✔
2944
#endif
2945

2946
    pixbuf = sixel_frame_get_pixels(frame);
420✔
2947
    memcpy(p, pixbuf, (size_t)(width * height * depth));
420✔
2948

2949
    status = sixel_output_convert_colorspace(output, p, size);
420✔
2950
    if (SIXEL_FAILED(status)) {
420!
2951
        goto end;
×
2952
    }
2953

2954
    if (encoder->cancel_flag && *encoder->cancel_flag) {
420!
2955
        goto end;
×
2956
    }
2957

2958
    status = sixel_encoder_convert_palette(encoder,
560✔
2959
                                           output,
140✔
2960
                                           dither,
140✔
2961
                                           frame_colorspace,
140✔
2962
                                           pixelformat,
140✔
2963
                                           &palette_ctx);
2964
    if (SIXEL_FAILED(status)) {
420!
2965
        goto end;
×
2966
    }
2967

2968
    if (encoder->capture_quantized) {
420!
2969
        status = sixel_encoder_capture_quantized(encoder,
×
2970
                                                 dither,
2971
                                                 p,
2972
                                                 size,
2973
                                                 width,
2974
                                                 height,
2975
                                                 pixelformat,
2976
                                                 output->colorspace);
2977
        if (SIXEL_FAILED(status)) {
×
2978
            goto end;
×
2979
        }
2980
    }
2981

2982
    if (encoder->assessment_observer != NULL) {
420!
2983
        sixel_assessment_stage_transition(
×
2984
            encoder->assessment_observer,
×
2985
            SIXEL_ASSESSMENT_STAGE_ENCODE);
2986
    }
2987
    status = sixel_encode(p, width, height, depth, dither, output);
420✔
2988
    if (encoder->assessment_observer != NULL) {
420!
2989
        sixel_assessment_stage_finish(encoder->assessment_observer);
×
2990
    }
2991
    if (status != SIXEL_OK) {
420!
2992
        goto end;
×
2993
    }
2994

2995
end:
280✔
2996
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
420✔
2997
    output->pixelformat = pixelformat;
420✔
2998
    output->source_colorspace = frame_colorspace;
420✔
2999
    sixel_allocator_free(encoder->allocator, p);
420✔
3000

3001
    return status;
420✔
3002
}
3003

3004

3005
static SIXELSTATUS
3006
sixel_encoder_output_with_macro(
93✔
3007
    sixel_frame_t   /* in */ *frame,
3008
    sixel_dither_t  /* in */ *dither,
3009
    sixel_output_t  /* in */ *output,
3010
    sixel_encoder_t /* in */ *encoder)
3011
{
3012
    SIXELSTATUS status = SIXEL_OK;
93✔
3013
    enum { message_buffer_size = 256 };
3014
    char buffer[message_buffer_size];
3015
    int nwrite;
3016
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3017
    int dulation;
3018
    struct timespec tv;
3019
#endif
3020
    int width;
3021
    int height;
3022
    int pixelformat;
3023
    int depth;
3024
    size_t size = 0;
93✔
3025
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
93✔
3026
    unsigned char *converted = NULL;
93✔
3027
    palette_conversion_t palette_ctx;
3028
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3029
    int delay;
3030
#endif
3031
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3032
    sixel_clock_t last_clock;
3033
#endif
3034
    double write_started_at;
3035
    double write_finished_at;
3036
    double write_duration;
3037

3038
    memset(&palette_ctx, 0, sizeof(palette_ctx));
93✔
3039

3040
    if (encoder != NULL && encoder->assessment_observer != NULL) {
93!
3041
        sixel_assessment_stage_transition(
×
3042
            encoder->assessment_observer,
×
3043
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3044
    }
3045

3046
#if defined(HAVE_CLOCK)
3047
    if (output->last_clock == 0) {
93!
3048
        output->last_clock = clock();
93✔
3049
    }
37✔
3050
#elif defined(HAVE_CLOCK_WIN)
3051
    if (output->last_clock == 0) {
3052
        output->last_clock = clock_win();
3053
    }
3054
#endif
3055

3056
    width = sixel_frame_get_width(frame);
93✔
3057
    height = sixel_frame_get_height(frame);
93✔
3058
    pixelformat = sixel_frame_get_pixelformat(frame);
93✔
3059
    depth = sixel_helper_compute_depth(pixelformat);
93✔
3060
    if (depth < 0) {
93!
3061
        status = SIXEL_LOGIC_ERROR;
×
3062
        sixel_helper_set_additional_message(
×
3063
            "sixel_encoder_output_with_macro: "
3064
            "sixel_helper_compute_depth() failed.");
3065
        goto end;
×
3066
    }
3067

3068
    frame_colorspace = sixel_frame_get_colorspace(frame);
93✔
3069
    size = (size_t)width * (size_t)height * (size_t)depth;
93✔
3070
    converted = (unsigned char *)sixel_allocator_malloc(
93✔
3071
        encoder->allocator, size);
37✔
3072
    if (converted == NULL) {
93!
3073
        sixel_helper_set_additional_message(
×
3074
            "sixel_encoder_output_with_macro: "
3075
            "sixel_allocator_malloc() failed.");
3076
        status = SIXEL_BAD_ALLOCATION;
×
3077
        goto end;
×
3078
    }
3079

3080
    memcpy(converted, sixel_frame_get_pixels(frame), size);
93✔
3081
    output->pixelformat = pixelformat;
93✔
3082
    output->source_colorspace = frame_colorspace;
93✔
3083
    output->colorspace = encoder->output_colorspace;
93✔
3084
    status = sixel_output_convert_colorspace(output, converted, size);
93✔
3085
    if (SIXEL_FAILED(status)) {
93!
3086
        goto end;
×
3087
    }
3088

3089
    status = sixel_encoder_convert_palette(encoder,
130✔
3090
                                           output,
37✔
3091
                                           dither,
37✔
3092
                                           frame_colorspace,
37✔
3093
                                           pixelformat,
37✔
3094
                                           &palette_ctx);
3095
    if (SIXEL_FAILED(status)) {
93!
3096
        goto end;
×
3097
    }
3098

3099
    if (sixel_frame_get_loop_no(frame) == 0) {
93✔
3100
        if (encoder->macro_number >= 0) {
55!
3101
            nwrite = sprintf(buffer, "\033P%d;0;1!z",
1✔
3102
                             encoder->macro_number);
3103
        } else {
1✔
3104
            nwrite = sprintf(buffer, "\033P%d;0;1!z",
54✔
3105
                             sixel_frame_get_frame_no(frame));
3106
        }
3107
        if (nwrite < 0) {
55!
3108
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3109
            sixel_helper_set_additional_message(
×
3110
                "sixel_encoder_output_with_macro: sprintf() failed.");
3111
            goto end;
×
3112
        }
3113
        write_started_at = 0.0;
55✔
3114
        write_finished_at = 0.0;
55✔
3115
        write_duration = 0.0;
55✔
3116
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3117
            write_started_at = sixel_assessment_timer_now();
×
3118
        }
3119
        nwrite = sixel_write_callback(buffer,
74✔
3120
                                      (int)strlen(buffer),
55✔
3121
                                      &encoder->outfd);
55✔
3122
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3123
            write_finished_at = sixel_assessment_timer_now();
×
3124
            write_duration = write_finished_at - write_started_at;
×
3125
            if (write_duration < 0.0) {
×
3126
                write_duration = 0.0;
×
3127
            }
3128
        }
3129
        if (nwrite < 0) {
55!
3130
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3131
            sixel_helper_set_additional_message(
×
3132
                "sixel_encoder_output_with_macro: "
3133
                "sixel_write_callback() failed.");
3134
            goto end;
×
3135
        }
3136
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3137
            sixel_assessment_record_output_write(
×
3138
                encoder->assessment_observer,
×
3139
                (size_t)nwrite,
3140
                write_duration);
3141
        }
3142

3143
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3144
            sixel_assessment_stage_transition(
×
3145
                encoder->assessment_observer,
×
3146
                SIXEL_ASSESSMENT_STAGE_ENCODE);
3147
        }
3148
        status = sixel_encode(converted,
74✔
3149
                              width,
19✔
3150
                              height,
19✔
3151
                              depth,
19✔
3152
                              dither,
19✔
3153
                              output);
19✔
3154
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3155
            sixel_assessment_stage_finish(
×
3156
                encoder->assessment_observer);
×
3157
        }
3158
        if (SIXEL_FAILED(status)) {
55!
3159
            goto end;
×
3160
        }
3161

3162
        write_started_at = 0.0;
55✔
3163
        write_finished_at = 0.0;
55✔
3164
        write_duration = 0.0;
55✔
3165
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3166
            write_started_at = sixel_assessment_timer_now();
×
3167
        }
3168
        nwrite = sixel_write_callback("\033\\", 2, &encoder->outfd);
55✔
3169
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3170
            write_finished_at = sixel_assessment_timer_now();
×
3171
            write_duration = write_finished_at - write_started_at;
×
3172
            if (write_duration < 0.0) {
×
3173
                write_duration = 0.0;
×
3174
            }
3175
        }
3176
        if (nwrite < 0) {
55!
3177
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3178
            sixel_helper_set_additional_message(
×
3179
                "sixel_encoder_output_with_macro: "
3180
                "sixel_write_callback() failed.");
3181
            goto end;
×
3182
        }
3183
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3184
            sixel_assessment_record_output_write(
×
3185
                encoder->assessment_observer,
×
3186
                (size_t)nwrite,
3187
                write_duration);
3188
        }
3189
    }
19✔
3190
    if (encoder->macro_number < 0) {
129✔
3191
        nwrite = sprintf(buffer, "\033[%d*z",
90✔
3192
                         sixel_frame_get_frame_no(frame));
3193
        if (nwrite < 0) {
90!
3194
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3195
            sixel_helper_set_additional_message(
×
3196
                "sixel_encoder_output_with_macro: sprintf() failed.");
3197
        }
3198
        write_started_at = 0.0;
90✔
3199
        write_finished_at = 0.0;
90✔
3200
        write_duration = 0.0;
90✔
3201
        if (encoder != NULL && encoder->assessment_observer != NULL) {
90!
3202
            write_started_at = sixel_assessment_timer_now();
×
3203
        }
3204
        nwrite = sixel_write_callback(buffer,
126✔
3205
                                      (int)strlen(buffer),
90✔
3206
                                      &encoder->outfd);
90✔
3207
        if (encoder != NULL && encoder->assessment_observer != NULL) {
90!
3208
            write_finished_at = sixel_assessment_timer_now();
×
3209
            write_duration = write_finished_at - write_started_at;
×
3210
            if (write_duration < 0.0) {
×
3211
                write_duration = 0.0;
×
3212
            }
3213
        }
3214
        if (nwrite < 0) {
90!
3215
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3216
            sixel_helper_set_additional_message(
×
3217
                "sixel_encoder_output_with_macro: "
3218
                "sixel_write_callback() failed.");
3219
            goto end;
×
3220
        }
3221
        if (encoder != NULL && encoder->assessment_observer != NULL) {
90!
3222
            sixel_assessment_record_output_write(
×
3223
                encoder->assessment_observer,
×
3224
                (size_t)nwrite,
3225
                write_duration);
3226
        }
3227
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3228
        delay = sixel_frame_get_delay(frame);
90✔
3229
        if (delay > 0 && !encoder->fignore_delay) {
90!
3230
# if defined(HAVE_CLOCK)
3231
            last_clock = clock();
63✔
3232
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
3233
#  if defined(__APPLE__)
3234
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
54✔
3235
                             / 100000);
27✔
3236
#  else
3237
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
36✔
3238
                             / CLOCKS_PER_SEC);
3239
#  endif
3240
            output->last_clock = last_clock;
63✔
3241
# elif defined(HAVE_CLOCK_WIN)
3242
            last_clock = clock_win();
3243
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3244
                             / CLOCKS_PER_SEC);
3245
            output->last_clock = last_clock;
3246
# else
3247
            dulation = 0;
3248
# endif
3249
            if (dulation < 1000 * 10 * delay) {
63!
3250
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3251
                tv.tv_sec = 0;
60✔
3252
                tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
60✔
3253
#  if defined(HAVE_NANOSLEEP)
3254
                nanosleep(&tv, NULL);
60✔
3255
#  else
3256
                nanosleep_win(&tv, NULL);
3257
#  endif
3258
# endif
3259
            }
24✔
3260
        }
27✔
3261
#endif
3262
    }
36✔
3263

3264
end:
20✔
3265
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
93✔
3266
    output->pixelformat = pixelformat;
93✔
3267
    output->source_colorspace = frame_colorspace;
93✔
3268
    sixel_allocator_free(encoder->allocator, converted);
93✔
3269

3270
    return status;
93✔
3271
}
3272

3273

3274
static SIXELSTATUS
3275
sixel_encoder_emit_iso2022_chars(
×
3276
    sixel_encoder_t *encoder,
3277
    sixel_frame_t *frame
3278
)
3279
{
3280
    char *buf_p, *buf;
3281
    int col, row;
3282
    int charset;
3283
    int is_96cs;
3284
    unsigned int charset_no;
3285
    unsigned int code;
3286
    int num_cols, num_rows;
3287
    SIXELSTATUS status;
3288
    size_t alloc_size;
3289
    int nwrite;
3290
    int target_fd;
3291
    int chunk_size;
3292

3293
    charset_no = encoder->drcs_charset_no;
×
3294
    if (charset_no == 0u) {
×
3295
        charset_no = 1u;
×
3296
    }
3297
    if (encoder->drcs_mmv == 0) {
×
3298
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3299
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3300
    } else if (encoder->drcs_mmv == 1) {
×
3301
        is_96cs = 0;
×
3302
        charset = (int)(charset_no + 0x3fu);
×
3303
    } else {
3304
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3305
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3306
    }
3307
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3308
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3309
             / encoder->cell_width;
×
3310
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3311
             / encoder->cell_height;
×
3312

3313
    /* cols x rows + designation(4 chars) + SI + SO + LFs */
3314
    alloc_size = num_cols * num_rows + (num_cols * num_rows / 96 + 1) * 4 + 2 + num_rows;
×
3315
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3316
    if (buf == NULL) {
×
3317
        sixel_helper_set_additional_message(
×
3318
            "sixel_encoder_emit_iso2022_chars: sixel_allocator_malloc() failed.");
3319
        status = SIXEL_BAD_ALLOCATION;
×
3320
        goto end;
×
3321
    }
3322

3323
    code = 0x20;
×
3324
    *(buf_p++) = '\016';  /* SI */
×
3325
    *(buf_p++) = '\033';
×
3326
    *(buf_p++) = ')';
×
3327
    *(buf_p++) = ' ';
×
3328
    *(buf_p++) = charset;
×
3329
    for(row = 0; row < num_rows; row++) {
×
3330
        for(col = 0; col < num_cols; col++) {
×
3331
            if ((code & 0x7f) == 0x0) {
×
3332
                if (charset == 0x7e) {
×
3333
                    is_96cs = 1 - is_96cs;
×
3334
                    charset = '0';
×
3335
                } else {
3336
                    charset++;
×
3337
                }
3338
                code = 0x20;
×
3339
                *(buf_p++) = '\033';
×
3340
                *(buf_p++) = is_96cs ? '-': ')';
×
3341
                *(buf_p++) = ' ';
×
3342
                *(buf_p++) = charset;
×
3343
            }
3344
            *(buf_p++) = code++;
×
3345
        }
3346
        *(buf_p++) = '\n';
×
3347
    }
3348
    *(buf_p++) = '\017';  /* SO */
×
3349

3350
    if (encoder->tile_outfd >= 0) {
×
3351
        target_fd = encoder->tile_outfd;
×
3352
    } else {
3353
        target_fd = encoder->outfd;
×
3354
    }
3355

3356
    chunk_size = (int)(buf_p - buf);
×
3357
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3358
                                          buf,
3359
                                          chunk_size,
3360
                                          target_fd);
3361
    if (nwrite != chunk_size) {
×
3362
        sixel_helper_set_additional_message(
×
3363
            "sixel_encoder_emit_iso2022_chars: write() failed.");
3364
        status = SIXEL_RUNTIME_ERROR;
×
3365
        goto end;
×
3366
    }
3367

3368
    sixel_allocator_free(encoder->allocator, buf);
×
3369

3370
    status = SIXEL_OK;
×
3371

3372
end:
3373
    return status;
×
3374
}
3375

3376

3377
/*
3378
 * This routine is derived from mlterm's drcssixel.c
3379
 * (https://raw.githubusercontent.com/arakiken/mlterm/master/drcssixel/drcssixel.c).
3380
 * The original implementation is credited to Araki Ken.
3381
 * Adjusted here to integrate with libsixel's encoder pipeline.
3382
 */
3383
static SIXELSTATUS
3384
sixel_encoder_emit_drcsmmv2_chars(
×
3385
    sixel_encoder_t *encoder,
3386
    sixel_frame_t *frame
3387
)
3388
{
3389
    char *buf_p, *buf;
3390
    int col, row;
3391
    int charset;
3392
    int is_96cs;
3393
    unsigned int charset_no;
3394
    unsigned int code;
3395
    int num_cols, num_rows;
3396
    SIXELSTATUS status;
3397
    size_t alloc_size;
3398
    int nwrite;
3399
    int target_fd;
3400
    int chunk_size;
3401

3402
    charset_no = encoder->drcs_charset_no;
×
3403
    if (charset_no == 0u) {
×
3404
        charset_no = 1u;
×
3405
    }
3406
    if (encoder->drcs_mmv == 0) {
×
3407
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3408
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3409
    } else if (encoder->drcs_mmv == 1) {
×
3410
        is_96cs = 0;
×
3411
        charset = (int)(charset_no + 0x3fu);
×
3412
    } else {
3413
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3414
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3415
    }
3416
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3417
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3418
             / encoder->cell_width;
×
3419
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3420
             / encoder->cell_height;
×
3421

3422
    /* cols x rows x 4(out of BMP) + rows(LFs) */
3423
    alloc_size = num_cols * num_rows * 4 + num_rows;
×
3424
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3425
    if (buf == NULL) {
×
3426
        sixel_helper_set_additional_message(
×
3427
            "sixel_encoder_emit_drcsmmv2_chars: sixel_allocator_malloc() failed.");
3428
        status = SIXEL_BAD_ALLOCATION;
×
3429
        goto end;
×
3430
    }
3431

3432
    for(row = 0; row < num_rows; row++) {
×
3433
        for(col = 0; col < num_cols; col++) {
×
3434
            *(buf_p++) = ((code >> 18) & 0x07) | 0xf0;
×
3435
            *(buf_p++) = ((code >> 12) & 0x3f) | 0x80;
×
3436
            *(buf_p++) = ((code >> 6) & 0x3f) | 0x80;
×
3437
            *(buf_p++) = (code & 0x3f) | 0x80;
×
3438
            code++;
×
3439
            if ((code & 0x7f) == 0x0) {
×
3440
                if (charset == 0x7e) {
×
3441
                    is_96cs = 1 - is_96cs;
×
3442
                    charset = '0';
×
3443
                } else {
3444
                    charset++;
×
3445
                }
3446
                code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3447
            }
3448
        }
3449
        *(buf_p++) = '\n';
×
3450
    }
3451

3452
    if (charset == 0x7e) {
×
3453
        is_96cs = 1 - is_96cs;
×
3454
    } else {
3455
        charset = '0';
×
3456
        charset++;
×
3457
    }
3458
    if (encoder->tile_outfd >= 0) {
×
3459
        target_fd = encoder->tile_outfd;
×
3460
    } else {
3461
        target_fd = encoder->outfd;
×
3462
    }
3463

3464
    chunk_size = (int)(buf_p - buf);
×
3465
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3466
                                          buf,
3467
                                          chunk_size,
3468
                                          target_fd);
3469
    if (nwrite != chunk_size) {
×
3470
        sixel_helper_set_additional_message(
×
3471
            "sixel_encoder_emit_drcsmmv2_chars: write() failed.");
3472
        status = SIXEL_RUNTIME_ERROR;
×
3473
        goto end;
×
3474
    }
3475

3476
    sixel_allocator_free(encoder->allocator, buf);
×
3477

3478
    status = SIXEL_OK;
×
3479

3480
end:
3481
    return status;
×
3482
}
3483

3484
static SIXELSTATUS
3485
sixel_encoder_encode_frame(
527✔
3486
    sixel_encoder_t *encoder,
3487
    sixel_frame_t   *frame,
3488
    sixel_output_t  *output)
3489
{
3490
    SIXELSTATUS status = SIXEL_FALSE;
527✔
3491
    sixel_dither_t *dither = NULL;
527✔
3492
    int height;
3493
    int is_animation = 0;
527✔
3494
    int nwrite;
3495
    int drcs_is_96cs_param;
3496
    int drcs_designate_char;
3497
    char buf[256];
3498
    sixel_write_function fn_write;
3499
    sixel_write_function write_callback;
3500
    sixel_write_function scroll_callback;
3501
    void *write_priv;
3502
    void *scroll_priv;
3503
    sixel_encoder_output_probe_t probe;
3504
    sixel_encoder_output_probe_t scroll_probe;
3505
    sixel_assessment_t *assessment;
3506

3507
    fn_write = sixel_write_callback;
527✔
3508
    write_callback = sixel_write_callback;
527✔
3509
    scroll_callback = sixel_write_callback;
527✔
3510
    write_priv = &encoder->outfd;
527✔
3511
    scroll_priv = &encoder->outfd;
527✔
3512
    probe.encoder = NULL;
527✔
3513
    probe.base_write = NULL;
527✔
3514
    probe.base_priv = NULL;
527✔
3515
    scroll_probe.encoder = NULL;
527✔
3516
    scroll_probe.base_write = NULL;
527✔
3517
    scroll_probe.base_priv = NULL;
527✔
3518
    assessment = NULL;
527✔
3519
    if (encoder != NULL) {
527!
3520
        assessment = encoder->assessment_observer;
527✔
3521
    }
185✔
3522
    if (assessment != NULL) {
527!
3523
        if (encoder->clipfirst) {
×
3524
            sixel_assessment_stage_transition(
×
3525
                assessment,
3526
                SIXEL_ASSESSMENT_STAGE_CROP);
3527
        } else {
3528
            sixel_assessment_stage_transition(
×
3529
                assessment,
3530
                SIXEL_ASSESSMENT_STAGE_SCALE);
3531
        }
3532
    }
3533

3534
    /*
3535
     *  Geometry timeline:
3536
     *
3537
     *      +-------+    +------+    +---------------+
3538
     *      | scale | -> | crop | -> | color convert |
3539
     *      +-------+    +------+    +---------------+
3540
     *
3541
     *  The order of the first two blocks mirrors `-c`, so we hop between
3542
     *  SCALE and CROP depending on `clipfirst`.
3543
     */
3544

3545
    if (encoder->clipfirst) {
527✔
3546
        status = sixel_encoder_do_clip(encoder, frame);
8✔
3547
        if (SIXEL_FAILED(status)) {
8!
3548
            goto end;
2✔
3549
        }
3550
        if (assessment != NULL) {
6!
3551
            sixel_assessment_stage_transition(
×
3552
                assessment,
3553
                SIXEL_ASSESSMENT_STAGE_SCALE);
3554
        }
3555
        status = sixel_encoder_do_resize(encoder, frame);
6✔
3556
        if (SIXEL_FAILED(status)) {
6!
3557
            goto end;
×
3558
        }
3559
    } else {
2✔
3560
        status = sixel_encoder_do_resize(encoder, frame);
519✔
3561
        if (SIXEL_FAILED(status)) {
519✔
3562
            goto end;
6✔
3563
        }
3564
        if (assessment != NULL) {
513!
3565
            sixel_assessment_stage_transition(
×
3566
                assessment,
3567
                SIXEL_ASSESSMENT_STAGE_CROP);
3568
        }
3569
        status = sixel_encoder_do_clip(encoder, frame);
513✔
3570
        if (SIXEL_FAILED(status)) {
513!
3571
            goto end;
1✔
3572
        }
3573
    }
3574

3575
    if (assessment != NULL) {
518!
3576
        sixel_assessment_stage_transition(
×
3577
            assessment,
3578
            SIXEL_ASSESSMENT_STAGE_COLORSPACE);
3579
    }
3580

3581
    status = sixel_frame_ensure_colorspace(frame,
698✔
3582
                                           encoder->working_colorspace);
180✔
3583
    if (SIXEL_FAILED(status)) {
518!
3584
        goto end;
×
3585
    }
3586

3587
    if (assessment != NULL) {
518!
3588
        sixel_assessment_stage_transition(
×
3589
            assessment,
3590
            SIXEL_ASSESSMENT_STAGE_PALETTE_HISTOGRAM);
3591
    }
3592

3593
    /* prepare dither context */
3594
    status = sixel_encoder_prepare_palette(encoder, frame, &dither);
518✔
3595
    if (status != SIXEL_OK) {
518✔
3596
        dither = NULL;
5✔
3597
        goto end;
5✔
3598
    }
3599

3600
    if (encoder->dither_cache != NULL) {
513!
3601
        encoder->dither_cache = dither;
×
3602
        sixel_dither_ref(dither);
×
3603
    }
3604

3605
    if (encoder->fdrcs) {
513!
3606
        status = sixel_encoder_ensure_cell_size(encoder);
×
3607
        if (SIXEL_FAILED(status)) {
×
3608
            goto end;
×
3609
        }
3610
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
×
3611
            sixel_helper_set_additional_message(
×
3612
                "drcs option cannot be used together with macro output.");
3613
            status = SIXEL_BAD_ARGUMENT;
×
3614
            goto end;
×
3615
        }
3616
    }
3617

3618
    /* evaluate -v option: print palette */
3619
    if (encoder->verbose) {
513✔
3620
        if ((sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE)) {
9✔
3621
            sixel_debug_print_palette(dither);
3✔
3622
        }
1✔
3623
    }
3✔
3624

3625
    /* evaluate -d option: set method for diffusion */
3626
    sixel_dither_set_diffusion_type(dither, encoder->method_for_diffuse);
513✔
3627
    sixel_dither_set_diffusion_scan(dither, encoder->method_for_scan);
513✔
3628
    sixel_dither_set_diffusion_carry(dither, encoder->method_for_carry);
513✔
3629

3630
    /* evaluate -C option: set complexion score */
3631
    if (encoder->complexion > 1) {
513✔
3632
        sixel_dither_set_complexion_score(dither, encoder->complexion);
6✔
3633
    }
2✔
3634

3635
    if (output) {
513!
3636
        sixel_output_ref(output);
×
3637
        if (encoder->assessment_observer != NULL) {
×
3638
            probe.encoder = encoder;
×
3639
            probe.base_write = fn_write;
×
3640
            probe.base_priv = &encoder->outfd;
×
3641
            write_callback = sixel_write_with_probe;
×
3642
            write_priv = &probe;
×
3643
        }
3644
    } else {
3645
        /* create output context */
3646
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
513✔
3647
            /* -u or -n option */
3648
            fn_write = sixel_hex_write_callback;
93✔
3649
        } else {
37✔
3650
            fn_write = sixel_write_callback;
420✔
3651
        }
3652
        write_callback = fn_write;
513✔
3653
        write_priv = &encoder->outfd;
513✔
3654
        if (encoder->assessment_observer != NULL) {
513!
3655
            probe.encoder = encoder;
×
3656
            probe.base_write = fn_write;
×
3657
            probe.base_priv = &encoder->outfd;
×
3658
            write_callback = sixel_write_with_probe;
×
3659
            write_priv = &probe;
×
3660
        }
3661
        status = sixel_output_new(&output,
513✔
3662
                                  write_callback,
177✔
3663
                                  write_priv,
177✔
3664
                                  encoder->allocator);
177✔
3665
        if (SIXEL_FAILED(status)) {
513!
3666
            goto end;
×
3667
        }
3668
    }
3669

3670
    if (encoder->fdrcs) {
513!
3671
        sixel_output_set_skip_dcs_envelope(output, 1);
×
3672
        sixel_output_set_skip_header(output, 1);
×
3673
    }
3674

3675
    sixel_output_set_8bit_availability(output, encoder->f8bit);
513✔
3676
    sixel_output_set_gri_arg_limit(output, encoder->has_gri_arg_limit);
513✔
3677
    sixel_output_set_palette_type(output, encoder->palette_type);
513✔
3678
    sixel_output_set_penetrate_multiplexer(
513✔
3679
        output, encoder->penetrate_multiplexer);
177✔
3680
    sixel_output_set_encode_policy(output, encoder->encode_policy);
513✔
3681
    sixel_output_set_ormode(output, encoder->ormode);
513✔
3682

3683
    if (sixel_frame_get_multiframe(frame) && !encoder->fstatic) {
513!
3684
        if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
117✔
3685
            is_animation = 1;
108✔
3686
        }
42✔
3687
        height = sixel_frame_get_height(frame);
117✔
3688
        if (encoder->assessment_observer != NULL) {
117!
3689
            scroll_probe.encoder = encoder;
×
3690
            scroll_probe.base_write = sixel_write_callback;
×
3691
            scroll_probe.base_priv = &encoder->outfd;
×
3692
            scroll_callback = sixel_write_with_probe;
×
3693
            scroll_priv = &scroll_probe;
×
3694
        } else {
3695
            scroll_callback = sixel_write_callback;
117✔
3696
            scroll_priv = &encoder->outfd;
117✔
3697
        }
3698
        (void) sixel_tty_scroll(scroll_callback,
162✔
3699
                                scroll_priv,
45✔
3700
                                encoder->outfd,
45✔
3701
                                height,
45✔
3702
                                is_animation);
45✔
3703
    }
45✔
3704

3705
    if (encoder->cancel_flag && *encoder->cancel_flag) {
513!
3706
        status = SIXEL_INTERRUPTED;
×
3707
        goto end;
×
3708
    }
3709

3710
    if (encoder->fdrcs) {  /* -@ option */
513!
3711
        if (encoder->drcs_mmv == 0) {
×
3712
            drcs_is_96cs_param =
×
3713
                (encoder->drcs_charset_no > 63u) ? 1 : 0;
×
3714
            drcs_designate_char =
×
3715
                (int)(((encoder->drcs_charset_no - 1u) % 63u) + 0x40u);
×
3716
        } else if (encoder->drcs_mmv == 1) {
×
3717
            drcs_is_96cs_param = 0;
×
3718
            drcs_designate_char =
×
3719
                (int)(encoder->drcs_charset_no + 0x3fu);
×
3720
        } else {
3721
            drcs_is_96cs_param =
×
3722
                (encoder->drcs_charset_no > 79u) ? 1 : 0;
×
3723
            drcs_designate_char =
×
3724
                (int)(((encoder->drcs_charset_no - 1u) % 79u) + 0x30u);
×
3725
        }
3726
        nwrite = sprintf(buf,
×
3727
                         "%s%s1;0;0;%d;1;3;%d;%d{ %c",
3728
                         (encoder->drcs_mmv > 0) ? (
×
3729
                             encoder->f8bit ? "\233?8800h": "\033[?8800h"
×
3730
                         ): "",
3731
                         encoder->f8bit ? "\220": "\033P",
×
3732
                         encoder->cell_width,
3733
                         encoder->cell_height,
3734
                         drcs_is_96cs_param,
3735
                         drcs_designate_char);
3736
        if (nwrite < 0) {
×
3737
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3738
            sixel_helper_set_additional_message(
×
3739
                "sixel_encoder_encode_frame: sprintf() failed.");
3740
            goto end;
×
3741
        }
3742
        nwrite = write_callback(buf, nwrite, write_priv);
×
3743
        if (nwrite < 0) {
×
3744
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3745
            sixel_helper_set_additional_message(
×
3746
                "sixel_encoder_encode_frame: write() failed.");
3747
            goto end;
×
3748
        }
3749
    }
3750

3751
    /* output sixel: junction of multi-frame processing strategy */
3752
    if (encoder->fuse_macro) {  /* -u option */
513✔
3753
        /* use macro */
3754
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
90✔
3755
    } else if (encoder->macro_number >= 0) { /* -n option */
459✔
3756
        /* use macro */
3757
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
3✔
3758
    } else {
1✔
3759
        /* do not use macro */
3760
        status = sixel_encoder_output_without_macro(frame, dither, output, encoder);
420✔
3761
    }
3762
    if (SIXEL_FAILED(status)) {
513!
3763
        goto end;
×
3764
    }
3765

3766
    if (encoder->cancel_flag && *encoder->cancel_flag) {
513!
3767
        nwrite = write_callback("\x18\033\\", 3, write_priv);
×
3768
        if (nwrite < 0) {
×
3769
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3770
            sixel_helper_set_additional_message(
×
3771
                "sixel_encoder_encode_frame: write_callback() failed.");
3772
            goto end;
×
3773
        }
3774
        status = SIXEL_INTERRUPTED;
×
3775
    }
3776
    if (SIXEL_FAILED(status)) {
513!
3777
        goto end;
×
3778
    }
3779

3780
    if (encoder->fdrcs) {  /* -@ option */
513!
3781
        if (encoder->f8bit) {
×
3782
            nwrite = write_callback("\234", 1, write_priv);
×
3783
        } else {
3784
            nwrite = write_callback("\033\\", 2, write_priv);
×
3785
        }
3786
        if (nwrite < 0) {
×
3787
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3788
            sixel_helper_set_additional_message(
×
3789
                "sixel_encoder_encode_frame: write_callback() failed.");
3790
            goto end;
×
3791
        }
3792

3793
        if (encoder->tile_outfd >= 0) {
×
3794
            if (encoder->drcs_mmv == 0) {
×
3795
                status = sixel_encoder_emit_iso2022_chars(encoder, frame);
×
3796
                if (SIXEL_FAILED(status)) {
×
3797
                    goto end;
×
3798
                }
3799
            } else {
3800
                status = sixel_encoder_emit_drcsmmv2_chars(encoder, frame);
×
3801
                if (SIXEL_FAILED(status)) {
×
3802
                    goto end;
×
3803
                }
3804
            }
3805
        }
3806
    }
3807

3808

3809
end:
336✔
3810
    if (output) {
527✔
3811
        sixel_output_unref(output);
513✔
3812
    }
177✔
3813
    if (dither) {
527✔
3814
        sixel_dither_unref(dither);
513✔
3815
    }
177✔
3816

3817
    return status;
527✔
3818
}
3819

3820

3821
/* create encoder object */
3822
SIXELAPI SIXELSTATUS
3823
sixel_encoder_new(
507✔
3824
    sixel_encoder_t     /* out */ **ppencoder, /* encoder object to be created */
3825
    sixel_allocator_t   /* in */  *allocator)  /* allocator, null if you use
3826
                                                  default allocator */
3827
{
3828
    SIXELSTATUS status = SIXEL_FALSE;
507✔
3829
    char const *env_default_bgcolor = NULL;
507✔
3830
    char const *env_default_ncolors = NULL;
507✔
3831
    int ncolors;
3832
#if HAVE__DUPENV_S
3833
    errno_t e;
3834
    size_t len;
3835
#endif  /* HAVE__DUPENV_S */
3836

3837
    if (allocator == NULL) {
507!
3838
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
507✔
3839
        if (SIXEL_FAILED(status)) {
507!
3840
            goto end;
×
3841
        }
3842
    } else {
169✔
3843
        sixel_allocator_ref(allocator);
×
3844
    }
3845

3846
    *ppencoder
169✔
3847
        = (sixel_encoder_t *)sixel_allocator_malloc(allocator,
676✔
3848
                                                    sizeof(sixel_encoder_t));
3849
    if (*ppencoder == NULL) {
507!
3850
        sixel_helper_set_additional_message(
×
3851
            "sixel_encoder_new: sixel_allocator_malloc() failed.");
3852
        status = SIXEL_BAD_ALLOCATION;
×
3853
        sixel_allocator_unref(allocator);
×
3854
        goto end;
×
3855
    }
3856

3857
    (*ppencoder)->ref                   = 1;
507✔
3858
    (*ppencoder)->reqcolors             = (-1);
507✔
3859
    (*ppencoder)->force_palette         = 0;
507✔
3860
    (*ppencoder)->mapfile               = NULL;
507✔
3861
    (*ppencoder)->palette_output        = NULL;
507✔
3862
    (*ppencoder)->loader_order          = NULL;
507✔
3863
    (*ppencoder)->color_option          = SIXEL_COLOR_OPTION_DEFAULT;
507✔
3864
    (*ppencoder)->builtin_palette       = 0;
507✔
3865
    (*ppencoder)->method_for_diffuse    = SIXEL_DIFFUSE_AUTO;
507✔
3866
    (*ppencoder)->method_for_scan       = SIXEL_SCAN_AUTO;
507✔
3867
    (*ppencoder)->method_for_carry      = SIXEL_CARRY_AUTO;
507✔
3868
    (*ppencoder)->method_for_largest    = SIXEL_LARGE_AUTO;
507✔
3869
    (*ppencoder)->method_for_rep        = SIXEL_REP_AUTO;
507✔
3870
    (*ppencoder)->quality_mode          = SIXEL_QUALITY_AUTO;
507✔
3871
    (*ppencoder)->lut_policy            = SIXEL_LUT_POLICY_AUTO;
507✔
3872
    (*ppencoder)->method_for_resampling = SIXEL_RES_BILINEAR;
507✔
3873
    (*ppencoder)->loop_mode             = SIXEL_LOOP_AUTO;
507✔
3874
    (*ppencoder)->palette_type          = SIXEL_PALETTETYPE_AUTO;
507✔
3875
    (*ppencoder)->f8bit                 = 0;
507✔
3876
    (*ppencoder)->has_gri_arg_limit     = 0;
507✔
3877
    (*ppencoder)->finvert               = 0;
507✔
3878
    (*ppencoder)->fuse_macro            = 0;
507✔
3879
    (*ppencoder)->fdrcs                 = 0;
507✔
3880
    (*ppencoder)->fignore_delay         = 0;
507✔
3881
    (*ppencoder)->complexion            = 1;
507✔
3882
    (*ppencoder)->fstatic               = 0;
507✔
3883
    (*ppencoder)->cell_width            = 0;
507✔
3884
    (*ppencoder)->cell_height           = 0;
507✔
3885
    (*ppencoder)->pixelwidth            = (-1);
507✔
3886
    (*ppencoder)->pixelheight           = (-1);
507✔
3887
    (*ppencoder)->percentwidth          = (-1);
507✔
3888
    (*ppencoder)->percentheight         = (-1);
507✔
3889
    (*ppencoder)->clipx                 = 0;
507✔
3890
    (*ppencoder)->clipy                 = 0;
507✔
3891
    (*ppencoder)->clipwidth             = 0;
507✔
3892
    (*ppencoder)->clipheight            = 0;
507✔
3893
    (*ppencoder)->clipfirst             = 0;
507✔
3894
    (*ppencoder)->macro_number          = (-1);
507✔
3895
    (*ppencoder)->verbose               = 0;
507✔
3896
    (*ppencoder)->penetrate_multiplexer = 0;
507✔
3897
    (*ppencoder)->encode_policy         = SIXEL_ENCODEPOLICY_AUTO;
507✔
3898
    (*ppencoder)->working_colorspace    = SIXEL_COLORSPACE_GAMMA;
507✔
3899
    (*ppencoder)->output_colorspace     = SIXEL_COLORSPACE_GAMMA;
507✔
3900
    (*ppencoder)->ormode                = 0;
507✔
3901
    (*ppencoder)->pipe_mode             = 0;
507✔
3902
    (*ppencoder)->bgcolor               = NULL;
507✔
3903
    (*ppencoder)->outfd                 = STDOUT_FILENO;
507✔
3904
    (*ppencoder)->tile_outfd            = (-1);
507✔
3905
    (*ppencoder)->finsecure             = 0;
507✔
3906
    (*ppencoder)->cancel_flag           = NULL;
507✔
3907
    (*ppencoder)->dither_cache          = NULL;
507✔
3908
    (*ppencoder)->drcs_charset_no       = 1u;
507✔
3909
    (*ppencoder)->drcs_mmv              = 2;
507✔
3910
    (*ppencoder)->capture_quantized     = 0;
507✔
3911
    (*ppencoder)->capture_source        = 0;
507✔
3912
    (*ppencoder)->capture_pixels        = NULL;
507✔
3913
    (*ppencoder)->capture_pixels_size   = 0;
507✔
3914
    (*ppencoder)->capture_palette       = NULL;
507✔
3915
    (*ppencoder)->capture_palette_size  = 0;
507✔
3916
    (*ppencoder)->capture_pixel_bytes   = 0;
507✔
3917
    (*ppencoder)->capture_width         = 0;
507✔
3918
    (*ppencoder)->capture_height        = 0;
507✔
3919
    (*ppencoder)->capture_pixelformat   = SIXEL_PIXELFORMAT_RGB888;
507✔
3920
    (*ppencoder)->capture_colorspace    = SIXEL_COLORSPACE_GAMMA;
507✔
3921
    (*ppencoder)->capture_ncolors       = 0;
507✔
3922
    (*ppencoder)->capture_valid         = 0;
507✔
3923
    (*ppencoder)->capture_source_frame  = NULL;
507✔
3924
    (*ppencoder)->assessment_observer   = NULL;
507✔
3925
    (*ppencoder)->last_loader_name[0]   = '\0';
507✔
3926
    (*ppencoder)->last_source_path[0]   = '\0';
507✔
3927
    (*ppencoder)->last_input_bytes      = 0u;
507✔
3928
    (*ppencoder)->allocator             = allocator;
507✔
3929

3930
    /* evaluate environment variable ${SIXEL_BGCOLOR} */
3931
#if HAVE__DUPENV_S
3932
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_BGCOLOR");
3933
    if (e != (0)) {
3934
        sixel_helper_set_additional_message(
3935
            "failed to get environment variable $SIXEL_BGCOLOR.");
3936
        return (SIXEL_LIBC_ERROR | (e & 0xff));
3937
    }
3938
#else
3939
    env_default_bgcolor = getenv("SIXEL_BGCOLOR");
507✔
3940
#endif  /* HAVE__DUPENV_S */
3941
    if (env_default_bgcolor != NULL) {
507!
3942
        status = sixel_parse_x_colorspec(&(*ppencoder)->bgcolor,
×
3943
                                         env_default_bgcolor,
3944
                                         allocator);
3945
        if (SIXEL_FAILED(status)) {
×
3946
            goto error;
×
3947
        }
3948
    }
3949

3950
    /* evaluate environment variable ${SIXEL_COLORS} */
3951
#if HAVE__DUPENV_S
3952
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_COLORS");
3953
    if (e != (0)) {
3954
        sixel_helper_set_additional_message(
3955
            "failed to get environment variable $SIXEL_COLORS.");
3956
        return (SIXEL_LIBC_ERROR | (e & 0xff));
3957
    }
3958
#else
3959
    env_default_ncolors = getenv("SIXEL_COLORS");
507✔
3960
#endif  /* HAVE__DUPENV_S */
3961
    if (env_default_ncolors) {
507!
3962
        ncolors = atoi(env_default_ncolors); /* may overflow */
×
3963
        if (ncolors > 1 && ncolors <= SIXEL_PALETTE_MAX) {
×
3964
            (*ppencoder)->reqcolors = ncolors;
×
3965
        }
3966
    }
3967

3968
    /* success */
3969
    status = SIXEL_OK;
507✔
3970

3971
    goto end;
507✔
3972

3973
error:
3974
    sixel_allocator_free(allocator, *ppencoder);
×
3975
    sixel_allocator_unref(allocator);
×
3976
    *ppencoder = NULL;
×
3977

3978
end:
338✔
3979
#if HAVE__DUPENV_S
3980
    free(env_default_bgcolor);
3981
    free(env_default_ncolors);
3982
#endif  /* HAVE__DUPENV_S */
3983
    return status;
507✔
3984
}
3985

3986

3987
/* create encoder object (deprecated version) */
3988
SIXELAPI /* deprecated */ sixel_encoder_t *
3989
sixel_encoder_create(void)
×
3990
{
3991
    SIXELSTATUS status = SIXEL_FALSE;
×
3992
    sixel_encoder_t *encoder = NULL;
×
3993

3994
    status = sixel_encoder_new(&encoder, NULL);
×
3995
    if (SIXEL_FAILED(status)) {
×
3996
        return NULL;
×
3997
    }
3998

3999
    return encoder;
×
4000
}
4001

4002

4003
/* destroy encoder object */
4004
static void
4005
sixel_encoder_destroy(sixel_encoder_t *encoder)
507✔
4006
{
4007
    sixel_allocator_t *allocator;
4008

4009
    if (encoder) {
507!
4010
        allocator = encoder->allocator;
507✔
4011
        sixel_allocator_free(allocator, encoder->mapfile);
507✔
4012
        sixel_allocator_free(allocator, encoder->palette_output);
507✔
4013
        sixel_allocator_free(allocator, encoder->loader_order);
507✔
4014
        sixel_allocator_free(allocator, encoder->bgcolor);
507✔
4015
        sixel_dither_unref(encoder->dither_cache);
507✔
4016
        if (encoder->outfd
516!
4017
            && encoder->outfd != STDOUT_FILENO
507!
4018
            && encoder->outfd != STDERR_FILENO) {
187!
4019
#if HAVE__CLOSE
4020
            (void) _close(encoder->outfd);
4021
#else
4022
            (void) close(encoder->outfd);
27✔
4023
#endif  /* HAVE__CLOSE */
4024
        }
9✔
4025
        if (encoder->tile_outfd >= 0
507!
4026
            && encoder->tile_outfd != encoder->outfd
169!
4027
            && encoder->tile_outfd != STDOUT_FILENO
×
4028
            && encoder->tile_outfd != STDERR_FILENO) {
×
4029
#if HAVE__CLOSE
4030
            (void) _close(encoder->tile_outfd);
4031
#else
4032
            (void) close(encoder->tile_outfd);
×
4033
#endif  /* HAVE__CLOSE */
4034
        }
4035
        if (encoder->capture_source_frame != NULL) {
507!
4036
            sixel_frame_unref(encoder->capture_source_frame);
×
4037
        }
4038
        sixel_allocator_free(allocator, encoder->capture_pixels);
507✔
4039
        sixel_allocator_free(allocator, encoder->capture_palette);
507✔
4040
        sixel_allocator_free(allocator, encoder);
507✔
4041
        sixel_allocator_unref(allocator);
507✔
4042
    }
169✔
4043
}
507✔
4044

4045

4046
/* increase reference count of encoder object (thread-unsafe) */
4047
SIXELAPI void
4048
sixel_encoder_ref(sixel_encoder_t *encoder)
1,107✔
4049
{
4050
    /* TODO: be thread safe */
4051
    ++encoder->ref;
1,107✔
4052
}
1,107✔
4053

4054

4055
/* decrease reference count of encoder object (thread-unsafe) */
4056
SIXELAPI void
4057
sixel_encoder_unref(sixel_encoder_t *encoder)
1,614✔
4058
{
4059
    /* TODO: be thread safe */
4060
    if (encoder != NULL && --encoder->ref == 0) {
1,614!
4061
        sixel_encoder_destroy(encoder);
507✔
4062
    }
169✔
4063
}
1,614✔
4064

4065

4066
/* set cancel state flag to encoder object */
4067
SIXELAPI SIXELSTATUS
4068
sixel_encoder_set_cancel_flag(
426✔
4069
    sixel_encoder_t /* in */ *encoder,
4070
    int             /* in */ *cancel_flag
4071
)
4072
{
4073
    SIXELSTATUS status = SIXEL_OK;
426✔
4074

4075
    encoder->cancel_flag = cancel_flag;
426✔
4076

4077
    return status;
426✔
4078
}
4079

4080

4081
/* set an option flag to encoder object */
4082
SIXELAPI SIXELSTATUS
4083
sixel_encoder_setopt(
681✔
4084
    sixel_encoder_t /* in */ *encoder,
4085
    int             /* in */ arg,
4086
    char const      /* in */ *value)
4087
{
4088
    SIXELSTATUS status = SIXEL_FALSE;
681✔
4089
    int number;
4090
    int parsed;
4091
    char unit[32];
4092
    char lowered[16];
4093
    size_t len;
4094
    size_t i;
4095
    long parsed_reqcolors;
4096
    char *endptr;
4097
    int forced_palette;
4098
    char *opt_copy;
4099
    char const *drcs_arg_delim;
4100
    char const *drcs_arg_charset;
4101
    char const *drcs_arg_second_delim;
4102
    char const *drcs_arg_path;
4103
    size_t drcs_arg_path_length;
4104
    size_t drcs_segment_length;
4105
    char drcs_segment[32];
4106
    int drcs_mmv_value;
4107
    long drcs_charset_value;
4108
    unsigned int drcs_charset_limit;
4109

4110
    sixel_encoder_ref(encoder);
681✔
4111
    opt_copy = NULL;
681✔
4112

4113
    switch(arg) {
681!
4114
    case SIXEL_OPTFLAG_OUTFILE:  /* o */
18✔
4115
        if (*value == '\0') {
27!
4116
            sixel_helper_set_additional_message(
×
4117
                "no file name specified.");
4118
            status = SIXEL_BAD_ARGUMENT;
×
4119
            goto end;
×
4120
        }
4121
        if (strcmp(value, "-") != 0) {
27!
4122
            if (encoder->outfd && encoder->outfd != STDOUT_FILENO) {
27!
4123
#if HAVE__CLOSE
4124
                (void) _close(encoder->outfd);
4125
#else
4126
                (void) close(encoder->outfd);
×
4127
#endif  /* HAVE__CLOSE */
4128
            }
4129
#if HAVE__OPEN
4130
            encoder->outfd = _open(value,
4131
                                   O_RDWR|O_CREAT|O_TRUNC,
4132
                                   S_IRUSR|S_IWUSR);
4133
#else
4134
            encoder->outfd = open(value,
27✔
4135
                                  O_RDWR|O_CREAT|O_TRUNC,
4136
                                  S_IRUSR|S_IWUSR);
4137
#endif  /* HAVE__OPEN */
4138
        }
9✔
4139
        break;
27✔
4140
    case SIXEL_OPTFLAG_7BIT_MODE:  /* 7 */
10✔
4141
        encoder->f8bit = 0;
15✔
4142
        break;
15✔
4143
    case SIXEL_OPTFLAG_8BIT_MODE:  /* 8 */
12✔
4144
        encoder->f8bit = 1;
18✔
4145
        break;
18✔
4146
    case SIXEL_OPTFLAG_HAS_GRI_ARG_LIMIT:  /* R */
4147
        encoder->has_gri_arg_limit = 1;
×
4148
        break;
×
4149
    case SIXEL_OPTFLAG_COLORS:  /* p */
18✔
4150
        forced_palette = 0;
27✔
4151
        errno = 0;
27✔
4152
        endptr = NULL;
27✔
4153
        if (*value == '!' && value[1] == '\0') {
27!
4154
            /*
4155
             * Force the default palette size even when the median cut
4156
             * finished early.
4157
             *
4158
             *   requested colors
4159
             *          |
4160
             *          v
4161
             *        [ 256 ]  <--- "-p!" triggers this shortcut
4162
             */
4163
            parsed_reqcolors = SIXEL_PALETTE_MAX;
×
4164
            forced_palette = 1;
×
4165
        } else {
4166
            parsed_reqcolors = strtol(value, &endptr, 10);
27✔
4167
            if (endptr != NULL && *endptr == '!') {
27!
4168
                forced_palette = 1;
×
4169
                ++endptr;
×
4170
            }
4171
            if (errno == ERANGE || endptr == value) {
27!
4172
                sixel_helper_set_additional_message(
×
4173
                    "cannot parse -p/--colors option.");
4174
                status = SIXEL_BAD_ARGUMENT;
×
4175
                goto end;
×
4176
            }
4177
            if (endptr != NULL && *endptr != '\0') {
27!
4178
                sixel_helper_set_additional_message(
×
4179
                    "cannot parse -p/--colors option.");
4180
                status = SIXEL_BAD_ARGUMENT;
×
4181
                goto end;
×
4182
            }
4183
        }
4184
        if (parsed_reqcolors < 1) {
27!
4185
            sixel_helper_set_additional_message(
×
4186
                "-p/--colors parameter must be 1 or more.");
4187
            status = SIXEL_BAD_ARGUMENT;
×
4188
            goto end;
×
4189
        }
4190
        if (parsed_reqcolors > SIXEL_PALETTE_MAX) {
27!
4191
            sixel_helper_set_additional_message(
×
4192
                "-p/--colors parameter must be less then or equal to 256.");
4193
            status = SIXEL_BAD_ARGUMENT;
×
4194
            goto end;
×
4195
        }
4196
        encoder->reqcolors = (int)parsed_reqcolors;
27✔
4197
        encoder->force_palette = forced_palette;
27✔
4198
        break;
27✔
4199
    case SIXEL_OPTFLAG_MAPFILE:  /* m */
18✔
4200
        if (encoder->mapfile) {
27✔
4201
            sixel_allocator_free(encoder->allocator, encoder->mapfile);
3✔
4202
        }
1✔
4203
        encoder->mapfile = arg_strdup(value, encoder->allocator);
27✔
4204
        if (encoder->mapfile == NULL) {
27!
4205
            sixel_helper_set_additional_message(
×
4206
                "sixel_encoder_setopt: sixel_allocator_malloc() failed.");
4207
            status = SIXEL_BAD_ALLOCATION;
×
4208
            goto end;
×
4209
        }
4210
        encoder->color_option = SIXEL_COLOR_OPTION_MAPFILE;
27✔
4211
        break;
27✔
4212
    case SIXEL_OPTFLAG_MAPFILE_OUTPUT:  /* M */
NEW
4213
        if (value == NULL || *value == '\0') {
×
NEW
4214
            sixel_helper_set_additional_message(
×
4215
                "sixel_encoder_setopt: mapfile-output path is empty.");
NEW
4216
            status = SIXEL_BAD_ARGUMENT;
×
NEW
4217
            goto end;
×
4218
        }
NEW
4219
        opt_copy = arg_strdup(value, encoder->allocator);
×
NEW
4220
        if (opt_copy == NULL) {
×
NEW
4221
            sixel_helper_set_additional_message(
×
4222
                "sixel_encoder_setopt: sixel_allocator_malloc() failed.");
NEW
4223
            status = SIXEL_BAD_ALLOCATION;
×
NEW
4224
            goto end;
×
4225
        }
NEW
4226
        status = sixel_encoder_enable_quantized_capture(encoder, 1);
×
NEW
4227
        if (SIXEL_FAILED(status)) {
×
NEW
4228
            sixel_allocator_free(encoder->allocator, opt_copy);
×
NEW
4229
            goto end;
×
4230
        }
NEW
4231
        sixel_allocator_free(encoder->allocator, encoder->palette_output);
×
NEW
4232
        encoder->palette_output = opt_copy;
×
NEW
4233
        opt_copy = NULL;
×
NEW
4234
        break;
×
4235
    case SIXEL_OPTFLAG_MONOCHROME:  /* e */
10✔
4236
        encoder->color_option = SIXEL_COLOR_OPTION_MONOCHROME;
15✔
4237
        break;
15✔
4238
    case SIXEL_OPTFLAG_HIGH_COLOR:  /* I */
28✔
4239
        encoder->color_option = SIXEL_COLOR_OPTION_HIGHCOLOR;
42✔
4240
        break;
42✔
4241
    case SIXEL_OPTFLAG_BUILTIN_PALETTE:  /* b */
22✔
4242
        if (strcmp(value, "xterm16") == 0) {
33✔
4243
            encoder->builtin_palette = SIXEL_BUILTIN_XTERM16;
6✔
4244
        } else if (strcmp(value, "xterm256") == 0) {
29✔
4245
            encoder->builtin_palette = SIXEL_BUILTIN_XTERM256;
6✔
4246
        } else if (strcmp(value, "vt340mono") == 0) {
23✔
4247
            encoder->builtin_palette = SIXEL_BUILTIN_VT340_MONO;
3✔
4248
        } else if (strcmp(value, "vt340color") == 0) {
19✔
4249
            encoder->builtin_palette = SIXEL_BUILTIN_VT340_COLOR;
3✔
4250
        } else if (strcmp(value, "gray1") == 0) {
16✔
4251
            encoder->builtin_palette = SIXEL_BUILTIN_G1;
3✔
4252
        } else if (strcmp(value, "gray2") == 0) {
13✔
4253
            encoder->builtin_palette = SIXEL_BUILTIN_G2;
3✔
4254
        } else if (strcmp(value, "gray4") == 0) {
10✔
4255
            encoder->builtin_palette = SIXEL_BUILTIN_G4;
3✔
4256
        } else if (strcmp(value, "gray8") == 0) {
7✔
4257
            encoder->builtin_palette = SIXEL_BUILTIN_G8;
3✔
4258
        } else {
1✔
4259
            sixel_helper_set_additional_message(
3✔
4260
                    "cannot parse builtin palette option.");
4261
            status = SIXEL_BAD_ARGUMENT;
3✔
4262
            goto end;
3✔
4263
        }
4264
        encoder->color_option = SIXEL_COLOR_OPTION_BUILTIN;
30✔
4265
        break;
30✔
4266
    case SIXEL_OPTFLAG_DIFFUSION:  /* d */
46✔
4267
        /* parse --diffusion option */
4268
        if (strcmp(value, "auto") == 0) {
69✔
4269
            encoder->method_for_diffuse = SIXEL_DIFFUSE_AUTO;
3✔
4270
        } else if (strcmp(value, "none") == 0) {
67✔
4271
            encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
15✔
4272
        } else if (strcmp(value, "fs") == 0) {
56✔
4273
            encoder->method_for_diffuse = SIXEL_DIFFUSE_FS;
6✔
4274
        } else if (strcmp(value, "atkinson") == 0) {
47✔
4275
            encoder->method_for_diffuse = SIXEL_DIFFUSE_ATKINSON;
12✔
4276
        } else if (strcmp(value, "jajuni") == 0) {
37✔
4277
            encoder->method_for_diffuse = SIXEL_DIFFUSE_JAJUNI;
6✔
4278
        } else if (strcmp(value, "stucki") == 0) {
29✔
4279
            encoder->method_for_diffuse = SIXEL_DIFFUSE_STUCKI;
6✔
4280
        } else if (strcmp(value, "burkes") == 0) {
23✔
4281
            encoder->method_for_diffuse = SIXEL_DIFFUSE_BURKES;
6✔
4282
        } else if (strcmp(value, "sierra1") == 0) {
17!
4283
            encoder->method_for_diffuse = SIXEL_DIFFUSE_SIERRA1;
×
4284
        } else if (strcmp(value, "sierra2") == 0) {
15!
4285
            encoder->method_for_diffuse = SIXEL_DIFFUSE_SIERRA2;
×
4286
        } else if (strcmp(value, "sierra3") == 0) {
15!
4287
            encoder->method_for_diffuse = SIXEL_DIFFUSE_SIERRA3;
×
4288
        } else if (strcmp(value, "a_dither") == 0) {
15✔
4289
            encoder->method_for_diffuse = SIXEL_DIFFUSE_A_DITHER;
6✔
4290
        } else if (strcmp(value, "x_dither") == 0) {
11✔
4291
            encoder->method_for_diffuse = SIXEL_DIFFUSE_X_DITHER;
6✔
4292
        } else if (strcmp(value, "lso1") == 0) {
5!
4293
            encoder->method_for_diffuse = SIXEL_DIFFUSE_LSO1;
×
4294
        } else if (strcmp(value, "lso2") == 0) {
3!
4295
            encoder->method_for_diffuse = SIXEL_DIFFUSE_LSO2;
×
4296
        } else if (strcmp(value, "lso3") == 0) {
3!
4297
            encoder->method_for_diffuse = SIXEL_DIFFUSE_LSO3;
×
4298
        } else {
4299
            sixel_helper_set_additional_message(
3✔
4300
                "specified diffusion method is not supported.");
4301
            status = SIXEL_BAD_ARGUMENT;
3✔
4302
            goto end;
3✔
4303
        }
4304
        break;
66✔
4305
    case SIXEL_OPTFLAG_DIFFUSION_SCAN:  /* y */
4306
        if (strcmp(value, "auto") == 0) {
×
4307
            encoder->method_for_scan = SIXEL_SCAN_AUTO;
×
4308
        } else if (strcmp(value, "serpentine") == 0) {
×
4309
            encoder->method_for_scan = SIXEL_SCAN_SERPENTINE;
×
4310
        } else if (strcmp(value, "raster") == 0) {
×
4311
            encoder->method_for_scan = SIXEL_SCAN_RASTER;
×
4312
        } else {
4313
            sixel_helper_set_additional_message(
×
4314
                "specified diffusion scan is not supported.");
4315
            status = SIXEL_BAD_ARGUMENT;
×
4316
            goto end;
×
4317
        }
4318
        break;
×
4319
    case SIXEL_OPTFLAG_DIFFUSION_CARRY:  /* Y */
4320
        if (strcmp(value, "auto") == 0) {
×
4321
            encoder->method_for_carry = SIXEL_CARRY_AUTO;
×
4322
        } else if (strcmp(value, "direct") == 0) {
×
4323
            encoder->method_for_carry = SIXEL_CARRY_DISABLE;
×
4324
        } else if (strcmp(value, "carry") == 0) {
×
4325
            encoder->method_for_carry = SIXEL_CARRY_ENABLE;
×
4326
        } else {
4327
            sixel_helper_set_additional_message(
×
4328
                "specified diffusion carry mode is not supported.");
4329
            status = SIXEL_BAD_ARGUMENT;
×
4330
            goto end;
×
4331
        }
4332
        break;
×
4333
    case SIXEL_OPTFLAG_FIND_LARGEST:  /* f */
10✔
4334
        /* parse --find-largest option */
4335
        if (value) {
15!
4336
            if (strcmp(value, "auto") == 0) {
15✔
4337
                encoder->method_for_largest = SIXEL_LARGE_AUTO;
3✔
4338
            } else if (strcmp(value, "norm") == 0) {
13✔
4339
                encoder->method_for_largest = SIXEL_LARGE_NORM;
6✔
4340
            } else if (strcmp(value, "lum") == 0) {
8✔
4341
                encoder->method_for_largest = SIXEL_LARGE_LUM;
3✔
4342
            } else {
1✔
4343
                sixel_helper_set_additional_message(
3✔
4344
                    "specified finding method is not supported.");
4345
                status = SIXEL_BAD_ARGUMENT;
3✔
4346
                goto end;
3✔
4347
            }
4348
        }
4✔
4349
        break;
12✔
4350
    case SIXEL_OPTFLAG_SELECT_COLOR:  /* s */
10✔
4351
        /* parse --select-color option */
4352
        if (strcmp(value, "auto") == 0) {
15✔
4353
            encoder->method_for_rep = SIXEL_REP_AUTO;
3✔
4354
        } else if (strcmp(value, "center") == 0) {
13✔
4355
            encoder->method_for_rep = SIXEL_REP_CENTER_BOX;
3✔
4356
        } else if (strcmp(value, "average") == 0) {
10✔
4357
            encoder->method_for_rep = SIXEL_REP_AVERAGE_COLORS;
3✔
4358
        } else if ((strcmp(value, "histogram") == 0) ||
7!
4359
                   (strcmp(value, "histgram") == 0)) {
3!
4360
            encoder->method_for_rep = SIXEL_REP_AVERAGE_PIXELS;
3✔
4361
        } else {
1✔
4362
            sixel_helper_set_additional_message(
3✔
4363
                "specified finding method is not supported.");
4364
            status = SIXEL_BAD_ARGUMENT;
3✔
4365
            goto end;
3✔
4366
        }
4367
        break;
12✔
4368
    case SIXEL_OPTFLAG_CROP:  /* c */
10✔
4369
#if HAVE_SSCANF_S
4370
        number = sscanf_s(value, "%dx%d+%d+%d",
4371
                          &encoder->clipwidth, &encoder->clipheight,
4372
                          &encoder->clipx, &encoder->clipy);
4373
#else
4374
        number = sscanf(value, "%dx%d+%d+%d",
20✔
4375
                        &encoder->clipwidth, &encoder->clipheight,
5✔
4376
                        &encoder->clipx, &encoder->clipy);
5✔
4377
#endif  /* HAVE_SSCANF_S */
4378
        if (number != 4) {
15!
4379
            status = SIXEL_BAD_ARGUMENT;
×
4380
            goto end;
×
4381
        }
4382
        if (encoder->clipwidth <= 0 || encoder->clipheight <= 0) {
15!
4383
            status = SIXEL_BAD_ARGUMENT;
×
4384
            goto end;
×
4385
        }
4386
        if (encoder->clipx < 0 || encoder->clipy < 0) {
15!
4387
            status = SIXEL_BAD_ARGUMENT;
×
4388
            goto end;
×
4389
        }
4390
        encoder->clipfirst = 0;
15✔
4391
        break;
15✔
4392
    case SIXEL_OPTFLAG_WIDTH:  /* w */
50✔
4393
#if HAVE_SSCANF_S
4394
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
4395
#else
4396
        parsed = sscanf(value, "%d%2s", &number, unit);
75✔
4397
#endif  /* HAVE_SSCANF_S */
4398
        if (parsed == 2 && strcmp(unit, "%") == 0) {
75!
4399
            encoder->pixelwidth = (-1);
12✔
4400
            encoder->percentwidth = number;
12✔
4401
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
67!
4402
            encoder->pixelwidth = number;
51✔
4403
            encoder->percentwidth = (-1);
51✔
4404
        } else if (strcmp(value, "auto") == 0) {
29✔
4405
            encoder->pixelwidth = (-1);
9✔
4406
            encoder->percentwidth = (-1);
9✔
4407
        } else {
3✔
4408
            sixel_helper_set_additional_message(
3✔
4409
                "cannot parse -w/--width option.");
4410
            status = SIXEL_BAD_ARGUMENT;
3✔
4411
            goto end;
3✔
4412
        }
4413
        if (encoder->clipwidth) {
72✔
4414
            encoder->clipfirst = 1;
6✔
4415
        }
2✔
4416
        break;
72✔
4417
    case SIXEL_OPTFLAG_HEIGHT:  /* h */
44✔
4418
#if HAVE_SSCANF_S
4419
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
4420
#else
4421
        parsed = sscanf(value, "%d%2s", &number, unit);
66✔
4422
#endif  /* HAVE_SSCANF_S */
4423
        if (parsed == 2 && strcmp(unit, "%") == 0) {
66!
4424
            encoder->pixelheight = (-1);
9✔
4425
            encoder->percentheight = number;
9✔
4426
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
60!
4427
            encoder->pixelheight = number;
45✔
4428
            encoder->percentheight = (-1);
45✔
4429
        } else if (strcmp(value, "auto") == 0) {
27✔
4430
            encoder->pixelheight = (-1);
9✔
4431
            encoder->percentheight = (-1);
9✔
4432
        } else {
3✔
4433
            sixel_helper_set_additional_message(
3✔
4434
                "cannot parse -h/--height option.");
4435
            status = SIXEL_BAD_ARGUMENT;
3✔
4436
            goto end;
3✔
4437
        }
4438
        if (encoder->clipheight) {
63✔
4439
            encoder->clipfirst = 1;
3✔
4440
        }
1✔
4441
        break;
63✔
4442
    case SIXEL_OPTFLAG_RESAMPLING:  /* r */
34✔
4443
        /* parse --resampling option */
4444
        if (strcmp(value, "nearest") == 0) {
51✔
4445
            encoder->method_for_resampling = SIXEL_RES_NEAREST;
18✔
4446
        } else if (strcmp(value, "gaussian") == 0) {
39✔
4447
            encoder->method_for_resampling = SIXEL_RES_GAUSSIAN;
3✔
4448
        } else if (strcmp(value, "hanning") == 0) {
31✔
4449
            encoder->method_for_resampling = SIXEL_RES_HANNING;
3✔
4450
        } else if (strcmp(value, "hamming") == 0) {
28✔
4451
            encoder->method_for_resampling = SIXEL_RES_HAMMING;
3✔
4452
        } else if (strcmp(value, "bilinear") == 0) {
25✔
4453
            encoder->method_for_resampling = SIXEL_RES_BILINEAR;
3✔
4454
        } else if (strcmp(value, "welsh") == 0) {
22✔
4455
            encoder->method_for_resampling = SIXEL_RES_WELSH;
3✔
4456
        } else if (strcmp(value, "bicubic") == 0) {
19✔
4457
            encoder->method_for_resampling = SIXEL_RES_BICUBIC;
3✔
4458
        } else if (strcmp(value, "lanczos2") == 0) {
16✔
4459
            encoder->method_for_resampling = SIXEL_RES_LANCZOS2;
6✔
4460
        } else if (strcmp(value, "lanczos3") == 0) {
11✔
4461
            encoder->method_for_resampling = SIXEL_RES_LANCZOS3;
3✔
4462
        } else if (strcmp(value, "lanczos4") == 0) {
7✔
4463
            encoder->method_for_resampling = SIXEL_RES_LANCZOS4;
3✔
4464
        } else {
1✔
4465
            sixel_helper_set_additional_message(
3✔
4466
                "specified desampling method is not supported.");
4467
            status = SIXEL_BAD_ARGUMENT;
3✔
4468
            goto end;
3✔
4469
        }
4470
        break;
48✔
4471
    case SIXEL_OPTFLAG_QUALITY:  /* q */
12✔
4472
        /* parse --quality option */
4473
        if (strcmp(value, "auto") == 0) {
18✔
4474
            encoder->quality_mode = SIXEL_QUALITY_AUTO;
6✔
4475
        } else if (strcmp(value, "high") == 0) {
14✔
4476
            encoder->quality_mode = SIXEL_QUALITY_HIGH;
3✔
4477
        } else if (strcmp(value, "low") == 0) {
10✔
4478
            encoder->quality_mode = SIXEL_QUALITY_LOW;
3✔
4479
        } else if (strcmp(value, "full") == 0) {
7✔
4480
            encoder->quality_mode = SIXEL_QUALITY_FULL;
3✔
4481
        } else {
1✔
4482
            sixel_helper_set_additional_message(
3✔
4483
                "cannot parse quality option.");
4484
            status = SIXEL_BAD_ARGUMENT;
3✔
4485
            goto end;
3✔
4486
        }
4487
        break;
15✔
4488
    case SIXEL_OPTFLAG_LOOPMODE:  /* l */
10✔
4489
        /* parse --loop-control option */
4490
        if (strcmp(value, "auto") == 0) {
15✔
4491
            encoder->loop_mode = SIXEL_LOOP_AUTO;
3✔
4492
        } else if (strcmp(value, "force") == 0) {
13!
4493
            encoder->loop_mode = SIXEL_LOOP_FORCE;
×
4494
        } else if (strcmp(value, "disable") == 0) {
12✔
4495
            encoder->loop_mode = SIXEL_LOOP_DISABLE;
9✔
4496
        } else {
3✔
4497
            sixel_helper_set_additional_message(
3✔
4498
                "cannot parse loop-control option.");
4499
            status = SIXEL_BAD_ARGUMENT;
3✔
4500
            goto end;
3✔
4501
        }
4502
        break;
12✔
4503
    case SIXEL_OPTFLAG_PALETTE_TYPE:  /* t */
18✔
4504
        /* parse --palette-type option */
4505
        if (strcmp(value, "auto") == 0) {
27✔
4506
            encoder->palette_type = SIXEL_PALETTETYPE_AUTO;
6✔
4507
        } else if (strcmp(value, "hls") == 0) {
23✔
4508
            encoder->palette_type = SIXEL_PALETTETYPE_HLS;
15✔
4509
        } else if (strcmp(value, "rgb") == 0) {
11✔
4510
            encoder->palette_type = SIXEL_PALETTETYPE_RGB;
3✔
4511
        } else {
1✔
4512
            sixel_helper_set_additional_message(
3✔
4513
                "cannot parse palette type option.");
4514
            status = SIXEL_BAD_ARGUMENT;
3✔
4515
            goto end;
3✔
4516
        }
4517
        break;
24✔
4518
    case SIXEL_OPTFLAG_BGCOLOR:  /* B */
30✔
4519
        /* parse --bgcolor option */
4520
        if (encoder->bgcolor) {
45✔
4521
            sixel_allocator_free(encoder->allocator, encoder->bgcolor);
6✔
4522
            encoder->bgcolor = NULL;
6✔
4523
        }
2✔
4524
        status = sixel_parse_x_colorspec(&encoder->bgcolor,
60✔
4525
                                         value,
15✔
4526
                                         encoder->allocator);
15✔
4527
        if (SIXEL_FAILED(status)) {
45✔
4528
            sixel_helper_set_additional_message(
21✔
4529
                "cannot parse bgcolor option.");
4530
            status = SIXEL_BAD_ARGUMENT;
21✔
4531
            goto end;
21✔
4532
        }
4533
        break;
24✔
4534
    case SIXEL_OPTFLAG_INSECURE:  /* k */
4535
        encoder->finsecure = 1;
×
4536
        break;
×
4537
    case SIXEL_OPTFLAG_INVERT:  /* i */
4✔
4538
        encoder->finvert = 1;
6✔
4539
        break;
6✔
4540
    case SIXEL_OPTFLAG_USE_MACRO:  /* u */
4✔
4541
        encoder->fuse_macro = 1;
6✔
4542
        break;
6✔
4543
    case SIXEL_OPTFLAG_MACRO_NUMBER:  /* n */
2✔
4544
        encoder->macro_number = atoi(value);
3✔
4545
        if (encoder->macro_number < 0) {
3!
4546
            status = SIXEL_BAD_ARGUMENT;
×
4547
            goto end;
×
4548
        }
4549
        break;
3✔
4550
    case SIXEL_OPTFLAG_IGNORE_DELAY:  /* g */
4✔
4551
        encoder->fignore_delay = 1;
6✔
4552
        break;
6✔
4553
    case SIXEL_OPTFLAG_VERBOSE:  /* v */
6✔
4554
        encoder->verbose = 1;
9✔
4555
        sixel_helper_set_loader_trace(1);
9✔
4556
        break;
9✔
4557
    case SIXEL_OPTFLAG_LOADERS:  /* J */
4558
        if (encoder->loader_order != NULL) {
×
4559
            sixel_allocator_free(encoder->allocator,
×
4560
                                 encoder->loader_order);
×
4561
            encoder->loader_order = NULL;
×
4562
        }
4563
        if (value != NULL && *value != '\0') {
×
4564
            encoder->loader_order = arg_strdup(value,
×
4565
                                               encoder->allocator);
4566
            if (encoder->loader_order == NULL) {
×
4567
                sixel_helper_set_additional_message(
×
4568
                    "sixel_encoder_setopt: "
4569
                    "sixel_allocator_malloc() failed.");
4570
                status = SIXEL_BAD_ALLOCATION;
×
4571
                goto end;
×
4572
            }
4573
        }
4574
        break;
×
4575
    case SIXEL_OPTFLAG_STATIC:  /* S */
2✔
4576
        encoder->fstatic = 1;
3✔
4577
        break;
3✔
4578
    case SIXEL_OPTFLAG_DRCS:  /* @ */
4579
        encoder->fdrcs = 1;
×
4580
        drcs_arg_delim = NULL;
×
4581
        drcs_arg_charset = NULL;
×
4582
        drcs_arg_second_delim = NULL;
×
4583
        drcs_arg_path = NULL;
×
4584
        drcs_arg_path_length = 0u;
×
4585
        drcs_segment_length = 0u;
×
4586
        drcs_mmv_value = 2;
×
4587
        drcs_charset_value = 1L;
×
4588
        drcs_charset_limit = 0u;
×
4589
        if (value != NULL && *value != '\0') {
×
4590
            drcs_arg_delim = strchr(value, ':');
×
4591
            if (drcs_arg_delim == NULL) {
×
4592
                drcs_segment_length = strlen(value);
×
4593
                if (drcs_segment_length >= sizeof(drcs_segment)) {
×
4594
                    sixel_helper_set_additional_message(
×
4595
                        "DRCS mapping revision is too long.");
4596
                    status = SIXEL_BAD_ARGUMENT;
×
4597
                    goto end;
×
4598
                }
4599
                memcpy(drcs_segment, value, drcs_segment_length);
×
4600
                drcs_segment[drcs_segment_length] = '\0';
×
4601
                errno = 0;
×
4602
                endptr = NULL;
×
4603
                drcs_mmv_value = (int)strtol(drcs_segment, &endptr, 10);
×
4604
                if (errno != 0 || endptr == drcs_segment || *endptr != '\0') {
×
4605
                    sixel_helper_set_additional_message(
×
4606
                        "cannot parse DRCS option.");
4607
                    status = SIXEL_BAD_ARGUMENT;
×
4608
                    goto end;
×
4609
                }
4610
            } else {
4611
                if (drcs_arg_delim != value) {
×
4612
                    drcs_segment_length =
×
4613
                        (size_t)(drcs_arg_delim - value);
×
4614
                    if (drcs_segment_length >= sizeof(drcs_segment)) {
×
4615
                        sixel_helper_set_additional_message(
×
4616
                            "DRCS mapping revision is too long.");
4617
                        status = SIXEL_BAD_ARGUMENT;
×
4618
                        goto end;
×
4619
                    }
4620
                    memcpy(drcs_segment, value, drcs_segment_length);
×
4621
                    drcs_segment[drcs_segment_length] = '\0';
×
4622
                    errno = 0;
×
4623
                    endptr = NULL;
×
4624
                    drcs_mmv_value = (int)strtol(drcs_segment, &endptr, 10);
×
4625
                    if (errno != 0 || endptr == drcs_segment || *endptr != '\0') {
×
4626
                        sixel_helper_set_additional_message(
×
4627
                            "cannot parse DRCS option.");
4628
                        status = SIXEL_BAD_ARGUMENT;
×
4629
                        goto end;
×
4630
                    }
4631
                }
4632
                drcs_arg_charset = drcs_arg_delim + 1;
×
4633
                drcs_arg_second_delim = strchr(drcs_arg_charset, ':');
×
4634
                if (drcs_arg_second_delim != NULL) {
×
4635
                    if (drcs_arg_second_delim != drcs_arg_charset) {
×
4636
                        drcs_segment_length =
×
4637
                            (size_t)(drcs_arg_second_delim - drcs_arg_charset);
×
4638
                        if (drcs_segment_length >= sizeof(drcs_segment)) {
×
4639
                            sixel_helper_set_additional_message(
×
4640
                                "DRCS charset number is too long.");
4641
                            status = SIXEL_BAD_ARGUMENT;
×
4642
                            goto end;
×
4643
                        }
4644
                        memcpy(drcs_segment,
×
4645
                               drcs_arg_charset,
4646
                               drcs_segment_length);
4647
                        drcs_segment[drcs_segment_length] = '\0';
×
4648
                        errno = 0;
×
4649
                        endptr = NULL;
×
4650
                        drcs_charset_value = strtol(drcs_segment,
×
4651
                                                    &endptr,
4652
                                                    10);
4653
                        if (errno != 0 || endptr == drcs_segment ||
×
4654
                                *endptr != '\0') {
×
4655
                            sixel_helper_set_additional_message(
×
4656
                                "cannot parse DRCS charset number.");
4657
                            status = SIXEL_BAD_ARGUMENT;
×
4658
                            goto end;
×
4659
                        }
4660
                    }
4661
                    drcs_arg_path = drcs_arg_second_delim + 1;
×
4662
                    drcs_arg_path_length = strlen(drcs_arg_path);
×
4663
                    if (drcs_arg_path_length == 0u) {
×
4664
                        drcs_arg_path = NULL;
×
4665
                    }
4666
                } else if (*drcs_arg_charset != '\0') {
×
4667
                    drcs_segment_length = strlen(drcs_arg_charset);
×
4668
                    if (drcs_segment_length >= sizeof(drcs_segment)) {
×
4669
                        sixel_helper_set_additional_message(
×
4670
                            "DRCS charset number is too long.");
4671
                        status = SIXEL_BAD_ARGUMENT;
×
4672
                        goto end;
×
4673
                    }
4674
                    memcpy(drcs_segment,
×
4675
                           drcs_arg_charset,
4676
                           drcs_segment_length);
4677
                    drcs_segment[drcs_segment_length] = '\0';
×
4678
                    errno = 0;
×
4679
                    endptr = NULL;
×
4680
                    drcs_charset_value = strtol(drcs_segment,
×
4681
                                                &endptr,
4682
                                                10);
4683
                    if (errno != 0 || endptr == drcs_segment ||
×
4684
                            *endptr != '\0') {
×
4685
                        sixel_helper_set_additional_message(
×
4686
                            "cannot parse DRCS charset number.");
4687
                        status = SIXEL_BAD_ARGUMENT;
×
4688
                        goto end;
×
4689
                    }
4690
                }
4691
            }
4692
        }
4693
        /*
4694
         * Layout of the DRCS option value:
4695
         *
4696
         *    value = <mmv>:<charset_no>:<path>
4697
         *          ^        ^                ^
4698
         *          |        |                |
4699
         *          |        |                +-- optional path that may reuse
4700
         *          |        |                    STDOUT when set to "-" or drop
4701
         *          |        |                    tiles when left blank
4702
         *          |        +-- charset number (defaults to 1 when omitted)
4703
         *          +-- mapping revision (defaults to 2 when omitted)
4704
         */
4705
        if (drcs_mmv_value < 0 || drcs_mmv_value > 2) {
×
4706
            sixel_helper_set_additional_message(
×
4707
                "unknown DRCS unicode mapping version.");
4708
            status = SIXEL_BAD_ARGUMENT;
×
4709
            goto end;
×
4710
        }
4711
        if (drcs_mmv_value == 0) {
×
4712
            drcs_charset_limit = 126u;
×
4713
        } else if (drcs_mmv_value == 1) {
×
4714
            drcs_charset_limit = 63u;
×
4715
        } else {
4716
            drcs_charset_limit = 158u;
×
4717
        }
4718
        if (drcs_charset_value < 1 ||
×
4719
            (unsigned long)drcs_charset_value > drcs_charset_limit) {
×
4720
            sixel_helper_set_additional_message(
×
4721
                "DRCS charset number is out of range.");
4722
            status = SIXEL_BAD_ARGUMENT;
×
4723
            goto end;
×
4724
        }
4725
        encoder->drcs_mmv = drcs_mmv_value;
×
4726
        encoder->drcs_charset_no = (unsigned short)drcs_charset_value;
×
4727
        if (encoder->tile_outfd >= 0
×
4728
            && encoder->tile_outfd != encoder->outfd
×
4729
            && encoder->tile_outfd != STDOUT_FILENO
×
4730
            && encoder->tile_outfd != STDERR_FILENO) {
×
4731
#if HAVE__CLOSE
4732
            (void) _close(encoder->tile_outfd);
4733
#else
4734
            (void) close(encoder->tile_outfd);
×
4735
#endif  /* HAVE__CLOSE */
4736
        }
4737
        encoder->tile_outfd = (-1);
×
4738
        if (drcs_arg_path != NULL) {
×
4739
            if (strcmp(drcs_arg_path, "-") == 0) {
×
4740
                encoder->tile_outfd = STDOUT_FILENO;
×
4741
            } else {
4742
#if HAVE__OPEN
4743
                encoder->tile_outfd = _open(drcs_arg_path,
4744
                                            O_RDWR|O_CREAT|O_TRUNC,
4745
                                            S_IRUSR|S_IWUSR);
4746
#else
4747
                encoder->tile_outfd = open(drcs_arg_path,
×
4748
                                           O_RDWR|O_CREAT|O_TRUNC,
4749
                                           S_IRUSR|S_IWUSR);
4750
#endif  /* HAVE__OPEN */
4751
                if (encoder->tile_outfd < 0) {
×
4752
                    sixel_helper_set_additional_message(
×
4753
                        "sixel_encoder_setopt: failed to open tile"
4754
                        " output path.");
4755
                    status = SIXEL_RUNTIME_ERROR;
×
4756
                    goto end;
×
4757
                }
4758
            }
4759
        }
4760
        break;
×
4761
    case SIXEL_OPTFLAG_PENETRATE:  /* P */
6✔
4762
        encoder->penetrate_multiplexer = 1;
9✔
4763
        break;
9✔
4764
    case SIXEL_OPTFLAG_ENCODE_POLICY:  /* E */
8✔
4765
        if (strcmp(value, "auto") == 0) {
12✔
4766
            encoder->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
3✔
4767
        } else if (strcmp(value, "fast") == 0) {
10✔
4768
            encoder->encode_policy = SIXEL_ENCODEPOLICY_FAST;
3✔
4769
        } else if (strcmp(value, "size") == 0) {
7✔
4770
            encoder->encode_policy = SIXEL_ENCODEPOLICY_SIZE;
3✔
4771
        } else {
1✔
4772
            sixel_helper_set_additional_message(
3✔
4773
                "cannot parse encode policy option.");
4774
            status = SIXEL_BAD_ARGUMENT;
3✔
4775
            goto end;
3✔
4776
        }
4777
        break;
9✔
4778
    case SIXEL_OPTFLAG_LUT_POLICY:  /* L */
4779
        if (strcmp(value, "auto") == 0) {
×
4780
            encoder->lut_policy = SIXEL_LUT_POLICY_AUTO;
×
4781
        } else if (strcmp(value, "5bit") == 0) {
×
4782
            encoder->lut_policy = SIXEL_LUT_POLICY_5BIT;
×
4783
        } else if (strcmp(value, "6bit") == 0) {
×
4784
            encoder->lut_policy = SIXEL_LUT_POLICY_6BIT;
×
4785
        } else if (strcmp(value, "robinhood") == 0) {
×
4786
            encoder->lut_policy = SIXEL_LUT_POLICY_ROBINHOOD;
×
4787
        } else if (strcmp(value, "hopscotch") == 0) {
×
4788
            encoder->lut_policy = SIXEL_LUT_POLICY_HOPSCOTCH;
×
4789
        } else {
4790
            sixel_helper_set_additional_message(
×
4791
                "cannot parse lut policy option.");
4792
            status = SIXEL_BAD_ARGUMENT;
×
4793
            goto end;
×
4794
        }
4795
        if (encoder->dither_cache != NULL) {
×
4796
            sixel_dither_set_lut_policy(encoder->dither_cache,
×
4797
                                        encoder->lut_policy);
4798
        }
4799
        break;
×
4800
    case SIXEL_OPTFLAG_WORKING_COLORSPACE:  /* W */
4801
        if (value == NULL) {
×
4802
            sixel_helper_set_additional_message(
×
4803
                "working-colorspace requires an argument.");
4804
            status = SIXEL_BAD_ARGUMENT;
×
4805
            goto end;
×
4806
        } else {
4807
            len = strlen(value);
×
4808

4809
            if (len >= sizeof(lowered)) {
×
4810
                sixel_helper_set_additional_message(
×
4811
                    "specified working colorspace name is too long.");
4812
                status = SIXEL_BAD_ARGUMENT;
×
4813
                goto end;
×
4814
            }
4815
            for (i = 0; i < len; ++i) {
×
4816
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
4817
            }
4818
            lowered[len] = '\0';
×
4819

4820
            if (strcmp(lowered, "gamma") == 0) {
×
4821
                encoder->working_colorspace = SIXEL_COLORSPACE_GAMMA;
×
4822
            } else if (strcmp(lowered, "linear") == 0) {
×
4823
                encoder->working_colorspace = SIXEL_COLORSPACE_LINEAR;
×
4824
            } else if (strcmp(lowered, "oklab") == 0) {
×
4825
                encoder->working_colorspace = SIXEL_COLORSPACE_OKLAB;
×
4826
            } else {
4827
                sixel_helper_set_additional_message(
×
4828
                    "unsupported working colorspace specified.");
4829
                status = SIXEL_BAD_ARGUMENT;
×
4830
                goto end;
×
4831
            }
4832
        }
4833
        break;
×
4834
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
4835
        if (value == NULL) {
×
4836
            sixel_helper_set_additional_message(
×
4837
                "output-colorspace requires an argument.");
4838
            status = SIXEL_BAD_ARGUMENT;
×
4839
            goto end;
×
4840
        } else {
4841
            len = strlen(value);
×
4842

4843
            if (len >= sizeof(lowered)) {
×
4844
                sixel_helper_set_additional_message(
×
4845
                    "specified output colorspace name is too long.");
4846
                status = SIXEL_BAD_ARGUMENT;
×
4847
                goto end;
×
4848
            }
4849
            for (i = 0; i < len; ++i) {
×
4850
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
4851
            }
4852
            lowered[len] = '\0';
×
4853

4854
            if (strcmp(lowered, "gamma") == 0) {
×
4855
                encoder->output_colorspace = SIXEL_COLORSPACE_GAMMA;
×
4856
            } else if (strcmp(lowered, "linear") == 0) {
×
4857
                encoder->output_colorspace = SIXEL_COLORSPACE_LINEAR;
×
4858
            } else if (strcmp(lowered, "smpte-c") == 0 ||
×
4859
                       strcmp(lowered, "smptec") == 0) {
×
4860
                encoder->output_colorspace = SIXEL_COLORSPACE_SMPTEC;
×
4861
            } else {
4862
                sixel_helper_set_additional_message(
×
4863
                    "unsupported output colorspace specified.");
4864
                status = SIXEL_BAD_ARGUMENT;
×
4865
                goto end;
×
4866
            }
4867
        }
4868
        break;
×
4869
    case SIXEL_OPTFLAG_ORMODE:  /* O */
4870
        encoder->ormode = 1;
×
4871
        break;
×
4872
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
6✔
4873
        encoder->complexion = atoi(value);
9✔
4874
        if (encoder->complexion < 1) {
9✔
4875
            sixel_helper_set_additional_message(
3✔
4876
                "complexion parameter must be 1 or more.");
4877
            status = SIXEL_BAD_ARGUMENT;
3✔
4878
            goto end;
3✔
4879
        }
4880
        break;
6✔
4881
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
4882
        encoder->pipe_mode = 1;
×
4883
        break;
×
4884
    case '?':  /* unknown option */
3✔
4885
    default:
4886
        /* exit if unknown options are specified */
4887
        sixel_helper_set_additional_message(
3✔
4888
            "unknown option is specified.");
4889
        status = SIXEL_BAD_ARGUMENT;
3✔
4890
        goto end;
3✔
4891
    }
4892

4893
    /* detects arguments conflictions */
4894
    if (encoder->reqcolors != (-1)) {
621✔
4895
        switch (encoder->color_option) {
99!
4896
        case SIXEL_COLOR_OPTION_MAPFILE:
4897
            sixel_helper_set_additional_message(
×
4898
                "option -p, --colors conflicts with -m, --mapfile.");
4899
            status = SIXEL_BAD_ARGUMENT;
×
4900
            goto end;
×
4901
        case SIXEL_COLOR_OPTION_MONOCHROME:
2✔
4902
            sixel_helper_set_additional_message(
3✔
4903
                "option -e, --monochrome conflicts with -p, --colors.");
4904
            status = SIXEL_BAD_ARGUMENT;
3✔
4905
            goto end;
3✔
4906
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
2✔
4907
            sixel_helper_set_additional_message(
3✔
4908
                "option -p, --colors conflicts with -I, --high-color.");
4909
            status = SIXEL_BAD_ARGUMENT;
3✔
4910
            goto end;
3✔
4911
        case SIXEL_COLOR_OPTION_BUILTIN:
2✔
4912
            sixel_helper_set_additional_message(
3✔
4913
                "option -p, --colors conflicts with -b, --builtin-palette.");
4914
            status = SIXEL_BAD_ARGUMENT;
3✔
4915
            goto end;
3✔
4916
        default:
60✔
4917
            break;
90✔
4918
        }
4919
    }
30✔
4920

4921
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
4922
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
612✔
4923
        sixel_helper_set_additional_message(
3✔
4924
            "option -8 --8bit-mode conflicts"
4925
            " with -P, --penetrate.");
4926
        status = SIXEL_BAD_ARGUMENT;
3✔
4927
        goto end;
3✔
4928
    }
4929

4930
    status = SIXEL_OK;
609✔
4931

4932
end:
454✔
4933
    if (opt_copy != NULL) {
681!
NEW
4934
        sixel_allocator_free(encoder->allocator, opt_copy);
×
4935
    }
4936
    sixel_encoder_unref(encoder);
681✔
4937

4938
    return status;
681✔
4939
}
4940

4941

4942
/* called when image loader component load a image frame */
4943
static SIXELSTATUS
4944
load_image_callback(sixel_frame_t *frame, void *data)
527✔
4945
{
4946
    sixel_encoder_t *encoder;
4947

4948
    encoder = (sixel_encoder_t *)data;
527✔
4949
    if (encoder->capture_source && encoder->capture_source_frame == NULL) {
527!
4950
        sixel_frame_ref(frame);
×
4951
        encoder->capture_source_frame = frame;
×
4952
    }
4953

4954
    return sixel_encoder_encode_frame(encoder, frame, NULL);
527✔
4955
}
4956

4957

4958
/* load source data from specified file and encode it to SIXEL format
4959
 * output to encoder->outfd */
4960
SIXELAPI SIXELSTATUS
4961
sixel_encoder_encode(
426✔
4962
    sixel_encoder_t *encoder,   /* encoder object */
4963
    char const      *filename)  /* input filename */
4964
{
4965
    SIXELSTATUS status = SIXEL_FALSE;
426✔
4966
    SIXELSTATUS palette_status = SIXEL_OK;
426✔
4967
    int fuse_palette = 1;
426✔
4968
    sixel_loader_t *loader;
4969

4970
    if (encoder == NULL) {
426!
4971
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
4972
#  pragma GCC diagnostic push
4973
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
4974
#endif
4975
        encoder = sixel_encoder_create();
×
4976
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
4977
#  pragma GCC diagnostic pop
4978
#endif
4979
        if (encoder == NULL) {
×
4980
            sixel_helper_set_additional_message(
×
4981
                "sixel_encoder_encode: sixel_encoder_create() failed.");
4982
            status = SIXEL_BAD_ALLOCATION;
×
4983
            goto end;
×
4984
        }
4985
    } else {
4986
        sixel_encoder_ref(encoder);
426✔
4987
    }
4988

4989
    if (encoder->assessment_observer != NULL) {
426!
4990
        sixel_assessment_stage_transition(
×
4991
            encoder->assessment_observer,
×
4992
            SIXEL_ASSESSMENT_STAGE_IMAGE_CHUNK);
4993
    }
4994
    encoder->last_loader_name[0] = '\0';
426✔
4995
    encoder->last_source_path[0] = '\0';
426✔
4996
    encoder->last_input_bytes = 0u;
426✔
4997

4998
    /* if required color is not set, set the max value */
4999
    if (encoder->reqcolors == (-1)) {
426✔
5000
        encoder->reqcolors = SIXEL_PALETTE_MAX;
408✔
5001
    }
136✔
5002

5003
    if (encoder->capture_source && encoder->capture_source_frame != NULL) {
426!
5004
        sixel_frame_unref(encoder->capture_source_frame);
×
5005
        encoder->capture_source_frame = NULL;
×
5006
    }
5007

5008
    /* if required color is less then 2, set the min value */
5009
    if (encoder->reqcolors < 2) {
426✔
5010
        encoder->reqcolors = SIXEL_PALETTE_MIN;
3✔
5011
    }
1✔
5012

5013
    /* if color space option is not set, choose RGB color space */
5014
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
426✔
5015
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
408✔
5016
    }
136✔
5017

5018
    /* if color option is not default value, prohibit to read
5019
       the file as a paletted image */
5020
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
426✔
5021
        fuse_palette = 0;
99✔
5022
    }
33✔
5023

5024
    /* if scaling options are set, prohibit to read the file as
5025
       a paletted image */
5026
    if (encoder->percentwidth > 0 ||
545✔
5027
        encoder->percentheight > 0 ||
414✔
5028
        encoder->pixelwidth > 0 ||
408✔
5029
        encoder->pixelheight > 0) {
390✔
5030
        fuse_palette = 0;
99✔
5031
    }
33✔
5032

5033
reload:
284✔
5034
    loader = NULL;
426✔
5035

5036
    sixel_helper_set_loader_trace(encoder->verbose);
426✔
5037
    sixel_helper_set_thumbnail_size_hint(
426✔
5038
        sixel_encoder_thumbnail_hint(encoder));
142✔
5039

5040
    status = sixel_loader_new(&loader, encoder->allocator);
426✔
5041
    if (SIXEL_FAILED(status)) {
426!
5042
        goto load_end;
×
5043
    }
5044

5045
    status = sixel_loader_setopt(loader,
568✔
5046
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
5047
                                 &encoder->fstatic);
426✔
5048
    if (SIXEL_FAILED(status)) {
426!
5049
        goto load_end;
×
5050
    }
5051

5052
    status = sixel_loader_setopt(loader,
426✔
5053
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
5054
                                 &fuse_palette);
5055
    if (SIXEL_FAILED(status)) {
426!
5056
        goto load_end;
×
5057
    }
5058

5059
    status = sixel_loader_setopt(loader,
568✔
5060
                                 SIXEL_LOADER_OPTION_REQCOLORS,
5061
                                 &encoder->reqcolors);
426✔
5062
    if (SIXEL_FAILED(status)) {
426!
5063
        goto load_end;
×
5064
    }
5065

5066
    status = sixel_loader_setopt(loader,
568✔
5067
                                 SIXEL_LOADER_OPTION_BGCOLOR,
5068
                                 encoder->bgcolor);
426✔
5069
    if (SIXEL_FAILED(status)) {
426!
5070
        goto load_end;
×
5071
    }
5072

5073
    status = sixel_loader_setopt(loader,
568✔
5074
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
5075
                                 &encoder->loop_mode);
426✔
5076
    if (SIXEL_FAILED(status)) {
426!
5077
        goto load_end;
×
5078
    }
5079

5080
    status = sixel_loader_setopt(loader,
568✔
5081
                                 SIXEL_LOADER_OPTION_INSECURE,
5082
                                 &encoder->finsecure);
426✔
5083
    if (SIXEL_FAILED(status)) {
426!
5084
        goto load_end;
×
5085
    }
5086

5087
    status = sixel_loader_setopt(loader,
568✔
5088
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
5089
                                 encoder->cancel_flag);
426✔
5090
    if (SIXEL_FAILED(status)) {
426!
5091
        goto load_end;
×
5092
    }
5093

5094
    status = sixel_loader_setopt(loader,
568✔
5095
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
5096
                                 encoder->loader_order);
426✔
5097
    if (SIXEL_FAILED(status)) {
426!
5098
        goto load_end;
×
5099
    }
5100

5101
    status = sixel_loader_setopt(loader,
568✔
5102
                                 SIXEL_LOADER_OPTION_CONTEXT,
5103
                                 encoder);
142✔
5104
    if (SIXEL_FAILED(status)) {
426!
5105
        goto load_end;
×
5106
    }
5107

5108
    /*
5109
     * Wire the optional assessment observer into the loader.
5110
     *
5111
     * The observer travels separately from the callback context so mapfile
5112
     * palette probes and other callbacks can keep using arbitrary structs.
5113
     */
5114
    status = sixel_loader_setopt(loader,
568✔
5115
                                 SIXEL_LOADER_OPTION_ASSESSMENT,
5116
                                 encoder->assessment_observer);
426✔
5117
    if (SIXEL_FAILED(status)) {
426!
5118
        goto load_end;
×
5119
    }
5120

5121
    status = sixel_loader_load_file(loader,
568✔
5122
                                    filename,
142✔
5123
                                    load_image_callback);
5124
    if (status != SIXEL_OK) {
426✔
5125
        goto load_end;
21✔
5126
    }
5127
    encoder->last_input_bytes = sixel_loader_get_last_input_bytes(loader);
405✔
5128
    if (sixel_loader_get_last_success_name(loader) != NULL) {
405!
5129
        (void)snprintf(encoder->last_loader_name,
405✔
5130
                       sizeof(encoder->last_loader_name),
5131
                       "%s",
5132
                       sixel_loader_get_last_success_name(loader));
5133
    } else {
135✔
5134
        encoder->last_loader_name[0] = '\0';
×
5135
    }
5136
    if (sixel_loader_get_last_source_path(loader) != NULL) {
405✔
5137
        (void)snprintf(encoder->last_source_path,
267✔
5138
                       sizeof(encoder->last_source_path),
5139
                       "%s",
5140
                       sixel_loader_get_last_source_path(loader));
5141
    } else {
89✔
5142
        encoder->last_source_path[0] = '\0';
138✔
5143
    }
5144
    if (encoder->assessment_observer != NULL) {
405!
5145
        sixel_assessment_record_loader(encoder->assessment_observer,
×
5146
                                       encoder->last_source_path,
×
5147
                                       encoder->last_loader_name,
×
5148
                                       encoder->last_input_bytes);
5149
    }
5150

5151
load_end:
270✔
5152
    sixel_loader_unref(loader);
426✔
5153
    loader = NULL;
426✔
5154

5155
    if (status != SIXEL_OK) {
426✔
5156
        goto end;
21✔
5157
    }
5158

5159
    palette_status = sixel_encoder_emit_palette_output(encoder);
405✔
5160
    if (SIXEL_FAILED(palette_status)) {
405!
NEW
5161
        status = palette_status;
×
NEW
5162
        goto end;
×
5163
    }
5164

5165
    if (encoder->pipe_mode) {
405!
5166
#if HAVE_CLEARERR
5167
        clearerr(stdin);
×
5168
#endif  /* HAVE_FSEEK */
5169
        while (encoder->cancel_flag && !*encoder->cancel_flag) {
×
5170
            status = sixel_tty_wait_stdin(1000000);
×
5171
            if (SIXEL_FAILED(status)) {
×
5172
                goto end;
×
5173
            }
5174
            if (status != SIXEL_OK) {
×
5175
                break;
×
5176
            }
5177
        }
5178
        if (!encoder->cancel_flag || !*encoder->cancel_flag) {
×
5179
            goto reload;
×
5180
        }
5181
    }
5182

5183
    /* the status may not be SIXEL_OK */
5184

5185
end:
270✔
5186
    sixel_encoder_unref(encoder);
426✔
5187

5188
    return status;
426✔
5189
}
5190

5191

5192
/* encode specified pixel data to SIXEL format
5193
 * output to encoder->outfd */
5194
SIXELAPI SIXELSTATUS
5195
sixel_encoder_encode_bytes(
×
5196
    sixel_encoder_t     /* in */    *encoder,
5197
    unsigned char       /* in */    *bytes,
5198
    int                 /* in */    width,
5199
    int                 /* in */    height,
5200
    int                 /* in */    pixelformat,
5201
    unsigned char       /* in */    *palette,
5202
    int                 /* in */    ncolors)
5203
{
5204
    SIXELSTATUS status = SIXEL_FALSE;
×
5205
    sixel_frame_t *frame = NULL;
×
5206

5207
    if (encoder == NULL || bytes == NULL) {
×
5208
        status = SIXEL_BAD_ARGUMENT;
×
5209
        goto end;
×
5210
    }
5211

5212
    status = sixel_frame_new(&frame, encoder->allocator);
×
5213
    if (SIXEL_FAILED(status)) {
×
5214
        goto end;
×
5215
    }
5216

5217
    status = sixel_frame_init(frame, bytes, width, height,
×
5218
                              pixelformat, palette, ncolors);
5219
    if (SIXEL_FAILED(status)) {
×
5220
        goto end;
×
5221
    }
5222

5223
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
×
5224
    if (SIXEL_FAILED(status)) {
×
5225
        goto end;
×
5226
    }
5227

5228
    status = SIXEL_OK;
×
5229

5230
end:
5231
    /* we need to free the frame before exiting, but we can't use the
5232
       sixel_frame_destroy function, because that will also attempt to
5233
       free the pixels and palette, which we don't own */
5234
    if (frame != NULL && encoder->allocator != NULL) {
×
5235
        sixel_allocator_free(encoder->allocator, frame);
×
5236
        sixel_allocator_unref(encoder->allocator);
×
5237
    }
5238
    return status;
×
5239
}
5240

5241

5242
/*
5243
 * Toggle source-frame capture for assessment consumers.
5244
 */
5245
SIXELAPI SIXELSTATUS
5246
sixel_encoder_enable_source_capture(
×
5247
    sixel_encoder_t *encoder,
5248
    int enable)
5249
{
5250
    if (encoder == NULL) {
×
5251
        sixel_helper_set_additional_message(
×
5252
            "sixel_encoder_enable_source_capture: encoder is null.");
5253
        return SIXEL_BAD_ARGUMENT;
×
5254
    }
5255

5256
    encoder->capture_source = enable ? 1 : 0;
×
5257
    if (!encoder->capture_source && encoder->capture_source_frame != NULL) {
×
5258
        sixel_frame_unref(encoder->capture_source_frame);
×
5259
        encoder->capture_source_frame = NULL;
×
5260
    }
5261

5262
    return SIXEL_OK;
×
5263
}
5264

5265

5266
/*
5267
 * Enable or disable the quantized-frame capture facility.
5268
 *
5269
 *     capture on --> encoder keeps the latest palette-quantized frame.
5270
 *     capture off --> encoder forgets previously stored frames.
5271
 */
5272
SIXELAPI SIXELSTATUS
5273
sixel_encoder_enable_quantized_capture(
×
5274
    sixel_encoder_t *encoder,
5275
    int enable)
5276
{
5277
    if (encoder == NULL) {
×
5278
        sixel_helper_set_additional_message(
×
5279
            "sixel_encoder_enable_quantized_capture: encoder is null.");
5280
        return SIXEL_BAD_ARGUMENT;
×
5281
    }
5282

5283
    encoder->capture_quantized = enable ? 1 : 0;
×
5284
    if (!encoder->capture_quantized) {
×
5285
        encoder->capture_valid = 0;
×
5286
    }
5287

5288
    return SIXEL_OK;
×
5289
}
5290

5291

5292
/*
5293
 * Materialize the captured quantized frame as a heap-allocated
5294
 * sixel_frame_t instance for assessment consumers.
5295
 */
5296
SIXELAPI SIXELSTATUS
5297
sixel_encoder_copy_quantized_frame(
×
5298
    sixel_encoder_t   *encoder,
5299
    sixel_allocator_t *allocator,
5300
    sixel_frame_t     **ppframe)
5301
{
5302
    SIXELSTATUS status = SIXEL_FALSE;
×
5303
    sixel_frame_t *frame;
5304
    unsigned char *pixels;
5305
    unsigned char *palette;
5306
    size_t palette_bytes;
5307

5308
    if (encoder == NULL || allocator == NULL || ppframe == NULL) {
×
5309
        sixel_helper_set_additional_message(
×
5310
            "sixel_encoder_copy_quantized_frame: invalid argument.");
5311
        return SIXEL_BAD_ARGUMENT;
×
5312
    }
5313

5314
    if (!encoder->capture_quantized || !encoder->capture_valid) {
×
5315
        sixel_helper_set_additional_message(
×
5316
            "sixel_encoder_copy_quantized_frame: no frame captured.");
5317
        return SIXEL_RUNTIME_ERROR;
×
5318
    }
5319

5320
    *ppframe = NULL;
×
5321
    frame = NULL;
×
5322
    pixels = NULL;
×
5323
    palette = NULL;
×
5324

5325
    status = sixel_frame_new(&frame, allocator);
×
5326
    if (SIXEL_FAILED(status)) {
×
5327
        return status;
×
5328
    }
5329

5330
    if (encoder->capture_pixel_bytes > 0) {
×
5331
        pixels = (unsigned char *)sixel_allocator_malloc(
×
5332
            allocator, encoder->capture_pixel_bytes);
5333
        if (pixels == NULL) {
×
5334
            sixel_helper_set_additional_message(
×
5335
                "sixel_encoder_copy_quantized_frame: "
5336
                "sixel_allocator_malloc() failed.");
5337
            status = SIXEL_BAD_ALLOCATION;
×
5338
            goto cleanup;
×
5339
        }
5340
        memcpy(pixels,
×
5341
               encoder->capture_pixels,
5342
               encoder->capture_pixel_bytes);
5343
    }
5344

5345
    palette_bytes = encoder->capture_palette_size;
×
5346
    if (palette_bytes > 0) {
×
5347
        palette = (unsigned char *)sixel_allocator_malloc(allocator,
×
5348
                                                          palette_bytes);
5349
        if (palette == NULL) {
×
5350
            sixel_helper_set_additional_message(
×
5351
                "sixel_encoder_copy_quantized_frame: "
5352
                "sixel_allocator_malloc() failed.");
5353
            status = SIXEL_BAD_ALLOCATION;
×
5354
            goto cleanup;
×
5355
        }
5356
        memcpy(palette,
×
5357
               encoder->capture_palette,
5358
               palette_bytes);
5359
    }
5360

5361
    status = sixel_frame_init(frame,
×
5362
                              pixels,
5363
                              encoder->capture_width,
5364
                              encoder->capture_height,
5365
                              encoder->capture_pixelformat,
5366
                              palette,
5367
                              encoder->capture_ncolors);
5368
    if (SIXEL_FAILED(status)) {
×
5369
        goto cleanup;
×
5370
    }
5371

5372
    pixels = NULL;
×
5373
    palette = NULL;
×
5374
    frame->colorspace = encoder->capture_colorspace;
×
5375
    *ppframe = frame;
×
5376
    return SIXEL_OK;
×
5377

5378
cleanup:
5379
    if (palette != NULL) {
×
5380
        sixel_allocator_free(allocator, palette);
×
5381
    }
5382
    if (pixels != NULL) {
×
5383
        sixel_allocator_free(allocator, pixels);
×
5384
    }
5385
    if (frame != NULL) {
×
5386
        sixel_frame_unref(frame);
×
5387
    }
5388
    return status;
×
5389
}
5390

5391

5392
/*
5393
 * Emit the captured palette in the requested format.
5394
 *
5395
 *   palette_output == NULL  -> skip
5396
 *   palette_output != NULL  -> materialize captured palette
5397
 */
5398
static SIXELSTATUS
5399
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder)
405✔
5400
{
5401
    SIXELSTATUS status;
5402
    sixel_frame_t *frame;
5403
    unsigned char const *palette;
5404
    int exported_colors;
5405
    FILE *stream;
5406
    int close_stream;
5407
    char const *path;
5408
    sixel_palette_format_t format_hint;
5409
    sixel_palette_format_t format_ext;
5410
    sixel_palette_format_t format_final;
5411
    char const *mode;
5412

5413
    status = SIXEL_OK;
405✔
5414
    frame = NULL;
405✔
5415
    palette = NULL;
405✔
5416
    exported_colors = 0;
405✔
5417
    stream = NULL;
405✔
5418
    close_stream = 0;
405✔
5419
    path = NULL;
405✔
5420
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
405✔
5421
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
405✔
5422
    format_final = SIXEL_PALETTE_FORMAT_NONE;
405✔
5423
    mode = "wb";
405✔
5424

5425
    if (encoder == NULL || encoder->palette_output == NULL) {
405!
5426
        return SIXEL_OK;
405✔
5427
    }
5428

NEW
5429
    status = sixel_encoder_copy_quantized_frame(encoder,
×
5430
                                                encoder->allocator,
5431
                                                &frame);
NEW
5432
    if (SIXEL_FAILED(status)) {
×
NEW
5433
        return status;
×
5434
    }
5435

NEW
5436
    palette = (unsigned char const *)sixel_frame_get_palette(frame);
×
NEW
5437
    exported_colors = sixel_frame_get_ncolors(frame);
×
NEW
5438
    if (palette == NULL || exported_colors <= 0) {
×
NEW
5439
        sixel_helper_set_additional_message(
×
5440
            "sixel_encoder_emit_palette_output: palette unavailable.");
NEW
5441
        status = SIXEL_BAD_INPUT;
×
NEW
5442
        goto cleanup;
×
5443
    }
NEW
5444
    if (exported_colors > 256) {
×
NEW
5445
        exported_colors = 256;
×
5446
    }
5447

NEW
5448
    path = sixel_palette_strip_prefix(encoder->palette_output, &format_hint);
×
NEW
5449
    if (path == NULL || *path == '\0') {
×
NEW
5450
        sixel_helper_set_additional_message(
×
5451
            "sixel_encoder_emit_palette_output: invalid path.");
NEW
5452
        status = SIXEL_BAD_ARGUMENT;
×
NEW
5453
        goto cleanup;
×
5454
    }
5455

NEW
5456
    format_ext = sixel_palette_format_from_extension(path);
×
NEW
5457
    format_final = format_hint;
×
NEW
5458
    if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
NEW
5459
        if (format_ext == SIXEL_PALETTE_FORMAT_NONE) {
×
NEW
5460
            if (strcmp(path, "-") == 0) {
×
NEW
5461
                sixel_helper_set_additional_message(
×
5462
                    "sixel_encoder_emit_palette_output: "
5463
                    "format required for '-'.");
NEW
5464
                status = SIXEL_BAD_ARGUMENT;
×
NEW
5465
                goto cleanup;
×
5466
            }
NEW
5467
            sixel_helper_set_additional_message(
×
5468
                "sixel_encoder_emit_palette_output: "
5469
                "unknown palette file extension.");
NEW
5470
            status = SIXEL_BAD_ARGUMENT;
×
NEW
5471
            goto cleanup;
×
5472
        }
NEW
5473
        format_final = format_ext;
×
5474
    }
NEW
5475
    if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
NEW
5476
        format_final = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
5477
    }
5478

NEW
5479
    if (strcmp(path, "-") == 0) {
×
NEW
5480
        stream = stdout;
×
5481
    } else {
NEW
5482
        if (format_final == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
5483
                format_final == SIXEL_PALETTE_FORMAT_GPL) {
NEW
5484
            mode = "w";
×
5485
        } else {
NEW
5486
            mode = "wb";
×
5487
        }
NEW
5488
        stream = fopen(path, mode);
×
NEW
5489
        if (stream == NULL) {
×
NEW
5490
            sixel_helper_set_additional_message(
×
5491
                "sixel_encoder_emit_palette_output: failed to open file.");
NEW
5492
            status = SIXEL_LIBC_ERROR;
×
NEW
5493
            goto cleanup;
×
5494
        }
NEW
5495
        close_stream = 1;
×
5496
    }
5497

NEW
5498
    switch (format_final) {
×
5499
    case SIXEL_PALETTE_FORMAT_ACT:
NEW
5500
        status = sixel_palette_write_act(stream, palette, exported_colors);
×
NEW
5501
        if (SIXEL_FAILED(status)) {
×
NEW
5502
            sixel_helper_set_additional_message(
×
5503
                "sixel_encoder_emit_palette_output: failed to write ACT.");
5504
        }
NEW
5505
        break;
×
5506
    case SIXEL_PALETTE_FORMAT_PAL_JASC:
NEW
5507
        status = sixel_palette_write_pal_jasc(stream,
×
5508
                                              palette,
5509
                                              exported_colors);
NEW
5510
        if (SIXEL_FAILED(status)) {
×
NEW
5511
            sixel_helper_set_additional_message(
×
5512
                "sixel_encoder_emit_palette_output: failed to write JASC.");
5513
        }
NEW
5514
        break;
×
5515
    case SIXEL_PALETTE_FORMAT_PAL_RIFF:
NEW
5516
        status = sixel_palette_write_pal_riff(stream,
×
5517
                                              palette,
5518
                                              exported_colors);
NEW
5519
        if (SIXEL_FAILED(status)) {
×
NEW
5520
            sixel_helper_set_additional_message(
×
5521
                "sixel_encoder_emit_palette_output: failed to write RIFF.");
5522
        }
NEW
5523
        break;
×
5524
    case SIXEL_PALETTE_FORMAT_GPL:
NEW
5525
        status = sixel_palette_write_gpl(stream,
×
5526
                                         palette,
5527
                                         exported_colors);
NEW
5528
        if (SIXEL_FAILED(status)) {
×
NEW
5529
            sixel_helper_set_additional_message(
×
5530
                "sixel_encoder_emit_palette_output: failed to write GPL.");
5531
        }
NEW
5532
        break;
×
5533
    default:
NEW
5534
        sixel_helper_set_additional_message(
×
5535
            "sixel_encoder_emit_palette_output: unsupported format.");
NEW
5536
        status = SIXEL_BAD_ARGUMENT;
×
NEW
5537
        break;
×
5538
    }
NEW
5539
    if (SIXEL_FAILED(status)) {
×
NEW
5540
        goto cleanup;
×
5541
    }
5542

NEW
5543
    if (close_stream) {
×
NEW
5544
        if (fclose(stream) != 0) {
×
NEW
5545
            sixel_helper_set_additional_message(
×
5546
                "sixel_encoder_emit_palette_output: fclose() failed.");
NEW
5547
            status = SIXEL_LIBC_ERROR;
×
NEW
5548
            stream = NULL;
×
NEW
5549
            goto cleanup;
×
5550
        }
NEW
5551
        stream = NULL;
×
5552
    } else {
NEW
5553
        if (fflush(stream) != 0) {
×
NEW
5554
            sixel_helper_set_additional_message(
×
5555
                "sixel_encoder_emit_palette_output: fflush() failed.");
NEW
5556
            status = SIXEL_LIBC_ERROR;
×
NEW
5557
            goto cleanup;
×
5558
        }
5559
    }
5560

5561
cleanup:
NEW
5562
    if (close_stream && stream != NULL) {
×
NEW
5563
        (void) fclose(stream);
×
5564
    }
NEW
5565
    if (frame != NULL) {
×
NEW
5566
        sixel_frame_unref(frame);
×
5567
    }
5568

NEW
5569
    return status;
×
5570
}
135✔
5571

5572

5573
/*
5574
 * Share the captured source frame with assessment consumers.
5575
 */
5576
SIXELAPI SIXELSTATUS
5577
sixel_encoder_copy_source_frame(
×
5578
    sixel_encoder_t *encoder,
5579
    sixel_frame_t  **ppframe)
5580
{
5581
    if (encoder == NULL || ppframe == NULL) {
×
5582
        sixel_helper_set_additional_message(
×
5583
            "sixel_encoder_copy_source_frame: invalid argument.");
5584
        return SIXEL_BAD_ARGUMENT;
×
5585
    }
5586

5587
    if (!encoder->capture_source || encoder->capture_source_frame == NULL) {
×
5588
        sixel_helper_set_additional_message(
×
5589
            "sixel_encoder_copy_source_frame: no frame captured.");
5590
        return SIXEL_RUNTIME_ERROR;
×
5591
    }
5592

5593
    sixel_frame_ref(encoder->capture_source_frame);
×
5594
    *ppframe = encoder->capture_source_frame;
×
5595

5596
    return SIXEL_OK;
×
5597
}
5598

5599

5600
#if HAVE_TESTS
5601
static int
5602
test1(void)
×
5603
{
5604
    int nret = EXIT_FAILURE;
×
5605
    sixel_encoder_t *encoder = NULL;
×
5606

5607
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5608
#  pragma GCC diagnostic push
5609
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
5610
#endif
5611
    encoder = sixel_encoder_create();
×
5612
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5613
#  pragma GCC diagnostic pop
5614
#endif
5615
    if (encoder == NULL) {
×
5616
        goto error;
×
5617
    }
5618
    sixel_encoder_ref(encoder);
×
5619
    sixel_encoder_unref(encoder);
×
5620
    nret = EXIT_SUCCESS;
×
5621

5622
error:
5623
    sixel_encoder_unref(encoder);
×
5624
    return nret;
×
5625
}
5626

5627

5628
static int
5629
test2(void)
×
5630
{
5631
    int nret = EXIT_FAILURE;
×
5632
    SIXELSTATUS status;
5633
    sixel_encoder_t *encoder = NULL;
×
5634
    sixel_frame_t *frame = NULL;
×
5635
    unsigned char *buffer;
5636
    int height = 0;
×
5637
    int is_animation = 0;
×
5638

5639
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5640
#  pragma GCC diagnostic push
5641
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
5642
#endif
5643
    encoder = sixel_encoder_create();
×
5644
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5645
#  pragma GCC diagnostic pop
5646
#endif
5647
    if (encoder == NULL) {
×
5648
        goto error;
×
5649
    }
5650

5651
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5652
#  pragma GCC diagnostic push
5653
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
5654
#endif
5655
    frame = sixel_frame_create();
×
5656
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5657
#  pragma GCC diagnostic pop
5658
#endif
5659
    if (encoder == NULL) {
×
5660
        goto error;
×
5661
    }
5662

5663
    buffer = (unsigned char *)sixel_allocator_malloc(encoder->allocator, 3);
×
5664
    if (buffer == NULL) {
×
5665
        goto error;
×
5666
    }
5667
    status = sixel_frame_init(frame, buffer, 1, 1,
×
5668
                              SIXEL_PIXELFORMAT_RGB888,
5669
                              NULL, 0);
5670
    if (SIXEL_FAILED(status)) {
×
5671
        goto error;
×
5672
    }
5673

5674
    if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
×
5675
        is_animation = 1;
×
5676
    }
5677

5678
    height = sixel_frame_get_height(frame);
×
5679

5680
    status = sixel_tty_scroll(sixel_write_callback,
×
5681
                              &encoder->outfd,
×
5682
                              encoder->outfd,
5683
                              height,
5684
                              is_animation);
5685
    if (SIXEL_FAILED(status)) {
×
5686
        goto error;
×
5687
    }
5688

5689
    nret = EXIT_SUCCESS;
×
5690

5691
error:
5692
    sixel_encoder_unref(encoder);
×
5693
    sixel_frame_unref(frame);
×
5694
    return nret;
×
5695
}
5696

5697

5698
static int
5699
test3(void)
×
5700
{
5701
    int nret = EXIT_FAILURE;
×
5702
    int result;
5703

5704
    result = sixel_tty_wait_stdin(1000);
×
5705
    if (result != 0) {
×
5706
        goto error;
×
5707
    }
5708

5709
    nret = EXIT_SUCCESS;
×
5710

5711
error:
5712
    return nret;
×
5713
}
5714

5715

5716
static int
5717
test4(void)
×
5718
{
5719
    int nret = EXIT_FAILURE;
×
5720
    sixel_encoder_t *encoder = NULL;
×
5721
    SIXELSTATUS status;
5722

5723
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5724
# pragma GCC diagnostic push
5725
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
5726
#endif
5727
    encoder = sixel_encoder_create();
×
5728
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5729
# pragma GCC diagnostic pop
5730
#endif
5731
    if (encoder == NULL) {
×
5732
        goto error;
×
5733
    }
5734

5735
    status = sixel_encoder_setopt(encoder,
×
5736
                                  SIXEL_OPTFLAG_LOOPMODE,
5737
                                  "force");
5738
    if (SIXEL_FAILED(status)) {
×
5739
        goto error;
×
5740
    }
5741

5742
    status = sixel_encoder_setopt(encoder,
×
5743
                                  SIXEL_OPTFLAG_PIPE_MODE,
5744
                                  "force");
5745
    if (SIXEL_FAILED(status)) {
×
5746
        goto error;
×
5747
    }
5748

5749
    nret = EXIT_SUCCESS;
×
5750

5751
error:
5752
    sixel_encoder_unref(encoder);
×
5753
    return nret;
×
5754
}
5755

5756

5757
static int
5758
test5(void)
×
5759
{
5760
    int nret = EXIT_FAILURE;
×
5761
    sixel_encoder_t *encoder = NULL;
×
5762
    sixel_allocator_t *allocator = NULL;
×
5763
    SIXELSTATUS status;
5764

5765
    status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
5766
    if (SIXEL_FAILED(status)) {
×
5767
        goto error;
×
5768
    }
5769

5770
    status = sixel_encoder_new(&encoder, allocator);
×
5771
    if (SIXEL_FAILED(status)) {
×
5772
        goto error;
×
5773
    }
5774

5775
    sixel_encoder_ref(encoder);
×
5776
    sixel_encoder_unref(encoder);
×
5777
    nret = EXIT_SUCCESS;
×
5778

5779
error:
5780
    sixel_encoder_unref(encoder);
×
5781
    return nret;
×
5782
}
5783

5784

5785
SIXELAPI int
5786
sixel_encoder_tests_main(void)
×
5787
{
5788
    int nret = EXIT_FAILURE;
×
5789
    size_t i;
5790
    typedef int (* testcase)(void);
5791

5792
    static testcase const testcases[] = {
5793
        test1,
5794
        test2,
5795
        test3,
5796
        test4,
5797
        test5
5798
    };
5799

5800
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
5801
        nret = testcases[i]();
×
5802
        if (nret != EXIT_SUCCESS) {
×
5803
            goto error;
×
5804
        }
5805
    }
5806

5807
    nret = EXIT_SUCCESS;
×
5808

5809
error:
5810
    return nret;
×
5811
}
5812
#endif  /* HAVE_TESTS */
5813

5814

5815
/* emacs Local Variables:      */
5816
/* emacs mode: c               */
5817
/* emacs tab-width: 4          */
5818
/* emacs indent-tabs-mode: nil */
5819
/* emacs c-basic-offset: 4     */
5820
/* emacs End:                  */
5821
/* vim: set expandtab ts=4 : */
5822
/* 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