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

saitoha / libsixel / 18800271060

25 Oct 2025 07:46AM UTC coverage: 52.018% (-0.02%) from 52.042%
18800271060

push

github

saitoha
quant: make img2sixel -p COLORS! generate an exact COLORS-color palette

5675 of 15880 branches covered (35.74%)

20 of 106 new or added lines in 3 files covered. (18.87%)

2 existing lines in 1 file now uncovered.

8261 of 15881 relevant lines covered (52.02%)

1208418.91 hits per line

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

63.04
/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 "tty.h"
108
#include "encoder.h"
109
#include "output.h"
110
#include "dither.h"
111
#include "frame.h"
112
#include "rgblookup.h"
113

114
#if defined(_WIN32)
115

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

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

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

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

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

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

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

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

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

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

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

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

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

210
#endif /* _WIN32 */
211

212

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

222
    len = strlen(s);
60✔
223

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

235

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

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

384
    status = SIXEL_OK;
24✔
385

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

389
    return status;
45✔
390
}
391

392

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

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

407
    return result;
4,100✔
408
}
409

410

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

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

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

438
    return result;
73✔
439
}
440

441
static SIXELSTATUS
442
sixel_encoder_ensure_cell_size(sixel_encoder_t *encoder)
×
443
{
444
#if defined(TIOCGWINSZ)
445
    struct winsize ws;
446
    int result;
447
    int fd = 0;
×
448

449
    if (encoder->cell_width > 0 && encoder->cell_height > 0) {
×
450
        return SIXEL_OK;
×
451
    }
452

453
#if HAVE__OPEN
454
    fd = _open("/dev/tty", O_RDONLY);
455
#else
456
    fd = open("/dev/tty", O_RDONLY);
×
457
#endif  /* #if HAVE__OPEN */
458
    if (fd >= 0) {
×
459
        result = ioctl(fd, TIOCGWINSZ, &ws);
×
460
        close(fd);
×
461
    } else {
462
        sixel_helper_set_additional_message(
×
463
            "failed to open /dev/tty");
464
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
465
    }
466
    if (result != 0) {
×
467
        sixel_helper_set_additional_message(
×
468
            "failed to query terminal geometry with ioctl().");
469
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
470
    }
471

472
    if (ws.ws_col <= 0 || ws.ws_row <= 0 ||
×
473
        ws.ws_xpixel <= ws.ws_col || ws.ws_ypixel <= ws.ws_row) {
×
474
        sixel_helper_set_additional_message(
×
475
            "terminal does not report pixel cell size for drcs option.");
476
        return SIXEL_BAD_ARGUMENT;
×
477
    }
478

479
    encoder->cell_width = ws.ws_xpixel / ws.ws_col;
×
480
    encoder->cell_height = ws.ws_ypixel / ws.ws_row;
×
481
    if (encoder->cell_width <= 0 || encoder->cell_height <= 0) {
×
482
        sixel_helper_set_additional_message(
×
483
            "terminal cell size reported zero via ioctl().");
484
        return SIXEL_BAD_ARGUMENT;
×
485
    }
486

487
    return SIXEL_OK;
×
488
#else
489
    (void) encoder;
490
    sixel_helper_set_additional_message(
491
        "drcs option is not supported on this platform.");
492
    return SIXEL_NOT_IMPLEMENTED;
493
#endif
494
}
495

496

497
/* returns monochrome dithering context object */
498
static SIXELSTATUS
499
sixel_prepare_monochrome_palette(
12✔
500
    sixel_dither_t  /* out */ **dither,
501
     int            /* in */  finvert)
502
{
503
    SIXELSTATUS status = SIXEL_FALSE;
12✔
504

505
    if (finvert) {
12✔
506
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_LIGHT);
3✔
507
    } else {
1✔
508
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_DARK);
9✔
509
    }
510
    if (*dither == NULL) {
12!
511
        sixel_helper_set_additional_message(
×
512
            "sixel_prepare_monochrome_palette: sixel_dither_get() failed.");
513
        status = SIXEL_RUNTIME_ERROR;
×
514
        goto end;
×
515
    }
516

517
    status = SIXEL_OK;
12✔
518

519
end:
8✔
520
    return status;
12✔
521
}
522

523

524
/* returns dithering context object with specified builtin palette */
525
typedef struct palette_conversion {
526
    unsigned char *original;
527
    unsigned char *copy;
528
    size_t size;
529
    int convert_inplace;
530
    int converted;
531
    int frame_colorspace;
532
} palette_conversion_t;
533

534
static SIXELSTATUS
535
sixel_encoder_convert_palette(sixel_encoder_t *encoder,
504✔
536
                              sixel_output_t *output,
537
                              sixel_dither_t *dither,
538
                              int frame_colorspace,
539
                              int pixelformat,
540
                              palette_conversion_t *ctx)
541
{
542
    SIXELSTATUS status = SIXEL_OK;
504✔
543
    unsigned char *palette;
544
    int palette_colors;
545

546
    ctx->original = NULL;
504✔
547
    ctx->copy = NULL;
504✔
548
    ctx->size = 0;
504✔
549
    ctx->convert_inplace = 0;
504✔
550
    ctx->converted = 0;
504✔
551
    ctx->frame_colorspace = frame_colorspace;
504✔
552

553
    palette = sixel_dither_get_palette(dither);
504✔
554
    palette_colors = sixel_dither_get_num_of_palette_colors(dither);
504✔
555
    ctx->original = palette;
504✔
556

557
    if (palette == NULL || palette_colors <= 0 ||
504!
558
            frame_colorspace == output->colorspace) {
504!
559
        return SIXEL_OK;
504✔
560
    }
561

562
    ctx->size = (size_t)palette_colors * 3;
×
563

564
    output->pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
565
    output->source_colorspace = frame_colorspace;
×
566

567
    if (palette != (unsigned char *)(dither + 1)) {
×
568
        ctx->copy = (unsigned char *)sixel_allocator_malloc(
×
569
            encoder->allocator, ctx->size);
570
        if (ctx->copy == NULL) {
×
571
            sixel_helper_set_additional_message(
×
572
                "sixel_encoder_convert_palette: "
573
                "sixel_allocator_malloc() failed.");
574
            status = SIXEL_BAD_ALLOCATION;
×
575
            goto end;
×
576
        }
577
        memcpy(ctx->copy, palette, ctx->size);
×
578
        dither->palette = ctx->copy;
×
579
    } else {
580
        ctx->convert_inplace = 1;
×
581
    }
582

583
    status = sixel_output_convert_colorspace(output,
×
584
                                             dither->palette,
585
                                             ctx->size);
586
    if (SIXEL_FAILED(status)) {
×
587
        goto end;
×
588
    }
589
    ctx->converted = 1;
×
590

591
end:
592
    output->pixelformat = pixelformat;
×
593
    output->source_colorspace = frame_colorspace;
×
594

595
    return status;
×
596
}
174✔
597

598
static void
599
sixel_encoder_restore_palette(sixel_encoder_t *encoder,
504✔
600
                              sixel_dither_t *dither,
601
                              palette_conversion_t *ctx)
602
{
603
    if (ctx->copy) {
504!
604
        dither->palette = ctx->original;
×
605
        sixel_allocator_free(encoder->allocator, ctx->copy);
×
606
        ctx->copy = NULL;
×
607
    } else if (ctx->convert_inplace && ctx->converted &&
504!
608
               ctx->original && ctx->size > 0) {
×
609
        (void)sixel_helper_convert_colorspace(ctx->original,
×
610
                                              ctx->size,
611
                                              SIXEL_PIXELFORMAT_RGB888,
612
                                              SIXEL_COLORSPACE_GAMMA,
613
                                              ctx->frame_colorspace);
614
    }
615
}
504✔
616

617
static SIXELSTATUS
618
sixel_prepare_builtin_palette(
27✔
619
    sixel_dither_t /* out */ **dither,
620
    int            /* in */  builtin_palette)
621
{
622
    SIXELSTATUS status = SIXEL_FALSE;
27✔
623

624
    *dither = sixel_dither_get(builtin_palette);
27✔
625
    if (*dither == NULL) {
27!
626
        sixel_helper_set_additional_message(
×
627
            "sixel_prepare_builtin_palette: sixel_dither_get() failed.");
628
        status = SIXEL_RUNTIME_ERROR;
×
629
        goto end;
×
630
    }
631

632
    status = SIXEL_OK;
27✔
633

634
end:
18✔
635
    return status;
27✔
636
}
637

638
static int
639
sixel_encoder_thumbnail_hint(sixel_encoder_t *encoder)
443✔
640
{
641
    int width_hint;
642
    int height_hint;
643
    long base;
644
    long size;
645

646
    width_hint = 0;
443✔
647
    height_hint = 0;
443✔
648
    base = 0;
443✔
649
    size = 0;
443✔
650

651
    if (encoder == NULL) {
443!
652
        return 0;
×
653
    }
654

655
    width_hint = encoder->pixelwidth;
443✔
656
    height_hint = encoder->pixelheight;
443✔
657

658
    /* Request extra resolution for downscaling to preserve detail. */
659
    if (width_hint > 0 && height_hint > 0) {
443✔
660
        /* Follow the CLI rule: double the larger axis before doubling
661
         * again for the final request size. */
662
        if (width_hint >= height_hint) {
9!
663
            base = (long)width_hint;
9✔
664
        } else {
3✔
665
            base = (long)height_hint;
×
666
        }
667
        base *= 2L;
9✔
668
    } else if (width_hint > 0) {
437✔
669
        base = (long)width_hint;
48✔
670
    } else if (height_hint > 0) {
402✔
671
        base = (long)height_hint;
36✔
672
    } else {
12✔
673
        return 0;
350✔
674
    }
675

676
    size = base * 2L;
93✔
677
    if (size > (long)INT_MAX) {
93!
678
        size = (long)INT_MAX;
×
679
    }
680
    if (size < 1L) {
93!
681
        size = 1L;
×
682
    }
683

684
    return (int)size;
93✔
685
}
149✔
686

687

688
typedef struct sixel_callback_context_for_mapfile {
689
    int reqcolors;
690
    sixel_dither_t *dither;
691
    sixel_allocator_t *allocator;
692
    int working_colorspace;
693
    int lut_policy;
694
} sixel_callback_context_for_mapfile_t;
695

696

697
/* callback function for sixel_helper_load_image_file() */
698
static SIXELSTATUS
699
load_image_callback_for_palette(
21✔
700
    sixel_frame_t   /* in */    *frame, /* frame object from image loader */
701
    void            /* in */    *data)  /* private data */
702
{
703
    SIXELSTATUS status = SIXEL_FALSE;
21✔
704
    sixel_callback_context_for_mapfile_t *callback_context;
705

706
    /* get callback context object from the private data */
707
    callback_context = (sixel_callback_context_for_mapfile_t *)data;
21✔
708

709
    status = sixel_frame_ensure_colorspace(frame,
28✔
710
                                           callback_context->working_colorspace);
7✔
711
    if (SIXEL_FAILED(status)) {
21!
712
        goto end;
×
713
    }
714

715
    switch (sixel_frame_get_pixelformat(frame)) {
21!
716
    case SIXEL_PIXELFORMAT_PAL1:
2✔
717
    case SIXEL_PIXELFORMAT_PAL2:
718
    case SIXEL_PIXELFORMAT_PAL4:
719
    case SIXEL_PIXELFORMAT_PAL8:
720
        if (sixel_frame_get_palette(frame) == NULL) {
3!
721
            status = SIXEL_LOGIC_ERROR;
×
722
            goto end;
×
723
        }
724
        /* create new dither object */
725
        status = sixel_dither_new(
3✔
726
            &callback_context->dither,
1✔
727
            sixel_frame_get_ncolors(frame),
1✔
728
            callback_context->allocator);
1✔
729
        if (SIXEL_FAILED(status)) {
3!
730
            goto end;
×
731
        }
732

733
        sixel_dither_set_lut_policy(callback_context->dither,
4✔
734
                                    callback_context->lut_policy);
1✔
735

736
        /* use palette which is extracted from the image */
737
        sixel_dither_set_palette(callback_context->dither,
4✔
738
                                 sixel_frame_get_palette(frame));
1✔
739
        /* success */
740
        status = SIXEL_OK;
3✔
741
        break;
3✔
742
    case SIXEL_PIXELFORMAT_G1:
743
        /* use 1bpp grayscale builtin palette */
744
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
745
        /* success */
746
        status = SIXEL_OK;
×
747
        break;
×
748
    case SIXEL_PIXELFORMAT_G2:
749
        /* use 2bpp grayscale builtin palette */
750
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
751
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
752
        /* success */
753
        status = SIXEL_OK;
×
754
        break;
×
755
    case SIXEL_PIXELFORMAT_G4:
756
        /* use 4bpp grayscale builtin palette */
757
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
758
        /* success */
759
        status = SIXEL_OK;
×
760
        break;
×
761
    case SIXEL_PIXELFORMAT_G8:
762
        /* use 8bpp grayscale builtin palette */
763
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
764
        /* success */
765
        status = SIXEL_OK;
×
766
        break;
×
767
    default:
12✔
768
        /* create new dither object */
769
        status = sixel_dither_new(
18✔
770
            &callback_context->dither,
6✔
771
            callback_context->reqcolors,
6✔
772
            callback_context->allocator);
6✔
773
        if (SIXEL_FAILED(status)) {
18!
774
            goto end;
×
775
        }
776

777
        sixel_dither_set_lut_policy(callback_context->dither,
24✔
778
                                    callback_context->lut_policy);
6✔
779

780
        /* create adaptive palette from given frame object */
781
        status = sixel_dither_initialize(callback_context->dither,
24✔
782
                                         sixel_frame_get_pixels(frame),
6✔
783
                                         sixel_frame_get_width(frame),
6✔
784
                                         sixel_frame_get_height(frame),
6✔
785
                                         sixel_frame_get_pixelformat(frame),
6✔
786
                                         SIXEL_LARGE_NORM,
787
                                         SIXEL_REP_CENTER_BOX,
788
                                         SIXEL_QUALITY_HIGH);
789
        if (SIXEL_FAILED(status)) {
18!
790
            sixel_dither_unref(callback_context->dither);
×
791
            goto end;
×
792
        }
793

794
        /* success */
795
        status = SIXEL_OK;
18✔
796

797
        break;
18✔
798
    }
7✔
799

800
end:
14✔
801
    return status;
21✔
802
}
803

804

805
/* create palette from specified map file */
806
static SIXELSTATUS
807
sixel_prepare_specified_palette(
26✔
808
    sixel_dither_t  /* out */   **dither,
809
    sixel_encoder_t /* in */    *encoder)
