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

saitoha / libsixel / 18969353343

31 Oct 2025 10:09AM UTC coverage: 42.341% (-0.2%) from 42.575%
18969353343

push

github

saitoha
drcs: merge -M,--mapping-version and -T,--tiles into -@,--drcs; accept args as MMV:CHARSET:PATH

6059 of 21310 branches covered (28.43%)

2 of 143 new or added lines in 2 files covered. (1.4%)

4 existing lines in 1 file now uncovered.

8762 of 20694 relevant lines covered (42.34%)

907260.42 hits per line

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

54.01
/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,100✔
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,100✔
406
#endif
407

408
    return result;
4,100✔
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,
504✔
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;
504✔
615
    unsigned char *palette;
616
    int palette_colors;
617

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

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

629
    if (palette == NULL || palette_colors <= 0 ||
504!
630
            frame_colorspace == output->colorspace) {
504!
631
        return SIXEL_OK;
504✔
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
}
174✔
669

670
static void
671
sixel_encoder_restore_palette(sixel_encoder_t *encoder,
504✔
672
                              sixel_dither_t *dither,
673
                              palette_conversion_t *ctx)
674
{
675
    if (ctx->copy) {
504!
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 &&
504!
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
}
504✔
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)
443✔
879
{
880
    int width_hint;
881
    int height_hint;
882
    long base;
883
    long size;
884

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

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

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

897
    /* Request extra resolution for downscaling to preserve detail. */
898
    if (width_hint > 0 && height_hint > 0) {
443✔
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) {
437✔
908
        base = (long)width_hint;
48✔
909
    } else if (height_hint > 0) {
402✔
910
        base = (long)height_hint;
36✔
911
    } else {
12✔
912
        return 0;
350✔
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
/* create palette from specified map file */
1045
static SIXELSTATUS
1046
sixel_prepare_specified_palette(
26✔
1047
    sixel_dither_t  /* out */   **dither,
1048
    sixel_encoder_t /* in */    *encoder)
1049
{
1050
    SIXELSTATUS status = SIXEL_FALSE;
26✔
1051
    sixel_callback_context_for_mapfile_t callback_context;
1052
    sixel_loader_t *loader;
1053
    int fstatic;
1054
    int fuse_palette;
1055
    int reqcolors;
1056
    int loop_override;
1057

1058
    callback_context.reqcolors = encoder->reqcolors;
26✔
1059
    callback_context.dither = NULL;
26✔
1060
    callback_context.allocator = encoder->allocator;
26✔
1061
    callback_context.working_colorspace = encoder->working_colorspace;
26✔
1062
    callback_context.lut_policy = encoder->lut_policy;
26✔
1063

1064
    loader = NULL;
26✔
1065
    fstatic = 1;
26✔
1066
    fuse_palette = 1;
26✔
1067
    reqcolors = SIXEL_PALETTE_MAX;
26✔
1068
    loop_override = SIXEL_LOOP_DISABLE;
26✔
1069

1070
    sixel_helper_set_loader_trace(encoder->verbose);
26✔
1071
    sixel_helper_set_thumbnail_size_hint(
26✔
1072
        sixel_encoder_thumbnail_hint(encoder));
10✔
1073
    status = sixel_loader_new(&loader, encoder->allocator);
26✔
1074
    if (SIXEL_FAILED(status)) {
26!
1075
        goto end_loader;
×
1076
    }
1077

1078
    status = sixel_loader_setopt(loader,
26✔
1079
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
1080
                                 &fstatic);
1081
    if (SIXEL_FAILED(status)) {
26!
1082
        goto end_loader;
×
1083
    }
1084

1085
    status = sixel_loader_setopt(loader,
26✔
1086
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
1087
                                 &fuse_palette);
1088
    if (SIXEL_FAILED(status)) {
26!
1089
        goto end_loader;
×
1090
    }
1091

1092
    status = sixel_loader_setopt(loader,
26✔
1093
                                 SIXEL_LOADER_OPTION_REQCOLORS,
1094
                                 &reqcolors);
1095
    if (SIXEL_FAILED(status)) {
26!
1096
        goto end_loader;
×
1097
    }
1098

1099
    status = sixel_loader_setopt(loader,
36✔
1100
                                 SIXEL_LOADER_OPTION_BGCOLOR,
1101
                                 encoder->bgcolor);
26✔
1102
    if (SIXEL_FAILED(status)) {
26!
1103
        goto end_loader;
×
1104
    }
1105

1106
    status = sixel_loader_setopt(loader,
26✔
1107
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
1108
                                 &loop_override);
1109
    if (SIXEL_FAILED(status)) {
26!
1110
        goto end_loader;
×
1111
    }
1112

1113
    status = sixel_loader_setopt(loader,
36✔
1114
                                 SIXEL_LOADER_OPTION_INSECURE,
1115
                                 &encoder->finsecure);
26✔
1116
    if (SIXEL_FAILED(status)) {
26!
1117
        goto end_loader;
×
1118
    }
1119

1120
    status = sixel_loader_setopt(loader,
36✔
1121
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
1122
                                 encoder->cancel_flag);
26✔
1123
    if (SIXEL_FAILED(status)) {
26!
1124
        goto end_loader;
×
1125
    }
1126

1127
    status = sixel_loader_setopt(loader,
36✔
1128
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
1129
                                 encoder->loader_order);
26✔
1130
    if (SIXEL_FAILED(status)) {
26!
1131
        goto end_loader;
×
1132
    }
1133

1134
    status = sixel_loader_setopt(loader,
26✔
1135
                                 SIXEL_LOADER_OPTION_CONTEXT,
1136
                                 &callback_context);
1137
    if (SIXEL_FAILED(status)) {
26!
1138
        goto end_loader;
×
1139
    }
1140

1141
    status = sixel_loader_load_file(loader,
36✔
1142
                                    encoder->mapfile,
26✔
1143
                                    load_image_callback_for_palette);
1144
    if (status != SIXEL_OK) {
26✔
1145
        goto end_loader;
5✔
1146
    }
1147

1148
end_loader:
14✔
1149
    sixel_loader_unref(loader);
26✔
1150

1151
    if (status != SIXEL_OK) {
26✔
1152
        return status;
5✔
1153
    }
1154

1155
    if (! callback_context.dither) {
21!
1156
        sixel_helper_set_additional_message(
×
1157
            "sixel_prepare_specified_palette() failed.\n"
1158
            "reason: mapfile is empty.");
1159
        return SIXEL_BAD_INPUT;
×
1160
    }
1161

1162
    *dither = callback_context.dither;
21✔
1163

1164
    return status;
21✔
1165
}
10✔
1166

1167

1168
/* create dither object from a frame */
1169
static SIXELSTATUS
1170
sixel_encoder_prepare_palette(
509✔
1171
    sixel_encoder_t *encoder,  /* encoder object */
1172
    sixel_frame_t   *frame,    /* input frame object */
1173
    sixel_dither_t  **dither)  /* dither object to be created from the frame */
1174
{
1175
    SIXELSTATUS status = SIXEL_FALSE;
509✔
1176
    int histogram_colors;
1177
    sixel_assessment_t *assessment;
1178
    int promoted_stage;
1179

1180
    assessment = NULL;
509✔
1181
    promoted_stage = 0;
509✔
1182
    if (encoder != NULL) {
509!
1183
        assessment = encoder->assessment_observer;
509✔
1184
    }
177✔
1185

1186
    switch (encoder->color_option) {
509!
1187
    case SIXEL_COLOR_OPTION_HIGHCOLOR:
24✔
1188
        if (encoder->dither_cache) {
36!
1189
            *dither = encoder->dither_cache;
×
1190
            status = SIXEL_OK;
×
1191
        } else {
1192
            status = sixel_dither_new(dither, (-1), encoder->allocator);
36✔
1193
            sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
36✔
1194
        }
1195
        goto end;
36✔
1196
    case SIXEL_COLOR_OPTION_MONOCHROME:
8✔
1197
        if (encoder->dither_cache) {
12!
1198
            *dither = encoder->dither_cache;
×
1199
            status = SIXEL_OK;
×
1200
        } else {
1201
            status = sixel_prepare_monochrome_palette(dither, encoder->finvert);
12✔
1202
        }
1203
        goto end;
12✔
1204
    case SIXEL_COLOR_OPTION_MAPFILE:
16✔
1205
        if (encoder->dither_cache) {
26!
1206
            *dither = encoder->dither_cache;
×
1207
            status = SIXEL_OK;
×
1208
        } else {
1209
            status = sixel_prepare_specified_palette(dither, encoder);
26✔
1210
        }
1211
        goto end;
26✔
1212
    case SIXEL_COLOR_OPTION_BUILTIN:
18✔
1213
        if (encoder->dither_cache) {
27!
1214
            *dither = encoder->dither_cache;
×
1215
            status = SIXEL_OK;
×
1216
        } else {
1217
            status = sixel_prepare_builtin_palette(dither, encoder->builtin_palette);
27✔
1218
        }
1219
        goto end;
27✔
1220
    case SIXEL_COLOR_OPTION_DEFAULT:
408✔
1221
    default:
1222
        break;
408✔
1223
    }
1224

1225
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE) {
408✔
1226
        if (!sixel_frame_get_palette(frame)) {
191!
1227
            status = SIXEL_LOGIC_ERROR;
×
1228
            goto end;
×
1229
        }
1230
        status = sixel_dither_new(dither, sixel_frame_get_ncolors(frame),
230✔
1231
                                  encoder->allocator);
39✔
1232
        if (SIXEL_FAILED(status)) {
191!
1233
            goto end;
×
1234
        }
1235
        sixel_dither_set_palette(*dither, sixel_frame_get_palette(frame));
191✔
1236
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
191✔
1237
        if (sixel_frame_get_transparent(frame) != (-1)) {
191!
1238
            sixel_dither_set_transparent(*dither, sixel_frame_get_transparent(frame));
×
1239
        }
1240
        if (*dither && encoder->dither_cache) {
191!
1241
            sixel_dither_unref(encoder->dither_cache);
×
1242
        }
1243
        goto end;
191✔
1244
    }
1245

1246
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_GRAYSCALE) {
217!
1247
        switch (sixel_frame_get_pixelformat(frame)) {
×
1248
        case SIXEL_PIXELFORMAT_G1:
1249
            *dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1250
            break;
×
1251
        case SIXEL_PIXELFORMAT_G2:
1252
            *dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
1253
            break;
×
1254
        case SIXEL_PIXELFORMAT_G4:
1255
            *dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
1256
            break;
×
1257
        case SIXEL_PIXELFORMAT_G8:
1258
            *dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
1259
            break;
×
1260
        default:
1261
            *dither = NULL;
×
1262
            status = SIXEL_LOGIC_ERROR;
×
1263
            goto end;
×
1264
        }
1265
        if (*dither && encoder->dither_cache) {
×
1266
            sixel_dither_unref(encoder->dither_cache);
×
1267
        }
1268
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
×
1269
        status = SIXEL_OK;
×
1270
        goto end;
×
1271
    }
1272

1273
    if (encoder->dither_cache) {
217!
1274
        sixel_dither_unref(encoder->dither_cache);
×
1275
    }
1276
    status = sixel_dither_new(dither, encoder->reqcolors, encoder->allocator);
217✔
1277
    if (SIXEL_FAILED(status)) {
217!
1278
        goto end;
×
1279
    }
1280

1281
    sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
217✔
1282

1283
    status = sixel_dither_initialize(*dither,
320✔
1284
                                     sixel_frame_get_pixels(frame),
103✔
1285
                                     sixel_frame_get_width(frame),
103✔
1286
                                     sixel_frame_get_height(frame),
103✔
1287
                                     sixel_frame_get_pixelformat(frame),
103✔
1288
                                     encoder->method_for_largest,
103✔
1289
                                     encoder->method_for_rep,
103✔
1290
                                     encoder->quality_mode);
103✔
1291
    if (SIXEL_FAILED(status)) {
217!
1292
        sixel_dither_unref(*dither);
×
1293
        goto end;
×
1294
    }
1295

1296
    if (assessment != NULL && promoted_stage == 0) {
217!
1297
        sixel_assessment_stage_transition(
×
1298
            assessment,
1299
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
1300
        promoted_stage = 1;
×
1301
    }
1302

1303
    histogram_colors = sixel_dither_get_num_of_histogram_colors(*dither);
217✔
1304
    if (histogram_colors <= encoder->reqcolors) {
217✔
1305
        encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
172✔
1306
    }
88✔
1307
    sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
217✔
1308

1309
    status = SIXEL_OK;
217✔
1310

1311
end:
332✔
1312
    if (assessment != NULL && promoted_stage == 0) {
509!
1313
        sixel_assessment_stage_transition(
×
1314
            assessment,
1315
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
1316
        promoted_stage = 1;
×
1317
    }
1318
    if (SIXEL_SUCCEEDED(status) && dither != NULL && *dither != NULL) {
509!
1319
        sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
504✔
1320
        /* pass down the user's demand for an exact palette size */
1321
        (*dither)->force_palette = encoder->force_palette;
504✔
1322
    }
174✔
1323
    return status;
509✔
1324
}
1325

1326

1327
/* resize a frame with settings of specified encoder object */
1328
static SIXELSTATUS
1329
sixel_encoder_do_resize(
516✔
1330
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
1331
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
1332
{
1333
    SIXELSTATUS status = SIXEL_FALSE;
516✔
1334
    int src_width;
1335
    int src_height;
1336
    int dst_width;
1337
    int dst_height;
1338

1339
    /* get frame width and height */
1340
    src_width = sixel_frame_get_width(frame);
516✔
1341
    src_height = sixel_frame_get_height(frame);
516✔
1342

1343
    if (src_width < 1) {
516✔
1344
         sixel_helper_set_additional_message(
6✔
1345
             "sixel_encoder_do_resize: "
1346
             "detected a frame with a non-positive width.");
1347
        return SIXEL_BAD_ARGUMENT;
6✔
1348
    }
1349

1350
    if (src_height < 1) {
510!
1351
         sixel_helper_set_additional_message(
×
1352
             "sixel_encoder_do_resize: "
1353
             "detected a frame with a non-positive height.");
1354
        return SIXEL_BAD_ARGUMENT;
×
1355
    }
1356

1357
    /* settings around scaling */
1358
    dst_width = encoder->pixelwidth;    /* may be -1 (default) */
510✔
1359
    dst_height = encoder->pixelheight;  /* may be -1 (default) */
510✔
1360

1361
    /* if the encoder has percentwidth or percentheight property,
1362
       convert them to pixelwidth / pixelheight */
1363
    if (encoder->percentwidth > 0) {
510✔
1364
        dst_width = src_width * encoder->percentwidth / 100;
12✔
1365
    }
4✔
1366
    if (encoder->percentheight > 0) {
510✔
1367
        dst_height = src_height * encoder->percentheight / 100;
9✔
1368
    }
3✔
1369

1370
    /* if only either width or height is set, set also the other
1371
       to retain frame aspect ratio */
1372
    if (dst_width > 0 && dst_height <= 0) {
510✔
1373
        dst_height = src_height * dst_width / src_width;
42✔
1374
    }
14✔
1375
    if (dst_height > 0 && dst_width <= 0) {
510✔
1376
        dst_width = src_width * dst_height / src_height;
33✔
1377
    }
11✔
1378

1379
    /* do resize */
1380
    if (dst_width > 0 && dst_height > 0) {
510!
1381
        status = sixel_frame_resize(frame, dst_width, dst_height,
124✔
1382
                                    encoder->method_for_resampling);
31✔
1383
        if (SIXEL_FAILED(status)) {
93!
1384
            goto end;
×
1385
        }
1386
    }
31✔
1387

1388
    /* success */
1389
    status = SIXEL_OK;
510✔
1390

1391
end:
332✔
1392
    return status;
510✔
1393
}
180✔
1394

1395

1396
/* clip a frame with settings of specified encoder object */
1397
static SIXELSTATUS
1398
sixel_encoder_do_clip(
512✔
1399
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
1400
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
1401
{
1402
    SIXELSTATUS status = SIXEL_FALSE;
512✔
1403
    int src_width;
1404
    int src_height;
1405
    int clip_x;
1406
    int clip_y;
1407
    int clip_w;
1408
    int clip_h;
1409

1410
    /* get frame width and height */
1411
    src_width = sixel_frame_get_width(frame);
512✔
1412
    src_height = sixel_frame_get_height(frame);
512✔
1413

1414
    /* settings around clipping */
1415
    clip_x = encoder->clipx;
512✔
1416
    clip_y = encoder->clipy;
512✔
1417
    clip_w = encoder->clipwidth;
512✔
1418
    clip_h = encoder->clipheight;
512✔
1419

1420
    /* adjust clipping width with comparing it to frame width */
1421
    if (clip_w + clip_x > src_width) {
512✔
1422
        if (clip_x > src_width) {
7✔
1423
            clip_w = 0;
3✔
1424
        } else {
1✔
1425
            clip_w = src_width - clip_x;
4✔
1426
        }
1427
    }
3✔
1428

1429
    /* adjust clipping height with comparing it to frame height */
1430
    if (clip_h + clip_y > src_height) {
512✔
1431
        if (clip_y > src_height) {
6✔
1432
            clip_h = 0;
3✔
1433
        } else {
1✔
1434
            clip_h = src_height - clip_y;
3✔
1435
        }
1436
    }
2✔
1437

1438
    /* do clipping */
1439
    if (clip_w > 0 && clip_h > 0) {
512!
1440
        status = sixel_frame_clip(frame, clip_x, clip_y, clip_w, clip_h);
15✔
1441
        if (SIXEL_FAILED(status)) {
15!
1442
            goto end;
3✔
1443
        }
1444
    }
4✔
1445

1446
    /* success */
1447
    status = SIXEL_OK;
509✔
1448

1449
end:
332✔
1450
    return status;
512✔
1451
}
1452

1453

1454
static void
1455
sixel_debug_print_palette(
3✔
1456
    sixel_dither_t /* in */ *dither /* dithering object */
1457
)
1458
{
1459
    unsigned char *palette;
1460
    int i;
1461

1462
    palette = sixel_dither_get_palette(dither);
3✔
1463
    fprintf(stderr, "palette:\n");
3✔
1464
    for (i = 0; i < sixel_dither_get_num_of_palette_colors(dither); ++i) {
51✔
1465
        fprintf(stderr, "%d: #%02x%02x%02x\n", i,
64✔
1466
                palette[i * 3 + 0],
48✔
1467
                palette[i * 3 + 1],
48✔
1468
                palette[i * 3 + 2]);
48✔
1469
    }
16✔
1470
}
3✔
1471

1472

1473
static SIXELSTATUS
1474
sixel_encoder_output_without_macro(
411✔
1475
    sixel_frame_t       /* in */ *frame,
1476
    sixel_dither_t      /* in */ *dither,
1477
    sixel_output_t      /* in */ *output,
1478
    sixel_encoder_t     /* in */ *encoder)
1479
{
1480
    SIXELSTATUS status = SIXEL_OK;
411✔
1481
    static unsigned char *p;
1482
    int depth;
1483
    enum { message_buffer_size = 256 };
1484
    char message[message_buffer_size];
1485
    int nwrite;
1486
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
1487
    int dulation;
1488
    int delay;
1489
    struct timespec tv;
1490
#endif
1491
    unsigned char *pixbuf;
1492
    int width;
1493
    int height;
1494
    int pixelformat = 0;
411✔
1495
    size_t size;
1496
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
411✔
1497
    palette_conversion_t palette_ctx;
1498

1499
    memset(&palette_ctx, 0, sizeof(palette_ctx));
411✔
1500
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
1501
    sixel_clock_t last_clock;
1502
#endif
1503

1504
    if (encoder == NULL) {
411!
1505
        sixel_helper_set_additional_message(
×
1506
            "sixel_encoder_output_without_macro: encoder object is null.");
1507
        status = SIXEL_BAD_ARGUMENT;
×
1508
        goto end;
×
1509
    }
1510

1511
    if (encoder->assessment_observer != NULL) {
411!
1512
        sixel_assessment_stage_transition(
×
1513
            encoder->assessment_observer,
×
1514
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
1515
    }
1516

1517
    if (encoder->color_option == SIXEL_COLOR_OPTION_DEFAULT) {
411✔
1518
        if (encoder->force_palette) {
315!
1519
            /* keep every slot when the user forced the palette size */
1520
            sixel_dither_set_optimize_palette(dither, 0);
×
1521
        } else {
1522
            sixel_dither_set_optimize_palette(dither, 1);
315✔
1523
        }
1524
    }
105✔
1525

1526
    pixelformat = sixel_frame_get_pixelformat(frame);
411✔
1527
    frame_colorspace = sixel_frame_get_colorspace(frame);
411✔
1528
    output->pixelformat = pixelformat;
411✔
1529
    output->source_colorspace = frame_colorspace;
411✔
1530
    output->colorspace = encoder->output_colorspace;
411✔
1531
    sixel_dither_set_pixelformat(dither, pixelformat);
411✔
1532
    depth = sixel_helper_compute_depth(pixelformat);
411✔
1533
    if (depth < 0) {
411!
1534
        status = SIXEL_LOGIC_ERROR;
×
1535
        nwrite = sprintf(message,
×
1536
                         "sixel_encoder_output_without_macro: "
1537
                         "sixel_helper_compute_depth(%08x) failed.",
1538
                         pixelformat);
1539
        if (nwrite > 0) {
×
1540
            sixel_helper_set_additional_message(message);
×
1541
        }
1542
        goto end;
×
1543
    }
1544

1545
    width = sixel_frame_get_width(frame);
411✔
1546
    height = sixel_frame_get_height(frame);
411✔
1547
    size = (size_t)(width * height * depth);
411✔
1548
    p = (unsigned char *)sixel_allocator_malloc(encoder->allocator, size);
411✔
1549
    if (p == NULL) {
411!
1550
        sixel_helper_set_additional_message(
×
1551
            "sixel_encoder_output_without_macro: sixel_allocator_malloc() failed.");
1552
        status = SIXEL_BAD_ALLOCATION;
×
1553
        goto end;
×
1554
    }
1555
#if defined(HAVE_CLOCK)
1556
    if (output->last_clock == 0) {
411!
1557
        output->last_clock = clock();
411✔
1558
    }
137✔
1559
#elif defined(HAVE_CLOCK_WIN)
1560
    if (output->last_clock == 0) {
1561
        output->last_clock = clock_win();
1562
    }
1563
#endif
1564
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
1565
    delay = sixel_frame_get_delay(frame);
411✔
1566
    if (delay > 0 && !encoder->fignore_delay) {
411✔
1567
# if defined(HAVE_CLOCK)
1568
        last_clock = clock();
76✔
1569
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
1570
#  if defined(__APPLE__)
1571
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
148✔
1572
                          / 100000);
74✔
1573
#  else
1574
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
2✔
1575
                          / CLOCKS_PER_SEC);
1576
#  endif
1577
        output->last_clock = last_clock;
76✔
1578
# elif defined(HAVE_CLOCK_WIN)
1579
        last_clock = clock_win();
1580
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
1581
                          / CLOCKS_PER_SEC);
1582
        output->last_clock = last_clock;
1583
# else
1584
        dulation = 0;
1585
# endif
1586
        if (dulation < 1000 * 10 * delay) {
76!
1587
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
1588
            tv.tv_sec = 0;
76✔
1589
            tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
76✔
1590
#  if defined(HAVE_NANOSLEEP)
1591
            nanosleep(&tv, NULL);
76✔
1592
#  else
1593
            nanosleep_win(&tv, NULL);
1594
#  endif
1595
# endif
1596
        }
74✔
1597
    }
74✔
1598
#endif
1599

1600
    pixbuf = sixel_frame_get_pixels(frame);
411✔
1601
    memcpy(p, pixbuf, (size_t)(width * height * depth));
411✔
1602

1603
    status = sixel_output_convert_colorspace(output, p, size);
411✔
1604
    if (SIXEL_FAILED(status)) {
411!
1605
        goto end;
×
1606
    }
1607

1608
    if (encoder->cancel_flag && *encoder->cancel_flag) {
411!
1609
        goto end;
×
1610
    }
1611

1612
    status = sixel_encoder_convert_palette(encoder,
548✔
1613
                                           output,
137✔
1614
                                           dither,
137✔
1615
                                           frame_colorspace,
137✔
1616
                                           pixelformat,
137✔
1617
                                           &palette_ctx);
1618
    if (SIXEL_FAILED(status)) {
411!
1619
        goto end;
×
1620
    }
1621

1622
    if (encoder->capture_quantized) {
411!
1623
        status = sixel_encoder_capture_quantized(encoder,
×
1624
                                                 dither,
1625
                                                 p,
1626
                                                 size,
1627
                                                 width,
1628
                                                 height,
1629
                                                 pixelformat,
1630
                                                 output->colorspace);
1631
        if (SIXEL_FAILED(status)) {
×
1632
            goto end;
×
1633
        }
1634
    }
1635

1636
    if (encoder->assessment_observer != NULL) {
411!
1637
        sixel_assessment_stage_transition(
×
1638
            encoder->assessment_observer,
×
1639
            SIXEL_ASSESSMENT_STAGE_ENCODE);
1640
    }
1641
    status = sixel_encode(p, width, height, depth, dither, output);
411✔
1642
    if (encoder->assessment_observer != NULL) {
411!
1643
        sixel_assessment_stage_finish(encoder->assessment_observer);
×
1644
    }
1645
    if (status != SIXEL_OK) {
411!
1646
        goto end;
×
1647
    }
1648

1649
end:
274✔
1650
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
411✔
1651
    output->pixelformat = pixelformat;
411✔
1652
    output->source_colorspace = frame_colorspace;
411✔
1653
    sixel_allocator_free(encoder->allocator, p);
411✔
1654

1655
    return status;
411✔
1656
}
1657

1658

1659
static SIXELSTATUS
1660
sixel_encoder_output_with_macro(
93✔
1661
    sixel_frame_t   /* in */ *frame,
1662
    sixel_dither_t  /* in */ *dither,
1663
    sixel_output_t  /* in */ *output,
1664
    sixel_encoder_t /* in */ *encoder)
1665
{
1666
    SIXELSTATUS status = SIXEL_OK;
93✔
1667
    enum { message_buffer_size = 256 };
1668
    char buffer[message_buffer_size];
1669
    int nwrite;
1670
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
1671
    int dulation;
1672
    struct timespec tv;
1673
#endif
1674
    int width;
1675
    int height;
1676
    int pixelformat;
1677
    int depth;
1678
    size_t size = 0;
93✔
1679
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
93✔
1680
    unsigned char *converted = NULL;
93✔
1681
    palette_conversion_t palette_ctx;
1682
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
1683
    int delay;
1684
#endif
1685
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
1686
    sixel_clock_t last_clock;
1687
#endif
1688
    double write_started_at;
1689
    double write_finished_at;
1690
    double write_duration;
1691

1692
    memset(&palette_ctx, 0, sizeof(palette_ctx));
93✔
1693

1694
    if (encoder != NULL && encoder->assessment_observer != NULL) {
93!
1695
        sixel_assessment_stage_transition(
×
1696
            encoder->assessment_observer,
×
1697
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
1698
    }
1699

1700
#if defined(HAVE_CLOCK)
1701
    if (output->last_clock == 0) {
93!
1702
        output->last_clock = clock();
93✔
1703
    }
37✔
1704
#elif defined(HAVE_CLOCK_WIN)
1705
    if (output->last_clock == 0) {
1706
        output->last_clock = clock_win();
1707
    }
1708
#endif
1709

1710
    width = sixel_frame_get_width(frame);
93✔
1711
    height = sixel_frame_get_height(frame);
93✔
1712
    pixelformat = sixel_frame_get_pixelformat(frame);
93✔
1713
    depth = sixel_helper_compute_depth(pixelformat);
93✔
1714
    if (depth < 0) {
93!
1715
        status = SIXEL_LOGIC_ERROR;
×
1716
        sixel_helper_set_additional_message(
×
1717
            "sixel_encoder_output_with_macro: "
1718
            "sixel_helper_compute_depth() failed.");
1719
        goto end;
×
1720
    }
1721

1722
    frame_colorspace = sixel_frame_get_colorspace(frame);
93✔
1723
    size = (size_t)width * (size_t)height * (size_t)depth;
93✔
1724
    converted = (unsigned char *)sixel_allocator_malloc(
93✔
1725
        encoder->allocator, size);
37✔
1726
    if (converted == NULL) {
93!
1727
        sixel_helper_set_additional_message(
×
1728
            "sixel_encoder_output_with_macro: "
1729
            "sixel_allocator_malloc() failed.");
1730
        status = SIXEL_BAD_ALLOCATION;
×
1731
        goto end;
×
1732
    }
1733

1734
    memcpy(converted, sixel_frame_get_pixels(frame), size);
93✔
1735
    output->pixelformat = pixelformat;
93✔
1736
    output->source_colorspace = frame_colorspace;
93✔
1737
    output->colorspace = encoder->output_colorspace;
93✔
1738
    status = sixel_output_convert_colorspace(output, converted, size);
93✔
1739
    if (SIXEL_FAILED(status)) {
93!
1740
        goto end;
×
1741
    }
1742

1743
    status = sixel_encoder_convert_palette(encoder,
130✔
1744
                                           output,
37✔
1745
                                           dither,
37✔
1746
                                           frame_colorspace,
37✔
1747
                                           pixelformat,
37✔
1748
                                           &palette_ctx);
1749
    if (SIXEL_FAILED(status)) {
93!
1750
        goto end;
×
1751
    }
1752

1753
    if (sixel_frame_get_loop_no(frame) == 0) {
93✔
1754
        if (encoder->macro_number >= 0) {
55!
1755
            nwrite = sprintf(buffer, "\033P%d;0;1!z",
1✔
1756
                             encoder->macro_number);
1757
        } else {
1✔
1758
            nwrite = sprintf(buffer, "\033P%d;0;1!z",
54✔
1759
                             sixel_frame_get_frame_no(frame));
1760
        }
1761
        if (nwrite < 0) {
55!
1762
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1763
            sixel_helper_set_additional_message(
×
1764
                "sixel_encoder_output_with_macro: sprintf() failed.");
1765
            goto end;
×
1766
        }
1767
        write_started_at = 0.0;
55✔
1768
        write_finished_at = 0.0;
55✔
1769
        write_duration = 0.0;
55✔
1770
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
1771
            write_started_at = sixel_assessment_timer_now();
×
1772
        }
1773
        nwrite = sixel_write_callback(buffer,
74✔
1774
                                      (int)strlen(buffer),
55✔
1775
                                      &encoder->outfd);
55✔
1776
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
1777
            write_finished_at = sixel_assessment_timer_now();
×
1778
            write_duration = write_finished_at - write_started_at;
×
1779
            if (write_duration < 0.0) {
×
1780
                write_duration = 0.0;
×
1781
            }
1782
        }
1783
        if (nwrite < 0) {
55!
1784
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1785
            sixel_helper_set_additional_message(
×
1786
                "sixel_encoder_output_with_macro: "
1787
                "sixel_write_callback() failed.");
1788
            goto end;
×
1789
        }
1790
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
1791
            sixel_assessment_record_output_write(
×
1792
                encoder->assessment_observer,
×
1793
                (size_t)nwrite,
1794
                write_duration);
1795
        }
1796

1797
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
1798
            sixel_assessment_stage_transition(
×
1799
                encoder->assessment_observer,
×
1800
                SIXEL_ASSESSMENT_STAGE_ENCODE);
1801
        }
1802
        status = sixel_encode(converted,
74✔
1803
                              width,
19✔
1804
                              height,
19✔
1805
                              depth,
19✔
1806
                              dither,
19✔
1807
                              output);
19✔
1808
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
1809
            sixel_assessment_stage_finish(
×
1810
                encoder->assessment_observer);
×
1811
        }