810
{
811
    SIXELSTATUS status = SIXEL_FALSE;
26✔
812
    sixel_callback_context_for_mapfile_t callback_context;
813

814
    callback_context.reqcolors = encoder->reqcolors;
26✔
815
    callback_context.dither = NULL;
26✔
816
    callback_context.allocator = encoder->allocator;
26✔
817
    callback_context.working_colorspace = encoder->working_colorspace;
26✔
818
    callback_context.lut_policy = encoder->lut_policy;
26✔
819

820
    sixel_helper_set_loader_trace(encoder->verbose);
26✔
821
    sixel_helper_set_thumbnail_size_hint(
26✔
822
        sixel_encoder_thumbnail_hint(encoder));
10✔
823
    status = sixel_helper_load_image_file(encoder->mapfile,
36✔
824
                                          1,   /* fstatic */
825
                                          1,   /* fuse_palette */
826
                                          SIXEL_PALETTE_MAX, /* reqcolors */
827
                                          encoder->bgcolor,
10✔
828
                                          SIXEL_LOOP_DISABLE,
829
                                          load_image_callback_for_palette,
830
                                          encoder->finsecure,
10✔
831
                                          encoder->cancel_flag,
26✔
832
                                          &callback_context,
833
                                          encoder->allocator);
10✔
834
    if (status != SIXEL_OK) {
26✔
835
        return status;
5✔
836
    }
837

838
    if (! callback_context.dither) {
21!
839
        sixel_helper_set_additional_message(
×
840
            "sixel_prepare_specified_palette() failed.\n"
841
            "reason: mapfile is empty.");
842
        return SIXEL_BAD_INPUT;
×
843
    }
844

845
    *dither = callback_context.dither;
21✔
846

847
    return status;
21✔
848
}
10✔
849

850

851
/* create dither object from a frame */
852
static SIXELSTATUS
853
sixel_encoder_prepare_palette(
509✔
854
    sixel_encoder_t *encoder,  /* encoder object */
855
    sixel_frame_t   *frame,    /* input frame object */
856
    sixel_dither_t  **dither)  /* dither object to be created from the frame */
857
{
858
    SIXELSTATUS status = SIXEL_FALSE;
509✔
859
    int histogram_colors;
860

861
    switch (encoder->color_option) {
509!
862
    case SIXEL_COLOR_OPTION_HIGHCOLOR:
24✔
863
        if (encoder->dither_cache) {
36!
864
            *dither = encoder->dither_cache;
×
865
            status = SIXEL_OK;
×
866
        } else {
867
            status = sixel_dither_new(dither, (-1), encoder->allocator);
36✔
868
            sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
36✔
869
        }
870
        goto end;
36✔
871
    case SIXEL_COLOR_OPTION_MONOCHROME:
8✔
872
        if (encoder->dither_cache) {
12!
873
            *dither = encoder->dither_cache;
×
874
            status = SIXEL_OK;
×
875
        } else {
876
            status = sixel_prepare_monochrome_palette(dither, encoder->finvert);
12✔
877
        }
878
        goto end;
12✔
879
    case SIXEL_COLOR_OPTION_MAPFILE:
16✔
880
        if (encoder->dither_cache) {
26!
881
            *dither = encoder->dither_cache;
×
882
            status = SIXEL_OK;
×
883
        } else {
884
            status = sixel_prepare_specified_palette(dither, encoder);
26✔
885
        }
886
        goto end;
26✔
887
    case SIXEL_COLOR_OPTION_BUILTIN:
18✔
888
        if (encoder->dither_cache) {
27!
889
            *dither = encoder->dither_cache;
×
890
            status = SIXEL_OK;
×
891
        } else {
892
            status = sixel_prepare_builtin_palette(dither, encoder->builtin_palette);
27✔
893
        }
894
        goto end;
27✔
895
    case SIXEL_COLOR_OPTION_DEFAULT:
408✔
896
    default:
897
        break;
408✔
898
    }
899

900
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE) {
408✔
901
        if (!sixel_frame_get_palette(frame)) {
191!
902
            status = SIXEL_LOGIC_ERROR;
×
903
            goto end;
×
904
        }
905
        status = sixel_dither_new(dither, sixel_frame_get_ncolors(frame),
230✔
906
                                  encoder->allocator);
39✔
907
        if (SIXEL_FAILED(status)) {
191!
908
            goto end;
×
909
        }
910
        sixel_dither_set_palette(*dither, sixel_frame_get_palette(frame));
191✔
911
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
191✔
912
        if (sixel_frame_get_transparent(frame) != (-1)) {
191!
913
            sixel_dither_set_transparent(*dither, sixel_frame_get_transparent(frame));
×
914
        }
915
        if (*dither && encoder->dither_cache) {
191!
916
            sixel_dither_unref(encoder->dither_cache);
×
917
        }
918
        goto end;
191✔
919
    }
920

921
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_GRAYSCALE) {
217!
922
        switch (sixel_frame_get_pixelformat(frame)) {
×
923
        case SIXEL_PIXELFORMAT_G1:
924
            *dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
925
            break;
×
926
        case SIXEL_PIXELFORMAT_G2:
927
            *dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
928
            break;
×
929
        case SIXEL_PIXELFORMAT_G4:
930
            *dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
931
            break;
×
932
        case SIXEL_PIXELFORMAT_G8:
933
            *dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
934
            break;
×
935
        default:
936
            *dither = NULL;
×
937
            status = SIXEL_LOGIC_ERROR;
×
938
            goto end;
×
939
        }
940
        if (*dither && encoder->dither_cache) {
×
941
            sixel_dither_unref(encoder->dither_cache);
×
942
        }
943
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
×
944
        status = SIXEL_OK;
×
945
        goto end;
×
946
    }
947

948
    if (encoder->dither_cache) {
217!
949
        sixel_dither_unref(encoder->dither_cache);
×
950
    }
951
    status = sixel_dither_new(dither, encoder->reqcolors, encoder->allocator);
217✔
952
    if (SIXEL_FAILED(status)) {
217!
953
        goto end;
×
954
    }
955

956
    sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
217✔
957

958
    status = sixel_dither_initialize(*dither,
320✔
959
                                     sixel_frame_get_pixels(frame),
103✔
960
                                     sixel_frame_get_width(frame),
103✔
961
                                     sixel_frame_get_height(frame),
103✔
962
                                     sixel_frame_get_pixelformat(frame),
103✔
963
                                     encoder->method_for_largest,
103✔
964
                                     encoder->method_for_rep,
103✔
965
                                     encoder->quality_mode);
103✔
966
    if (SIXEL_FAILED(status)) {
217!
967
        sixel_dither_unref(*dither);
×
968
        goto end;
×
969
    }
970

971
    histogram_colors = sixel_dither_get_num_of_histogram_colors(*dither);
217✔
972
    if (histogram_colors <= encoder->reqcolors) {
217✔
973
        encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
172✔
974
    }
88✔
975
    sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
217✔
976

977
    status = SIXEL_OK;
217✔
978

979
end:
332✔
980
    if (SIXEL_SUCCEEDED(status) && dither != NULL && *dither != NULL) {
509!
981
        sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
504✔
982
        /* pass down the user's demand for an exact palette size */
983
        (*dither)->force_palette = encoder->force_palette;
504✔
984
    }
174✔
985
    return status;
509✔
986
}
987

988

989
/* resize a frame with settings of specified encoder object */
990
static SIXELSTATUS
991
sixel_encoder_do_resize(
516✔
992
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
993
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
994
{
995
    SIXELSTATUS status = SIXEL_FALSE;
516✔
996
    int src_width;
997
    int src_height;
998
    int dst_width;
999
    int dst_height;
1000

1001
    /* get frame width and height */
1002
    src_width = sixel_frame_get_width(frame);
516✔
1003
    src_height = sixel_frame_get_height(frame);
516✔
1004

1005
    if (src_width < 1) {
516✔
1006
         sixel_helper_set_additional_message(
6✔
1007
             "sixel_encoder_do_resize: "
1008
             "detected a frame with a non-positive width.");
1009
        return SIXEL_BAD_ARGUMENT;
6✔
1010
    }
1011

1012
    if (src_height < 1) {
510!
1013
         sixel_helper_set_additional_message(
×
1014
             "sixel_encoder_do_resize: "
1015
             "detected a frame with a non-positive height.");
1016
        return SIXEL_BAD_ARGUMENT;
×
1017
    }
1018

1019
    /* settings around scaling */
1020
    dst_width = encoder->pixelwidth;    /* may be -1 (default) */
510✔
1021
    dst_height = encoder->pixelheight;  /* may be -1 (default) */
510✔
1022

1023
    /* if the encoder has percentwidth or percentheight property,
1024
       convert them to pixelwidth / pixelheight */
1025
    if (encoder->percentwidth > 0) {
510✔
1026
        dst_width = src_width * encoder->percentwidth / 100;
12✔
1027
    }
4✔
1028
    if (encoder->percentheight > 0) {
510✔
1029
        dst_height = src_height * encoder->percentheight / 100;
9✔
1030
    }
3✔
1031

1032
    /* if only either width or height is set, set also the other
1033
       to retain frame aspect ratio */
1034
    if (dst_width > 0 && dst_height <= 0) {
510✔
1035
        dst_height = src_height * dst_width / src_width;
42✔
1036
    }
14✔
1037
    if (dst_height > 0 && dst_width <= 0) {
510✔
1038
        dst_width = src_width * dst_height / src_height;
33✔
1039
    }
11✔
1040

1041
    /* do resize */
1042
    if (dst_width > 0 && dst_height > 0) {
510!
1043
        status = sixel_frame_resize(frame, dst_width, dst_height,
124✔
1044
                                    encoder->method_for_resampling);
31✔
1045
        if (SIXEL_FAILED(status)) {
93!
1046
            goto end;
×
1047
        }
1048
    }
31✔
1049

1050
    /* success */
1051
    status = SIXEL_OK;
510✔
1052

1053
end:
332✔
1054
    return status;
510✔
1055
}
180✔
1056

1057

1058
/* clip a frame with settings of specified encoder object */
1059
static SIXELSTATUS
1060
sixel_encoder_do_clip(
512✔
1061
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
1062
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
1063
{
1064
    SIXELSTATUS status = SIXEL_FALSE;
512✔
1065
    int src_width;
1066
    int src_height;
1067
    int clip_x;
1068
    int clip_y;
1069
    int clip_w;
1070
    int clip_h;
1071

1072
    /* get frame width and height */
1073
    src_width = sixel_frame_get_width(frame);
512✔
1074
    src_height = sixel_frame_get_height(frame);
512✔
1075

1076
    /* settings around clipping */
1077
    clip_x = encoder->clipx;
512✔
1078
    clip_y = encoder->clipy;
512✔
1079
    clip_w = encoder->clipwidth;
512✔
1080
    clip_h = encoder->clipheight;
512✔
1081

1082
    /* adjust clipping width with comparing it to frame width */
1083
    if (clip_w + clip_x > src_width) {
512✔
1084
        if (clip_x > src_width) {
7✔
1085
            clip_w = 0;
3✔
1086
        } else {
1✔
1087
            clip_w = src_width - clip_x;
4✔
1088
        }
1089
    }
3✔
1090

1091
    /* adjust clipping height with comparing it to frame height */
1092
    if (clip_h + clip_y > src_height) {
512✔
1093
        if (clip_y > src_height) {
6✔
1094
            clip_h = 0;
3✔
1095
        } else {
1✔
1096
            clip_h = src_height - clip_y;
3✔
1097
        }
1098
    }
2✔
1099

1100
    /* do clipping */
1101
    if (clip_w > 0 && clip_h > 0) {
512!
1102
        status = sixel_frame_clip(frame, clip_x, clip_y, clip_w, clip_h);
15✔
1103
        if (SIXEL_FAILED(status)) {
15!
1104
            goto end;
3✔
1105
        }
1106
    }
4✔
1107

1108
    /* success */
1109
    status = SIXEL_OK;
509✔
1110

1111
end:
332✔
1112
    return status;
512✔
1113
}
1114

1115

1116
static void
1117
sixel_debug_print_palette(
3✔
1118
    sixel_dither_t /* in */ *dither /* dithering object */
1119
)
1120
{
1121
    unsigned char *palette;
1122
    int i;
1123

1124
    palette = sixel_dither_get_palette(dither);
3✔
1125
    fprintf(stderr, "palette:\n");
3✔
1126
    for (i = 0; i < sixel_dither_get_num_of_palette_colors(dither); ++i) {
51✔
1127
        fprintf(stderr, "%d: #%02x%02x%02x\n", i,
64✔
1128
                palette[i * 3 + 0],
48✔
1129
                palette[i * 3 + 1],
48✔
1130
                palette[i * 3 + 2]);
48✔
1131
    }
16✔
1132
}
3✔
1133

1134

1135
static SIXELSTATUS
1136
sixel_encoder_output_without_macro(
411✔
1137
    sixel_frame_t       /* in */ *frame,
1138
    sixel_dither_t      /* in */ *dither,
1139
    sixel_output_t      /* in */ *output,
1140
    sixel_encoder_t     /* in */ *encoder)
1141
{
1142
    SIXELSTATUS status = SIXEL_OK;
411✔
1143
    static unsigned char *p;
1144
    int depth;
1145
    enum { message_buffer_size = 256 };
1146
    char message[message_buffer_size];
1147
    int nwrite;
1148
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
1149
    int dulation;
1150
    int delay;
1151
    struct timespec tv;
1152
#endif
1153
    unsigned char *pixbuf;
1154
    int width;
1155
    int height;
1156
    int pixelformat = 0;
411✔
1157
    size_t size;
1158
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
411✔
1159
    palette_conversion_t palette_ctx;
1160

1161
    memset(&palette_ctx, 0, sizeof(palette_ctx));
411✔
1162
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
1163
    sixel_clock_t last_clock;
1164
#endif
1165

1166
    if (encoder == NULL) {
411!
1167
        sixel_helper_set_additional_message(
×
1168
            "sixel_encoder_output_without_macro: encoder object is null.");
1169
        status = SIXEL_BAD_ARGUMENT;
×
1170
        goto end;
×
1171
    }
1172

1173
    if (encoder->color_option == SIXEL_COLOR_OPTION_DEFAULT) {
411✔
1174
        if (encoder->force_palette) {
315!
1175
            /* keep every slot when the user forced the palette size */
NEW
1176
            sixel_dither_set_optimize_palette(dither, 0);
×
1177
        } else {
1178
            sixel_dither_set_optimize_palette(dither, 1);
315✔
1179
        }
1180
    }
105✔
1181

1182
    pixelformat = sixel_frame_get_pixelformat(frame);
411✔
1183
    frame_colorspace = sixel_frame_get_colorspace(frame);
411✔
1184
    output->pixelformat = pixelformat;
411✔
1185
    output->source_colorspace = frame_colorspace;
411✔
1186
    output->colorspace = encoder->output_colorspace;
411✔
1187
    sixel_dither_set_pixelformat(dither, pixelformat);
411✔
1188
    depth = sixel_helper_compute_depth(pixelformat);
411✔
1189
    if (depth < 0) {
411!
1190
        status = SIXEL_LOGIC_ERROR;
×
1191
        nwrite = sprintf(message,
×
1192
                         "sixel_encoder_output_without_macro: "
1193
                         "sixel_helper_compute_depth(%08x) failed.",
1194
                         pixelformat);
1195
        if (nwrite > 0) {
×
1196
            sixel_helper_set_additional_message(message);
×
1197
        }
1198
        goto end;
×
1199
    }
1200

1201
    width = sixel_frame_get_width(frame);
411✔
1202
    height = sixel_frame_get_height(frame);
411✔
1203
    size = (size_t)(width * height * depth);
411✔
1204
    p = (unsigned char *)sixel_allocator_malloc(encoder->allocator, size);
411✔
1205
    if (p == NULL) {
411!
1206
        sixel_helper_set_additional_message(
×
1207
            "sixel_encoder_output_without_macro: sixel_allocator_malloc() failed.");
1208
        status = SIXEL_BAD_ALLOCATION;
×
1209
        goto end;
×
1210
    }
1211
#if defined(HAVE_CLOCK)
1212
    if (output->last_clock == 0) {
411!
1213
        output->last_clock = clock();
411✔
1214
    }
137✔
1215
#elif defined(HAVE_CLOCK_WIN)
1216
    if (output->last_clock == 0) {
1217
        output->last_clock = clock_win();
1218
    }
1219
#endif
1220
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
1221
    delay = sixel_frame_get_delay(frame);
411✔
1222
    if (delay > 0 && !encoder->fignore_delay) {
411✔
1223
# if defined(HAVE_CLOCK)
1224
        last_clock = clock();
76✔
1225
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
1226
#  if defined(__APPLE__)
1227
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
148✔
1228
                          / 100000);
74✔
1229
#  else
1230
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
2✔
1231
                          / CLOCKS_PER_SEC);
1232
#  endif
1233
        output->last_clock = last_clock;
76✔
1234
# elif defined(HAVE_CLOCK_WIN)
1235
        last_clock = clock_win();
1236
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
1237
                          / CLOCKS_PER_SEC);
1238
        output->last_clock = last_clock;
1239
# else
1240
        dulation = 0;
1241
# endif
1242
        if (dulation < 1000 * 10 * delay) {
76!
1243
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
1244
            tv.tv_sec = 0;
76✔
1245
            tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
76✔
1246
#  if defined(HAVE_NANOSLEEP)
1247
            nanosleep(&tv, NULL);
76✔
1248
#  else
1249
            nanosleep_win(&tv, NULL);
1250
#  endif
1251
# endif
1252
        }
74✔
1253
    }