1812
        if (SIXEL_FAILED(status)) {
55!
1813
            goto end;
×
1814
        }
1815

1816
        write_started_at = 0.0;
55✔
1817
        write_finished_at = 0.0;
55✔
1818
        write_duration = 0.0;
55✔
1819
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
1820
            write_started_at = sixel_assessment_timer_now();
×
1821
        }
1822
        nwrite = sixel_write_callback("\033\\", 2, &encoder->outfd);
55✔
1823
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
1824
            write_finished_at = sixel_assessment_timer_now();
×
1825
            write_duration = write_finished_at - write_started_at;
×
1826
            if (write_duration < 0.0) {
×
1827
                write_duration = 0.0;
×
1828
            }
1829
        }
1830
        if (nwrite < 0) {
55!
1831
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1832
            sixel_helper_set_additional_message(
×
1833
                "sixel_encoder_output_with_macro: "
1834
                "sixel_write_callback() failed.");
1835
            goto end;
×
1836
        }
1837
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
1838
            sixel_assessment_record_output_write(
×
1839
                encoder->assessment_observer,
×
1840
                (size_t)nwrite,
1841
                write_duration);
1842
        }
1843
    }
19✔
1844
    if (encoder->macro_number < 0) {
129✔
1845
        nwrite = sprintf(buffer, "\033[%d*z",
90✔
1846
                         sixel_frame_get_frame_no(frame));
1847
        if (nwrite < 0) {
90!
1848
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1849
            sixel_helper_set_additional_message(
×
1850
                "sixel_encoder_output_with_macro: sprintf() failed.");
1851
        }
1852
        write_started_at = 0.0;
90✔
1853
        write_finished_at = 0.0;
90✔
1854
        write_duration = 0.0;
90✔
1855
        if (encoder != NULL && encoder->assessment_observer != NULL) {
90!
1856
            write_started_at = sixel_assessment_timer_now();
×
1857
        }
1858
        nwrite = sixel_write_callback(buffer,
126✔
1859
                                      (int)strlen(buffer),
90✔
1860
                                      &encoder->outfd);
90✔
1861
        if (encoder != NULL && encoder->assessment_observer != NULL) {
90!
1862
            write_finished_at = sixel_assessment_timer_now();
×
1863
            write_duration = write_finished_at - write_started_at;
×
1864
            if (write_duration < 0.0) {
×
1865
                write_duration = 0.0;
×
1866
            }
1867
        }
1868
        if (nwrite < 0) {
90!
1869
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1870
            sixel_helper_set_additional_message(
×
1871
                "sixel_encoder_output_with_macro: "
1872
                "sixel_write_callback() failed.");
1873
            goto end;
×
1874
        }
1875
        if (encoder != NULL && encoder->assessment_observer != NULL) {
90!
1876
            sixel_assessment_record_output_write(
×
1877
                encoder->assessment_observer,
×
1878
                (size_t)nwrite,
1879
                write_duration);
1880
        }
1881
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
1882
        delay = sixel_frame_get_delay(frame);
90✔
1883
        if (delay > 0 && !encoder->fignore_delay) {
90!
1884
# if defined(HAVE_CLOCK)
1885
            last_clock = clock();
63✔
1886
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
1887
#  if defined(__APPLE__)
1888
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
54✔
1889
                             / 100000);
27✔
1890
#  else
1891
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
36✔
1892
                             / CLOCKS_PER_SEC);
1893
#  endif
1894
            output->last_clock = last_clock;
63✔
1895
# elif defined(HAVE_CLOCK_WIN)
1896
            last_clock = clock_win();
1897
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
1898
                             / CLOCKS_PER_SEC);
1899
            output->last_clock = last_clock;
1900
# else
1901
            dulation = 0;
1902
# endif
1903
            if (dulation < 1000 * 10 * delay) {
63!
1904
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
1905
                tv.tv_sec = 0;
60✔
1906
                tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
60✔
1907
#  if defined(HAVE_NANOSLEEP)
1908
                nanosleep(&tv, NULL);
60✔
1909
#  else
1910
                nanosleep_win(&tv, NULL);
1911
#  endif
1912
# endif
1913
            }
24✔
1914
        }
27✔
1915
#endif
1916
    }
36✔
1917

1918
end:
20✔
1919
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
93✔
1920
    output->pixelformat = pixelformat;
93✔
1921
    output->source_colorspace = frame_colorspace;
93✔
1922
    sixel_allocator_free(encoder->allocator, converted);
93✔
1923

1924
    return status;
93✔
1925
}
1926

1927

1928
static SIXELSTATUS
1929
sixel_encoder_emit_iso2022_chars(
×
1930
    sixel_encoder_t *encoder,
1931
    sixel_frame_t *frame
1932
)
1933
{
1934
    char *buf_p, *buf;
1935
    int col, row;
1936
    int charset;
1937
    int is_96cs;
1938
    unsigned int charset_no;
1939
    unsigned int code;
1940
    int num_cols, num_rows;
1941
    SIXELSTATUS status;
1942
    size_t alloc_size;
1943
    int nwrite;
1944
    int target_fd;
1945
    int chunk_size;
1946

NEW
1947
    charset_no = encoder->drcs_charset_no;
×
NEW
1948
    if (charset_no == 0u) {
×
NEW
1949
        charset_no = 1u;
×
1950
    }
NEW
1951
    if (encoder->drcs_mmv == 0) {
×
NEW
1952
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
NEW
1953
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
NEW
1954
    } else if (encoder->drcs_mmv == 1) {
×
NEW
1955
        is_96cs = 0;
×
NEW
1956
        charset = (int)(charset_no + 0x3fu);
×
1957
    } else {
NEW
1958
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
NEW
1959
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
1960
    }
1961
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
1962
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
1963
             / encoder->cell_width;
×
1964
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
1965
             / encoder->cell_height;
×
1966

1967
    /* cols x rows + designation(4 chars) + SI + SO + LFs */
1968
    alloc_size = num_cols * num_rows + (num_cols * num_rows / 96 + 1) * 4 + 2 + num_rows;
×
1969
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
1970
    if (buf == NULL) {
×
1971
        sixel_helper_set_additional_message(
×
1972
            "sixel_encoder_emit_iso2022_chars: sixel_allocator_malloc() failed.");
1973
        status = SIXEL_BAD_ALLOCATION;
×
1974
        goto end;
×
1975
    }
1976

1977
    code = 0x20;
×
1978
    *(buf_p++) = '\016';  /* SI */
×
1979
    *(buf_p++) = '\033';
×
1980
    *(buf_p++) = ')';
×
1981
    *(buf_p++) = ' ';
×
1982
    *(buf_p++) = charset;
×
1983
    for(row = 0; row < num_rows; row++) {
×
1984
        for(col = 0; col < num_cols; col++) {
×
1985
            if ((code & 0x7f) == 0x0) {
×
1986
                if (charset == 0x7e) {
×
1987
                    is_96cs = 1 - is_96cs;
×
1988
                    charset = '0';
×
1989
                } else {
1990
                    charset++;
×
1991
                }
1992
                code = 0x20;
×
1993
                *(buf_p++) = '\033';
×
1994
                *(buf_p++) = is_96cs ? '-': ')';
×
1995
                *(buf_p++) = ' ';
×
1996
                *(buf_p++) = charset;
×
1997
            }
1998
            *(buf_p++) = code++;
×
1999
        }
2000
        *(buf_p++) = '\n';
×
2001
    }
2002
    *(buf_p++) = '\017';  /* SO */
×
2003

2004
    if (encoder->tile_outfd >= 0) {
×
2005
        target_fd = encoder->tile_outfd;
×
2006
    } else {
2007
        target_fd = encoder->outfd;
×
2008
    }
2009

2010
    chunk_size = (int)(buf_p - buf);
×
2011
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
2012
                                          buf,
2013
                                          chunk_size,
2014
                                          target_fd);
2015
    if (nwrite != chunk_size) {
×
2016
        sixel_helper_set_additional_message(
×
2017
            "sixel_encoder_emit_iso2022_chars: write() failed.");
2018
        status = SIXEL_RUNTIME_ERROR;
×
2019
        goto end;
×
2020
    }
2021

2022
    sixel_allocator_free(encoder->allocator, buf);
×
2023

2024
    status = SIXEL_OK;
×
2025

2026
end:
2027
    return status;
×
2028
}
2029

2030

2031
/*
2032
 * This routine is derived from mlterm's drcssixel.c
2033
 * (https://raw.githubusercontent.com/arakiken/mlterm/master/drcssixel/drcssixel.c).
2034
 * The original implementation is credited to Araki Ken.
2035
 * Adjusted here to integrate with libsixel's encoder pipeline.
2036
 */
2037
static SIXELSTATUS
2038
sixel_encoder_emit_drcsmmv2_chars(
×
2039
    sixel_encoder_t *encoder,
2040
    sixel_frame_t *frame
2041
)
2042
{
2043
    char *buf_p, *buf;
2044
    int col, row;
2045
    int charset;
2046
    int is_96cs;
2047
    unsigned int charset_no;
2048
    unsigned int code;
2049
    int num_cols, num_rows;
2050
    SIXELSTATUS status;
2051
    size_t alloc_size;
2052
    int nwrite;
2053
    int target_fd;
2054
    int chunk_size;
2055

NEW
2056
    charset_no = encoder->drcs_charset_no;
×
NEW
2057
    if (charset_no == 0u) {
×
NEW
2058
        charset_no = 1u;
×
2059
    }
NEW
2060
    if (encoder->drcs_mmv == 0) {
×
NEW
2061
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
NEW
2062
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
NEW
2063
    } else if (encoder->drcs_mmv == 1) {
×
NEW
2064
        is_96cs = 0;
×
NEW
2065
        charset = (int)(charset_no + 0x3fu);
×
2066
    } else {
NEW
2067
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
NEW
2068
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
2069
    }
2070
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
2071
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
2072
             / encoder->cell_width;
×
2073
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
2074
             / encoder->cell_height;
×
2075

2076
    /* cols x rows x 4(out of BMP) + rows(LFs) */
2077
    alloc_size = num_cols * num_rows * 4 + num_rows;
×
2078
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
2079
    if (buf == NULL) {
×
2080
        sixel_helper_set_additional_message(
×
2081
            "sixel_encoder_emit_drcsmmv2_chars: sixel_allocator_malloc() failed.");
2082
        status = SIXEL_BAD_ALLOCATION;
×
2083
        goto end;
×
2084
    }
2085

2086
    for(row = 0; row < num_rows; row++) {
×
2087
        for(col = 0; col < num_cols; col++) {
×
2088
            *(buf_p++) = ((code >> 18) & 0x07) | 0xf0;
×
2089
            *(buf_p++) = ((code >> 12) & 0x3f) | 0x80;
×
2090
            *(buf_p++) = ((code >> 6) & 0x3f) | 0x80;
×
2091
            *(buf_p++) = (code & 0x3f) | 0x80;
×
2092
            code++;
×
2093
            if ((code & 0x7f) == 0x0) {
×
2094
                if (charset == 0x7e) {
×
2095
                    is_96cs = 1 - is_96cs;
×
2096
                    charset = '0';
×
2097
                } else {
2098
                    charset++;
×
2099
                }
2100
                code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
2101
            }
2102
        }
2103
        *(buf_p++) = '\n';
×
2104
    }
2105

2106
    if (charset == 0x7e) {
×
2107
        is_96cs = 1 - is_96cs;
×
2108
    } else {
2109
        charset = '0';
×
2110
        charset++;
×
2111
    }
2112
    if (encoder->tile_outfd >= 0) {
×
2113
        target_fd = encoder->tile_outfd;
×
2114
    } else {
2115
        target_fd = encoder->outfd;
×
2116
    }
2117

2118
    chunk_size = (int)(buf_p - buf);
×
2119
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
2120
                                          buf,
2121
                                          chunk_size,
2122
                                          target_fd);
2123
    if (nwrite != chunk_size) {
×
2124
        sixel_helper_set_additional_message(
×
2125
            "sixel_encoder_emit_drcsmmv2_chars: write() failed.");
2126
        status = SIXEL_RUNTIME_ERROR;
×
2127
        goto end;
×
2128
    }
2129

2130
    sixel_allocator_free(encoder->allocator, buf);
×
2131

2132
    status = SIXEL_OK;
×
2133

2134
end:
2135
    return status;
×
2136
}
2137

2138
static SIXELSTATUS
2139
sixel_encoder_encode_frame(
518✔
2140
    sixel_encoder_t *encoder,
2141
    sixel_frame_t   *frame,
2142
    sixel_output_t  *output)
2143
{
2144
    SIXELSTATUS status = SIXEL_FALSE;
518✔
2145
    sixel_dither_t *dither = NULL;
518✔
2146
    int height;
2147
    int is_animation = 0;
518✔
2148
    int nwrite;
2149
    int drcs_is_96cs_param;
2150
    int drcs_designate_char;
2151
    char buf[256];
2152
    sixel_write_function fn_write;
2153
    sixel_write_function write_callback;
2154
    sixel_write_function scroll_callback;
2155
    void *write_priv;
2156
    void *scroll_priv;
2157
    sixel_encoder_output_probe_t probe;
2158
    sixel_encoder_output_probe_t scroll_probe;
2159
    sixel_assessment_t *assessment;
2160

2161
    fn_write = sixel_write_callback;
518✔
2162
    write_callback = sixel_write_callback;
518✔
2163
    scroll_callback = sixel_write_callback;
518✔
2164
    write_priv = &encoder->outfd;
518✔
2165
    scroll_priv = &encoder->outfd;
518✔
2166
    probe.encoder = NULL;
518✔
2167
    probe.base_write = NULL;
518✔
2168
    probe.base_priv = NULL;
518✔
2169
    scroll_probe.encoder = NULL;
518✔
2170
    scroll_probe.base_write = NULL;
518✔
2171
    scroll_probe.base_priv = NULL;
518✔
2172
    assessment = NULL;
518✔
2173
    if (encoder != NULL) {
518!
2174
        assessment = encoder->assessment_observer;
518✔
2175
    }
182✔
2176
    if (assessment != NULL) {
518!
2177
        if (encoder->clipfirst) {
×
2178
            sixel_assessment_stage_transition(
×
2179
                assessment,
2180
                SIXEL_ASSESSMENT_STAGE_CROP);
2181
        } else {
2182
            sixel_assessment_stage_transition(
×
2183
                assessment,
2184
                SIXEL_ASSESSMENT_STAGE_SCALE);
2185
        }
2186
    }
2187

2188
    /*
2189
     *  Geometry timeline:
2190
     *
2191
     *      +-------+    +------+    +---------------+
2192
     *      | scale | -> | crop | -> | color convert |
2193
     *      +-------+    +------+    +---------------+
2194
     *
2195
     *  The order of the first two blocks mirrors `-c`, so we hop between
2196
     *  SCALE and CROP depending on `clipfirst`.
2197
     */
2198

2199
    if (encoder->clipfirst) {
518✔
2200
        status = sixel_encoder_do_clip(encoder, frame);
8✔
2201
        if (SIXEL_FAILED(status)) {
8!
2202
            goto end;
2✔
2203
        }
2204
        if (assessment != NULL) {
6!
2205
            sixel_assessment_stage_transition(
×
2206
                assessment,
2207
                SIXEL_ASSESSMENT_STAGE_SCALE);
2208
        }
2209
        status = sixel_encoder_do_resize(encoder, frame);
6✔
2210
        if (SIXEL_FAILED(status)) {
6!
2211
            goto end;
×
2212
        }
2213
    } else {
2✔
2214
        status = sixel_encoder_do_resize(encoder, frame);
510✔
2215
        if (SIXEL_FAILED(status)) {
510✔
2216
            goto end;
6✔
2217
        }
2218
        if (assessment != NULL) {
504!
2219
            sixel_assessment_stage_transition(
×
2220
                assessment,
2221
                SIXEL_ASSESSMENT_STAGE_CROP);
2222
        }
2223
        status = sixel_encoder_do_clip(encoder, frame);
504✔
2224
        if (SIXEL_FAILED(status)) {
504!
2225
            goto end;
1✔
2226
        }
2227
    }
2228

2229
    if (assessment != NULL) {
509!
2230
        sixel_assessment_stage_transition(
×
2231
            assessment,
2232
            SIXEL_ASSESSMENT_STAGE_COLORSPACE);
2233
    }
2234

2235
    status = sixel_frame_ensure_colorspace(frame,
686✔
2236
                                           encoder->working_colorspace);
177✔
2237
    if (SIXEL_FAILED(status)) {
509!
2238
        goto end;
×
2239
    }
2240

2241
    if (assessment != NULL) {
509!
2242
        sixel_assessment_stage_transition(
×
2243
            assessment,
2244
            SIXEL_ASSESSMENT_STAGE_PALETTE_HISTOGRAM);
2245
    }
2246

2247
    /* prepare dither context */
2248
    status = sixel_encoder_prepare_palette(encoder, frame, &dither);
509✔
2249
    if (status != SIXEL_OK) {
509✔
2250
        dither = NULL;
5✔
2251
        goto end;
5✔
2252
    }
2253

2254
    if (encoder->dither_cache != NULL) {
504!
2255
        encoder->dither_cache = dither;
×
2256
        sixel_dither_ref(dither);
×
2257
    }
2258

2259
    if (encoder->fdrcs) {
504!
2260
        status = sixel_encoder_ensure_cell_size(encoder);
×
2261
        if (SIXEL_FAILED(status)) {
×
2262
            goto end;
×
2263
        }
2264
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
×
2265
            sixel_helper_set_additional_message(
×
2266
                "drcs option cannot be used together with macro output.");
2267
            status = SIXEL_BAD_ARGUMENT;
×
2268
            goto end;
×
2269
        }
2270
    }
2271

2272
    /* evaluate -v option: print palette */
2273
    if (encoder->verbose) {
504✔
2274
        if ((sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE)) {
9✔
2275
            sixel_debug_print_palette(dither);
3✔
2276
        }
1✔
2277
    }
3✔
2278

2279
    /* evaluate -d option: set method for diffusion */
2280
    sixel_dither_set_diffusion_type(dither, encoder->method_for_diffuse);
504✔
2281
    sixel_dither_set_diffusion_scan(dither, encoder->method_for_scan);
504✔
2282
    sixel_dither_set_diffusion_carry(dither, encoder->method_for_carry);
504✔
2283

2284
    /* evaluate -C option: set complexion score */
2285
    if (encoder->complexion > 1) {
504✔
2286
        sixel_dither_set_complexion_score(dither, encoder->complexion);
6✔
2287
    }
2✔
2288

2289
    if (output) {
504!
2290
        sixel_output_ref(output);
×
2291
        if (encoder->assessment_observer != NULL) {
×
2292
            probe.encoder = encoder;
×
2293
            probe.base_write = fn_write;
×
2294
            probe.base_priv = &encoder->outfd;
×
2295
            write_callback = sixel_write_with_probe;
×
2296
            write_priv = &probe;
×
2297
        }
2298
    } else {
2299
        /* create output context */
2300
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
504✔
2301
            /* -u or -n option */
2302
            fn_write = sixel_hex_write_callback;
93✔
2303
        } else {
37✔
2304
            fn_write = sixel_write_callback;
411✔
2305
        }
2306
        write_callback = fn_write;
504✔
2307
        write_priv = &encoder->outfd;
504✔
2308
        if (encoder->assessment_observer != NULL) {
504!
2309
            probe.encoder = encoder;
×
2310
            probe.base_write = fn_write;
×
2311
            probe.base_priv = &encoder->outfd;
×
2312
            write_callback = sixel_write_with_probe;
×
2313
            write_priv = &probe;
×
2314
        }
2315
        status = sixel_output_new(&output,
504✔
2316
                                  write_callback,
174✔
2317
                                  write_priv,
174✔
2318
                                  encoder->allocator);
174✔
2319
        if (SIXEL_FAILED(status)) {
504!
2320
            goto end;
×
2321
        }
2322
    }
2323

2324
    if (encoder->fdrcs) {
504!
2325
        sixel_output_set_skip_dcs_envelope(output, 1);
×
2326
        sixel_output_set_skip_header(output, 1);
×
2327
    }
2328

2329
    sixel_output_set_8bit_availability(output, encoder->f8bit);
504✔
2330
    sixel_output_set_gri_arg_limit(output, encoder->has_gri_arg_limit);
504✔
2331
    sixel_output_set_palette_type(output, encoder->palette_type);
504✔
2332
    sixel_output_set_penetrate_multiplexer(
504✔
2333
        output, encoder->penetrate_multiplexer);
174✔
2334
    sixel_output_set_encode_policy(output, encoder->encode_policy);
504✔
2335
    sixel_output_set_ormode(output, encoder->ormode);
504✔
2336

2337
    if (sixel_frame_get_multiframe(frame) && !encoder->fstatic) {
504!
2338
        if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
117✔
2339
            is_animation = 1;
108✔
2340
        }
42✔
2341
        height = sixel_frame_get_height(frame);