74✔
1254
#endif
1255

1256
    pixbuf = sixel_frame_get_pixels(frame);
411✔
1257
    memcpy(p, pixbuf, (size_t)(width * height * depth));
411✔
1258

1259
    status = sixel_output_convert_colorspace(output, p, size);
411✔
1260
    if (SIXEL_FAILED(status)) {
411!
1261
        goto end;
×
1262
    }
1263

1264
    if (encoder->cancel_flag && *encoder->cancel_flag) {
411!
1265
        goto end;
×
1266
    }
1267

1268
    status = sixel_encoder_convert_palette(encoder,
548✔
1269
                                           output,
137✔
1270
                                           dither,
137✔
1271
                                           frame_colorspace,
137✔
1272
                                           pixelformat,
137✔
1273
                                           &palette_ctx);
1274
    if (SIXEL_FAILED(status)) {
411!
1275
        goto end;
×
1276
    }
1277

1278
    status = sixel_encode(p, width, height, depth, dither, output);
411✔
1279
    if (status != SIXEL_OK) {
411!
1280
        goto end;
×
1281
    }
1282

1283
end:
274✔
1284
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
411✔
1285
    output->pixelformat = pixelformat;
411✔
1286
    output->source_colorspace = frame_colorspace;
411✔
1287
    sixel_allocator_free(encoder->allocator, p);
411✔
1288

1289
    return status;
411✔
1290
}
1291

1292

1293
static SIXELSTATUS
1294
sixel_encoder_output_with_macro(
93✔
1295
    sixel_frame_t   /* in */ *frame,
1296
    sixel_dither_t  /* in */ *dither,
1297
    sixel_output_t  /* in */ *output,
1298
    sixel_encoder_t /* in */ *encoder)
1299
{
1300
    SIXELSTATUS status = SIXEL_OK;
93✔
1301
    enum { message_buffer_size = 256 };
1302
    char buffer[message_buffer_size];
1303
    int nwrite;
1304
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
1305
    int dulation;
1306
    struct timespec tv;
1307
#endif
1308
    int width;
1309
    int height;
1310
    int pixelformat;
1311
    int depth;
1312
    size_t size = 0;
93✔
1313
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
93✔
1314
    unsigned char *converted = NULL;
93✔
1315
    palette_conversion_t palette_ctx;
1316
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
1317
    int delay;
1318
#endif
1319
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
1320
    sixel_clock_t last_clock;
1321
#endif
1322

1323
    memset(&palette_ctx, 0, sizeof(palette_ctx));
93✔
1324

1325
#if defined(HAVE_CLOCK)
1326
    if (output->last_clock == 0) {
93!
1327
        output->last_clock = clock();
93✔
1328
    }
37✔
1329
#elif defined(HAVE_CLOCK_WIN)
1330
    if (output->last_clock == 0) {
1331
        output->last_clock = clock_win();
1332
    }
1333
#endif
1334

1335
    width = sixel_frame_get_width(frame);
93✔
1336
    height = sixel_frame_get_height(frame);
93✔
1337
    pixelformat = sixel_frame_get_pixelformat(frame);
93✔
1338
    depth = sixel_helper_compute_depth(pixelformat);
93✔
1339
    if (depth < 0) {
93!
1340
        status = SIXEL_LOGIC_ERROR;
×
1341
        sixel_helper_set_additional_message(
×
1342
            "sixel_encoder_output_with_macro: "
1343
            "sixel_helper_compute_depth() failed.");
1344
        goto end;
×
1345
    }
1346

1347
    frame_colorspace = sixel_frame_get_colorspace(frame);
93✔
1348
    size = (size_t)width * (size_t)height * (size_t)depth;
93✔
1349
    converted = (unsigned char *)sixel_allocator_malloc(
93✔
1350
        encoder->allocator, size);
37✔
1351
    if (converted == NULL) {
93!
1352
        sixel_helper_set_additional_message(
×
1353
            "sixel_encoder_output_with_macro: "
1354
            "sixel_allocator_malloc() failed.");
1355
        status = SIXEL_BAD_ALLOCATION;
×
1356
        goto end;
×
1357
    }
1358

1359
    memcpy(converted, sixel_frame_get_pixels(frame), size);
93✔
1360
    output->pixelformat = pixelformat;
93✔
1361
    output->source_colorspace = frame_colorspace;
93✔
1362
    output->colorspace = encoder->output_colorspace;
93✔
1363
    status = sixel_output_convert_colorspace(output, converted, size);
93✔
1364
    if (SIXEL_FAILED(status)) {
93!
1365
        goto end;
×
1366
    }
1367

1368
    status = sixel_encoder_convert_palette(encoder,
130✔
1369
                                           output,
37✔
1370
                                           dither,
37✔
1371
                                           frame_colorspace,
37✔
1372
                                           pixelformat,
37✔
1373
                                           &palette_ctx);
1374
    if (SIXEL_FAILED(status)) {
93!
1375
        goto end;
×
1376
    }
1377

1378
    if (sixel_frame_get_loop_no(frame) == 0) {
93✔
1379
        if (encoder->macro_number >= 0) {
55!
1380
            nwrite = sprintf(buffer, "\033P%d;0;1!z",
1✔
1381
                             encoder->macro_number);
1382
        } else {
1✔
1383
            nwrite = sprintf(buffer, "\033P%d;0;1!z",
54✔
1384
                             sixel_frame_get_frame_no(frame));
1385
        }
1386
        if (nwrite < 0) {
55!
1387
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1388
            sixel_helper_set_additional_message(
×
1389
                "sixel_encoder_output_with_macro: sprintf() failed.");
1390
            goto end;
×
1391
        }
1392
        nwrite = sixel_write_callback(buffer,
74✔
1393
                                      (int)strlen(buffer),
55✔
1394
                                      &encoder->outfd);
55✔
1395
        if (nwrite < 0) {
55!
1396
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1397
            sixel_helper_set_additional_message(
×
1398
                "sixel_encoder_output_with_macro: "
1399
                "sixel_write_callback() failed.");
1400
            goto end;
×
1401
        }
1402

1403
        status = sixel_encode(converted,
74✔
1404
                              width,
19✔
1405
                              height,
19✔
1406
                              depth,
19✔
1407
                              dither,
19✔
1408
                              output);
19✔
1409
        if (SIXEL_FAILED(status)) {
55!
1410
            goto end;
×
1411
        }
1412

1413
        nwrite = sixel_write_callback("\033\\", 2, &encoder->outfd);
55✔
1414
        if (nwrite < 0) {
55!
1415
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1416
            sixel_helper_set_additional_message(
×
1417
                "sixel_encoder_output_with_macro: "
1418
                "sixel_write_callback() failed.");
1419
            goto end;
×
1420
        }
1421
    }
19✔
1422
    if (encoder->macro_number < 0) {
129✔
1423
        nwrite = sprintf(buffer, "\033[%d*z",
90✔
1424
                         sixel_frame_get_frame_no(frame));
1425
        if (nwrite < 0) {
90!
1426
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1427
            sixel_helper_set_additional_message(
×
1428
                "sixel_encoder_output_with_macro: sprintf() failed.");
1429
        }
1430
        nwrite = sixel_write_callback(buffer,
126✔
1431
                                      (int)strlen(buffer),
90✔
1432
                                      &encoder->outfd);
90✔
1433
        if (nwrite < 0) {
90!
1434
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1435
            sixel_helper_set_additional_message(
×
1436
                "sixel_encoder_output_with_macro: "
1437
                "sixel_write_callback() failed.");
1438
            goto end;
×
1439
        }
1440
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
1441
        delay = sixel_frame_get_delay(frame);
90✔
1442
        if (delay > 0 && !encoder->fignore_delay) {
90!
1443
# if defined(HAVE_CLOCK)
1444
            last_clock = clock();
63✔
1445
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
1446
#  if defined(__APPLE__)
1447
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
54✔
1448
                             / 100000);
27✔
1449
#  else
1450
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
36✔
1451
                             / CLOCKS_PER_SEC);
1452
#  endif
1453
            output->last_clock = last_clock;
63✔
1454
# elif defined(HAVE_CLOCK_WIN)
1455
            last_clock = clock_win();
1456
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
1457
                             / CLOCKS_PER_SEC);
1458
            output->last_clock = last_clock;
1459
# else
1460
            dulation = 0;
1461
# endif
1462
            if (dulation < 1000 * 10 * delay) {
63!
1463
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
1464
                tv.tv_sec = 0;
59✔
1465
                tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
59✔
1466
#  if defined(HAVE_NANOSLEEP)
1467
                nanosleep(&tv, NULL);
59✔
1468
#  else
1469
                nanosleep_win(&tv, NULL);
1470
#  endif
1471
# endif
1472
            }
23✔
1473
        }
27✔
1474
#endif
1475
    }
36✔
1476

1477
end:
20✔
1478
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
93✔
1479
    output->pixelformat = pixelformat;
93✔
1480
    output->source_colorspace = frame_colorspace;
93✔
1481
    sixel_allocator_free(encoder->allocator, converted);
93✔
1482

1483
    return status;
93✔
1484
}
1485

1486

1487
static SIXELSTATUS
1488
sixel_encoder_emit_iso2022_chars(
×
1489
    sixel_encoder_t *encoder,
1490
    sixel_frame_t *frame
1491
)
1492
{
1493
    char *buf_p, *buf;
1494
    int col, row;
1495
    int charset = encoder->start_dscs;
×
1496
    int is_96cs = 0;
×
1497
    unsigned int code;
1498
    int num_cols, num_rows;
1499
    SIXELSTATUS status;
1500
    size_t alloc_size;
1501
    int nwrite;
1502
    int target_fd;
1503

1504
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
1505
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
1506
             / encoder->cell_width;
×
1507
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
1508
             / encoder->cell_height;
×
1509

1510
    /* cols x rows + designation(4 chars) + SI + SO + LFs */
1511
    alloc_size = num_cols * num_rows + (num_cols * num_rows / 96 + 1) * 4 + 2 + num_rows;
×
1512
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
1513
    if (buf == NULL) {
×
1514
        sixel_helper_set_additional_message(
×
1515
            "sixel_encoder_emit_iso2022_chars: sixel_allocator_malloc() failed.");
1516
        status = SIXEL_BAD_ALLOCATION;
×
1517
        goto end;
×
1518
    }
1519

1520
    code = 0x20;
×
1521
    *(buf_p++) = '\016';  /* SI */
×
1522
    *(buf_p++) = '\033';
×
1523
    *(buf_p++) = ')';
×
1524
    *(buf_p++) = ' ';
×
1525
    *(buf_p++) = charset;
×
1526
    for(row = 0; row < num_rows; row++) {
×
1527
        for(col = 0; col < num_cols; col++) {
×
1528
            if ((code & 0x7f) == 0x0) {
×
1529
                if (charset == 0x7e) {
×
1530
                    is_96cs = 1 - is_96cs;
×
1531
                    charset = '0';
×
1532
                } else {
1533
                    charset++;
×
1534
                }
1535
                code = 0x20;
×
1536
                *(buf_p++) = '\033';
×
1537
                *(buf_p++) = is_96cs ? '-': ')';
×
1538
                *(buf_p++) = ' ';
×
1539
                *(buf_p++) = charset;
×
1540
            }
1541
            *(buf_p++) = code++;
×
1542
        }
1543
        *(buf_p++) = '\n';
×
1544
    }
1545
    *(buf_p++) = '\017';  /* SO */
×
1546

1547
    if (encoder->tile_outfd >= 0) {
×
1548
        target_fd = encoder->tile_outfd;
×
1549
    } else {
1550
        target_fd = encoder->outfd;
×
1551
    }
1552

1553
    nwrite = write(target_fd, buf, buf_p - buf);
×
1554
    if (nwrite != buf_p - buf) {
×
1555
        sixel_helper_set_additional_message(
×
1556
            "sixel_encoder_emit_iso2022_chars: write() failed.");
1557
        status = SIXEL_RUNTIME_ERROR;
×
1558
        goto end;
×
1559
    }
1560

1561
    sixel_allocator_free(encoder->allocator, buf);
×
1562

1563
    status = SIXEL_OK;
×
1564

1565
end:
1566
    return status;
×
1567
}
1568

1569

1570
/*
1571
 * This routine is derived from mlterm's drcssixel.c
1572
 * (https://raw.githubusercontent.com/arakiken/mlterm/master/drcssixel/drcssixel.c).
1573
 * The original implementation is credited to Araki Ken.
1574
 * Adjusted here to integrate with libsixel's encoder pipeline.
1575
 */
1576
static SIXELSTATUS
1577
sixel_encoder_emit_drcsmmv2_chars(
×
1578
    sixel_encoder_t *encoder,
1579
    sixel_frame_t *frame
1580
)
1581
{
1582
    char *buf_p, *buf;
1583
    int col, row;
1584
    int charset = encoder->start_dscs;
×
1585
    int is_96cs = 0;
×
1586
    unsigned int code;
1587
    int num_cols, num_rows;
1588
    SIXELSTATUS status;
1589
    size_t alloc_size;
1590
    int nwrite;
1591
    int target_fd;
1592

1593
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
1594
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
1595
             / encoder->cell_width;
×
1596
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
1597
             / encoder->cell_height;
×
1598

1599
    /* cols x rows x 4(out of BMP) + rows(LFs) */
1600
    alloc_size = num_cols * num_rows * 4 + num_rows;
×
1601
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
1602
    if (buf == NULL) {
×
1603
        sixel_helper_set_additional_message(
×
1604
            "sixel_encoder_emit_drcsmmv2_chars: sixel_allocator_malloc() failed.");
1605
        status = SIXEL_BAD_ALLOCATION;
×
1606
        goto end;
×
1607
    }
1608

1609
    for(row = 0; row < num_rows; row++) {
×
1610
        for(col = 0; col < num_cols; col++) {
×
1611
            *(buf_p++) = ((code >> 18) & 0x07) | 0xf0;
×
1612
            *(buf_p++) = ((code >> 12) & 0x3f) | 0x80;
×
1613
            *(buf_p++) = ((code >> 6) & 0x3f) | 0x80;
×
1614
            *(buf_p++) = (code & 0x3f) | 0x80;
×
1615
            code++;
×
1616
            if ((code & 0x7f) == 0x0) {
×
1617
                if (charset == 0x7e) {
×
1618
                    is_96cs = 1 - is_96cs;
×
1619
                    charset = '0';
×
1620
                } else {
1621
                    charset++;
×
1622
                }
1623
                code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
1624
            }
1625
        }
1626
        *(buf_p++) = '\n';
×
1627
    }
1628

1629
    if (charset == 0x7e) {
×
1630
        is_96cs = 1 - is_96cs;
×
1631
    } else {
1632
        charset = '0';
×
1633
        charset++;
×
1634
    }
1635
    if (encoder->tile_outfd >= 0) {
×
1636
        target_fd = encoder->tile_outfd;
×
1637
    } else {
1638
        target_fd = encoder->outfd;
×
1639
    }
1640

1641
    nwrite = write(target_fd, buf, buf_p - buf);
×
1642
    if (nwrite != buf_p - buf) {
×
1643
        sixel_helper_set_additional_message(
×
1644
            "sixel_encoder_emit_drcsmmv2_chars: write() failed.");
1645
        status = SIXEL_RUNTIME_ERROR;
×
1646
        goto end;
×
1647
    }
1648

1649
    sixel_allocator_free(encoder->allocator, buf);
×
1650

1651
    status = SIXEL_OK;
×
1652

1653
end:
1654
    return status;
×
1655
}
1656

1657
static SIXELSTATUS
1658
sixel_encoder_encode_frame(
518✔
1659
    sixel_encoder_t *encoder,
1660
    sixel_frame_t   *frame,
1661
    sixel_output_t  *output)
1662
{
1663
    SIXELSTATUS status = SIXEL_FALSE;
518✔
1664
    sixel_dither_t *dither = NULL;
518✔
1665
    int height;
1666
    int is_animation = 0;
518✔
1667
    int nwrite;
1668
    char buf[256];
1669
    sixel_write_function fn_write = NULL;
518✔
1670

1671
    /* evaluate -w, -h, and -c option: crop/scale input source */
1672
    if (encoder->clipfirst) {
518✔
1673
        /* clipping */
1674
        status = sixel_encoder_do_clip(encoder, frame);
8✔
1675
        if (SIXEL_FAILED(status)) {
8!
1676
            goto end;
2✔
1677
        }
1678

1679
        /* scaling */
1680
        status = sixel_encoder_do_resize(encoder, frame);
6✔
1681
        if (SIXEL_FAILED(status)) {
6!
1682
            goto end;
×
1683
        }
1684
    } else {
2✔
1685
        /* scaling */
1686
        status = sixel_encoder_do_resize(encoder, frame);
510✔
1687
        if (SIXEL_FAILED(status)) {
510✔
1688
            goto end;
6✔
1689
        }
1690

1691
        /* clipping */
1692
        status = sixel_encoder_do_clip(encoder, frame);
504✔
1693
        if (SIXEL_FAILED(status)) {
504!
1694
            goto end;
1✔
1695
        }
1696
    }
1697

1698
    status = sixel_frame_ensure_colorspace(frame,
686✔
1699
                                           encoder->working_colorspace);
177✔
1700
    if (SIXEL_FAILED(status)) {
509!
1701
        goto end;
×
1702
    }
1703

1704
    /* prepare dither context */
1705
    status = sixel_encoder_prepare_palette(encoder, frame, &dither);
509✔
1706
    if (status != SIXEL_OK) {
509✔
1707
        dither = NULL;
5✔
1708
        goto end;
5✔
1709
    }
1710

1711
    if (encoder->dither_cache != NULL) {
504!
1712
        encoder->dither_cache = dither;
×
1713
        sixel_dither_ref(dither);
×
1714
    }
1715

1716
    if (encoder->fdrcs) {
504!
1717
        status = sixel_encoder_ensure_cell_size(encoder);
×
1718
        if (SIXEL_FAILED(status)) {
×
1719
            goto end;
×
1720
        }
1721
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
×
1722
            sixel_helper_set_additional_message(
×
1723
                "drcs option cannot be used together with macro output.");
1724
            status = SIXEL_BAD_ARGUMENT;
×
1725
            goto end;
×
1726
        }
1727
    }
1728

1729
    /* evaluate -v option: print palette */
1730
    if (encoder->verbose) {
504✔
1731
        if ((sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE)) {
9✔
1732
            sixel_debug_print_palette(dither);
3✔
1733
        }
1✔
1734
    }
3✔
1735

1736
    /* evaluate -d option: set method for diffusion */
1737
    sixel_dither_set_diffusion_type(dither, encoder->method_for_diffuse);
504✔
1738
    sixel_dither_set_diffusion_scan(dither, encoder->method_for_scan);
504✔
1739
    sixel_dither_set_diffusion_carry(dither, encoder->method_for_carry);
504✔
1740

1741
    /* evaluate -C option: set complexion score */
1742
    if (encoder->complexion > 1) {
504✔
1743
        sixel_dither_set_complexion_score(dither, encoder->complexion);
6✔
1744
    }
2✔
1745

1746
    if (output) {
504!
1747
        sixel_output_ref(output);
×
1748
    } else {
1749
        /* create output context */
1750
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
504✔
1751
            /* -u or -n option */
1752
            fn_write = sixel_hex_write_callback;
93✔
1753
        } else {
37✔
1754
            fn_write = sixel_write_callback;
411✔
1755
        }
1756
        status = sixel_output_new(&output,
504✔
1757
                                  fn_write,
174✔
1758
                                  &encoder->outfd,
504✔
1759
                                  encoder->allocator);
174✔
1760
        if (SIXEL_FAILED(status)) {
504!
1761
            goto end;
×
1762
        }
1763
    }
1764

1765
    if (encoder->fdrcs) {
504!
1766
        sixel_output_set_skip_dcs_envelope(output, 1);
×
1767
        sixel_output_set_skip_header(output, 1);
×
1768
    }
1769

1770
    sixel_output_set_8bit_availability(output, encoder->f8bit);
504✔
1771
    sixel_output_set_gri_arg_limit(output, encoder->has_gri_arg_limit);
504✔
1772
    sixel_output_set_palette_type(output, encoder->palette_type);
504✔
1773
    sixel_output_set_penetrate_multiplexer(
504✔
1774
        output, encoder->penetrate_multiplexer);
174✔
1775
    sixel_output_set_encode_policy(output, encoder->encode_policy);
504✔
1776
    sixel_output_set_ormode(output, encoder->ormode);
504✔
1777

1778
    if (sixel_frame_get_multiframe(frame) && !encoder->fstatic) {
504!
1779
        if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
117✔
1780
            is_animation = 1;
108✔
1781
        }
42✔
1782
        height = sixel_frame_get_height(frame);
117✔
1783
        (void) sixel_tty_scroll(sixel_write_callback, encoder->outfd, height, is_animation);
117✔
1784
    }
45✔
1785

1786
    if (encoder->cancel_flag && *encoder->cancel_flag) {
504!
1787
        status = SIXEL_INTERRUPTED;
×
1788
        goto end;
×
1789
    }
1790

1791
    if (encoder->fdrcs) {  /* -@ option */
504!
1792
        nwrite = sprintf(buf,
×
1793
                         "%s%s1;0;0;%d;1;3;%d;0{ %c",
1794
                         (encoder->drcs_mmv > 0) ? (
×
1795
                             encoder->f8bit ? "\233?8800h": "\033[?8800h"
×
1796
                         ): "",
1797
                         encoder->f8bit ? "\220": "\033P",
1798
                         encoder->cell_width,
1799
                         encoder->cell_height,
1800
                         encoder->start_dscs);
×
1801
        if (nwrite < 0) {
×
1802
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1803
            sixel_helper_set_additional_message(
×
1804
                "sixel_encoder_encode_frame: sprintf() failed.");
1805
            goto end;
×
1806
        }
1807
        nwrite = fn_write(buf, nwrite, &encoder->outfd);
×
1808
        if (nwrite < 0) {
×
1809
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1810
            sixel_helper_set_additional_message(
×
1811
                "sixel_encoder_encode_frame: write() failed.");
1812
            goto end;
×
1813
        }
1814
    }
1815

1816
    /* output sixel: junction of multi-frame processing strategy */
1817
    if (encoder->fuse_macro) {  /* -u option */
504✔
1818
        /* use macro */
1819
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
90✔
1820
    } else if (encoder->macro_number >= 0) { /* -n option */
450✔
1821
        /* use macro */
1822
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
3✔
1823
    } else {
1✔
1824
        /* do not use macro */
1825
        status = sixel_encoder_output_without_macro(frame, dither, output, encoder);
411✔
1826
    }
1827
    if (SIXEL_FAILED(status)) {
504!
1828
        goto end;
×
1829
    }
1830

1831
    if (encoder->cancel_flag && *encoder->cancel_flag) {
504!
1832
        nwrite = sixel_write_callback("\x18\033\\", 3, &encoder->outfd);
×
1833
        if (nwrite < 0) {
×
1834
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1835
            sixel_helper_set_additional_message(
×
1836
                "sixel_encoder_encode_frame: sixel_write_callback() failed.");
1837
            goto end;
×
1838
        }
1839
        status = SIXEL_INTERRUPTED;
×
1840
    }
1841
    if (SIXEL_FAILED(status)) {
504!
1842
        goto end;
×
1843
    }
1844

1845
    if (encoder->fdrcs) {  /* -@ option */
504!
1846
        if (encoder->f8bit) {
×
1847
            nwrite = fn_write("\234", 1, &encoder->outfd);
×
1848
        } else {
1849
            nwrite = fn_write("\033\\", 2, &encoder->outfd);
×
1850
        }
1851
        if (nwrite < 0) {
×
1852
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
1853
            sixel_helper_set_additional_message(
×
1854
                "sixel_encoder_encode_frame: fn_write() failed.");
1855
            goto end;
×
1856
        }
1857

1858
        if (encoder->tile_outfd >= 0) {
×
1859
            if (encoder->drcs_mmv == 0) {
×
1860
                status = sixel_encoder_emit_iso2022_chars(encoder, frame);
×
1861
                if (SIXEL_FAILED(status)) {
×
1862
                    goto end;
×
1863
                }
1864
            } else {
1865
                status = sixel_encoder_emit_drcsmmv2_chars(encoder, frame);
×
1866
                if (SIXEL_FAILED(status)) {
×
1867
                    goto end;
×
1868
                }
1869
            }
1870
        }
1871
    }
1872

1873

1874
end:
330✔
1875
    if (output) {
518✔
1876
        sixel_output_unref(output);
504✔
1877
    }
174✔
1878
    if (dither) {
518✔
1879
        sixel_dither_unref(dither);
504✔
1880
    }
174✔
1881

1882
    return status;
518✔
1883
}
1884

1885

1886
/* create encoder object */
1887
SIXELAPI SIXELSTATUS
1888
sixel_encoder_new(
498✔
1889
    sixel_encoder_t     /* out */ **ppencoder, /* encoder object to be created */
1890
    sixel_allocator_t   /* in */  *allocator)  /* allocator, null if you use
1891
                                                  default allocator */