117✔
2342
        if (encoder->assessment_observer != NULL) {
117!
2343
            scroll_probe.encoder = encoder;
×
2344
            scroll_probe.base_write = sixel_write_callback;
×
2345
            scroll_probe.base_priv = &encoder->outfd;
×
2346
            scroll_callback = sixel_write_with_probe;
×
2347
            scroll_priv = &scroll_probe;
×
2348
        } else {
2349
            scroll_callback = sixel_write_callback;
117✔
2350
            scroll_priv = &encoder->outfd;
117✔
2351
        }
2352
        (void) sixel_tty_scroll(scroll_callback,
162✔
2353
                                scroll_priv,
45✔
2354
                                encoder->outfd,
45✔
2355
                                height,
45✔
2356
                                is_animation);
45✔
2357
    }
45✔
2358

2359
    if (encoder->cancel_flag && *encoder->cancel_flag) {
504!
2360
        status = SIXEL_INTERRUPTED;
×
2361
        goto end;
×
2362
    }
2363

2364
    if (encoder->fdrcs) {  /* -@ option */
504!
NEW
2365
        if (encoder->drcs_mmv == 0) {
×
NEW
2366
            drcs_is_96cs_param =
×
NEW
2367
                (encoder->drcs_charset_no > 63u) ? 1 : 0;
×
NEW
2368
            drcs_designate_char =
×
NEW
2369
                (int)(((encoder->drcs_charset_no - 1u) % 63u) + 0x40u);
×
NEW
2370
        } else if (encoder->drcs_mmv == 1) {
×
NEW
2371
            drcs_is_96cs_param = 0;
×
NEW
2372
            drcs_designate_char =
×
NEW
2373
                (int)(encoder->drcs_charset_no + 0x3fu);
×
2374
        } else {
NEW
2375
            drcs_is_96cs_param =
×
NEW
2376
                (encoder->drcs_charset_no > 79u) ? 1 : 0;
×
NEW
2377
            drcs_designate_char =
×
NEW
2378
                (int)(((encoder->drcs_charset_no - 1u) % 79u) + 0x30u);
×
2379
        }
UNCOV
2380
        nwrite = sprintf(buf,
×
2381
                         "%s%s1;0;0;%d;1;3;%d;%d{ %c",
2382
                         (encoder->drcs_mmv > 0) ? (
×
2383
                             encoder->f8bit ? "\233?8800h": "\033[?8800h"
×
2384
                         ): "",
2385
                         encoder->f8bit ? "\220": "\033P",
×
2386
                         encoder->cell_width,
2387
                         encoder->cell_height,
2388
                         drcs_is_96cs_param,
2389
                         drcs_designate_char);
2390
        if (nwrite < 0) {
×
2391
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
2392
            sixel_helper_set_additional_message(
×
2393
                "sixel_encoder_encode_frame: sprintf() failed.");
2394
            goto end;
×
2395
        }
2396
        nwrite = write_callback(buf, nwrite, write_priv);
×
2397
        if (nwrite < 0) {
×
2398
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
2399
            sixel_helper_set_additional_message(
×
2400
                "sixel_encoder_encode_frame: write() failed.");
2401
            goto end;
×
2402
        }
2403
    }
2404

2405
    /* output sixel: junction of multi-frame processing strategy */
2406
    if (encoder->fuse_macro) {  /* -u option */
504✔
2407
        /* use macro */
2408
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
90✔
2409
    } else if (encoder->macro_number >= 0) { /* -n option */
450✔
2410
        /* use macro */
2411
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
3✔
2412
    } else {
1✔
2413
        /* do not use macro */
2414
        status = sixel_encoder_output_without_macro(frame, dither, output, encoder);
411✔
2415
    }
2416
    if (SIXEL_FAILED(status)) {
504!
2417
        goto end;
×
2418
    }
2419

2420
    if (encoder->cancel_flag && *encoder->cancel_flag) {
504!
2421
        nwrite = write_callback("\x18\033\\", 3, write_priv);
×
2422
        if (nwrite < 0) {
×
2423
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
2424
            sixel_helper_set_additional_message(
×
2425
                "sixel_encoder_encode_frame: write_callback() failed.");
2426
            goto end;
×
2427
        }
2428
        status = SIXEL_INTERRUPTED;
×
2429
    }
2430
    if (SIXEL_FAILED(status)) {
504!
2431
        goto end;
×
2432
    }
2433

2434
    if (encoder->fdrcs) {  /* -@ option */
504!
2435
        if (encoder->f8bit) {
×
2436
            nwrite = write_callback("\234", 1, write_priv);
×
2437
        } else {
2438
            nwrite = write_callback("\033\\", 2, write_priv);
×
2439
        }
2440
        if (nwrite < 0) {
×
2441
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
2442
            sixel_helper_set_additional_message(
×
2443
                "sixel_encoder_encode_frame: write_callback() failed.");
2444
            goto end;
×
2445
        }
2446

2447
        if (encoder->tile_outfd >= 0) {
×
2448
            if (encoder->drcs_mmv == 0) {
×
2449
                status = sixel_encoder_emit_iso2022_chars(encoder, frame);
×
2450
                if (SIXEL_FAILED(status)) {
×
2451
                    goto end;
×
2452
                }
2453
            } else {
2454
                status = sixel_encoder_emit_drcsmmv2_chars(encoder, frame);
×
2455
                if (SIXEL_FAILED(status)) {
×
2456
                    goto end;
×
2457
                }
2458
            }
2459
        }
2460
    }
2461

2462

2463
end:
330✔
2464
    if (output) {
518✔
2465
        sixel_output_unref(output);
504✔
2466
    }
174✔
2467
    if (dither) {
518✔
2468
        sixel_dither_unref(dither);
504✔
2469
    }
174✔
2470

2471
    return status;
518✔
2472
}
2473

2474

2475
/* create encoder object */
2476
SIXELAPI SIXELSTATUS
2477
sixel_encoder_new(
498✔
2478
    sixel_encoder_t     /* out */ **ppencoder, /* encoder object to be created */
2479
    sixel_allocator_t   /* in */  *allocator)  /* allocator, null if you use
2480
                                                  default allocator */
2481
{
2482
    SIXELSTATUS status = SIXEL_FALSE;
498✔
2483
    char const *env_default_bgcolor = NULL;
498✔
2484
    char const *env_default_ncolors = NULL;
498✔
2485
    int ncolors;
2486
#if HAVE__DUPENV_S
2487
    errno_t e;
2488
    size_t len;
2489
#endif  /* HAVE__DUPENV_S */
2490

2491
    if (allocator == NULL) {
498!
2492
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
498✔
2493
        if (SIXEL_FAILED(status)) {
498!
2494
            goto end;
×
2495
        }
2496
    } else {
166✔
2497
        sixel_allocator_ref(allocator);
×
2498
    }
2499

2500
    *ppencoder
166✔
2501
        = (sixel_encoder_t *)sixel_allocator_malloc(allocator,
664✔
2502
                                                    sizeof(sixel_encoder_t));
2503
    if (*ppencoder == NULL) {
498!
2504
        sixel_helper_set_additional_message(
×
2505
            "sixel_encoder_new: sixel_allocator_malloc() failed.");
2506
        status = SIXEL_BAD_ALLOCATION;
×
2507
        sixel_allocator_unref(allocator);
×
2508
        goto end;
×
2509
    }
2510

2511
    (*ppencoder)->ref                   = 1;
498✔
2512
    (*ppencoder)->reqcolors             = (-1);
498✔
2513
    (*ppencoder)->force_palette         = 0;
498✔
2514
    (*ppencoder)->mapfile               = NULL;
498✔
2515
    (*ppencoder)->loader_order          = NULL;
498✔
2516
    (*ppencoder)->color_option          = SIXEL_COLOR_OPTION_DEFAULT;
498✔
2517
    (*ppencoder)->builtin_palette       = 0;
498✔
2518
    (*ppencoder)->method_for_diffuse    = SIXEL_DIFFUSE_AUTO;
498✔
2519
    (*ppencoder)->method_for_scan       = SIXEL_SCAN_AUTO;
498✔
2520
    (*ppencoder)->method_for_carry      = SIXEL_CARRY_AUTO;
498✔
2521
    (*ppencoder)->method_for_largest    = SIXEL_LARGE_AUTO;
498✔
2522
    (*ppencoder)->method_for_rep        = SIXEL_REP_AUTO;
498✔
2523
    (*ppencoder)->quality_mode          = SIXEL_QUALITY_AUTO;
498✔
2524
    (*ppencoder)->lut_policy            = SIXEL_LUT_POLICY_AUTO;
498✔
2525
    (*ppencoder)->method_for_resampling = SIXEL_RES_BILINEAR;
498✔
2526
    (*ppencoder)->loop_mode             = SIXEL_LOOP_AUTO;
498✔
2527
    (*ppencoder)->palette_type          = SIXEL_PALETTETYPE_AUTO;
498✔
2528
    (*ppencoder)->f8bit                 = 0;
498✔
2529
    (*ppencoder)->has_gri_arg_limit     = 0;
498✔
2530
    (*ppencoder)->finvert               = 0;
498✔
2531
    (*ppencoder)->fuse_macro            = 0;
498✔
2532
    (*ppencoder)->fdrcs                 = 0;
498✔
2533
    (*ppencoder)->fignore_delay         = 0;
498✔
2534
    (*ppencoder)->complexion            = 1;
498✔
2535
    (*ppencoder)->fstatic               = 0;
498✔
2536
    (*ppencoder)->cell_width            = 0;
498✔
2537
    (*ppencoder)->cell_height           = 0;
498✔
2538
    (*ppencoder)->pixelwidth            = (-1);
498✔
2539
    (*ppencoder)->pixelheight           = (-1);
498✔
2540
    (*ppencoder)->percentwidth          = (-1);
498✔
2541
    (*ppencoder)->percentheight         = (-1);
498✔
2542
    (*ppencoder)->clipx                 = 0;
498✔
2543
    (*ppencoder)->clipy                 = 0;
498✔
2544
    (*ppencoder)->clipwidth             = 0;
498✔
2545
    (*ppencoder)->clipheight            = 0;
498✔
2546
    (*ppencoder)->clipfirst             = 0;
498✔
2547
    (*ppencoder)->macro_number          = (-1);
498✔
2548
    (*ppencoder)->verbose               = 0;
498✔
2549
    (*ppencoder)->penetrate_multiplexer = 0;
498✔
2550
    (*ppencoder)->encode_policy         = SIXEL_ENCODEPOLICY_AUTO;
498✔
2551
    (*ppencoder)->working_colorspace    = SIXEL_COLORSPACE_GAMMA;
498✔
2552
    (*ppencoder)->output_colorspace     = SIXEL_COLORSPACE_GAMMA;
498✔
2553
    (*ppencoder)->ormode                = 0;
498✔
2554
    (*ppencoder)->pipe_mode             = 0;
498✔
2555
    (*ppencoder)->bgcolor               = NULL;
498✔
2556
    (*ppencoder)->outfd                 = STDOUT_FILENO;
498✔
2557
    (*ppencoder)->tile_outfd            = (-1);
498✔
2558
    (*ppencoder)->finsecure             = 0;
498✔
2559
    (*ppencoder)->cancel_flag           = NULL;
498✔
2560
    (*ppencoder)->dither_cache          = NULL;
498✔
2561
    (*ppencoder)->drcs_charset_no       = 1u;
498✔
2562
    (*ppencoder)->drcs_mmv              = 2;
498✔
2563
    (*ppencoder)->capture_quantized     = 0;
498✔
2564
    (*ppencoder)->capture_source        = 0;
498✔
2565
    (*ppencoder)->capture_pixels        = NULL;
498✔
2566
    (*ppencoder)->capture_pixels_size   = 0;
498✔
2567
    (*ppencoder)->capture_palette       = NULL;
498✔
2568
    (*ppencoder)->capture_palette_size  = 0;
498✔
2569
    (*ppencoder)->capture_pixel_bytes   = 0;
498✔
2570
    (*ppencoder)->capture_width         = 0;
498✔
2571
    (*ppencoder)->capture_height        = 0;
498✔
2572
    (*ppencoder)->capture_pixelformat   = SIXEL_PIXELFORMAT_RGB888;
498✔
2573
    (*ppencoder)->capture_colorspace    = SIXEL_COLORSPACE_GAMMA;
498✔
2574
    (*ppencoder)->capture_ncolors       = 0;
498✔
2575
    (*ppencoder)->capture_valid         = 0;
498✔
2576
    (*ppencoder)->capture_source_frame  = NULL;
498✔
2577
    (*ppencoder)->assessment_observer   = NULL;
498✔
2578
    (*ppencoder)->last_loader_name[0]   = '\0';
498✔
2579
    (*ppencoder)->last_source_path[0]   = '\0';
498✔
2580
    (*ppencoder)->last_input_bytes      = 0u;
498✔
2581
    (*ppencoder)->allocator             = allocator;
498✔
2582

2583
    /* evaluate environment variable ${SIXEL_BGCOLOR} */
2584
#if HAVE__DUPENV_S
2585
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_BGCOLOR");
2586
    if (e != (0)) {
2587
        sixel_helper_set_additional_message(
2588
            "failed to get environment variable $SIXEL_BGCOLOR.");
2589
        return (SIXEL_LIBC_ERROR | (e & 0xff));
2590
    }
2591
#else
2592
    env_default_bgcolor = getenv("SIXEL_BGCOLOR");
498✔
2593
#endif  /* HAVE__DUPENV_S */
2594
    if (env_default_bgcolor != NULL) {
498!
2595
        status = sixel_parse_x_colorspec(&(*ppencoder)->bgcolor,
×
2596
                                         env_default_bgcolor,
2597
                                         allocator);
2598
        if (SIXEL_FAILED(status)) {
×
2599
            goto error;
×
2600
        }
2601
    }
2602

2603
    /* evaluate environment variable ${SIXEL_COLORS} */
2604
#if HAVE__DUPENV_S
2605
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_COLORS");
2606
    if (e != (0)) {
2607
        sixel_helper_set_additional_message(
2608
            "failed to get environment variable $SIXEL_COLORS.");
2609
        return (SIXEL_LIBC_ERROR | (e & 0xff));
2610
    }
2611
#else
2612
    env_default_ncolors = getenv("SIXEL_COLORS");
498✔
2613
#endif  /* HAVE__DUPENV_S */
2614
    if (env_default_ncolors) {
498!
2615
        ncolors = atoi(env_default_ncolors); /* may overflow */
×
2616
        if (ncolors > 1 && ncolors <= SIXEL_PALETTE_MAX) {
×
2617
            (*ppencoder)->reqcolors = ncolors;
×
2618
        }
2619
    }
2620

2621
    /* success */
2622
    status = SIXEL_OK;
498✔
2623

2624
    goto end;
498✔
2625

2626
error:
2627
    sixel_allocator_free(allocator, *ppencoder);
×
2628
    sixel_allocator_unref(allocator);
×
2629
    *ppencoder = NULL;
×
2630

2631
end:
332✔
2632
#if HAVE__DUPENV_S
2633
    free(env_default_bgcolor);
2634
    free(env_default_ncolors);
2635
#endif  /* HAVE__DUPENV_S */
2636
    return status;
498✔
2637
}
2638

2639

2640
/* create encoder object (deprecated version) */
2641
SIXELAPI /* deprecated */ sixel_encoder_t *
2642
sixel_encoder_create(void)
×
2643
{
2644
    SIXELSTATUS status = SIXEL_FALSE;
×
2645
    sixel_encoder_t *encoder = NULL;
×
2646

2647
    status = sixel_encoder_new(&encoder, NULL);
×
2648
    if (SIXEL_FAILED(status)) {
×
2649
        return NULL;
×
2650
    }
2651

2652
    return encoder;
×
2653
}
2654

2655

2656
/* destroy encoder object */
2657
static void
2658
sixel_encoder_destroy(sixel_encoder_t *encoder)
498✔
2659
{
2660
    sixel_allocator_t *allocator;
2661

2662
    if (encoder) {
498!
2663
        allocator = encoder->allocator;
498✔
2664
        sixel_allocator_free(allocator, encoder->mapfile);
498✔
2665
        sixel_allocator_free(allocator, encoder->loader_order);
498✔
2666
        sixel_allocator_free(allocator, encoder->bgcolor);
498✔
2667
        sixel_dither_unref(encoder->dither_cache);
498✔
2668
        if (encoder->outfd
504!
2669
            && encoder->outfd != STDOUT_FILENO
498!
2670
            && encoder->outfd != STDERR_FILENO) {
178!
2671
#if HAVE__CLOSE
2672
            (void) _close(encoder->outfd);
2673
#else
2674
            (void) close(encoder->outfd);
18✔
2675
#endif  /* HAVE__CLOSE */
2676
        }
6✔
2677
        if (encoder->tile_outfd >= 0
498!
2678
            && encoder->tile_outfd != encoder->outfd
166!
2679
            && encoder->tile_outfd != STDOUT_FILENO
×
2680
            && encoder->tile_outfd != STDERR_FILENO) {
×
2681
#if HAVE__CLOSE
2682
            (void) _close(encoder->tile_outfd);
2683
#else
2684
            (void) close(encoder->tile_outfd);
×
2685
#endif  /* HAVE__CLOSE */
2686
        }
2687
        if (encoder->capture_source_frame != NULL) {
498!
2688
            sixel_frame_unref(encoder->capture_source_frame);
×
2689
        }
2690
        sixel_allocator_free(allocator, encoder->capture_pixels);
498✔
2691
        sixel_allocator_free(allocator, encoder->capture_palette);
498✔
2692
        sixel_allocator_free(allocator, encoder);
498✔
2693
        sixel_allocator_unref(allocator);
498✔
2694
    }
166✔
2695
}
498✔
2696

2697