1892
{
1893
    SIXELSTATUS status = SIXEL_FALSE;
498✔
1894
    char const *env_default_bgcolor = NULL;
498✔
1895
    char const *env_default_ncolors = NULL;
498✔
1896
    int ncolors;
1897
#if HAVE__DUPENV_S
1898
    errno_t e;
1899
    size_t len;
1900
#endif  /* HAVE__DUPENV_S */
1901

1902
    if (allocator == NULL) {
498!
1903
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
498✔
1904
        if (SIXEL_FAILED(status)) {
498!
1905
            goto end;
×
1906
        }
1907
    } else {
166✔
1908
        sixel_allocator_ref(allocator);
×
1909
    }
1910

1911
    *ppencoder
166✔
1912
        = (sixel_encoder_t *)sixel_allocator_malloc(allocator,
664✔
1913
                                                    sizeof(sixel_encoder_t));
1914
    if (*ppencoder == NULL) {
498!
1915
        sixel_helper_set_additional_message(
×
1916
            "sixel_encoder_new: sixel_allocator_malloc() failed.");
1917
        status = SIXEL_BAD_ALLOCATION;
×
1918
        sixel_allocator_unref(allocator);
×
1919
        goto end;
×
1920
    }
1921

1922
    (*ppencoder)->ref                   = 1;
498✔
1923
    (*ppencoder)->reqcolors             = (-1);
498✔
1924
    (*ppencoder)->force_palette         = 0;
498✔
1925
    (*ppencoder)->mapfile               = NULL;
498✔
1926
    (*ppencoder)->color_option          = SIXEL_COLOR_OPTION_DEFAULT;
498✔
1927
    (*ppencoder)->builtin_palette       = 0;
498✔
1928
    (*ppencoder)->method_for_diffuse    = SIXEL_DIFFUSE_AUTO;
498✔
1929
    (*ppencoder)->method_for_scan       = SIXEL_SCAN_AUTO;
498✔
1930
    (*ppencoder)->method_for_carry      = SIXEL_CARRY_AUTO;
498✔
1931
    (*ppencoder)->method_for_largest    = SIXEL_LARGE_AUTO;
498✔
1932
    (*ppencoder)->method_for_rep        = SIXEL_REP_AUTO;
498✔
1933
    (*ppencoder)->quality_mode          = SIXEL_QUALITY_AUTO;
498✔
1934
    (*ppencoder)->lut_policy            = SIXEL_LUT_POLICY_AUTO;
498✔
1935
    (*ppencoder)->method_for_resampling = SIXEL_RES_BILINEAR;
498✔
1936
    (*ppencoder)->loop_mode             = SIXEL_LOOP_AUTO;
498✔
1937
    (*ppencoder)->palette_type          = SIXEL_PALETTETYPE_AUTO;
498✔
1938
    (*ppencoder)->f8bit                 = 0;
498✔
1939
    (*ppencoder)->has_gri_arg_limit     = 0;
498✔
1940
    (*ppencoder)->finvert               = 0;
498✔
1941
    (*ppencoder)->fuse_macro            = 0;
498✔
1942
    (*ppencoder)->fdrcs                 = 0;
498✔
1943
    (*ppencoder)->fignore_delay         = 0;
498✔
1944
    (*ppencoder)->complexion            = 1;
498✔
1945
    (*ppencoder)->fstatic               = 0;
498✔
1946
    (*ppencoder)->cell_width            = 0;
498✔
1947
    (*ppencoder)->cell_height           = 0;
498✔
1948
    (*ppencoder)->pixelwidth            = (-1);
498✔
1949
    (*ppencoder)->pixelheight           = (-1);
498✔
1950
    (*ppencoder)->percentwidth          = (-1);
498✔
1951
    (*ppencoder)->percentheight         = (-1);
498✔
1952
    (*ppencoder)->clipx                 = 0;
498✔
1953
    (*ppencoder)->clipy                 = 0;
498✔
1954
    (*ppencoder)->clipwidth             = 0;
498✔
1955
    (*ppencoder)->clipheight            = 0;
498✔
1956
    (*ppencoder)->clipfirst             = 0;
498✔
1957
    (*ppencoder)->macro_number          = (-1);
498✔
1958
    (*ppencoder)->verbose               = 0;
498✔
1959
    (*ppencoder)->penetrate_multiplexer = 0;
498✔
1960
    (*ppencoder)->encode_policy         = SIXEL_ENCODEPOLICY_AUTO;
498✔
1961
    (*ppencoder)->working_colorspace    = SIXEL_COLORSPACE_GAMMA;
498✔
1962
    (*ppencoder)->output_colorspace     = SIXEL_COLORSPACE_GAMMA;
498✔
1963
    (*ppencoder)->ormode                = 0;
498✔
1964
    (*ppencoder)->pipe_mode             = 0;
498✔
1965
    (*ppencoder)->bgcolor               = NULL;
498✔
1966
    (*ppencoder)->outfd                 = STDOUT_FILENO;
498✔
1967
    (*ppencoder)->tile_outfd            = (-1);
498✔
1968
    (*ppencoder)->finsecure             = 0;
498✔
1969
    (*ppencoder)->cancel_flag           = NULL;
498✔
1970
    (*ppencoder)->dither_cache          = NULL;
498✔
1971
    (*ppencoder)->start_dscs            = '0';
498✔
1972
    (*ppencoder)->drcs_mmv              = 2;
498✔
1973
    (*ppencoder)->allocator             = allocator;
498✔
1974

1975
    /* evaluate environment variable ${SIXEL_BGCOLOR} */
1976
#if HAVE__DUPENV_S
1977
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_BGCOLOR");
1978
    if (e != (0)) {
1979
        sixel_helper_set_additional_message(
1980
            "failed to get environment variable $SIXEL_BGCOLOR.");
1981
        return (SIXEL_LIBC_ERROR | (e & 0xff));
1982
    }
1983
#else
1984
    env_default_bgcolor = getenv("SIXEL_BGCOLOR");
498✔
1985
#endif  /* HAVE__DUPENV_S */
1986
    if (env_default_bgcolor != NULL) {
498!
1987
        status = sixel_parse_x_colorspec(&(*ppencoder)->bgcolor,
×
1988
                                         env_default_bgcolor,
1989
                                         allocator);
1990
        if (SIXEL_FAILED(status)) {
×
1991
            goto error;
×
1992
        }
1993
    }
1994

1995
    /* evaluate environment variable ${SIXEL_COLORS} */
1996
#if HAVE__DUPENV_S
1997
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_COLORS");
1998
    if (e != (0)) {
1999
        sixel_helper_set_additional_message(
2000
            "failed to get environment variable $SIXEL_COLORS.");
2001
        return (SIXEL_LIBC_ERROR | (e & 0xff));
2002
    }
2003
#else
2004
    env_default_ncolors = getenv("SIXEL_COLORS");
498✔
2005
#endif  /* HAVE__DUPENV_S */
2006
    if (env_default_ncolors) {
498!
2007
        ncolors = atoi(env_default_ncolors); /* may overflow */
×
2008
        if (ncolors > 1 && ncolors <= SIXEL_PALETTE_MAX) {
×
2009
            (*ppencoder)->reqcolors = ncolors;
×
2010
        }
2011
    }
2012

2013
    /* success */
2014
    status = SIXEL_OK;
498✔
2015

2016
    goto end;
498✔
2017

2018
error:
2019
    sixel_allocator_free(allocator, *ppencoder);
×
2020
    sixel_allocator_unref(allocator);
×
2021
    *ppencoder = NULL;
×
2022

2023
end:
332✔
2024
#if HAVE__DUPENV_S
2025
    free(env_default_bgcolor);
2026
    free(env_default_ncolors);
2027
#endif  /* HAVE__DUPENV_S */
2028
    return status;
498✔
2029
}
2030

2031

2032
/* create encoder object (deprecated version) */
2033
SIXELAPI /* deprecated */ sixel_encoder_t *
2034
sixel_encoder_create(void)
×
2035
{
2036
    SIXELSTATUS status = SIXEL_FALSE;
×
2037
    sixel_encoder_t *encoder = NULL;
×
2038

2039
    status = sixel_encoder_new(&encoder, NULL);
×
2040
    if (SIXEL_FAILED(status)) {
×
2041
        return NULL;
×
2042
    }
2043

2044
    return encoder;
×
2045
}
2046

2047

2048
/* destroy encoder object */
2049
static void
2050
sixel_encoder_destroy(sixel_encoder_t *encoder)
498✔
2051
{
2052
    sixel_allocator_t *allocator;
2053

2054
    if (encoder) {
498!
2055
        allocator = encoder->allocator;
498✔
2056
        sixel_allocator_free(allocator, encoder->mapfile);
498✔
2057
        sixel_allocator_free(allocator, encoder->bgcolor);
498✔
2058
        sixel_dither_unref(encoder->dither_cache);
498✔
2059
        if (encoder->outfd
504!
2060
            && encoder->outfd != STDOUT_FILENO
498!
2061
            && encoder->outfd != STDERR_FILENO) {
178!
2062
#if HAVE__CLOSE
2063
            (void) _close(encoder->outfd);
2064
#else
2065
            (void) close(encoder->outfd);
18✔
2066
#endif  /* HAVE__CLOSE */
2067
        }
6✔
2068
        if (encoder->tile_outfd >= 0
498!
2069
            && encoder->tile_outfd != encoder->outfd
166!
2070
            && encoder->tile_outfd != STDOUT_FILENO
×
2071
            && encoder->tile_outfd != STDERR_FILENO) {
×
2072
#if HAVE__CLOSE
2073
            (void) _close(encoder->tile_outfd);
2074
#else
2075
            (void) close(encoder->tile_outfd);
×
2076
#endif  /* HAVE__CLOSE */
2077
        }
2078
        sixel_allocator_free(allocator, encoder);
498✔
2079
        sixel_allocator_unref(allocator);
498✔
2080
    }
166✔
2081
}
498✔
2082

2083

2084
/* increase reference count of encoder object (thread-unsafe) */
2085
SIXELAPI void
2086
sixel_encoder_ref(sixel_encoder_t *encoder)
1,089✔
2087
{
2088
    /* TODO: be thread safe */
2089
    ++encoder->ref;
1,089✔
2090
}
1,089✔
2091

2092

2093
/* decrease reference count of encoder object (thread-unsafe) */
2094
SIXELAPI void
2095
sixel_encoder_unref(sixel_encoder_t *encoder)
1,587✔
2096
{
2097
    /* TODO: be thread safe */
2098
    if (encoder != NULL && --encoder->ref == 0) {
1,587!
2099
        sixel_encoder_destroy(encoder);
498✔
2100
    }
166✔
2101
}
1,587✔
2102

2103

2104
/* set cancel state flag to encoder object */
2105
SIXELAPI SIXELSTATUS
2106
sixel_encoder_set_cancel_flag(
417✔
2107
    sixel_encoder_t /* in */ *encoder,
2108
    int             /* in */ *cancel_flag
2109
)
2110
{
2111
    SIXELSTATUS status = SIXEL_OK;
417✔
2112

2113
    encoder->cancel_flag = cancel_flag;
417✔
2114

2115
    return status;
417✔
2116
}
2117

2118

2119
/* set an option flag to encoder object */
2120
SIXELAPI SIXELSTATUS
2121
sixel_encoder_setopt(
672✔
2122
    sixel_encoder_t /* in */ *encoder,
2123
    int             /* in */ arg,
2124
    char const      /* in */ *value)
2125
{
2126
    SIXELSTATUS status = SIXEL_FALSE;
672✔
2127
    int number;
2128
    int parsed;
2129
    char unit[32];
2130
    char lowered[16];
2131
    size_t len;
2132
    size_t i;
2133
    long parsed_reqcolors;
2134
    char *endptr;
2135
    int forced_palette;
2136

2137
    sixel_encoder_ref(encoder);
672✔
2138

2139
    switch(arg) {
672!
2140
    case SIXEL_OPTFLAG_OUTFILE:  /* o */
12✔
2141
        if (*value == '\0') {
18!
2142
            sixel_helper_set_additional_message(
×
2143
                "no file name specified.");
2144
            status = SIXEL_BAD_ARGUMENT;
×
2145
            goto end;
×
2146
        }
2147
        if (strcmp(value, "-") != 0) {
18!
2148
            if (encoder->outfd && encoder->outfd != STDOUT_FILENO) {
18!
2149
#if HAVE__CLOSE
2150
                (void) _close(encoder->outfd);
2151
#else
2152
                (void) close(encoder->outfd);
×
2153
#endif  /* HAVE__CLOSE */
2154
            }
2155
#if HAVE__OPEN
2156
            encoder->outfd = _open(value,
2157
                                   O_RDWR|O_CREAT|O_TRUNC,
2158
                                   S_IRUSR|S_IWUSR);
2159
#else
2160
            encoder->outfd = open(value,
18✔
2161
                                  O_RDWR|O_CREAT|O_TRUNC,
2162
                                  S_IRUSR|S_IWUSR);
2163
#endif  /* HAVE__OPEN */
2164
        }
6✔
2165
        break;
18✔
2166
    case SIXEL_OPTFLAG_ALT_CHARSET_PATH:  /* T */
2167
        if (*value == '\0') {
×
2168
            sixel_helper_set_additional_message(
×
2169
                "no file name specified.");
2170
            status = SIXEL_BAD_ARGUMENT;
×
2171
            goto end;
×
2172
        }
2173
        if (encoder->tile_outfd >= 0
×
2174
            && encoder->tile_outfd != encoder->outfd
×
2175
            && encoder->tile_outfd != STDOUT_FILENO
×
2176
            && encoder->tile_outfd != STDERR_FILENO) {
×
2177
#if HAVE__CLOSE
2178
            (void) _close(encoder->tile_outfd);
2179
#else
2180
            (void) close(encoder->tile_outfd);
×
2181
#endif  /* HAVE__CLOSE */
2182
        }
2183
        encoder->tile_outfd = (-1);
×
2184
        if (strcmp(value, "-") == 0) {
×
2185
            encoder->tile_outfd = STDOUT_FILENO;
×
2186
        } else {
2187
#if HAVE__OPEN
2188
            encoder->tile_outfd = _open(value,
2189
                                           O_RDWR|O_CREAT|O_TRUNC,
2190
                                           S_IRUSR|S_IWUSR);
2191
#else
2192
            encoder->tile_outfd = open(value,
×
2193
                                          O_RDWR|O_CREAT|O_TRUNC,
2194
                                          S_IRUSR|S_IWUSR);
2195
#endif  /* HAVE__OPEN */
2196
            if (encoder->tile_outfd < 0) {
×
2197
                sixel_helper_set_additional_message(
×
2198
                    "sixel_encoder_setopt: failed to open tile"
2199
                    " output path.");
2200
                status = SIXEL_RUNTIME_ERROR;
×
2201
                goto end;
×
2202
            }
2203
        }
2204
        break;
×
2205
    case SIXEL_OPTFLAG_7BIT_MODE:  /* 7 */
10✔
2206
        encoder->f8bit = 0;
15✔
2207
        break;
15✔
2208
    case SIXEL_OPTFLAG_8BIT_MODE:  /* 8 */
12✔
2209
        encoder->f8bit = 1;
18✔
2210
        break;
18✔
2211
    case SIXEL_OPTFLAG_HAS_GRI_ARG_LIMIT:  /* R */
2212
        encoder->has_gri_arg_limit = 1;
×
2213
        break;
×
2214
    case SIXEL_OPTFLAG_COLORS:  /* p */
18✔
2215
        forced_palette = 0;
27✔
2216
        errno = 0;
27✔
2217
        endptr = NULL;
27✔
2218
        parsed_reqcolors = strtol(value, &endptr, 10);
27✔
2219
        if (endptr != NULL && *endptr == '!') {
27!
NEW
2220
            forced_palette = 1;
×
NEW
2221
            ++endptr;
×
2222
        }
2223
        if (errno == ERANGE || endptr == value) {
27!
NEW
2224
            sixel_helper_set_additional_message(
×
2225
                "cannot parse -p/--colors option.");
NEW
2226
            status = SIXEL_BAD_ARGUMENT;
×
NEW
2227
            goto end;
×
2228
        }
2229
        if (endptr != NULL && *endptr != '\0') {
27!
NEW
2230
            sixel_helper_set_additional_message(
×
2231
                "cannot parse -p/--colors option.");
NEW
2232
            status = SIXEL_BAD_ARGUMENT;
×
NEW
2233
            goto end;
×
2234
        }
2235
        if (parsed_reqcolors < 1) {
27!
UNCOV
2236
            sixel_helper_set_additional_message(
×
2237
                "-p/--colors parameter must be 1 or more.");
2238
            status = SIXEL_BAD_ARGUMENT;
×
2239
            goto end;
×
2240
        }
2241
        if (parsed_reqcolors > SIXEL_PALETTE_MAX) {
27!
UNCOV
2242
            sixel_helper_set_additional_message(
×
2243
                "-p/--colors parameter must be less then or equal to 256.");
2244
            status = SIXEL_BAD_ARGUMENT;
×
2245
            goto end;
×
2246
        }
2247
        encoder->reqcolors = (int)parsed_reqcolors;
27✔
2248
        encoder->force_palette = forced_palette;
27✔
2249
        break;
27✔
2250
    case SIXEL_OPTFLAG_MAPFILE:  /* m */
18✔
2251
        if (encoder->mapfile) {
27✔
2252
            sixel_allocator_free(encoder->allocator, encoder->mapfile);
3✔
2253
        }
1✔
2254
        encoder->mapfile = arg_strdup(value, encoder->allocator);
27✔
2255
        if (encoder->mapfile == NULL) {
27!
2256
            sixel_helper_set_additional_message(
×
2257
                "sixel_encoder_setopt: sixel_allocator_malloc() failed.");
2258
            status = SIXEL_BAD_ALLOCATION;
×
2259
            goto end;
×
2260
        }
2261
        encoder->color_option = SIXEL_COLOR_OPTION_MAPFILE;
27✔
2262
        break;
27✔
2263
    case SIXEL_OPTFLAG_MONOCHROME:  /* e */
10✔
2264
        encoder->color_option = SIXEL_COLOR_OPTION_MONOCHROME;
15✔
2265
        break;
15✔
2266
    case SIXEL_OPTFLAG_HIGH_COLOR:  /* I */
28✔
2267
        encoder->color_option = SIXEL_COLOR_OPTION_HIGHCOLOR;
42✔
2268
        break;
42✔
2269
    case SIXEL_OPTFLAG_BUILTIN_PALETTE:  /* b */
22✔
2270
        if (strcmp(value, "xterm16") == 0) {
33✔
2271
            encoder->builtin_palette = SIXEL_BUILTIN_XTERM16;
6✔
2272
        } else if (strcmp(value, "xterm256") == 0) {
29✔
2273
            encoder->builtin_palette = SIXEL_BUILTIN_XTERM256;
6✔
2274
        } else if (strcmp(value, "vt340mono") == 0) {
23✔
2275
            encoder->builtin_palette = SIXEL_BUILTIN_VT340_MONO;
3✔
2276
        } else if (strcmp(value, "vt340color") == 0) {
19✔
2277
            encoder->builtin_palette = SIXEL_BUILTIN_VT340_COLOR;
3✔
2278
        } else if (strcmp(value, "gray1") == 0) {
16✔
2279
            encoder->builtin_palette = SIXEL_BUILTIN_G1;
3✔
2280
        } else if (strcmp(value, "gray2") == 0) {
13✔
2281
            encoder->builtin_palette = SIXEL_BUILTIN_G2;
3✔
2282
        } else if (strcmp(value, "gray4") == 0) {
10✔
2283
            encoder->builtin_palette = SIXEL_BUILTIN_G4;
3✔
2284
        } else if (strcmp(value, "gray8") == 0) {
7✔
2285
            encoder->builtin_palette = SIXEL_BUILTIN_G8;
3✔
2286
        } else {
1✔
2287
            sixel_helper_set_additional_message(
3✔
2288
                    "cannot parse builtin palette option.");
2289
            status = SIXEL_BAD_ARGUMENT;
3✔
2290
            goto end;
3✔
2291
        }
2292
        encoder->color_option = SIXEL_COLOR_OPTION_BUILTIN;
30✔
2293
        break;
30✔
2294
    case SIXEL_OPTFLAG_DIFFUSION:  /* d */
46✔
2295
        /* parse --diffusion option */
2296
        if (strcmp(value, "auto") == 0) {
69✔
2297
            encoder->method_for_diffuse = SIXEL_DIFFUSE_AUTO;
3✔
2298
        } else if (strcmp(value, "none") == 0) {
67✔
2299
            encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
15✔
2300
        } else if (strcmp(value, "fs") == 0) {
56✔
2301
            encoder->method_for_diffuse = SIXEL_DIFFUSE_FS;
6✔
2302
        } else if (strcmp(value, "atkinson") == 0) {
47✔
2303
            encoder->method_for_diffuse = SIXEL_DIFFUSE_ATKINSON;
12✔
2304
        } else if (strcmp(value, "jajuni") == 0) {
37✔
2305
            encoder->method_for_diffuse = SIXEL_DIFFUSE_JAJUNI;
6✔
2306
        } else if (strcmp(value, "stucki") == 0) {
29✔
2307
            encoder->method_for_diffuse = SIXEL_DIFFUSE_STUCKI;
6✔
2308
        } else if (strcmp(value, "burkes") == 0) {
23✔
2309
            encoder->method_for_diffuse = SIXEL_DIFFUSE_BURKES;
6✔
2310
        } else if (strcmp(value, "a_dither") == 0) {
17✔
2311
            encoder->method_for_diffuse = SIXEL_DIFFUSE_A_DITHER;
6✔
2312
        } else if (strcmp(value, "x_dither") == 0) {
11✔
2313
            encoder->method_for_diffuse = SIXEL_DIFFUSE_X_DITHER;
6✔
2314
        } else if (strcmp(value, "lso1") == 0) {
5!
2315
            encoder->method_for_diffuse = SIXEL_DIFFUSE_LSO1;
×
2316
        } else if (strcmp(value, "lso2") == 0) {
3!
2317
            encoder->method_for_diffuse = SIXEL_DIFFUSE_LSO2;
×
2318
        } else if (strcmp(value, "lso3") == 0) {
3!
2319
            encoder->method_for_diffuse = SIXEL_DIFFUSE_LSO3;
×
2320
        } else {
2321
            sixel_helper_set_additional_message(
3✔
2322
                "specified diffusion method is not supported.");
2323
            status = SIXEL_BAD_ARGUMENT;
3✔
2324
            goto end;
3✔
2325
        }
2326
        break;
66✔
2327
    case SIXEL_OPTFLAG_DIFFUSION_SCAN:  /* y */
2328
        if (strcmp(value, "auto") == 0) {
×
2329
            encoder->method_for_scan = SIXEL_SCAN_AUTO;
×
2330
        } else if (strcmp(value, "serpentine") == 0) {
×
2331
            encoder->method_for_scan = SIXEL_SCAN_SERPENTINE;
×
2332
        } else if (strcmp(value, "raster") == 0) {
×
2333
            encoder->method_for_scan = SIXEL_SCAN_RASTER;
×
2334
        } else {
2335
            sixel_helper_set_additional_message(
×
2336
                "specified diffusion scan is not supported.");
2337
            status = SIXEL_BAD_ARGUMENT;
×
2338
            goto end;
×
2339
        }
2340
        break;
×
2341
    case SIXEL_OPTFLAG_DIFFUSION_CARRY:  /* Y */
2342
        if (strcmp(value, "auto") == 0) {
×
2343
            encoder->method_for_carry = SIXEL_CARRY_AUTO;
×
2344
        } else if (strcmp(value, "direct") == 0) {
×
2345
            encoder->method_for_carry = SIXEL_CARRY_DISABLE;
×
2346
        } else if (strcmp(value, "carry") == 0) {
×
2347
            encoder->method_for_carry = SIXEL_CARRY_ENABLE;
×
2348
        } else {
2349
            sixel_helper_set_additional_message(
×
2350
                "specified diffusion carry mode is not supported.");
2351
            status = SIXEL_BAD_ARGUMENT;
×
2352
            goto end;
×
2353
        }
2354
        break;
×
2355
    case SIXEL_OPTFLAG_FIND_LARGEST:  /* f */
10✔
2356
        /* parse --find-largest option */
2357
        if (value) {
15!
2358
            if (strcmp(value, "auto") == 0) {
15✔
2359
                encoder->method_for_largest = SIXEL_LARGE_AUTO;
3✔
2360
            } else if (strcmp(value, "norm") == 0) {
13✔
2361
                encoder->method_for_largest = SIXEL_LARGE_NORM;
6✔
2362
            } else if (strcmp(value, "lum") == 0) {
8✔
2363
                encoder->method_for_largest = SIXEL_LARGE_LUM;
3✔
2364
            } else {
1✔
2365
                sixel_helper_set_additional_message(
3✔
2366
                    "specified finding method is not supported.");
2367
                status = SIXEL_BAD_ARGUMENT;
3✔
2368
                goto end;
3✔
2369
            }
2370
        }
4✔
2371
        break;
12✔
2372
    case SIXEL_OPTFLAG_SELECT_COLOR:  /* s */
10✔
2373
        /* parse --select-color option */
2374
        if (strcmp(value, "auto") == 0) {
15✔
2375
            encoder->method_for_rep = SIXEL_REP_AUTO;
3✔
2376
        } else if (strcmp(value, "center") == 0) {
13✔
2377
            encoder->method_for_rep = SIXEL_REP_CENTER_BOX;
3✔
2378
        } else if (strcmp(value, "average") == 0) {
10✔
2379
            encoder->method_for_rep = SIXEL_REP_AVERAGE_COLORS;
3✔
2380
        } else if ((strcmp(value, "histogram") == 0) ||
7!
2381
                   (strcmp(value, "histgram") == 0)) {
3!
2382
            encoder->method_for_rep = SIXEL_REP_AVERAGE_PIXELS;
3✔
2383
        } else {
1✔
2384
            sixel_helper_set_additional_message(
3✔
2385
                "specified finding method is not supported.");
2386
            status = SIXEL_BAD_ARGUMENT;
3✔
2387
            goto end;
3✔
2388
        }
2389
        break;
12✔
2390
    case SIXEL_OPTFLAG_CROP:  /* c */
10✔
2391
#if HAVE_SSCANF_S
2392
        number = sscanf_s(value, "%dx%d+%d+%d",
2393
                          &encoder->clipwidth, &encoder->clipheight,
2394
                          &encoder->clipx, &encoder->clipy);
2395
#else
2396
        number = sscanf(value, "%dx%d+%d+%d",
20✔
2397
                        &encoder->clipwidth, &encoder->clipheight,
5✔
2398
                        &encoder->clipx, &encoder->clipy);
5✔
2399
#endif  /* HAVE_SSCANF_S */
2400
        if (number != 4) {
15!
2401
            status = SIXEL_BAD_ARGUMENT;
×
2402
            goto end;
×
2403
        }
2404
        if (encoder->clipwidth <= 0 || encoder->clipheight <= 0) {
15!
2405
            status = SIXEL_BAD_ARGUMENT;
×
2406
            goto end;
×
2407
        }
2408
        if (encoder->clipx < 0 || encoder->clipy < 0) {
15!
2409
            status = SIXEL_BAD_ARGUMENT;
×
2410
            goto end;
×
2411
        }
2412
        encoder->clipfirst = 0;
15✔
2413
        break;
15✔
2414
    case SIXEL_OPTFLAG_WIDTH:  /* w */
50✔
2415
#if HAVE_SSCANF_S
2416
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
2417
#else
2418
        parsed = sscanf(value, "%d%2s", &number, unit);
75✔
2419
#endif  /* HAVE_SSCANF_S */
2420
        if (parsed == 2 && strcmp(unit, "%") == 0) {
75!
2421
            encoder->pixelwidth = (-1);
12✔
2422
            encoder->percentwidth = number;
12✔
2423
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
67!
2424
            encoder->pixelwidth = number;
51✔
2425
            encoder->percentwidth = (-1);
51✔
2426
        } else if (strcmp(value, "auto") == 0) {
29✔
2427
            encoder->pixelwidth = (-1);
9✔
2428
            encoder->percentwidth = (-1);
9✔
2429
        } else {
3✔
2430
            sixel_helper_set_additional_message(
3✔
2431
                "cannot parse -w/--width option.");
2432
            status = SIXEL_BAD_ARGUMENT;
3✔
2433
            goto end;
3✔
2434
        }
2435
        if (encoder->clipwidth) {
72✔
2436
            encoder->clipfirst = 1;
6✔
2437
        }
2✔
2438
        break;
72✔
2439
    case SIXEL_OPTFLAG_HEIGHT:  /* h */
44✔
2440
#if HAVE_SSCANF_S
2441
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
2442
#else
2443
        parsed = sscanf(value, "%d%2s", &number, unit);
66✔
2444
#endif  /* HAVE_SSCANF_S */
2445
        if (parsed == 2 && strcmp(unit, "%") == 0) {
66!
2446
            encoder->pixelheight = (-1);
9✔
2447
            encoder->percentheight = number;
9✔
2448
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
60!
2449
            encoder->pixelheight = number;
45✔
2450
            encoder->percentheight = (-1);
45✔
2451
        } else if (strcmp(value, "auto") == 0) {
27✔
2452
            encoder->pixelheight = (-1);
9✔
2453
            encoder->percentheight = (-1);
9✔
2454
        } else {
3✔
2455
            sixel_helper_set_additional_message(
3✔
2456
                "cannot parse -h/--height option.");
2457
            status = SIXEL_BAD_ARGUMENT;
3✔
2458
            goto end;
3✔
2459
        }
2460
        if (encoder->clipheight) {
63✔
2461
            encoder->clipfirst = 1;
3✔
2462
        }
1✔
2463
        break;
63✔
2464
    case SIXEL_OPTFLAG_RESAMPLING:  /* r */
34✔
2465
        /* parse --resampling option */
2466
        if (strcmp(value, "nearest") == 0) {
51✔
2467
            encoder->method_for_resampling = SIXEL_RES_NEAREST;
18✔
2468
        } else if (strcmp(value, "gaussian") == 0) {
39✔
2469
            encoder->method_for_resampling = SIXEL_RES_GAUSSIAN;
3✔
2470
        } else if (strcmp(value, "hanning") == 0) {
31✔
2471
            encoder->method_for_resampling = SIXEL_RES_HANNING;
3✔
2472
        } else if (strcmp(value, "hamming") == 0) {
28✔
2473
            encoder->method_for_resampling = SIXEL_RES_HAMMING;
3✔
2474
        } else if (strcmp(value, "bilinear") == 0) {
25✔
2475
            encoder->method_for_resampling = SIXEL_RES_BILINEAR;
3✔
2476
        } else if (strcmp(value, "welsh") == 0) {
22✔
2477
            encoder->method_for_resampling = SIXEL_RES_WELSH;
3✔
2478
        } else if (strcmp(value, "bicubic") == 0) {
19✔
2479
            encoder->method_for_resampling = SIXEL_RES_BICUBIC;
3✔
2480
        } else if (strcmp(value, "lanczos2") == 0) {
16✔
2481
            encoder->method_for_resampling = SIXEL_RES_LANCZOS2;
6✔
2482
        } else if (strcmp(value, "lanczos3") == 0) {
11✔
2483
            encoder->method_for_resampling = SIXEL_RES_LANCZOS3;
3✔
2484
        } else if (strcmp(value, "lanczos4") == 0) {
7✔
2485
            encoder->method_for_resampling = SIXEL_RES_LANCZOS4;
3✔
2486
        } else {
1✔
2487
            sixel_helper_set_additional_message(
3✔
2488
                "specified desampling method is not supported.");
2489
            status = SIXEL_BAD_ARGUMENT;
3✔
2490
            goto end;
3✔
2491
        }
2492
        break;
48✔
2493
    case SIXEL_OPTFLAG_QUALITY:  /* q */
12✔
2494
        /* parse --quality option */
2495
        if (strcmp(value, "auto") == 0) {
18✔
2496
            encoder->quality_mode = SIXEL_QUALITY_AUTO;
6✔
2497
        } else if (strcmp(value, "high") == 0) {
14✔
2498
            encoder->quality_mode = SIXEL_QUALITY_HIGH;
3✔
2499
        } else if (strcmp(value, "low") == 0) {
10✔
2500
            encoder->quality_mode = SIXEL_QUALITY_LOW;
3✔
2501
        } else if (strcmp(value, "full") == 0) {
7✔
2502
            encoder->quality_mode = SIXEL_QUALITY_FULL;
3✔
2503
        } else {
1✔
2504
            sixel_helper_set_additional_message(
3✔
2505
                "cannot parse quality option.");
2506
            status = SIXEL_BAD_ARGUMENT;
3✔
2507
            goto end;
3✔
2508
        }
2509
        break;
15✔
2510
    case SIXEL_OPTFLAG_LOOPMODE:  /* l */
10✔
2511
        /* parse --loop-control option */
2512
        if (strcmp(value, "auto") == 0) {
15✔
2513
            encoder->loop_mode = SIXEL_LOOP_AUTO;
3✔
2514
        } else if (strcmp(value, "force") == 0) {
13!
2515
            encoder->loop_mode = SIXEL_LOOP_FORCE;
×
2516
        } else if (strcmp(value, "disable") == 0) {
12✔
2517
            encoder->loop_mode = SIXEL_LOOP_DISABLE;
9✔
2518
        } else {
3✔
2519
            sixel_helper_set_additional_message(
3✔
2520
                "cannot parse loop-control option.");
2521
            status = SIXEL_BAD_ARGUMENT;
3✔
2522
            goto end;
3✔
2523
        }
2524
        break;
12✔
2525
    case SIXEL_OPTFLAG_PALETTE_TYPE:  /* t */
18✔
2526
        /* parse --palette-type option */
2527
        if (strcmp(value, "auto") == 0) {
27✔
2528
            encoder->palette_type = SIXEL_PALETTETYPE_AUTO;
6✔
2529
        } else if (strcmp(value, "hls") == 0) {
23✔
2530
            encoder->palette_type = SIXEL_PALETTETYPE_HLS;
15✔
2531
        } else if (strcmp(value, "rgb") == 0) {
11✔
2532
            encoder->palette_type = SIXEL_PALETTETYPE_RGB;
3✔
2533
        } else {
1✔
2534
            sixel_helper_set_additional_message(
3✔
2535
                "cannot parse palette type option.");
2536
            status = SIXEL_BAD_ARGUMENT;
3✔
2537
            goto end;
3✔
2538
        }
2539
        break;
24✔
2540
    case SIXEL_OPTFLAG_BGCOLOR:  /* B */
30✔
2541
        /* parse --bgcolor option */
2542
        if (encoder->bgcolor) {
45✔
2543
            sixel_allocator_free(encoder->allocator, encoder->bgcolor);
6✔
2544
            encoder->bgcolor = NULL;
6✔
2545
        }
2✔
2546
        status = sixel_parse_x_colorspec(&encoder->bgcolor,
60✔
2547
                                         value,
15✔
2548
                                         encoder->allocator);
15✔
2549
        if (SIXEL_FAILED(status)) {
45✔
2550
            sixel_helper_set_additional_message(
21✔
2551
                "cannot parse bgcolor option.");
2552
            status = SIXEL_BAD_ARGUMENT;
21✔
2553
            goto end;
21✔
2554
        }
2555
        break;
24✔
2556
    case SIXEL_OPTFLAG_INSECURE:  /* k */
2557
        encoder->finsecure = 1;
×
2558
        break;
×
2559
    case SIXEL_OPTFLAG_INVERT:  /* i */
4✔
2560
        encoder->finvert = 1;
6✔
2561
        break;
6✔
2562
    case SIXEL_OPTFLAG_USE_MACRO:  /* u */
4✔
2563
        encoder->fuse_macro = 1;
6✔
2564
        break;
6✔
2565
    case SIXEL_OPTFLAG_MACRO_NUMBER:  /* n */
2✔
2566
        encoder->macro_number = atoi(value);
3✔
2567
        if (encoder->macro_number < 0) {
3!
2568
            status = SIXEL_BAD_ARGUMENT;
×
2569
            goto end;
×
2570
        }
2571
        break;
3✔
2572
    case SIXEL_OPTFLAG_IGNORE_DELAY:  /* g */
4✔
2573
        encoder->fignore_delay = 1;
6✔
2574
        break;
6✔
2575
    case SIXEL_OPTFLAG_VERBOSE:  /* v */
6✔
2576
        encoder->verbose = 1;
9✔
2577
        sixel_helper_set_loader_trace(1);
9✔
2578
        break;
9✔
2579
    case SIXEL_OPTFLAG_STATIC:  /* S */
2✔
2580
        encoder->fstatic = 1;
3✔
2581
        break;
3✔
2582
    case SIXEL_OPTFLAG_DRCS:  /* @ */
2583
        encoder->fdrcs = 1;
×
2584
        if (strlen(value) == 1 && value[0] >= 32) {
×
2585
            encoder->start_dscs = value[0];
×
2586
        } else {
2587
            sixel_helper_set_additional_message(
×
2588
                "cannot parse DSCS option.");
2589
            status = SIXEL_BAD_ARGUMENT;
×
2590
            goto end;
×
2591
        }
2592
        break;
×
2593
    case SIXEL_OPTFLAG_MAPPING_VERSION:  /* M */
2594
        encoder->drcs_mmv = atoi(value);
×
2595
        if (encoder->drcs_mmv < 0 || encoder->drcs_mmv >= 3) {
×
2596
            sixel_helper_set_additional_message(
×
2597
                "unknown DRCS unicode mapping version.");
2598
            status = SIXEL_BAD_ARGUMENT;
×
2599
            goto end;
×
2600
        }
2601
        break;
×
2602
    case SIXEL_OPTFLAG_PENETRATE:  /* P */
6✔
2603
        encoder->penetrate_multiplexer = 1;
9✔
2604
        break;
9✔
2605
    case SIXEL_OPTFLAG_ENCODE_POLICY:  /* E */
8✔
2606
        if (strcmp(value, "auto") == 0) {
12✔
2607
            encoder->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
3✔
2608
        } else if (strcmp(value, "fast") == 0) {
10✔
2609
            encoder->encode_policy = SIXEL_ENCODEPOLICY_FAST;
3✔
2610
        } else if (strcmp(value, "size") == 0) {
7✔
2611
            encoder->encode_policy = SIXEL_ENCODEPOLICY_SIZE;
3✔
2612
        } else {
1✔
2613
            sixel_helper_set_additional_message(
3✔
2614
                "cannot parse encode policy option.");
2615
            status = SIXEL_BAD_ARGUMENT;
3✔
2616
            goto end;
3✔
2617
        }
2618
        break;
9✔
2619
    case SIXEL_OPTFLAG_LUT_POLICY:  /* L */
2620
        if (strcmp(value, "auto") == 0) {
×
2621
            encoder->lut_policy = SIXEL_LUT_POLICY_AUTO;
×
2622
        } else if (strcmp(value, "5bit") == 0) {
×
2623
            encoder->lut_policy = SIXEL_LUT_POLICY_5BIT;
×
2624
        } else if (strcmp(value, "6bit") == 0) {
×
2625
            encoder->lut_policy = SIXEL_LUT_POLICY_6BIT;
×
2626
        } else if (strcmp(value, "robinhood") == 0) {
×
2627
            encoder->lut_policy = SIXEL_LUT_POLICY_ROBINHOOD;
×
2628
        } else if (strcmp(value, "hopscotch") == 0) {
×
2629
            encoder->lut_policy = SIXEL_LUT_POLICY_HOPSCOTCH;
×
2630
        } else {
2631
            sixel_helper_set_additional_message(
×
2632
                "cannot parse lut policy option.");
2633
            status = SIXEL_BAD_ARGUMENT;
×
2634
            goto end;
×
2635
        }
2636
        if (encoder->dither_cache != NULL) {
×
2637
            sixel_dither_set_lut_policy(encoder->dither_cache,
×
2638
                                        encoder->lut_policy);
2639
        }
2640
        break;
×
2641
    case SIXEL_OPTFLAG_WORKING_COLORSPACE:  /* W */
2642
        if (value == NULL) {
×
2643
            sixel_helper_set_additional_message(
×
2644
                "working-colorspace requires an argument.");
2645
            status = SIXEL_BAD_ARGUMENT;
×
2646
            goto end;
×
2647
        } else {
2648
            len = strlen(value);
×
2649

2650
            if (len >= sizeof(lowered)) {
×
2651
                sixel_helper_set_additional_message(
×
2652
                    "specified working colorspace name is too long.");
2653
                status = SIXEL_BAD_ARGUMENT;
×
2654
                goto end;
×
2655
            }
2656
            for (i = 0; i < len; ++i) {
×
2657
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
2658
            }
2659
            lowered[len] = '\0';
×
2660

2661
            if (strcmp(lowered, "gamma") == 0) {
×
2662
                encoder->working_colorspace = SIXEL_COLORSPACE_GAMMA;
×
2663
            } else if (strcmp(lowered, "linear") == 0) {
×
2664
                encoder->working_colorspace = SIXEL_COLORSPACE_LINEAR;
×
2665
            } else if (strcmp(lowered, "oklab") == 0) {
×
2666
                encoder->working_colorspace = SIXEL_COLORSPACE_OKLAB;
×
2667
            } else {
2668
                sixel_helper_set_additional_message(
×
2669
                    "unsupported working colorspace specified.");
2670
                status = SIXEL_BAD_ARGUMENT;
×
2671
                goto end;
×
2672
            }
2673
        }
2674
        break;
×
2675
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
2676
        if (value == NULL) {
×
2677
            sixel_helper_set_additional_message(
×
2678
                "output-colorspace requires an argument.");
2679
            status = SIXEL_BAD_ARGUMENT;
×
2680
            goto end;
×
2681
        } else {
2682
            len = strlen(value);
×
2683

2684
            if (len >= sizeof(lowered)) {
×
2685
                sixel_helper_set_additional_message(
×
2686
                    "specified output colorspace name is too long.");
2687
                status = SIXEL_BAD_ARGUMENT;
×
2688
                goto end;
×
2689
            }
2690
            for (i = 0; i < len; ++i) {
×
2691
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
2692
            }
2693
            lowered[len] = '\0';
×
2694

2695
            if (strcmp(lowered, "gamma") == 0) {
×
2696
                encoder->output_colorspace = SIXEL_COLORSPACE_GAMMA;
×
2697
            } else if (strcmp(lowered, "linear") == 0) {
×
2698
                encoder->output_colorspace = SIXEL_COLORSPACE_LINEAR;
×
2699
            } else if (strcmp(lowered, "smpte-c") == 0 ||
×
2700
                       strcmp(lowered, "smptec") == 0) {
×
2701
                encoder->output_colorspace = SIXEL_COLORSPACE_SMPTEC;
×
2702
            } else {
2703
                sixel_helper_set_additional_message(
×
2704
                    "unsupported output colorspace specified.");
2705
                status = SIXEL_BAD_ARGUMENT;
×
2706
                goto end;
×
2707
            }
2708
        }
2709
        break;
×
2710
    case SIXEL_OPTFLAG_ORMODE:  /* O */
2711
        encoder->ormode = 1;
×
2712
        break;
×
2713
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
6✔
2714
        encoder->complexion = atoi(value);
9✔
2715
        if (encoder->complexion < 1) {
9✔
2716
            sixel_helper_set_additional_message(
3✔
2717
                "complexion parameter must be 1 or more.");
2718
            status = SIXEL_BAD_ARGUMENT;
3✔
2719
            goto end;
3✔
2720
        }
2721
        break;
6✔
2722
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
2723
        encoder->pipe_mode = 1;
×
2724
        break;
×
2725
    case '?':  /* unknown option */
3✔
2726
    default:
2727
        /* exit if unknown options are specified */
2728
        sixel_helper_set_additional_message(
3✔
2729
            "unknown option is specified.");
2730
        status = SIXEL_BAD_ARGUMENT;
3✔
2731
        goto end;
3✔
2732
    }
2733

2734
    /* detects arguments conflictions */
2735
    if (encoder->reqcolors != (-1)) {
612✔
2736
        switch (encoder->color_option) {
99!
2737
        case SIXEL_COLOR_OPTION_MAPFILE:
2738
            sixel_helper_set_additional_message(
×
2739
                "option -p, --colors conflicts with -m, --mapfile.");
2740
            status = SIXEL_BAD_ARGUMENT;
×
2741
            goto end;
×
2742
        case SIXEL_COLOR_OPTION_MONOCHROME:
2✔
2743
            sixel_helper_set_additional_message(
3✔
2744
                "option -e, --monochrome conflicts with -p, --colors.");
2745
            status = SIXEL_BAD_ARGUMENT;
3✔
2746
            goto end;
3✔
2747
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
2✔
2748
            sixel_helper_set_additional_message(
3✔
2749
                "option -p, --colors conflicts with -I, --high-color.");
2750
            status = SIXEL_BAD_ARGUMENT;
3✔
2751
            goto end;
3✔
2752
        case SIXEL_COLOR_OPTION_BUILTIN:
2✔
2753
            sixel_helper_set_additional_message(
3✔
2754
                "option -p, --colors conflicts with -b, --builtin-palette.");
2755
            status = SIXEL_BAD_ARGUMENT;
3✔
2756
            goto end;
3✔
2757
        default:
60✔
2758
            break;
90✔
2759
        }
2760
    }
30✔
2761

2762
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
2763
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
603✔
2764
        sixel_helper_set_additional_message(
3✔
2765
            "option -8 --8bit-mode conflicts"
2766
            " with -P, --penetrate.");
2767
        status = SIXEL_BAD_ARGUMENT;
3✔
2768
        goto end;
3✔
2769
    }