2698
/* increase reference count of encoder object (thread-unsafe) */
2699
SIXELAPI void
2700
sixel_encoder_ref(sixel_encoder_t *encoder)
1,089✔
2701
{
2702
    /* TODO: be thread safe */
2703
    ++encoder->ref;
1,089✔
2704
}
1,089✔
2705

2706

2707
/* decrease reference count of encoder object (thread-unsafe) */
2708
SIXELAPI void
2709
sixel_encoder_unref(sixel_encoder_t *encoder)
1,587✔
2710
{
2711
    /* TODO: be thread safe */
2712
    if (encoder != NULL && --encoder->ref == 0) {
1,587!
2713
        sixel_encoder_destroy(encoder);
498✔
2714
    }
166✔
2715
}
1,587✔
2716

2717

2718
/* set cancel state flag to encoder object */
2719
SIXELAPI SIXELSTATUS
2720
sixel_encoder_set_cancel_flag(
417✔
2721
    sixel_encoder_t /* in */ *encoder,
2722
    int             /* in */ *cancel_flag
2723
)
2724
{
2725
    SIXELSTATUS status = SIXEL_OK;
417✔
2726

2727
    encoder->cancel_flag = cancel_flag;
417✔
2728

2729
    return status;
417✔
2730
}
2731

2732

2733
/* set an option flag to encoder object */
2734
SIXELAPI SIXELSTATUS
2735
sixel_encoder_setopt(
672✔
2736
    sixel_encoder_t /* in */ *encoder,
2737
    int             /* in */ arg,
2738
    char const      /* in */ *value)