2770

2771
    status = SIXEL_OK;
600✔
2772

2773
end:
448✔
2774
    sixel_encoder_unref(encoder);
672✔
2775

2776
    return status;
672✔
2777
}
2778

2779

2780
/* called when image loader component load a image frame */
2781
static SIXELSTATUS
2782
load_image_callback(sixel_frame_t *frame, void *data)
518✔
2783
{
2784
    return sixel_encoder_encode_frame((sixel_encoder_t *)data, frame, NULL);
518✔
2785
}
2786

2787

2788
/* load source data from specified file and encode it to SIXEL format
2789
 * output to encoder->outfd */
2790
SIXELAPI SIXELSTATUS
2791
sixel_encoder_encode(
417✔
2792
    sixel_encoder_t *encoder,   /* encoder object */
2793
    char const      *filename)  /* input filename */
2794
{
2795
    SIXELSTATUS status = SIXEL_FALSE;
417✔
2796
    int fuse_palette = 1;
417✔
2797

2798
    if (encoder == NULL) {
417!
2799
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2800
#  pragma GCC diagnostic push
2801
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
2802
#endif
2803
        encoder = sixel_encoder_create();
×
2804
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2805
#  pragma GCC diagnostic pop
2806
#endif
2807
        if (encoder == NULL) {
×
2808
            sixel_helper_set_additional_message(
×
2809
                "sixel_encoder_encode: sixel_encoder_create() failed.");
2810
            status = SIXEL_BAD_ALLOCATION;
×
2811
            goto end;
×
2812
        }
2813
    } else {
2814
        sixel_encoder_ref(encoder);
417✔
2815
    }
2816

2817
    /* if required color is not set, set the max value */
2818
    if (encoder->reqcolors == (-1)) {
417✔
2819
        encoder->reqcolors = SIXEL_PALETTE_MAX;
399✔
2820
    }
133✔
2821

2822
    /* if required color is less then 2, set the min value */
2823
    if (encoder->reqcolors < 2) {
417✔
2824
        encoder->reqcolors = SIXEL_PALETTE_MIN;
3✔
2825
    }
1✔
2826

2827
    /* if color space option is not set, choose RGB color space */
2828
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
417✔
2829
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
399✔
2830
    }
133✔
2831

2832
    /* if color option is not default value, prohibit to read
2833
       the file as a paletted image */
2834
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
417✔
2835
        fuse_palette = 0;
99✔
2836
    }
33✔
2837

2838
    /* if scaling options are set, prohibit to read the file as
2839
       a paletted image */
2840
    if (encoder->percentwidth > 0 ||
533✔
2841
        encoder->percentheight > 0 ||
405✔
2842
        encoder->pixelwidth > 0 ||
399✔
2843
        encoder->pixelheight > 0) {
381✔
2844
        fuse_palette = 0;
99✔
2845
    }
33✔
2846

2847
reload:
278✔
2848
    sixel_helper_set_loader_trace(encoder->verbose);
417✔
2849
    sixel_helper_set_thumbnail_size_hint(
417✔
2850
        sixel_encoder_thumbnail_hint(encoder));
139✔
2851
    status = sixel_helper_load_image_file(filename,
556✔
2852
                                          encoder->fstatic,
139✔
2853
                                          fuse_palette,
139✔
2854
                                          encoder->reqcolors,
139✔
2855
                                          encoder->bgcolor,
139✔
2856
                                          encoder->loop_mode,
139✔
2857
                                          load_image_callback,
2858
                                          encoder->finsecure,
139✔
2859
                                          encoder->cancel_flag,
417✔
2860
                                          (void *)encoder,
139✔
2861
                                          encoder->allocator);
139✔
2862
    if (status != SIXEL_OK) {
417✔
2863
        goto end;
21✔
2864
    }
2865

2866
    if (encoder->pipe_mode) {
396!
2867
#if HAVE_CLEARERR
2868
        clearerr(stdin);
×
2869
#endif  /* HAVE_FSEEK */
2870
        while (encoder->cancel_flag && !*encoder->cancel_flag) {
×
2871
            status = sixel_tty_wait_stdin(1000000);
×
2872
            if (SIXEL_FAILED(status)) {
×
2873
                goto end;
×
2874
            }
2875
            if (status != SIXEL_OK) {
×
2876
                break;
×
2877
            }
2878
        }
2879
        if (!encoder->cancel_flag || !*encoder->cancel_flag) {
×
2880
            goto reload;
×
2881
        }
2882
    }
2883

2884
    /* the status may not be SIXEL_OK */
2885

2886
end:
264✔
2887
    sixel_encoder_unref(encoder);
417✔
2888

2889
    return status;
417✔
2890
}
2891

2892

2893
/* encode specified pixel data to SIXEL format
2894
 * output to encoder->outfd */
2895
SIXELAPI SIXELSTATUS
2896
sixel_encoder_encode_bytes(
×
2897
    sixel_encoder_t     /* in */    *encoder,
2898
    unsigned char       /* in */    *bytes,
2899
    int                 /* in */    width,
2900
    int                 /* in */    height,
2901
    int                 /* in */    pixelformat,
2902
    unsigned char       /* in */    *palette,
2903
    int                 /* in */    ncolors)
2904
{
2905
    SIXELSTATUS status = SIXEL_FALSE;
×
2906
    sixel_frame_t *frame = NULL;
×
2907

2908
    if (encoder == NULL || bytes == NULL) {
×
2909
        status = SIXEL_BAD_ARGUMENT;
×
2910
        goto end;
×
2911
    }
2912

2913
    status = sixel_frame_new(&frame, encoder->allocator);
×
2914
    if (SIXEL_FAILED(status)) {
×
2915
        goto end;
×
2916
    }
2917

2918
    status = sixel_frame_init(frame, bytes, width, height,
×
2919
                              pixelformat, palette, ncolors);
2920
    if (SIXEL_FAILED(status)) {
×
2921
        goto end;
×
2922
    }
2923

2924
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
×
2925
    if (SIXEL_FAILED(status)) {
×
2926
        goto end;
×
2927
    }
2928

2929
    status = SIXEL_OK;
×
2930

2931
end:
2932
    /* we need to free the frame before exiting, but we can't use the
2933
       sixel_frame_destroy function, because that will also attempt to
2934
       free the pixels and palette, which we don't own */
2935
    if (frame != NULL && encoder->allocator != NULL) {
×
2936
        sixel_allocator_free(encoder->allocator, frame);
×
2937
        sixel_allocator_unref(encoder->allocator);
×
2938
    }
2939
    return status;
×
2940
}
2941

2942

2943
#if HAVE_TESTS
2944
static int
2945
test1(void)
×
2946
{
2947
    int nret = EXIT_FAILURE;
×
2948
    sixel_encoder_t *encoder = NULL;
×
2949

2950
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2951
#  pragma GCC diagnostic push
2952
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
2953
#endif
2954
    encoder = sixel_encoder_create();
×
2955
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2956
#  pragma GCC diagnostic pop
2957
#endif
2958
    if (encoder == NULL) {
×
2959
        goto error;
×
2960
    }
2961
    sixel_encoder_ref(encoder);
×
2962
    sixel_encoder_unref(encoder);
×
2963
    nret = EXIT_SUCCESS;
×
2964

2965
error:
2966
    sixel_encoder_unref(encoder);
×
2967
    return nret;
×
2968
}
2969

2970

2971
static int
2972
test2(void)
×
2973
{
2974
    int nret = EXIT_FAILURE;
×
2975
    SIXELSTATUS status;
2976
    sixel_encoder_t *encoder = NULL;
×
2977
    sixel_frame_t *frame = NULL;
×
2978
    unsigned char *buffer;
2979
    int height = 0;
×
2980
    int is_animation = 0;
×
2981

2982
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2983
#  pragma GCC diagnostic push
2984
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
2985
#endif
2986
    encoder = sixel_encoder_create();
×
2987
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2988
#  pragma GCC diagnostic pop
2989
#endif
2990
    if (encoder == NULL) {
×
2991
        goto error;
×
2992
    }
2993

2994
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2995
#  pragma GCC diagnostic push
2996
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
2997
#endif
2998
    frame = sixel_frame_create();
×
2999
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
3000
#  pragma GCC diagnostic pop
3001
#endif
3002
    if (encoder == NULL) {
×
3003
        goto error;
×
3004
    }
3005

3006
    buffer = (unsigned char *)sixel_allocator_malloc(encoder->allocator, 3);
×
3007
    if (buffer == NULL) {
×
3008
        goto error;
×
3009
    }
3010
    status = sixel_frame_init(frame, buffer, 1, 1,
×
3011
                              SIXEL_PIXELFORMAT_RGB888,
3012
                              NULL, 0);
3013
    if (SIXEL_FAILED(status)) {
×
3014
        goto error;
×
3015
    }
3016

3017
    if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
×
3018
        is_animation = 1;
×
3019
    }
3020

3021
    height = sixel_frame_get_height(frame);
×
3022

3023
    status = sixel_tty_scroll(sixel_write_callback, encoder->outfd, height, is_animation);
×
3024
    if (SIXEL_FAILED(status)) {
×
3025
        goto error;
×
3026
    }
3027

3028
    nret = EXIT_SUCCESS;
×
3029

3030
error:
3031
    sixel_encoder_unref(encoder);
×
3032
    sixel_frame_unref(frame);
×
3033
    return nret;
×
3034
}
3035

3036

3037
static int
3038
test3(void)
×
3039
{
3040
    int nret = EXIT_FAILURE;
×
3041
    int result;
3042

3043
    result = sixel_tty_wait_stdin(1000);
×
3044
    if (result != 0) {
×
3045
        goto error;
×
3046
    }
3047

3048
    nret = EXIT_SUCCESS;
×
3049

3050
error:
3051
    return nret;
×
3052
}
3053

3054

3055
static int
3056
test4(void)
×
3057
{
3058
    int nret = EXIT_FAILURE;
×
3059
    sixel_encoder_t *encoder = NULL;
×
3060
    SIXELSTATUS status;
3061

3062
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
3063
# pragma GCC diagnostic push
3064
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
3065
#endif
3066
    encoder = sixel_encoder_create();
×
3067
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
3068
# pragma GCC diagnostic pop
3069
#endif
3070
    if (encoder == NULL) {
×
3071
        goto error;
×
3072
    }
3073

3074
    status = sixel_encoder_setopt(encoder,
×
3075
                                  SIXEL_OPTFLAG_LOOPMODE,
3076
                                  "force");
3077
    if (SIXEL_FAILED(status)) {
×
3078
        goto error;
×
3079
    }
3080

3081
    status = sixel_encoder_setopt(encoder,
×
3082
                                  SIXEL_OPTFLAG_PIPE_MODE,
3083
                                  "force");
3084
    if (SIXEL_FAILED(status)) {
×
3085
        goto error;
×
3086
    }
3087

3088
    nret = EXIT_SUCCESS;
×
3089

3090
error:
3091
    sixel_encoder_unref(encoder);
×
3092
    return nret;
×
3093
}
3094

3095

3096
static int
3097
test5(void)
×
3098
{
3099
    int nret = EXIT_FAILURE;
×
3100
    sixel_encoder_t *encoder = NULL;
×
3101
    sixel_allocator_t *allocator = NULL;
×
3102
    SIXELSTATUS status;
3103

3104
    status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
3105
    if (SIXEL_FAILED(status)) {
×
3106
        goto error;
×
3107
    }
3108

3109
    status = sixel_encoder_new(&encoder, allocator);
×
3110
    if (SIXEL_FAILED(status)) {
×
3111
        goto error;
×
3112
    }
3113

3114
    sixel_encoder_ref(encoder);
×
3115
    sixel_encoder_unref(encoder);
×
3116
    nret = EXIT_SUCCESS;
×
3117

3118
error:
3119
    sixel_encoder_unref(encoder);
×
3120
    return nret;
×
3121
}
3122

3123

3124
SIXELAPI int
3125
sixel_encoder_tests_main(void)
×
3126
{
3127
    int nret = EXIT_FAILURE;
×
3128
    size_t i;
3129
    typedef int (* testcase)(void);
3130

3131
    static testcase const testcases[] = {
3132
        test1,
3133
        test2,
3134
        test3,
3135
        test4,
3136
        test5
3137
    };
3138

3139
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
3140
        nret = testcases[i]();
×
3141
        if (nret != EXIT_SUCCESS) {
×
3142
            goto error;
×
3143
        }
3144
    }
3145

3146
    nret = EXIT_SUCCESS;
×
3147

3148
error:
3149
    return nret;
×
3150
}
3151
#endif  /* HAVE_TESTS */
3152

3153

3154
/* emacs Local Variables:      */
3155
/* emacs mode: c               */
3156
/* emacs tab-width: 4          */
3157
/* emacs indent-tabs-mode: nil */
3158
/* emacs c-basic-offset: 4     */
3159
/* emacs End:                  */
3160
/* vim: set expandtab ts=4 : */
3161
/* 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