2739
{
2740
    SIXELSTATUS status = SIXEL_FALSE;
672✔
2741
    int number;
2742
    int parsed;
2743
    char unit[32];
2744
    char lowered[16];
2745
    size_t len;
2746
    size_t i;
2747
    long parsed_reqcolors;
2748
    char *endptr;
2749
    int forced_palette;
2750
    char const *drcs_arg_delim;
2751
    char const *drcs_arg_charset;
2752
    char const *drcs_arg_second_delim;
2753
    char const *drcs_arg_path;
2754
    size_t drcs_arg_path_length;
2755
    size_t drcs_segment_length;
2756
    char drcs_segment[32];
2757
    int drcs_mmv_value;
2758
    long drcs_charset_value;
2759
    unsigned int drcs_charset_limit;
2760

2761
    sixel_encoder_ref(encoder);
672✔
2762

2763
    switch(arg) {
672!
2764
    case SIXEL_OPTFLAG_OUTFILE:  /* o */
12✔
2765
        if (*value == '\0') {
18!
2766
            sixel_helper_set_additional_message(
×
2767
                "no file name specified.");
2768
            status = SIXEL_BAD_ARGUMENT;
×
2769
            goto end;
×
2770
        }
2771
        if (strcmp(value, "-") != 0) {
18!
2772
            if (encoder->outfd && encoder->outfd != STDOUT_FILENO) {
18!
2773
#if HAVE__CLOSE
2774
                (void) _close(encoder->outfd);
2775
#else
2776
                (void) close(encoder->outfd);
×
2777
#endif  /* HAVE__CLOSE */
2778
            }
2779
#if HAVE__OPEN
2780
            encoder->outfd = _open(value,
2781
                                   O_RDWR|O_CREAT|O_TRUNC,
2782
                                   S_IRUSR|S_IWUSR);
2783
#else
2784
            encoder->outfd = open(value,
18✔
2785
                                  O_RDWR|O_CREAT|O_TRUNC,
2786
                                  S_IRUSR|S_IWUSR);
2787
#endif  /* HAVE__OPEN */
2788
        }
6✔
2789
        break;
18✔
2790
    case SIXEL_OPTFLAG_7BIT_MODE:  /* 7 */
10✔
2791
        encoder->f8bit = 0;
15✔
2792
        break;
15✔
2793
    case SIXEL_OPTFLAG_8BIT_MODE:  /* 8 */
12✔
2794
        encoder->f8bit = 1;
18✔
2795
        break;
18✔
2796
    case SIXEL_OPTFLAG_HAS_GRI_ARG_LIMIT:  /* R */
2797
        encoder->has_gri_arg_limit = 1;
×
2798
        break;
×
2799
    case SIXEL_OPTFLAG_COLORS:  /* p */
18✔
2800
        forced_palette = 0;
27✔
2801
        errno = 0;
27✔
2802
        endptr = NULL;
27✔
2803
        if (*value == '!' && value[1] == '\0') {
27!
2804
            /*
2805
             * Force the default palette size even when the median cut
2806
             * finished early.
2807
             *
2808
             *   requested colors
2809
             *          |
2810
             *          v
2811
             *        [ 256 ]  <--- "-p!" triggers this shortcut
2812
             */
2813
            parsed_reqcolors = SIXEL_PALETTE_MAX;
×
2814
            forced_palette = 1;
×
2815
        } else {
2816
            parsed_reqcolors = strtol(value, &endptr, 10);
27✔
2817
            if (endptr != NULL && *endptr == '!') {
27!
2818
                forced_palette = 1;
×
2819
                ++endptr;
×
2820
            }
2821
            if (errno == ERANGE || endptr == value) {
27!
2822
                sixel_helper_set_additional_message(
×
2823
                    "cannot parse -p/--colors option.");
2824
                status = SIXEL_BAD_ARGUMENT;
×
2825
                goto end;
×
2826
            }
2827
            if (endptr != NULL && *endptr != '\0') {
27!
2828
                sixel_helper_set_additional_message(
×
2829
                    "cannot parse -p/--colors option.");
2830
                status = SIXEL_BAD_ARGUMENT;
×
2831
                goto end;
×
2832
            }
2833
        }
2834
        if (parsed_reqcolors < 1) {
27!
2835
            sixel_helper_set_additional_message(
×
2836
                "-p/--colors parameter must be 1 or more.");
2837
            status = SIXEL_BAD_ARGUMENT;
×
2838
            goto end;
×
2839
        }
2840
        if (parsed_reqcolors > SIXEL_PALETTE_MAX) {
27!
2841
            sixel_helper_set_additional_message(
×
2842
                "-p/--colors parameter must be less then or equal to 256.");
2843
            status = SIXEL_BAD_ARGUMENT;
×
2844
            goto end;
×
2845
        }
2846
        encoder->reqcolors = (int)parsed_reqcolors;
27✔
2847
        encoder->force_palette = forced_palette;
27✔
2848
        break;
27✔
2849
    case SIXEL_OPTFLAG_MAPFILE:  /* m */
18✔
2850
        if (encoder->mapfile) {
27✔
2851
            sixel_allocator_free(encoder->allocator, encoder->mapfile);
3✔
2852
        }
1✔
2853
        encoder->mapfile = arg_strdup(value, encoder->allocator);
27✔
2854
        if (encoder->mapfile == NULL) {
27!
2855
            sixel_helper_set_additional_message(
×
2856
                "sixel_encoder_setopt: sixel_allocator_malloc() failed.");
2857
            status = SIXEL_BAD_ALLOCATION;
×
2858
            goto end;
×
2859
        }
2860
        encoder->color_option = SIXEL_COLOR_OPTION_MAPFILE;
27✔
2861
        break;
27✔
2862
    case SIXEL_OPTFLAG_MONOCHROME:  /* e */
10✔
2863
        encoder->color_option = SIXEL_COLOR_OPTION_MONOCHROME;
15✔
2864
        break;
15✔
2865
    case SIXEL_OPTFLAG_HIGH_COLOR:  /* I */
28✔
2866
        encoder->color_option = SIXEL_COLOR_OPTION_HIGHCOLOR;
42✔
2867
        break;
42✔
2868
    case SIXEL_OPTFLAG_BUILTIN_PALETTE:  /* b */
22✔
2869
        if (strcmp(value, "xterm16") == 0) {
33✔
2870
            encoder->builtin_palette = SIXEL_BUILTIN_XTERM16;
6✔
2871
        } else if (strcmp(value, "xterm256") == 0) {
29✔
2872
            encoder->builtin_palette = SIXEL_BUILTIN_XTERM256;
6✔
2873
        } else if (strcmp(value, "vt340mono") == 0) {
23✔
2874
            encoder->builtin_palette = SIXEL_BUILTIN_VT340_MONO;
3✔
2875
        } else if (strcmp(value, "vt340color") == 0) {
19✔
2876
            encoder->builtin_palette = SIXEL_BUILTIN_VT340_COLOR;
3✔
2877
        } else if (strcmp(value, "gray1") == 0) {
16✔
2878
            encoder->builtin_palette = SIXEL_BUILTIN_G1;
3✔
2879
        } else if (strcmp(value, "gray2") == 0) {
13✔
2880
            encoder->builtin_palette = SIXEL_BUILTIN_G2;
3✔
2881
        } else if (strcmp(value, "gray4") == 0) {
10✔
2882
            encoder->builtin_palette = SIXEL_BUILTIN_G4;
3✔
2883
        } else if (strcmp(value, "gray8") == 0) {
7✔
2884
            encoder->builtin_palette = SIXEL_BUILTIN_G8;
3✔
2885
        } else {
1✔
2886
            sixel_helper_set_additional_message(
3✔
2887
                    "cannot parse builtin palette option.");
2888
            status = SIXEL_BAD_ARGUMENT;
3✔
2889
            goto end;
3✔
2890
        }
2891
        encoder->color_option = SIXEL_COLOR_OPTION_BUILTIN;
30✔
2892
        break;
30✔
2893
    case SIXEL_OPTFLAG_DIFFUSION:  /* d */
46✔
2894
        /* parse --diffusion option */
2895
        if (strcmp(value, "auto") == 0) {
69✔
2896
            encoder->method_for_diffuse = SIXEL_DIFFUSE_AUTO;
3✔
2897
        } else if (strcmp(value, "none") == 0) {
67✔
2898
            encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
15✔
2899
        } else if (strcmp(value, "fs") == 0) {
56✔
2900
            encoder->method_for_diffuse = SIXEL_DIFFUSE_FS;
6✔
2901
        } else if (strcmp(value, "atkinson") == 0) {
47✔
2902
            encoder->method_for_diffuse = SIXEL_DIFFUSE_ATKINSON;
12✔
2903
        } else if (strcmp(value, "jajuni") == 0) {
37✔
2904
            encoder->method_for_diffuse = SIXEL_DIFFUSE_JAJUNI;
6✔
2905
        } else if (strcmp(value, "stucki") == 0) {
29✔
2906
            encoder->method_for_diffuse = SIXEL_DIFFUSE_STUCKI;
6✔
2907
        } else if (strcmp(value, "burkes") == 0) {
23✔
2908
            encoder->method_for_diffuse = SIXEL_DIFFUSE_BURKES;
6✔
2909
        } else if (strcmp(value, "sierra1") == 0) {
17!
2910
            encoder->method_for_diffuse = SIXEL_DIFFUSE_SIERRA1;
×
2911
        } else if (strcmp(value, "sierra2") == 0) {
15!
2912
            encoder->method_for_diffuse = SIXEL_DIFFUSE_SIERRA2;
×
2913
        } else if (strcmp(value, "sierra3") == 0) {
15!
2914
            encoder->method_for_diffuse = SIXEL_DIFFUSE_SIERRA3;
×
2915
        } else if (strcmp(value, "a_dither") == 0) {
15✔
2916
            encoder->method_for_diffuse = SIXEL_DIFFUSE_A_DITHER;
6✔
2917
        } else if (strcmp(value, "x_dither") == 0) {
11✔
2918
            encoder->method_for_diffuse = SIXEL_DIFFUSE_X_DITHER;
6✔
2919
        } else if (strcmp(value, "lso1") == 0) {
5!
2920
            encoder->method_for_diffuse = SIXEL_DIFFUSE_LSO1;
×
2921
        } else if (strcmp(value, "lso2") == 0) {
3!
2922
            encoder->method_for_diffuse = SIXEL_DIFFUSE_LSO2;
×
2923
        } else if (strcmp(value, "lso3") == 0) {
3!
2924
            encoder->method_for_diffuse = SIXEL_DIFFUSE_LSO3;
×
2925
        } else {
2926
            sixel_helper_set_additional_message(
3✔
2927
                "specified diffusion method is not supported.");
2928
            status = SIXEL_BAD_ARGUMENT;
3✔
2929
            goto end;
3✔
2930
        }
2931
        break;
66✔
2932
    case SIXEL_OPTFLAG_DIFFUSION_SCAN:  /* y */
2933
        if (strcmp(value, "auto") == 0) {
×
2934
            encoder->method_for_scan = SIXEL_SCAN_AUTO;
×
2935
        } else if (strcmp(value, "serpentine") == 0) {
×
2936
            encoder->method_for_scan = SIXEL_SCAN_SERPENTINE;
×
2937
        } else if (strcmp(value, "raster") == 0) {
×
2938
            encoder->method_for_scan = SIXEL_SCAN_RASTER;
×
2939
        } else {
2940
            sixel_helper_set_additional_message(
×
2941
                "specified diffusion scan is not supported.");
2942
            status = SIXEL_BAD_ARGUMENT;
×
2943
            goto end;
×
2944
        }
2945
        break;
×
2946
    case SIXEL_OPTFLAG_DIFFUSION_CARRY:  /* Y */
2947
        if (strcmp(value, "auto") == 0) {
×
2948
            encoder->method_for_carry = SIXEL_CARRY_AUTO;
×
2949
        } else if (strcmp(value, "direct") == 0) {
×
2950
            encoder->method_for_carry = SIXEL_CARRY_DISABLE;
×
2951
        } else if (strcmp(value, "carry") == 0) {
×
2952
            encoder->method_for_carry = SIXEL_CARRY_ENABLE;
×
2953
        } else {
2954
            sixel_helper_set_additional_message(
×
2955
                "specified diffusion carry mode is not supported.");
2956
            status = SIXEL_BAD_ARGUMENT;
×
2957
            goto end;
×
2958
        }
2959
        break;
×
2960
    case SIXEL_OPTFLAG_FIND_LARGEST:  /* f */
10✔
2961
        /* parse --find-largest option */
2962
        if (value) {
15!
2963
            if (strcmp(value, "auto") == 0) {
15✔
2964
                encoder->method_for_largest = SIXEL_LARGE_AUTO;
3✔
2965
            } else if (strcmp(value, "norm") == 0) {
13✔
2966
                encoder->method_for_largest = SIXEL_LARGE_NORM;
6✔
2967
            } else if (strcmp(value, "lum") == 0) {
8✔
2968
                encoder->method_for_largest = SIXEL_LARGE_LUM;
3✔
2969
            } else {
1✔
2970
                sixel_helper_set_additional_message(
3✔
2971
                    "specified finding method is not supported.");
2972
                status = SIXEL_BAD_ARGUMENT;
3✔
2973
                goto end;
3✔
2974
            }
2975
        }
4✔
2976
        break;
12✔
2977
    case SIXEL_OPTFLAG_SELECT_COLOR:  /* s */
10✔
2978
        /* parse --select-color option */
2979
        if (strcmp(value, "auto") == 0) {
15✔
2980
            encoder->method_for_rep = SIXEL_REP_AUTO;
3✔
2981
        } else if (strcmp(value, "center") == 0) {
13✔
2982
            encoder->method_for_rep = SIXEL_REP_CENTER_BOX;
3✔
2983
        } else if (strcmp(value, "average") == 0) {
10✔
2984
            encoder->method_for_rep = SIXEL_REP_AVERAGE_COLORS;
3✔
2985
        } else if ((strcmp(value, "histogram") == 0) ||
7!
2986
                   (strcmp(value, "histgram") == 0)) {
3!
2987
            encoder->method_for_rep = SIXEL_REP_AVERAGE_PIXELS;
3✔
2988
        } else {
1✔
2989
            sixel_helper_set_additional_message(
3✔
2990
                "specified finding method is not supported.");
2991
            status = SIXEL_BAD_ARGUMENT;
3✔
2992
            goto end;
3✔
2993
        }
2994
        break;
12✔
2995
    case SIXEL_OPTFLAG_CROP:  /* c */
10✔
2996
#if HAVE_SSCANF_S
2997
        number = sscanf_s(value, "%dx%d+%d+%d",
2998
                          &encoder->clipwidth, &encoder->clipheight,
2999
                          &encoder->clipx, &encoder->clipy);
3000
#else
3001
        number = sscanf(value, "%dx%d+%d+%d",
20✔
3002
                        &encoder->clipwidth, &encoder->clipheight,
5✔
3003
                        &encoder->clipx, &encoder->clipy);
5✔
3004
#endif  /* HAVE_SSCANF_S */
3005
        if (number != 4) {
15!
3006
            status = SIXEL_BAD_ARGUMENT;
×
3007
            goto end;
×
3008
        }
3009
        if (encoder->clipwidth <= 0 || encoder->clipheight <= 0) {
15!
3010
            status = SIXEL_BAD_ARGUMENT;
×
3011
            goto end;
×
3012
        }
3013
        if (encoder->clipx < 0 || encoder->clipy < 0) {
15!
3014
            status = SIXEL_BAD_ARGUMENT;
×
3015
            goto end;
×
3016
        }
3017
        encoder->clipfirst = 0;
15✔
3018
        break;
15✔
3019
    case SIXEL_OPTFLAG_WIDTH:  /* w */
50✔
3020
#if HAVE_SSCANF_S
3021
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
3022
#else
3023
        parsed = sscanf(value, "%d%2s", &number, unit);
75✔
3024
#endif  /* HAVE_SSCANF_S */
3025
        if (parsed == 2 && strcmp(unit, "%") == 0) {
75!
3026
            encoder->pixelwidth = (-1);
12✔
3027
            encoder->percentwidth = number;
12✔
3028
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
67!
3029
            encoder->pixelwidth = number;
51✔
3030
            encoder->percentwidth = (-1);
51✔
3031
        } else if (strcmp(value, "auto") == 0) {
29✔
3032
            encoder->pixelwidth = (-1);
9✔
3033
            encoder->percentwidth = (-1);
9✔
3034
        } else {
3✔
3035
            sixel_helper_set_additional_message(
3✔
3036
                "cannot parse -w/--width option.");
3037
            status = SIXEL_BAD_ARGUMENT;
3✔
3038
            goto end;
3✔
3039
        }
3040
        if (encoder->clipwidth) {
72✔
3041
            encoder->clipfirst = 1;
6✔
3042
        }
2✔
3043
        break;
72✔
3044
    case SIXEL_OPTFLAG_HEIGHT:  /* h */
44✔
3045
#if HAVE_SSCANF_S
3046
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
3047
#else
3048
        parsed = sscanf(value, "%d%2s", &number, unit);
66✔
3049
#endif  /* HAVE_SSCANF_S */
3050
        if (parsed == 2 && strcmp(unit, "%") == 0) {
66!
3051
            encoder->pixelheight = (-1);
9✔
3052
            encoder->percentheight = number;
9✔
3053
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
60!
3054
            encoder->pixelheight = number;
45✔
3055
            encoder->percentheight = (-1);
45✔
3056
        } else if (strcmp(value, "auto") == 0) {
27✔
3057
            encoder->pixelheight = (-1);
9✔
3058
            encoder->percentheight = (-1);
9✔
3059
        } else {
3✔
3060
            sixel_helper_set_additional_message(
3✔
3061
                "cannot parse -h/--height option.");
3062
            status = SIXEL_BAD_ARGUMENT;
3✔
3063
            goto end;
3✔
3064
        }
3065
        if (encoder->clipheight) {
63✔
3066
            encoder->clipfirst = 1;
3✔
3067
        }
1✔
3068
        break;
63✔
3069
    case SIXEL_OPTFLAG_RESAMPLING:  /* r */
34✔
3070
        /* parse --resampling option */
3071
        if (strcmp(value, "nearest") == 0) {
51✔
3072
            encoder->method_for_resampling = SIXEL_RES_NEAREST;
18✔
3073
        } else if (strcmp(value, "gaussian") == 0) {
39✔
3074
            encoder->method_for_resampling = SIXEL_RES_GAUSSIAN;
3✔
3075
        } else if (strcmp(value, "hanning") == 0) {
31✔
3076
            encoder->method_for_resampling = SIXEL_RES_HANNING;
3✔
3077
        } else if (strcmp(value, "hamming") == 0) {
28✔
3078
            encoder->method_for_resampling = SIXEL_RES_HAMMING;
3✔
3079
        } else if (strcmp(value, "bilinear") == 0) {
25✔
3080
            encoder->method_for_resampling = SIXEL_RES_BILINEAR;
3✔
3081
        } else if (strcmp(value, "welsh") == 0) {
22✔
3082
            encoder->method_for_resampling = SIXEL_RES_WELSH;
3✔
3083
        } else if (strcmp(value, "bicubic") == 0) {
19✔
3084
            encoder->method_for_resampling = SIXEL_RES_BICUBIC;
3✔
3085
        } else if (strcmp(value, "lanczos2") == 0) {
16✔
3086
            encoder->method_for_resampling = SIXEL_RES_LANCZOS2;
6✔
3087
        } else if (strcmp(value, "lanczos3") == 0) {
11✔
3088
            encoder->method_for_resampling = SIXEL_RES_LANCZOS3;
3✔
3089
        } else if (strcmp(value, "lanczos4") == 0) {
7✔
3090
            encoder->method_for_resampling = SIXEL_RES_LANCZOS4;
3✔
3091
        } else {
1✔
3092
            sixel_helper_set_additional_message(
3✔
3093
                "specified desampling method is not supported.");
3094
            status = SIXEL_BAD_ARGUMENT;
3✔
3095
            goto end;
3✔
3096
        }
3097
        break;
48✔
3098
    case SIXEL_OPTFLAG_QUALITY:  /* q */
12✔
3099
        /* parse --quality option */
3100
        if (strcmp(value, "auto") == 0) {
18✔
3101
            encoder->quality_mode = SIXEL_QUALITY_AUTO;
6✔
3102
        } else if (strcmp(value, "high") == 0) {
14✔
3103
            encoder->quality_mode = SIXEL_QUALITY_HIGH;
3✔
3104
        } else if (strcmp(value, "low") == 0) {
10✔
3105
            encoder->quality_mode = SIXEL_QUALITY_LOW;
3✔
3106
        } else if (strcmp(value, "full") == 0) {
7✔
3107
            encoder->quality_mode = SIXEL_QUALITY_FULL;
3✔
3108
        } else {
1✔
3109
            sixel_helper_set_additional_message(
3✔
3110
                "cannot parse quality option.");
3111
            status = SIXEL_BAD_ARGUMENT;
3✔
3112
            goto end;
3✔
3113
        }
3114
        break;
15✔
3115
    case SIXEL_OPTFLAG_LOOPMODE:  /* l */
10✔
3116
        /* parse --loop-control option */
3117
        if (strcmp(value, "auto") == 0) {
15✔
3118
            encoder->loop_mode = SIXEL_LOOP_AUTO;
3✔
3119
        } else if (strcmp(value, "force") == 0) {
13!
3120
            encoder->loop_mode = SIXEL_LOOP_FORCE;
×
3121
        } else if (strcmp(value, "disable") == 0) {
12✔
3122
            encoder->loop_mode = SIXEL_LOOP_DISABLE;
9✔
3123
        } else {
3✔
3124
            sixel_helper_set_additional_message(
3✔
3125
                "cannot parse loop-control option.");
3126
            status = SIXEL_BAD_ARGUMENT;
3✔
3127
            goto end;
3✔
3128
        }
3129
        break;
12✔
3130
    case SIXEL_OPTFLAG_PALETTE_TYPE:  /* t */
18✔
3131
        /* parse --palette-type option */
3132
        if (strcmp(value, "auto") == 0) {
27✔
3133
            encoder->palette_type = SIXEL_PALETTETYPE_AUTO;
6✔
3134
        } else if (strcmp(value, "hls") == 0) {
23✔
3135
            encoder->palette_type = SIXEL_PALETTETYPE_HLS;
15✔
3136
        } else if (strcmp(value, "rgb") == 0) {
11✔
3137
            encoder->palette_type = SIXEL_PALETTETYPE_RGB;
3✔
3138
        } else {
1✔
3139
            sixel_helper_set_additional_message(
3✔
3140
                "cannot parse palette type option.");
3141
            status = SIXEL_BAD_ARGUMENT;
3✔
3142
            goto end;
3✔
3143
        }
3144
        break;
24✔
3145
    case SIXEL_OPTFLAG_BGCOLOR:  /* B */
30✔
3146
        /* parse --bgcolor option */
3147
        if (encoder->bgcolor) {
45✔
3148
            sixel_allocator_free(encoder->allocator, encoder->bgcolor);
6✔
3149
            encoder->bgcolor = NULL;
6✔
3150
        }
2✔
3151
        status = sixel_parse_x_colorspec(&encoder->bgcolor,
60✔
3152
                                         value,
15✔
3153
                                         encoder->allocator);
15✔
3154
        if (SIXEL_FAILED(status)) {
45✔
3155
            sixel_helper_set_additional_message(
21✔
3156
                "cannot parse bgcolor option.");
3157
            status = SIXEL_BAD_ARGUMENT;
21✔
3158
            goto end;
21✔
3159
        }
3160
        break;
24✔
3161
    case SIXEL_OPTFLAG_INSECURE:  /* k */
3162
        encoder->finsecure = 1;
×
3163
        break;
×
3164
    case SIXEL_OPTFLAG_INVERT:  /* i */
4✔
3165
        encoder->finvert = 1;
6✔
3166
        break;
6✔
3167
    case SIXEL_OPTFLAG_USE_MACRO:  /* u */
4✔
3168
        encoder->fuse_macro = 1;
6✔
3169
        break;
6✔
3170
    case SIXEL_OPTFLAG_MACRO_NUMBER:  /* n */
2✔
3171
        encoder->macro_number = atoi(value);
3✔
3172
        if (encoder->macro_number < 0) {
3!
3173
            status = SIXEL_BAD_ARGUMENT;
×
3174
            goto end;
×
3175
        }
3176
        break;
3✔
3177
    case SIXEL_OPTFLAG_IGNORE_DELAY:  /* g */
4✔
3178
        encoder->fignore_delay = 1;
6✔
3179
        break;
6✔
3180
    case SIXEL_OPTFLAG_VERBOSE:  /* v */
6✔
3181
        encoder->verbose = 1;
9✔
3182
        sixel_helper_set_loader_trace(1);
9✔
3183
        break;
9✔
3184
    case SIXEL_OPTFLAG_LOADERS:  /* J */
3185
        if (encoder->loader_order != NULL) {
×
3186
            sixel_allocator_free(encoder->allocator,
×
3187
                                 encoder->loader_order);
×
3188
            encoder->loader_order = NULL;
×
3189
        }
3190
        if (value != NULL && *value != '\0') {
×
3191
            encoder->loader_order = arg_strdup(value,
×
3192
                                               encoder->allocator);
3193
            if (encoder->loader_order == NULL) {
×
3194
                sixel_helper_set_additional_message(
×
3195
                    "sixel_encoder_setopt: "
3196
                    "sixel_allocator_malloc() failed.");
3197
                status = SIXEL_BAD_ALLOCATION;
×
3198
                goto end;
×
3199
            }
3200
        }
3201
        break;
×
3202
    case SIXEL_OPTFLAG_STATIC:  /* S */
2✔
3203
        encoder->fstatic = 1;
3✔
3204
        break;
3✔
3205
    case SIXEL_OPTFLAG_DRCS:  /* @ */
3206
        encoder->fdrcs = 1;
×
NEW
3207
        drcs_arg_delim = NULL;
×
NEW
3208
        drcs_arg_charset = NULL;
×
NEW
3209
        drcs_arg_second_delim = NULL;
×
NEW
3210
        drcs_arg_path = NULL;
×
NEW
3211
        drcs_arg_path_length = 0u;
×
NEW
3212
        drcs_segment_length = 0u;
×
NEW
3213
        drcs_mmv_value = 2;
×
NEW
3214
        drcs_charset_value = 1L;
×
NEW
3215
        drcs_charset_limit = 0u;
×
NEW
3216
        if (value != NULL && *value != '\0') {
×
NEW
3217
            drcs_arg_delim = strchr(value, ':');
×
NEW
3218
            if (drcs_arg_delim == NULL) {
×
NEW
3219
                drcs_segment_length = strlen(value);
×
NEW
3220
                if (drcs_segment_length >= sizeof(drcs_segment)) {
×
NEW
3221
                    sixel_helper_set_additional_message(
×
3222
                        "DRCS mapping revision is too long.");
NEW
3223
                    status = SIXEL_BAD_ARGUMENT;
×
NEW
3224
                    goto end;
×
3225
                }
NEW
3226
                memcpy(drcs_segment, value, drcs_segment_length);
×
NEW
3227
                drcs_segment[drcs_segment_length] = '\0';
×
NEW
3228
                errno = 0;
×
NEW
3229
                endptr = NULL;
×
NEW
3230
                drcs_mmv_value = (int)strtol(drcs_segment, &endptr, 10);
×
NEW
3231
                if (errno != 0 || endptr == drcs_segment || *endptr != '\0') {
×
NEW
3232
                    sixel_helper_set_additional_message(
×
3233
                        "cannot parse DRCS option.");
NEW
3234
                    status = SIXEL_BAD_ARGUMENT;
×
NEW
3235
                    goto end;
×
3236
                }
3237
            } else {
NEW
3238
                if (drcs_arg_delim != value) {
×
NEW
3239
                    drcs_segment_length =
×
NEW
3240
                        (size_t)(drcs_arg_delim - value);
×
NEW
3241
                    if (drcs_segment_length >= sizeof(drcs_segment)) {
×
NEW
3242
                        sixel_helper_set_additional_message(
×
3243
                            "DRCS mapping revision is too long.");
NEW
3244
                        status = SIXEL_BAD_ARGUMENT;
×
NEW
3245
                        goto end;
×
3246
                    }
NEW
3247
                    memcpy(drcs_segment, value, drcs_segment_length);
×
NEW
3248
                    drcs_segment[drcs_segment_length] = '\0';
×
NEW
3249
                    errno = 0;
×
NEW
3250
                    endptr = NULL;
×
NEW
3251
                    drcs_mmv_value = (int)strtol(drcs_segment, &endptr, 10);
×
NEW
3252
                    if (errno != 0 || endptr == drcs_segment || *endptr != '\0') {
×
NEW
3253
                        sixel_helper_set_additional_message(
×
3254
                            "cannot parse DRCS option.");
NEW
3255
                        status = SIXEL_BAD_ARGUMENT;
×
NEW
3256
                        goto end;
×
3257
                    }
3258
                }
NEW
3259
                drcs_arg_charset = drcs_arg_delim + 1;
×
NEW
3260
                drcs_arg_second_delim = strchr(drcs_arg_charset, ':');
×
NEW
3261
                if (drcs_arg_second_delim != NULL) {
×
NEW
3262
                    if (drcs_arg_second_delim != drcs_arg_charset) {
×
NEW
3263
                        drcs_segment_length =
×
NEW
3264
                            (size_t)(drcs_arg_second_delim - drcs_arg_charset);
×
NEW
3265
                        if (drcs_segment_length >= sizeof(drcs_segment)) {
×
NEW
3266
                            sixel_helper_set_additional_message(
×
3267
                                "DRCS charset number is too long.");
NEW
3268
                            status = SIXEL_BAD_ARGUMENT;
×
NEW
3269
                            goto end;
×
3270
                        }
NEW
3271
                        memcpy(drcs_segment,
×
3272
                               drcs_arg_charset,
3273
                               drcs_segment_length);
NEW
3274
                        drcs_segment[drcs_segment_length] = '\0';
×
NEW
3275
                        errno = 0;
×
NEW
3276
                        endptr = NULL;
×
NEW
3277
                        drcs_charset_value = strtol(drcs_segment,
×
3278
                                                    &endptr,
3279
                                                    10);
NEW
3280
                        if (errno != 0 || endptr == drcs_segment ||
×
NEW
3281
                                *endptr != '\0') {
×
NEW
3282
                            sixel_helper_set_additional_message(
×
3283
                                "cannot parse DRCS charset number.");
NEW
3284
                            status = SIXEL_BAD_ARGUMENT;
×
NEW
3285
                            goto end;
×
3286
                        }
3287
                    }
NEW
3288
                    drcs_arg_path = drcs_arg_second_delim + 1;
×
NEW
3289
                    drcs_arg_path_length = strlen(drcs_arg_path);
×
NEW
3290
                    if (drcs_arg_path_length == 0u) {
×
NEW
3291
                        drcs_arg_path = NULL;
×
3292
                    }
NEW
3293
                } else if (*drcs_arg_charset != '\0') {
×
NEW
3294
                    drcs_segment_length = strlen(drcs_arg_charset);
×
NEW
3295
                    if (drcs_segment_length >= sizeof(drcs_segment)) {
×
NEW
3296
                        sixel_helper_set_additional_message(
×
3297
                            "DRCS charset number is too long.");
NEW
3298
                        status = SIXEL_BAD_ARGUMENT;
×
NEW
3299
                        goto end;
×
3300
                    }
NEW
3301
                    memcpy(drcs_segment,
×
3302
                           drcs_arg_charset,
3303
                           drcs_segment_length);
NEW
3304
                    drcs_segment[drcs_segment_length] = '\0';
×
NEW
3305
                    errno = 0;
×
NEW
3306
                    endptr = NULL;
×
NEW
3307
                    drcs_charset_value = strtol(drcs_segment,
×
3308
                                                &endptr,
3309
                                                10);
NEW
3310
                    if (errno != 0 || endptr == drcs_segment ||
×
NEW
3311
                            *endptr != '\0') {
×
NEW
3312
                        sixel_helper_set_additional_message(
×
3313
                            "cannot parse DRCS charset number.");
NEW
3314
                        status = SIXEL_BAD_ARGUMENT;
×
NEW
3315
                        goto end;
×
3316
                    }
3317
                }
3318
            }
3319
        }
3320
        /*
3321
         * Layout of the DRCS option value:
3322
         *
3323
         *    value = <mmv>:<charset_no>:<path>
3324
         *          ^        ^                ^
3325
         *          |        |                |
3326
         *          |        |                +-- optional path that may reuse
3327
         *          |        |                    STDOUT when set to "-" or drop
3328
         *          |        |                    tiles when left blank
3329
         *          |        +-- charset number (defaults to 1 when omitted)
3330
         *          +-- mapping revision (defaults to 2 when omitted)
3331
         */
NEW
3332
        if (drcs_mmv_value < 0 || drcs_mmv_value > 2) {
×
UNCOV
3333
            sixel_helper_set_additional_message(
×
3334
                "unknown DRCS unicode mapping version.");
3335
            status = SIXEL_BAD_ARGUMENT;
×
3336
            goto end;
×
3337
        }
NEW
3338
        if (drcs_mmv_value == 0) {
×
NEW
3339
            drcs_charset_limit = 126u;
×
NEW
3340
        } else if (drcs_mmv_value == 1) {
×
NEW
3341
            drcs_charset_limit = 63u;
×
3342
        } else {
NEW
3343
            drcs_charset_limit = 158u;
×
3344
        }
NEW
3345
        if (drcs_charset_value < 1 ||
×
NEW
3346
            (unsigned long)drcs_charset_value > drcs_charset_limit) {
×
UNCOV
3347
            sixel_helper_set_additional_message(
×
3348
                "DRCS charset number is out of range.");
3349
            status = SIXEL_BAD_ARGUMENT;
×
3350
            goto end;
×
3351
        }
NEW
3352
        encoder->drcs_mmv = drcs_mmv_value;
×
NEW
3353
        encoder->drcs_charset_no = (unsigned short)drcs_charset_value;
×
NEW
3354
        if (encoder->tile_outfd >= 0
×
NEW
3355
            && encoder->tile_outfd != encoder->outfd
×
NEW
3356
            && encoder->tile_outfd != STDOUT_FILENO
×
NEW
3357
            && encoder->tile_outfd != STDERR_FILENO) {
×
3358
#if HAVE__CLOSE
3359
            (void) _close(encoder->tile_outfd);
3360
#else
NEW
3361
            (void) close(encoder->tile_outfd);
×
3362
#endif  /* HAVE__CLOSE */
3363
        }
NEW
3364
        encoder->tile_outfd = (-1);
×
NEW
3365
        if (drcs_arg_path != NULL) {
×
NEW
3366
            if (strcmp(drcs_arg_path, "-") == 0) {
×
NEW
3367
                encoder->tile_outfd = STDOUT_FILENO;
×
3368
            } else {
3369
#if HAVE__OPEN
3370
                encoder->tile_outfd = _open(drcs_arg_path,
3371
                                            O_RDWR|O_CREAT|O_TRUNC,
3372
                                            S_IRUSR|S_IWUSR);
3373
#else
NEW
3374
                encoder->tile_outfd = open(drcs_arg_path,
×
3375
                                           O_RDWR|O_CREAT|O_TRUNC,
3376
                                           S_IRUSR|S_IWUSR);
3377
#endif  /* HAVE__OPEN */
NEW
3378
                if (encoder->tile_outfd < 0) {
×
NEW
3379
                    sixel_helper_set_additional_message(
×
3380
                        "sixel_encoder_setopt: failed to open tile"
3381
                        " output path.");
NEW
3382
                    status = SIXEL_RUNTIME_ERROR;
×
NEW
3383
                    goto end;
×
3384
                }
3385
            }
3386
        }
UNCOV
3387
        break;
×
3388
    case SIXEL_OPTFLAG_PENETRATE:  /* P */
6✔
3389
        encoder->penetrate_multiplexer = 1;
9✔
3390
        break;
9✔
3391
    case SIXEL_OPTFLAG_ENCODE_POLICY:  /* E */
8✔
3392
        if (strcmp(value, "auto") == 0) {
12✔
3393
            encoder->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
3✔
3394
        } else if (strcmp(value, "fast") == 0) {
10✔
3395
            encoder->encode_policy = SIXEL_ENCODEPOLICY_FAST;
3✔
3396
        } else if (strcmp(value, "size") == 0) {
7✔
3397
            encoder->encode_policy = SIXEL_ENCODEPOLICY_SIZE;
3✔
3398
        } else {
1✔
3399
            sixel_helper_set_additional_message(
3✔
3400
                "cannot parse encode policy option.");
3401
            status = SIXEL_BAD_ARGUMENT;
3✔
3402
            goto end;
3✔
3403
        }
3404
        break;
9✔
3405
    case SIXEL_OPTFLAG_LUT_POLICY:  /* L */
3406
        if (strcmp(value, "auto") == 0) {
×
3407
            encoder->lut_policy = SIXEL_LUT_POLICY_AUTO;
×
3408
        } else if (strcmp(value, "5bit") == 0) {
×
3409
            encoder->lut_policy = SIXEL_LUT_POLICY_5BIT;
×
3410
        } else if (strcmp(value, "6bit") == 0) {
×
3411
            encoder->lut_policy = SIXEL_LUT_POLICY_6BIT;
×
3412
        } else if (strcmp(value, "robinhood") == 0) {
×
3413
            encoder->lut_policy = SIXEL_LUT_POLICY_ROBINHOOD;
×
3414
        } else if (strcmp(value, "hopscotch") == 0) {
×
3415
            encoder->lut_policy = SIXEL_LUT_POLICY_HOPSCOTCH;
×
3416
        } else {
3417
            sixel_helper_set_additional_message(
×
3418
                "cannot parse lut policy option.");
3419
            status = SIXEL_BAD_ARGUMENT;
×
3420
            goto end;
×
3421
        }
3422
        if (encoder->dither_cache != NULL) {
×
3423
            sixel_dither_set_lut_policy(encoder->dither_cache,
×
3424
                                        encoder->lut_policy);
3425
        }
3426
        break;
×
3427
    case SIXEL_OPTFLAG_WORKING_COLORSPACE:  /* W */
3428
        if (value == NULL) {
×
3429
            sixel_helper_set_additional_message(
×
3430
                "working-colorspace requires an argument.");
3431
            status = SIXEL_BAD_ARGUMENT;
×
3432
            goto end;
×
3433
        } else {
3434
            len = strlen(value);
×
3435

3436
            if (len >= sizeof(lowered)) {
×
3437
                sixel_helper_set_additional_message(
×
3438
                    "specified working colorspace name is too long.");
3439
                status = SIXEL_BAD_ARGUMENT;
×
3440
                goto end;
×
3441
            }
3442
            for (i = 0; i < len; ++i) {
×
3443
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
3444
            }
3445
            lowered[len] = '\0';
×
3446

3447
            if (strcmp(lowered, "gamma") == 0) {
×
3448
                encoder->working_colorspace = SIXEL_COLORSPACE_GAMMA;
×
3449
            } else if (strcmp(lowered, "linear") == 0) {
×
3450
                encoder->working_colorspace = SIXEL_COLORSPACE_LINEAR;
×
3451
            } else if (strcmp(lowered, "oklab") == 0) {
×
3452
                encoder->working_colorspace = SIXEL_COLORSPACE_OKLAB;
×
3453
            } else {
3454
                sixel_helper_set_additional_message(
×
3455
                    "unsupported working colorspace specified.");
3456
                status = SIXEL_BAD_ARGUMENT;
×
3457
                goto end;
×
3458
            }
3459
        }
3460
        break;
×
3461
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
3462
        if (value == NULL) {
×
3463
            sixel_helper_set_additional_message(
×
3464
                "output-colorspace requires an argument.");
3465
            status = SIXEL_BAD_ARGUMENT;
×
3466
            goto end;
×
3467
        } else {
3468
            len = strlen(value);
×
3469

3470
            if (len >= sizeof(lowered)) {
×
3471
                sixel_helper_set_additional_message(
×
3472
                    "specified output colorspace name is too long.");
3473
                status = SIXEL_BAD_ARGUMENT;
×
3474
                goto end;
×
3475
            }
3476
            for (i = 0; i < len; ++i) {
×
3477
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
3478
            }
3479
            lowered[len] = '\0';
×
3480

3481
            if (strcmp(lowered, "gamma") == 0) {
×
3482
                encoder->output_colorspace = SIXEL_COLORSPACE_GAMMA;
×
3483
            } else if (strcmp(lowered, "linear") == 0) {
×
3484
                encoder->output_colorspace = SIXEL_COLORSPACE_LINEAR;
×
3485
            } else if (strcmp(lowered, "smpte-c") == 0 ||
×
3486
                       strcmp(lowered, "smptec") == 0) {
×
3487
                encoder->output_colorspace = SIXEL_COLORSPACE_SMPTEC;
×
3488
            } else {
3489
                sixel_helper_set_additional_message(
×
3490
                    "unsupported output colorspace specified.");
3491
                status = SIXEL_BAD_ARGUMENT;
×
3492
                goto end;
×
3493
            }
3494
        }
3495
        break;
×
3496
    case SIXEL_OPTFLAG_ORMODE:  /* O */
3497
        encoder->ormode = 1;
×
3498
        break;
×
3499
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
6✔
3500
        encoder->complexion = atoi(value);
9✔
3501
        if (encoder->complexion < 1) {
9✔
3502
            sixel_helper_set_additional_message(
3✔
3503
                "complexion parameter must be 1 or more.");
3504
            status = SIXEL_BAD_ARGUMENT;
3✔
3505
            goto end;
3✔
3506
        }
3507
        break;
6✔
3508
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
3509
        encoder->pipe_mode = 1;
×
3510
        break;
×
3511
    case '?':  /* unknown option */
3✔
3512
    default:
3513
        /* exit if unknown options are specified */
3514
        sixel_helper_set_additional_message(
3✔
3515
            "unknown option is specified.");
3516
        status = SIXEL_BAD_ARGUMENT;
3✔
3517
        goto end;
3✔
3518
    }
3519

3520
    /* detects arguments conflictions */
3521
    if (encoder->reqcolors != (-1)) {
612✔
3522
        switch (encoder->color_option) {
99!
3523
        case SIXEL_COLOR_OPTION_MAPFILE:
3524
            sixel_helper_set_additional_message(
×
3525
                "option -p, --colors conflicts with -m, --mapfile.");
3526
            status = SIXEL_BAD_ARGUMENT;
×
3527
            goto end;
×
3528
        case SIXEL_COLOR_OPTION_MONOCHROME:
2✔
3529
            sixel_helper_set_additional_message(
3✔
3530
                "option -e, --monochrome conflicts with -p, --colors.");
3531
            status = SIXEL_BAD_ARGUMENT;
3✔
3532
            goto end;
3✔
3533
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
2✔
3534
            sixel_helper_set_additional_message(
3✔
3535
                "option -p, --colors conflicts with -I, --high-color.");
3536
            status = SIXEL_BAD_ARGUMENT;
3✔
3537
            goto end;
3✔
3538
        case SIXEL_COLOR_OPTION_BUILTIN:
2✔
3539
            sixel_helper_set_additional_message(
3✔
3540
                "option -p, --colors conflicts with -b, --builtin-palette.");
3541
            status = SIXEL_BAD_ARGUMENT;
3✔
3542
            goto end;
3✔
3543
        default:
60✔
3544
            break;
90✔
3545
        }
3546
    }
30✔
3547

3548
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
3549
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
603✔
3550
        sixel_helper_set_additional_message(
3✔
3551
            "option -8 --8bit-mode conflicts"
3552
            " with -P, --penetrate.");
3553
        status = SIXEL_BAD_ARGUMENT;
3✔
3554
        goto end;
3✔
3555
    }
3556

3557
    status = SIXEL_OK;
600✔
3558

3559
end:
448✔
3560
    sixel_encoder_unref(encoder);
672✔
3561

3562
    return status;
672✔
3563
}
3564

3565

3566
/* called when image loader component load a image frame */
3567
static SIXELSTATUS
3568
load_image_callback(sixel_frame_t *frame, void *data)
518✔
3569
{
3570
    sixel_encoder_t *encoder;
3571

3572
    encoder = (sixel_encoder_t *)data;
518✔
3573
    if (encoder->capture_source && encoder->capture_source_frame == NULL) {
518!
3574
        sixel_frame_ref(frame);
×
3575
        encoder->capture_source_frame = frame;
×
3576
    }
3577

3578
    return sixel_encoder_encode_frame(encoder, frame, NULL);
518✔
3579
}
3580

3581

3582
/* load source data from specified file and encode it to SIXEL format
3583
 * output to encoder->outfd */
3584
SIXELAPI SIXELSTATUS
3585
sixel_encoder_encode(
417✔
3586
    sixel_encoder_t *encoder,   /* encoder object */
3587
    char const      *filename)  /* input filename */
3588
{
3589
    SIXELSTATUS status = SIXEL_FALSE;
417✔
3590
    int fuse_palette = 1;
417✔
3591
    sixel_loader_t *loader;
3592

3593
    if (encoder == NULL) {
417!
3594
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
3595
#  pragma GCC diagnostic push
3596
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
3597
#endif
3598
        encoder = sixel_encoder_create();
×
3599
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
3600
#  pragma GCC diagnostic pop
3601
#endif
3602
        if (encoder == NULL) {
×
3603
            sixel_helper_set_additional_message(
×
3604
                "sixel_encoder_encode: sixel_encoder_create() failed.");
3605
            status = SIXEL_BAD_ALLOCATION;
×
3606
            goto end;
×
3607
        }
3608
    } else {
3609
        sixel_encoder_ref(encoder);
417✔
3610
    }
3611

3612
    if (encoder->assessment_observer != NULL) {
417!
3613
        sixel_assessment_stage_transition(
×
3614
            encoder->assessment_observer,
×
3615
            SIXEL_ASSESSMENT_STAGE_IMAGE_CHUNK);
3616
    }
3617
    encoder->last_loader_name[0] = '\0';
417✔
3618
    encoder->last_source_path[0] = '\0';
417✔
3619
    encoder->last_input_bytes = 0u;
417✔
3620

3621
    /* if required color is not set, set the max value */
3622
    if (encoder->reqcolors == (-1)) {
417✔
3623
        encoder->reqcolors = SIXEL_PALETTE_MAX;
399✔
3624
    }
133✔
3625

3626
    if (encoder->capture_source && encoder->capture_source_frame != NULL) {
417!
3627
        sixel_frame_unref(encoder->capture_source_frame);
×
3628
        encoder->capture_source_frame = NULL;
×
3629
    }
3630

3631
    /* if required color is less then 2, set the min value */
3632
    if (encoder->reqcolors < 2) {
417✔
3633
        encoder->reqcolors = SIXEL_PALETTE_MIN;
3✔
3634
    }
1✔
3635

3636
    /* if color space option is not set, choose RGB color space */
3637
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
417✔
3638
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
399✔
3639
    }
133✔
3640

3641
    /* if color option is not default value, prohibit to read
3642
       the file as a paletted image */
3643
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
417✔
3644
        fuse_palette = 0;
99✔
3645
    }
33✔
3646

3647
    /* if scaling options are set, prohibit to read the file as
3648
       a paletted image */
3649
    if (encoder->percentwidth > 0 ||
533✔
3650
        encoder->percentheight > 0 ||
405✔
3651
        encoder->pixelwidth > 0 ||
399✔
3652
        encoder->pixelheight > 0) {
381✔
3653
        fuse_palette = 0;
99✔
3654
    }
33✔
3655

3656
reload:
278✔
3657
    loader = NULL;
417✔
3658

3659
    sixel_helper_set_loader_trace(encoder->verbose);
417✔
3660
    sixel_helper_set_thumbnail_size_hint(
417✔
3661
        sixel_encoder_thumbnail_hint(encoder));
139✔
3662

3663
    status = sixel_loader_new(&loader, encoder->allocator);
417✔
3664
    if (SIXEL_FAILED(status)) {
417!
3665
        goto load_end;
×
3666
    }
3667

3668
    status = sixel_loader_setopt(loader,
556✔
3669
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
3670
                                 &encoder->fstatic);
417✔
3671
    if (SIXEL_FAILED(status)) {
417!
3672
        goto load_end;
×
3673
    }
3674

3675
    status = sixel_loader_setopt(loader,
417✔
3676
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
3677
                                 &fuse_palette);
3678
    if (SIXEL_FAILED(status)) {
417!
3679
        goto load_end;
×
3680
    }
3681

3682
    status = sixel_loader_setopt(loader,
556✔
3683
                                 SIXEL_LOADER_OPTION_REQCOLORS,
3684
                                 &encoder->reqcolors);
417✔
3685
    if (SIXEL_FAILED(status)) {
417!
3686
        goto load_end;
×
3687
    }
3688

3689
    status = sixel_loader_setopt(loader,
556✔
3690
                                 SIXEL_LOADER_OPTION_BGCOLOR,
3691
                                 encoder->bgcolor);
417✔
3692
    if (SIXEL_FAILED(status)) {
417!
3693
        goto load_end;
×
3694
    }
3695

3696
    status = sixel_loader_setopt(loader,
556✔
3697
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
3698
                                 &encoder->loop_mode);
417✔
3699
    if (SIXEL_FAILED(status)) {
417!
3700
        goto load_end;
×
3701
    }
3702

3703
    status = sixel_loader_setopt(loader,
556✔
3704
                                 SIXEL_LOADER_OPTION_INSECURE,
3705
                                 &encoder->finsecure);
417✔
3706
    if (SIXEL_FAILED(status)) {
417!
3707
        goto load_end;
×
3708
    }
3709

3710
    status = sixel_loader_setopt(loader,
556✔
3711
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
3712
                                 encoder->cancel_flag);
417✔
3713
    if (SIXEL_FAILED(status)) {
417!
3714
        goto load_end;
×
3715
    }
3716

3717
    status = sixel_loader_setopt(loader,
556✔
3718
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
3719
                                 encoder->loader_order);
417✔
3720
    if (SIXEL_FAILED(status)) {
417!
3721
        goto load_end;
×
3722
    }
3723

3724
    status = sixel_loader_setopt(loader,
556✔
3725
                                 SIXEL_LOADER_OPTION_CONTEXT,
3726
                                 encoder);
139✔
3727
    if (SIXEL_FAILED(status)) {
417!
3728
        goto load_end;
×
3729
    }
3730

3731
    /*
3732
     * Wire the optional assessment observer into the loader.
3733
     *
3734
     * The observer travels separately from the callback context so mapfile
3735
     * palette probes and other callbacks can keep using arbitrary structs.
3736
     */
3737
    status = sixel_loader_setopt(loader,
556✔
3738
                                 SIXEL_LOADER_OPTION_ASSESSMENT,
3739
                                 encoder->assessment_observer);
417✔
3740
    if (SIXEL_FAILED(status)) {
417!
3741
        goto load_end;
×
3742
    }
3743

3744
    status = sixel_loader_load_file(loader,
556✔
3745
                                    filename,
139✔
3746
                                    load_image_callback);
3747
    if (status != SIXEL_OK) {
417✔
3748
        goto load_end;
21✔
3749
    }
3750
    encoder->last_input_bytes = sixel_loader_get_last_input_bytes(loader);
396✔
3751
    if (sixel_loader_get_last_success_name(loader) != NULL) {
396!
3752
        (void)snprintf(encoder->last_loader_name,
396✔
3753
                       sizeof(encoder->last_loader_name),
3754
                       "%s",
3755
                       sixel_loader_get_last_success_name(loader));
3756
    } else {
132✔
3757
        encoder->last_loader_name[0] = '\0';
×
3758
    }
3759
    if (sixel_loader_get_last_source_path(loader) != NULL) {
396✔
3760
        (void)snprintf(encoder->last_source_path,
258✔
3761
                       sizeof(encoder->last_source_path),
3762
                       "%s",
3763
                       sixel_loader_get_last_source_path(loader));
3764
    } else {
86✔
3765
        encoder->last_source_path[0] = '\0';
138✔
3766
    }
3767
    if (encoder->assessment_observer != NULL) {
396!
3768
        sixel_assessment_record_loader(encoder->assessment_observer,
×
3769
                                       encoder->last_source_path,
×
3770
                                       encoder->last_loader_name,
×
3771
                                       encoder->last_input_bytes);
3772
    }
3773

3774
load_end:
264✔
3775
    sixel_loader_unref(loader);
417✔
3776
    loader = NULL;
417✔
3777

3778
    if (status != SIXEL_OK) {
417✔
3779
        goto end;
21✔
3780
    }
3781

3782
    if (encoder->pipe_mode) {
396!
3783
#if HAVE_CLEARERR
3784
        clearerr(stdin);
×
3785
#endif  /* HAVE_FSEEK */
3786
        while (encoder->cancel_flag && !*encoder->cancel_flag) {
×
3787
            status = sixel_tty_wait_stdin(1000000);
×
3788
            if (SIXEL_FAILED(status)) {
×
3789
                goto end;
×
3790
            }
3791
            if (status != SIXEL_OK) {
×
3792
                break;
×
3793
            }
3794
        }
3795
        if (!encoder->cancel_flag || !*encoder->cancel_flag) {
×
3796
            goto reload;
×
3797
        }
3798
    }
3799

3800
    /* the status may not be SIXEL_OK */
3801

3802
end:
264✔
3803
    sixel_encoder_unref(encoder);
417✔
3804

3805
    return status;
417✔
3806
}
3807

3808

3809
/* encode specified pixel data to SIXEL format
3810
 * output to encoder->outfd */
3811
SIXELAPI SIXELSTATUS
3812
sixel_encoder_encode_bytes(
×
3813
    sixel_encoder_t     /* in */    *encoder,
3814
    unsigned char       /* in */    *bytes,
3815
    int                 /* in */    width,
3816
    int                 /* in */    height,
3817
    int                 /* in */    pixelformat,
3818
    unsigned char       /* in */    *palette,
3819
    int                 /* in */    ncolors)
3820
{
3821
    SIXELSTATUS status = SIXEL_FALSE;
×
3822
    sixel_frame_t *frame = NULL;
×
3823

3824
    if (encoder == NULL || bytes == NULL) {
×
3825
        status = SIXEL_BAD_ARGUMENT;
×
3826
        goto end;
×
3827
    }
3828

3829
    status = sixel_frame_new(&frame, encoder->allocator);
×
3830
    if (SIXEL_FAILED(status)) {
×
3831
        goto end;
×
3832
    }
3833

3834
    status = sixel_frame_init(frame, bytes, width, height,
×
3835
                              pixelformat, palette, ncolors);
3836
    if (SIXEL_FAILED(status)) {
×
3837
        goto end;
×
3838
    }
3839

3840
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
×
3841
    if (SIXEL_FAILED(status)) {
×
3842
        goto end;
×
3843
    }
3844

3845
    status = SIXEL_OK;
×
3846

3847
end:
3848
    /* we need to free the frame before exiting, but we can't use the
3849
       sixel_frame_destroy function, because that will also attempt to
3850
       free the pixels and palette, which we don't own */
3851
    if (frame != NULL && encoder->allocator != NULL) {
×
3852
        sixel_allocator_free(encoder->allocator, frame);
×
3853
        sixel_allocator_unref(encoder->allocator);
×
3854
    }
3855
    return status;
×
3856
}
3857

3858

3859
/*
3860
 * Toggle source-frame capture for assessment consumers.
3861
 */
3862
SIXELAPI SIXELSTATUS
3863
sixel_encoder_enable_source_capture(
×
3864
    sixel_encoder_t *encoder,
3865
    int enable)
3866
{
3867
    if (encoder == NULL) {
×
3868
        sixel_helper_set_additional_message(
×
3869
            "sixel_encoder_enable_source_capture: encoder is null.");
3870
        return SIXEL_BAD_ARGUMENT;
×
3871
    }
3872

3873
    encoder->capture_source = enable ? 1 : 0;
×
3874
    if (!encoder->capture_source && encoder->capture_source_frame != NULL) {
×
3875
        sixel_frame_unref(encoder->capture_source_frame);
×
3876
        encoder->capture_source_frame = NULL;
×
3877
    }
3878

3879
    return SIXEL_OK;
×
3880
}
3881

3882

3883
/*
3884
 * Enable or disable the quantized-frame capture facility.
3885
 *
3886
 *     capture on --> encoder keeps the latest palette-quantized frame.
3887
 *     capture off --> encoder forgets previously stored frames.
3888
 */
3889
SIXELAPI SIXELSTATUS
3890
sixel_encoder_enable_quantized_capture(
×
3891
    sixel_encoder_t *encoder,
3892
    int enable)
3893
{
3894
    if (encoder == NULL) {
×
3895
        sixel_helper_set_additional_message(
×
3896
            "sixel_encoder_enable_quantized_capture: encoder is null.");
3897
        return SIXEL_BAD_ARGUMENT;
×
3898
    }
3899

3900
    encoder->capture_quantized = enable ? 1 : 0;
×
3901
    if (!encoder->capture_quantized) {
×
3902
        encoder->capture_valid = 0;
×
3903
    }
3904

3905
    return SIXEL_OK;
×
3906
}
3907

3908

3909
/*
3910
 * Materialize the captured quantized frame as a heap-allocated
3911
 * sixel_frame_t instance for assessment consumers.
3912
 */
3913
SIXELAPI SIXELSTATUS
3914
sixel_encoder_copy_quantized_frame(
×
3915
    sixel_encoder_t   *encoder,
3916
    sixel_allocator_t *allocator,
3917
    sixel_frame_t     **ppframe)
3918
{
3919
    SIXELSTATUS status = SIXEL_FALSE;
×
3920
    sixel_frame_t *frame;
3921
    unsigned char *pixels;
3922
    unsigned char *palette;
3923
    size_t palette_bytes;
3924

3925
    if (encoder == NULL || allocator == NULL || ppframe == NULL) {
×
3926
        sixel_helper_set_additional_message(
×
3927
            "sixel_encoder_copy_quantized_frame: invalid argument.");
3928
        return SIXEL_BAD_ARGUMENT;
×
3929
    }
3930

3931
    if (!encoder->capture_quantized || !encoder->capture_valid) {
×
3932
        sixel_helper_set_additional_message(
×
3933
            "sixel_encoder_copy_quantized_frame: no frame captured.");
3934
        return SIXEL_RUNTIME_ERROR;
×
3935
    }
3936

3937
    *ppframe = NULL;
×
3938
    frame = NULL;
×
3939
    pixels = NULL;
×
3940
    palette = NULL;
×
3941

3942
    status = sixel_frame_new(&frame, allocator);
×
3943
    if (SIXEL_FAILED(status)) {
×
3944
        return status;
×
3945
    }
3946

3947
    if (encoder->capture_pixel_bytes > 0) {
×
3948
        pixels = (unsigned char *)sixel_allocator_malloc(
×
3949
            allocator, encoder->capture_pixel_bytes);
3950
        if (pixels == NULL) {
×
3951
            sixel_helper_set_additional_message(
×
3952
                "sixel_encoder_copy_quantized_frame: "
3953
                "sixel_allocator_malloc() failed.");
3954
            status = SIXEL_BAD_ALLOCATION;
×
3955
            goto cleanup;
×
3956
        }
3957
        memcpy(pixels,
×
3958
               encoder->capture_pixels,
3959
               encoder->capture_pixel_bytes);
3960
    }
3961

3962
    palette_bytes = encoder->capture_palette_size;
×
3963
    if (palette_bytes > 0) {
×
3964
        palette = (unsigned char *)sixel_allocator_malloc(allocator,
×
3965
                                                          palette_bytes);
3966
        if (palette == NULL) {
×
3967
            sixel_helper_set_additional_message(
×
3968
                "sixel_encoder_copy_quantized_frame: "
3969
                "sixel_allocator_malloc() failed.");
3970
            status = SIXEL_BAD_ALLOCATION;
×
3971
            goto cleanup;
×
3972
        }
3973
        memcpy(palette,
×
3974
               encoder->capture_palette,
3975
               palette_bytes);
3976
    }
3977

3978
    status = sixel_frame_init(frame,
×
3979
                              pixels,
3980
                              encoder->capture_width,
3981
                              encoder->capture_height,
3982
                              encoder->capture_pixelformat,
3983
                              palette,
3984
                              encoder->capture_ncolors);
3985
    if (SIXEL_FAILED(status)) {
×
3986
        goto cleanup;
×
3987
    }
3988

3989
    pixels = NULL;
×
3990
    palette = NULL;
×
3991
    frame->colorspace = encoder->capture_colorspace;
×
3992
    *ppframe = frame;
×
3993
    return SIXEL_OK;
×
3994

3995
cleanup:
3996
    if (palette != NULL) {
×
3997
        sixel_allocator_free(allocator, palette);
×
3998
    }
3999
    if (pixels != NULL) {
×
4000
        sixel_allocator_free(allocator, pixels);
×
4001
    }
4002
    if (frame != NULL) {
×
4003
        sixel_frame_unref(frame);
×
4004
    }
4005
    return status;
×
4006
}
4007

4008

4009
/*
4010
 * Share the captured source frame with assessment consumers.
4011
 */
4012
SIXELAPI SIXELSTATUS
4013
sixel_encoder_copy_source_frame(
×
4014
    sixel_encoder_t *encoder,
4015
    sixel_frame_t  **ppframe)
4016
{
4017
    if (encoder == NULL || ppframe == NULL) {
×
4018
        sixel_helper_set_additional_message(
×
4019
            "sixel_encoder_copy_source_frame: invalid argument.");
4020
        return SIXEL_BAD_ARGUMENT;
×
4021
    }
4022

4023
    if (!encoder->capture_source || encoder->capture_source_frame == NULL) {
×
4024
        sixel_helper_set_additional_message(
×
4025
            "sixel_encoder_copy_source_frame: no frame captured.");
4026
        return SIXEL_RUNTIME_ERROR;
×
4027
    }
4028

4029
    sixel_frame_ref(encoder->capture_source_frame);
×
4030
    *ppframe = encoder->capture_source_frame;
×
4031

4032
    return SIXEL_OK;
×
4033
}
4034

4035

4036
#if HAVE_TESTS
4037
static int
4038
test1(void)
×
4039
{
4040
    int nret = EXIT_FAILURE;
×
4041
    sixel_encoder_t *encoder = NULL;
×
4042

4043
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
4044
#  pragma GCC diagnostic push
4045
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
4046
#endif
4047
    encoder = sixel_encoder_create();
×
4048
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
4049
#  pragma GCC diagnostic pop
4050
#endif
4051
    if (encoder == NULL) {
×
4052
        goto error;
×
4053
    }
4054
    sixel_encoder_ref(encoder);
×
4055
    sixel_encoder_unref(encoder);
×
4056
    nret = EXIT_SUCCESS;
×
4057

4058
error:
4059
    sixel_encoder_unref(encoder);
×
4060
    return nret;
×
4061
}
4062

4063

4064
static int
4065
test2(void)
×
4066
{
4067
    int nret = EXIT_FAILURE;
×
4068
    SIXELSTATUS status;
4069
    sixel_encoder_t *encoder = NULL;
×
4070
    sixel_frame_t *frame = NULL;
×
4071
    unsigned char *buffer;
4072
    int height = 0;
×
4073
    int is_animation = 0;
×
4074

4075
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
4076
#  pragma GCC diagnostic push
4077
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
4078
#endif
4079
    encoder = sixel_encoder_create();
×
4080
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
4081
#  pragma GCC diagnostic pop
4082
#endif
4083
    if (encoder == NULL) {
×
4084
        goto error;
×
4085
    }
4086

4087
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
4088
#  pragma GCC diagnostic push
4089
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
4090
#endif
4091
    frame = sixel_frame_create();
×
4092
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
4093
#  pragma GCC diagnostic pop
4094
#endif
4095
    if (encoder == NULL) {
×
4096
        goto error;
×
4097
    }
4098

4099
    buffer = (unsigned char *)sixel_allocator_malloc(encoder->allocator, 3);
×
4100
    if (buffer == NULL) {
×
4101
        goto error;
×
4102
    }
4103
    status = sixel_frame_init(frame, buffer, 1, 1,
×
4104
                              SIXEL_PIXELFORMAT_RGB888,
4105
                              NULL, 0);
4106
    if (SIXEL_FAILED(status)) {
×
4107
        goto error;
×
4108
    }
4109

4110
    if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
×
4111
        is_animation = 1;
×
4112
    }
4113

4114
    height = sixel_frame_get_height(frame);
×
4115

4116
    status = sixel_tty_scroll(sixel_write_callback,
×
4117
                              &encoder->outfd,
×
4118
                              encoder->outfd,
4119
                              height,
4120
                              is_animation);
4121
    if (SIXEL_FAILED(status)) {
×
4122
        goto error;
×
4123
    }
4124

4125
    nret = EXIT_SUCCESS;
×
4126

4127
error:
4128
    sixel_encoder_unref(encoder);
×
4129
    sixel_frame_unref(frame);
×
4130
    return nret;
×
4131
}
4132

4133

4134
static int
4135
test3(void)
×
4136
{
4137
    int nret = EXIT_FAILURE;
×
4138
    int result;
4139

4140
    result = sixel_tty_wait_stdin(1000);
×
4141
    if (result != 0) {
×
4142
        goto error;
×
4143
    }
4144

4145
    nret = EXIT_SUCCESS;
×
4146

4147
error:
4148
    return nret;
×
4149
}
4150

4151

4152
static int
4153
test4(void)
×
4154
{
4155
    int nret = EXIT_FAILURE;
×
4156
    sixel_encoder_t *encoder = NULL;
×
4157
    SIXELSTATUS status;
4158

4159
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
4160
# pragma GCC diagnostic push
4161
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
4162
#endif
4163
    encoder = sixel_encoder_create();
×
4164
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
4165
# pragma GCC diagnostic pop
4166
#endif
4167
    if (encoder == NULL) {
×
4168
        goto error;
×
4169
    }
4170

4171
    status = sixel_encoder_setopt(encoder,
×
4172
                                  SIXEL_OPTFLAG_LOOPMODE,
4173
                                  "force");
4174
    if (SIXEL_FAILED(status)) {
×
4175
        goto error;
×
4176
    }
4177

4178
    status = sixel_encoder_setopt(encoder,
×
4179
                                  SIXEL_OPTFLAG_PIPE_MODE,
4180
                                  "force");
4181
    if (SIXEL_FAILED(status)) {
×
4182
        goto error;
×
4183
    }
4184

4185
    nret = EXIT_SUCCESS;
×
4186

4187
error:
4188
    sixel_encoder_unref(encoder);
×
4189
    return nret;
×
4190
}
4191

4192

4193
static int
4194
test5(void)
×
4195
{
4196
    int nret = EXIT_FAILURE;
×
4197
    sixel_encoder_t *encoder = NULL;
×
4198
    sixel_allocator_t *allocator = NULL;
×
4199
    SIXELSTATUS status;
4200

4201
    status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
4202
    if (SIXEL_FAILED(status)) {
×
4203
        goto error;
×
4204
    }
4205

4206
    status = sixel_encoder_new(&encoder, allocator);
×
4207
    if (SIXEL_FAILED(status)) {
×
4208
        goto error;
×
4209
    }
4210

4211
    sixel_encoder_ref(encoder);
×
4212
    sixel_encoder_unref(encoder);
×
4213
    nret = EXIT_SUCCESS;
×
4214

4215
error:
4216
    sixel_encoder_unref(encoder);
×
4217
    return nret;
×
4218
}
4219

4220

4221
SIXELAPI int
4222
sixel_encoder_tests_main(void)
×
4223
{
4224
    int nret = EXIT_FAILURE;
×
4225
    size_t i;
4226
    typedef int (* testcase)(void);
4227

4228
    static testcase const testcases[] = {
4229
        test1,
4230
        test2,
4231
        test3,
4232
        test4,
4233
        test5
4234
    };
4235

4236
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
4237
        nret = testcases[i]();
×
4238
        if (nret != EXIT_SUCCESS) {
×
4239
            goto error;
×
4240
        }
4241
    }
4242

4243
    nret = EXIT_SUCCESS;
×
4244

4245
error:
4246
    return nret;
×
4247
}
4248
#endif  /* HAVE_TESTS */
4249

4250

4251
/* emacs Local Variables:      */
4252
/* emacs mode: c               */
4253
/* emacs tab-width: 4          */
4254
/* emacs indent-tabs-mode: nil */
4255
/* emacs c-basic-offset: 4     */
4256
/* emacs End:                  */
4257
/* vim: set expandtab ts=4 : */
4258
/* 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