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

saitoha / libsixel / 19490171609

19 Nov 2025 04:48AM UTC coverage: 39.633% (-2.0%) from 41.622%
19490171609

push

github

saitoha
feat: add new pixelformat SIXEL_PIXELFORMAT_LINEARRGBFLOAT32/SIXEL_PIXELFORMAT_OKLABFLOAT32

8885 of 32084 branches covered (27.69%)

77 of 868 new or added lines in 14 files covered. (8.87%)

197 existing lines in 20 files now uncovered.

11758 of 29667 relevant lines covered (39.63%)

669587.84 hits per line

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

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

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

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

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

109
#include <sixel.h>
110
#include "loader.h"
111
#include "assessment.h"
112
#include "tty.h"
113
#include "encoder.h"
114
#include "output.h"
115
#include "options.h"
116
#include "dither.h"
117
#include "rgblookup.h"
118
#include "clipboard.h"
119
#include "compat_stub.h"
120
#include "sixel_threads_config.h"
121

122
#define SIXEL_ENCODER_PRECISION_ENVVAR "SIXEL_FLOAT32_DITHER"
123

124
typedef enum sixel_encoder_precision_mode {
125
    SIXEL_ENCODER_PRECISION_MODE_AUTO = 0,
126
    SIXEL_ENCODER_PRECISION_MODE_8BIT,
127
    SIXEL_ENCODER_PRECISION_MODE_FLOAT32
128
} sixel_encoder_precision_mode_t;
129

130
static void clipboard_select_format(char *dest,
131
                                    size_t dest_size,
132
                                    char const *format,
133
                                    char const *fallback);
134
static SIXELSTATUS clipboard_create_spool(sixel_allocator_t *allocator,
135
                                          char const *prefix,
136
                                          char **path_out,
137
                                          int *fd_out);
138
static SIXELSTATUS clipboard_write_file(char const *path,
139
                                        unsigned char const *data,
140
                                        size_t size);
141
static SIXELSTATUS clipboard_read_file(char const *path,
142
                                       unsigned char **data,
143
                                       size_t *size);
144
static int sixel_encoder_threads_token_is_auto(char const *text);
145
static int sixel_encoder_parse_threads_argument(char const *text,
146
                                                int *value);
147

148
#if defined(_WIN32)
149

150
# include <windows.h>
151
# if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
152
#  include <io.h>
153
# endif
154
# if defined(_MSC_VER)
155
#   include <time.h>
156
# endif
157

158
# if defined(CLOCKS_PER_SEC)
159
#  undef CLOCKS_PER_SEC
160
# endif
161
# define CLOCKS_PER_SEC 1000
162

163
# if !defined(HAVE_NANOSLEEP)
164
# define HAVE_NANOSLEEP_WIN 1
165
static int
166
nanosleep_win(
167
    struct timespec const *req,
168
    struct timespec *rem)
169
{
170
    LONGLONG nanoseconds;
171
    LARGE_INTEGER dueTime;
172
    HANDLE timer;
173

174
    if (req == NULL || req->tv_sec < 0 || req->tv_nsec < 0 ||
175
        req->tv_nsec >= 1000000000L) {
176
        errno = EINVAL;
177
        return (-1);
178
    }
179

180
    /* Convert to 100-nanosecond intervals (Windows FILETIME units) */
181
    nanoseconds = req->tv_sec * 1000000000LL + req->tv_nsec;
182
    dueTime.QuadPart = -(nanoseconds / 100); /* Negative for relative time */
183

184
    timer = CreateWaitableTimer(NULL, TRUE, NULL);
185
    if (timer == NULL) {
186
        errno = EFAULT;  /* Approximate error */
187
        return (-1);
188
    }
189

190
    if (! SetWaitableTimer(timer, &dueTime, 0, NULL, NULL, FALSE)) {
191
        (void) CloseHandle(timer);
192
        errno = EFAULT;
193
        return (-1);
194
    }
195

196
    (void) WaitForSingleObject(timer, INFINITE);
197
    (void) CloseHandle(timer);
198

199
    /* No interruption handling, so rem is unchanged */
200
    if (rem != NULL) {
201
        rem->tv_sec = 0;
202
        rem->tv_nsec = 0;
203
    }
204

205
    return (0);
206
}
207
# endif  /* HAVE_NANOSLEEP */
208

209
# if !defined(HAVE_CLOCK)
210
# define HAVE_CLOCK_WIN 1
211
static sixel_clock_t
212
clock_win(void)
213
{
214
    FILETIME ct, et, kt, ut;
215
    ULARGE_INTEGER u, k;
216

217
    if (! GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) {
218
        return (sixel_clock_t)(-1);
219
    }
220
    u.LowPart = ut.dwLowDateTime; u.HighPart = ut.dwHighDateTime;
221
    k.LowPart = kt.dwLowDateTime; k.HighPart = kt.dwHighDateTime;
222
    /* 100ns -> ms */
223
    return (sixel_clock_t)((u.QuadPart + k.QuadPart) / 10000ULL);
224
}
225
# endif  /* HAVE_CLOCK */
226

227
#endif /* _WIN32 */
228

229

230
static sixel_option_choice_t const g_option_choices_builtin_palette[] = {
231
    { "xterm16", SIXEL_BUILTIN_XTERM16 },
232
    { "xterm256", SIXEL_BUILTIN_XTERM256 },
233
    { "vt340mono", SIXEL_BUILTIN_VT340_MONO },
234
    { "vt340color", SIXEL_BUILTIN_VT340_COLOR },
235
    { "gray1", SIXEL_BUILTIN_G1 },
236
    { "gray2", SIXEL_BUILTIN_G2 },
237
    { "gray4", SIXEL_BUILTIN_G4 },
238
    { "gray8", SIXEL_BUILTIN_G8 }
239
};
240

241
static sixel_option_choice_t const g_option_choices_diffusion[] = {
242
    { "auto", SIXEL_DIFFUSE_AUTO },
243
    { "none", SIXEL_DIFFUSE_NONE },
244
    { "fs", SIXEL_DIFFUSE_FS },
245
    { "atkinson", SIXEL_DIFFUSE_ATKINSON },
246
    { "jajuni", SIXEL_DIFFUSE_JAJUNI },
247
    { "stucki", SIXEL_DIFFUSE_STUCKI },
248
    { "burkes", SIXEL_DIFFUSE_BURKES },
249
    { "sierra1", SIXEL_DIFFUSE_SIERRA1 },
250
    { "sierra2", SIXEL_DIFFUSE_SIERRA2 },
251
    { "sierra3", SIXEL_DIFFUSE_SIERRA3 },
252
    { "a_dither", SIXEL_DIFFUSE_A_DITHER },
253
    { "x_dither", SIXEL_DIFFUSE_X_DITHER },
254
    { "lso2", SIXEL_DIFFUSE_LSO2 },
255
};
256

257
static sixel_option_choice_t const g_option_choices_diffusion_scan[] = {
258
    { "auto", SIXEL_SCAN_AUTO },
259
    { "serpentine", SIXEL_SCAN_SERPENTINE },
260
    { "raster", SIXEL_SCAN_RASTER }
261
};
262

263
static sixel_option_choice_t const g_option_choices_diffusion_carry[] = {
264
    { "auto", SIXEL_CARRY_AUTO },
265
    { "direct", SIXEL_CARRY_DISABLE },
266
    { "carry", SIXEL_CARRY_ENABLE }
267
};
268

269
static sixel_option_choice_t const g_option_choices_find_largest[] = {
270
    { "auto", SIXEL_LARGE_AUTO },
271
    { "norm", SIXEL_LARGE_NORM },
272
    { "lum", SIXEL_LARGE_LUM }
273
};
274

275
static sixel_option_choice_t const g_option_choices_select_color[] = {
276
    { "auto", SIXEL_REP_AUTO },
277
    { "center", SIXEL_REP_CENTER_BOX },
278
    { "average", SIXEL_REP_AVERAGE_COLORS },
279
    { "histogram", SIXEL_REP_AVERAGE_PIXELS },
280
    { "histgram", SIXEL_REP_AVERAGE_PIXELS }
281
};
282

283
static sixel_option_choice_t const g_option_choices_quantize_model[] = {
284
    { "auto", SIXEL_QUANTIZE_MODEL_AUTO },
285
    { "heckbert", SIXEL_QUANTIZE_MODEL_MEDIANCUT },
286
    { "kmeans", SIXEL_QUANTIZE_MODEL_KMEANS }
287
};
288

289
static sixel_option_choice_t const g_option_choices_final_merge[] = {
290
    { "auto", SIXEL_FINAL_MERGE_AUTO },
291
    { "none", SIXEL_FINAL_MERGE_NONE },
292
    { "ward", SIXEL_FINAL_MERGE_WARD },
293
    { "hkmeans", SIXEL_FINAL_MERGE_HKMEANS }
294
};
295

296
static sixel_option_choice_t const g_option_choices_resampling[] = {
297
    { "nearest", SIXEL_RES_NEAREST },
298
    { "gaussian", SIXEL_RES_GAUSSIAN },
299
    { "hanning", SIXEL_RES_HANNING },
300
    { "hamming", SIXEL_RES_HAMMING },
301
    { "bilinear", SIXEL_RES_BILINEAR },
302
    { "welsh", SIXEL_RES_WELSH },
303
    { "bicubic", SIXEL_RES_BICUBIC },
304
    { "lanczos2", SIXEL_RES_LANCZOS2 },
305
    { "lanczos3", SIXEL_RES_LANCZOS3 },
306
    { "lanczos4", SIXEL_RES_LANCZOS4 }
307
};
308

309
static sixel_option_choice_t const g_option_choices_quality[] = {
310
    { "auto", SIXEL_QUALITY_AUTO },
311
    { "high", SIXEL_QUALITY_HIGH },
312
    { "low", SIXEL_QUALITY_LOW },
313
    { "full", SIXEL_QUALITY_FULL }
314
};
315

316
static sixel_option_choice_t const g_option_choices_loopmode[] = {
317
    { "auto", SIXEL_LOOP_AUTO },
318
    { "force", SIXEL_LOOP_FORCE },
319
    { "disable", SIXEL_LOOP_DISABLE }
320
};
321

322
static sixel_option_choice_t const g_option_choices_palette_type[] = {
323
    { "auto", SIXEL_PALETTETYPE_AUTO },
324
    { "hls", SIXEL_PALETTETYPE_HLS },
325
    { "rgb", SIXEL_PALETTETYPE_RGB }
326
};
327

328
static sixel_option_choice_t const g_option_choices_encode_policy[] = {
329
    { "auto", SIXEL_ENCODEPOLICY_AUTO },
330
    { "fast", SIXEL_ENCODEPOLICY_FAST },
331
    { "size", SIXEL_ENCODEPOLICY_SIZE }
332
};
333

334
static sixel_option_choice_t const g_option_choices_lut_policy[] = {
335
    { "auto", SIXEL_LUT_POLICY_AUTO },
336
    { "5bit", SIXEL_LUT_POLICY_5BIT },
337
    { "6bit", SIXEL_LUT_POLICY_6BIT },
338
    { "none", SIXEL_LUT_POLICY_NONE },
339
    { "certlut", SIXEL_LUT_POLICY_CERTLUT }
340
};
341

342
static sixel_option_choice_t const g_option_choices_working_colorspace[] = {
343
    { "gamma", SIXEL_COLORSPACE_GAMMA },
344
    { "linear", SIXEL_COLORSPACE_LINEAR },
345
    { "oklab", SIXEL_COLORSPACE_OKLAB }
346
};
347

348
static sixel_option_choice_t const g_option_choices_output_colorspace[] = {
349
    { "gamma", SIXEL_COLORSPACE_GAMMA },
350
    { "linear", SIXEL_COLORSPACE_LINEAR },
351
    { "smpte-c", SIXEL_COLORSPACE_SMPTEC },
352
    { "smptec", SIXEL_COLORSPACE_SMPTEC }
353
};
354

355
static sixel_option_choice_t const g_option_choices_precision[] = {
356
    { "auto", SIXEL_ENCODER_PRECISION_MODE_AUTO },
357
    { "8bit", SIXEL_ENCODER_PRECISION_MODE_8BIT },
358
    { "float32", SIXEL_ENCODER_PRECISION_MODE_FLOAT32 }
359
};
360

361

362
static char *
363
arg_strdup(
57✔
364
    char const          /* in */ *s,          /* source buffer */
365
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
366
                                                 destination buffer */
367
{
368
    char *p;
369
    size_t len;
370

371
    len = strlen(s);
57✔
372

373
    p = (char *)sixel_allocator_malloc(allocator, len + 1);
57✔
374
    if (p) {
57!
375
        (void)sixel_compat_strcpy(p, len + 1, s);
57✔
376
    }
19✔
377
    return p;
57✔
378
}
379

380
static SIXELSTATUS
381
sixel_encoder_apply_precision_override(
×
382
    sixel_encoder_precision_mode_t mode)
383
{
384
    char const *value;
385

386
    value = NULL;
×
387

388
    if (mode == SIXEL_ENCODER_PRECISION_MODE_AUTO) {
×
389
        return SIXEL_OK;
×
390
    }
391

392
    if (mode == SIXEL_ENCODER_PRECISION_MODE_FLOAT32) {
×
393
        value = "1";
×
394
    } else if (mode == SIXEL_ENCODER_PRECISION_MODE_8BIT) {
×
395
        value = "0";
×
396
    } else {
397
        sixel_helper_set_additional_message(
×
398
            "sixel_encoder_setopt: invalid precision override.");
399
        return SIXEL_BAD_ARGUMENT;
×
400
    }
401

402
    if (sixel_compat_setenv(SIXEL_ENCODER_PRECISION_ENVVAR, value) != 0) {
×
403
        sixel_helper_set_additional_message(
×
404
            "sixel_encoder_setopt: failed to set SIXEL_FLOAT32_DITHER.");
405
        return SIXEL_LIBC_ERROR;
×
406
    }
407

408
    return SIXEL_OK;
×
409
}
410

411

412
/* An clone function of XColorSpec() of xlib */
413
static SIXELSTATUS
414
sixel_parse_x_colorspec(
45✔
415
    unsigned char       /* out */ **bgcolor,     /* destination buffer */
416
    char const          /* in */  *s,            /* source buffer */
417
    sixel_allocator_t   /* in */  *allocator)    /* allocator object for
418
                                                    destination buffer */
419
{
420
    SIXELSTATUS status = SIXEL_FALSE;
45✔
421
    char *p;
422
    unsigned char components[3];
423
    int component_index = 0;
45✔
424
    unsigned long v;
425
    char *endptr;
426
    char *buf = NULL;
45✔
427
    struct color const *pcolor;
428

429
    /* from rgb_lookup.h generated by gpref */
430
    pcolor = lookup_rgb(s, strlen(s));
45✔
431
    if (pcolor) {
45✔
432
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
3✔
433
        if (*bgcolor == NULL) {
3!
434
            sixel_helper_set_additional_message(
×
435
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
436
            status = SIXEL_BAD_ALLOCATION;
×
437
            goto end;
×
438
        }
439
        (*bgcolor)[0] = pcolor->r;
3✔
440
        (*bgcolor)[1] = pcolor->g;
3✔
441
        (*bgcolor)[2] = pcolor->b;
3✔
442
    } else if (s[0] == 'r' && s[1] == 'g' && s[2] == 'b' && s[3] == ':') {
43!
443
        p = buf = arg_strdup(s + 4, allocator);
6✔
444
        if (buf == NULL) {
6!
445
            sixel_helper_set_additional_message(
×
446
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
447
            status = SIXEL_BAD_ALLOCATION;
×
448
            goto end;
×
449
        }
450
        while (*p) {
15!
451
            v = 0;
15✔
452
            for (endptr = p; endptr - p <= 12; ++endptr) {
36!
453
                if (*endptr >= '0' && *endptr <= '9') {
36✔
454
                    v = (v << 4) | (unsigned long)(*endptr - '0');
15✔
455
                } else if (*endptr >= 'a' && *endptr <= 'f') {
26!
456
                    v = (v << 4) | (unsigned long)(*endptr - 'a' + 10);
3✔
457
                } else if (*endptr >= 'A' && *endptr <= 'F') {
19!
458
                    v = (v << 4) | (unsigned long)(*endptr - 'A' + 10);
3✔
459
                } else {
1✔
460
                    break;
5✔
461
                }
462
            }
7✔
463
            if (endptr - p == 0) {
15!
464
                break;
×
465
            }
466
            if (endptr - p > 4) {
15!
467
                break;
×
468
            }
469
            v = v << ((4 - (endptr - p)) * 4) >> 8;
15✔
470
            components[component_index++] = (unsigned char)v;
15✔
471
            p = endptr;
15✔
472
            if (component_index == 3) {
15✔
473
                break;
3✔
474
            }
475
            if (*p == '\0') {
12✔
476
                break;
3✔
477
            }
478
            if (*p != '/') {
9!
479
                break;
×
480
            }
481
            ++p;
9✔
482
        }
483
        if (component_index != 3 || *p != '\0' || *p == '/') {
6!
484
            status = SIXEL_BAD_ARGUMENT;
3✔
485
            goto end;
3✔
486
        }
487
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
3✔
488
        if (*bgcolor == NULL) {
3!
489
            sixel_helper_set_additional_message(
×
490
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
491
            status = SIXEL_BAD_ALLOCATION;
×
492
            goto end;
×
493
        }
494
        (*bgcolor)[0] = components[0];
3✔
495
        (*bgcolor)[1] = components[1];
3✔
496
        (*bgcolor)[2] = components[2];
3✔
497
    } else if (*s == '#') {
37✔
498
        buf = arg_strdup(s + 1, allocator);
27✔
499
        if (buf == NULL) {
27!
500
            sixel_helper_set_additional_message(
×
501
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
502
            status = SIXEL_BAD_ALLOCATION;
×
503
            goto end;
×
504
        }
505
        for (p = endptr = buf; endptr - p <= 12; ++endptr) {
192✔
506
            if (*endptr >= '0' && *endptr <= '9') {
189✔
507
                *endptr -= '0';
99✔
508
            } else if (*endptr >= 'a' && *endptr <= 'f') {
123!
509
                *endptr -= 'a' - 10;
57✔
510
            } else if (*endptr >= 'A' && *endptr <= 'F') {
52✔
511
                *endptr -= 'A' - 10;
9✔
512
            } else if (*endptr == '\0') {
27✔
513
                break;
21✔
514
            } else {
515
                status = SIXEL_BAD_ARGUMENT;
3✔
516
                goto end;
3✔
517
            }
518
        }
55✔
519
        if (endptr - p > 12) {
24✔
520
            status = SIXEL_BAD_ARGUMENT;
3✔
521
            goto end;
3✔
522
        }
523
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
21✔
524
        if (*bgcolor == NULL) {
21!
525
            sixel_helper_set_additional_message(
×
526
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
527
            status = SIXEL_BAD_ALLOCATION;
×
528
            goto end;
×
529
        }
530
        switch (endptr - p) {
21✔
531
        case 3:
6✔
532
            (*bgcolor)[0] = (unsigned char)(p[0] << 4);
9✔
533
            (*bgcolor)[1] = (unsigned char)(p[1] << 4);
9✔
534
            (*bgcolor)[2] = (unsigned char)(p[2] << 4);
9✔
535
            break;
9✔
536
        case 6:
2✔
537
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
538
            (*bgcolor)[1] = (unsigned char)(p[2] << 4 | p[3]);
3✔
539
            (*bgcolor)[2] = (unsigned char)(p[4] << 4 | p[4]);
3✔
540
            break;
3✔
541
        case 9:
2✔
542
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
543
            (*bgcolor)[1] = (unsigned char)(p[3] << 4 | p[4]);
3✔
544
            (*bgcolor)[2] = (unsigned char)(p[6] << 4 | p[7]);
3✔
545
            break;
3✔
546
        case 12:
2✔
547
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
548
            (*bgcolor)[1] = (unsigned char)(p[4] << 4 | p[5]);
3✔
549
            (*bgcolor)[2] = (unsigned char)(p[8] << 4 | p[9]);
3✔
550
            break;
3✔
551
        default:
2✔
552
            status = SIXEL_BAD_ARGUMENT;
3✔
553
            goto end;
3✔
554
        }
555
    } else {
6✔
556
        status = SIXEL_BAD_ARGUMENT;
9✔
557
        goto end;
9✔
558
    }
559

560
    status = SIXEL_OK;
24✔
561

562
end:
30✔
563
    sixel_allocator_free(allocator, buf);
45✔
564

565
    return status;
45✔
566
}
567

568

569
/* generic writer function for passing to sixel_output_new() */
570
static int
571
sixel_write_callback(char *data, int size, void *priv)
5,888✔
572
{
573
    int result;
574

575
    result = (int)sixel_compat_write(*(int *)priv,
8,048✔
576
                                     data,
2,160✔
577
                                     (size_t)size);
2,160✔
578

579
    return result;
5,888✔
580
}
581

582

583
/* the writer function with hex-encoding for passing to sixel_output_new() */
584
static int
585
sixel_hex_write_callback(
72✔
586
    char    /* in */ *data,
587
    int     /* in */ size,
588
    void    /* in */ *priv)
589
{
590
    char hex[SIXEL_OUTPUT_PACKET_SIZE * 2];
591
    int i;
592
    int j;
593
    int result;
594

595
    for (i = j = 0; i < size; ++i, ++j) {
701,274✔
596
        hex[j] = (data[i] >> 4) & 0xf;
701,202✔
597
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
701,202!
598
        hex[++j] = data[i] & 0xf;
701,202✔
599
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
701,202✔
600
    }
233,734✔
601

602
    result = (int)sixel_compat_write(*(int *)priv,
144✔
603
                                     hex,
24✔
604
                                     (size_t)(size * 2));
72✔
605

606
    return result;
72✔
607
}
608

609
typedef struct sixel_encoder_output_probe {
610
    sixel_encoder_t *encoder;
611
    sixel_write_function base_write;
612
    void *base_priv;
613
} sixel_encoder_output_probe_t;
614

615
static int
616
sixel_write_with_probe(char *data, int size, void *priv)
72✔
617
{
618
    sixel_encoder_output_probe_t *probe;
619
    int written;
620
    double started_at;
621
    double finished_at;
622
    double duration;
623

624
    probe = (sixel_encoder_output_probe_t *)priv;
72✔
625
    if (probe == NULL || probe->base_write == NULL) {
72!
626
        return 0;
×
627
    }
628
    started_at = 0.0;
72✔
629
    finished_at = 0.0;
72✔
630
    duration = 0.0;
72✔
631
    if (probe->encoder != NULL &&
72!
632
            probe->encoder->assessment_observer != NULL) {
72!
633
        started_at = sixel_assessment_timer_now();
72✔
634
    }
24✔
635
    written = probe->base_write(data, size, probe->base_priv);
72✔
636
    if (probe->encoder != NULL &&
72!
637
            probe->encoder->assessment_observer != NULL) {
72!
638
        finished_at = sixel_assessment_timer_now();
72✔
639
        duration = finished_at - started_at;
72✔
640
        if (duration < 0.0) {
72!
641
            duration = 0.0;
×
642
        }
643
    }
24✔
644
    if (written > 0 && probe->encoder != NULL &&
72!
645
            probe->encoder->assessment_observer != NULL) {
72!
646
        sixel_assessment_record_output_write(
72✔
647
            probe->encoder->assessment_observer,
72✔
648
            (size_t)written,
24✔
649
            duration);
24✔
650
    }
24✔
651
    return written;
72✔
652
}
24✔
653

654
/*
655
 * Reuse the fn_write probe for raw escape writes so that every
656
 * assessment bucket receives the same accounting.
657
 *
658
 *     encoder        probe wrapper       write(2)
659
 *     +------+    +----------------+    +---------+
660
 *     | data | -> | sixel_write_*  | -> | target  |
661
 *     +------+    +----------------+    +---------+
662
 */
663
static int
664
sixel_encoder_probe_fd_write(sixel_encoder_t *encoder,
×
665
                             char *data,
666
                             int size,
667
                             int fd)
668
{
669
    sixel_encoder_output_probe_t probe;
670
    int written;
671

672
    probe.encoder = encoder;
×
673
    probe.base_write = sixel_write_callback;
×
674
    probe.base_priv = &fd;
×
675
    written = sixel_write_with_probe(data, size, &probe);
×
676

677
    return written;
×
678
}
679

680
static SIXELSTATUS
681
sixel_encoder_ensure_cell_size(sixel_encoder_t *encoder)
×
682
{
683
#if defined(TIOCGWINSZ)
684
    struct winsize ws;
685
    int result;
686
    int fd = 0;
×
687

688
    if (encoder->cell_width > 0 && encoder->cell_height > 0) {
×
689
        return SIXEL_OK;
×
690
    }
691

692
    fd = sixel_compat_open("/dev/tty", O_RDONLY);
×
693
    if (fd >= 0) {
×
694
        result = ioctl(fd, TIOCGWINSZ, &ws);
×
695
        (void)sixel_compat_close(fd);
×
696
    } else {
697
        sixel_helper_set_additional_message(
×
698
            "failed to open /dev/tty");
699
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
700
    }
701
    if (result != 0) {
×
702
        sixel_helper_set_additional_message(
×
703
            "failed to query terminal geometry with ioctl().");
704
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
705
    }
706

707
    if (ws.ws_col <= 0 || ws.ws_row <= 0 ||
×
708
        ws.ws_xpixel <= ws.ws_col || ws.ws_ypixel <= ws.ws_row) {
×
709
        sixel_helper_set_additional_message(
×
710
            "terminal does not report pixel cell size for drcs option.");
711
        return SIXEL_BAD_ARGUMENT;
×
712
    }
713

714
    encoder->cell_width = ws.ws_xpixel / ws.ws_col;
×
715
    encoder->cell_height = ws.ws_ypixel / ws.ws_row;
×
716
    if (encoder->cell_width <= 0 || encoder->cell_height <= 0) {
×
717
        sixel_helper_set_additional_message(
×
718
            "terminal cell size reported zero via ioctl().");
719
        return SIXEL_BAD_ARGUMENT;
×
720
    }
721

722
    return SIXEL_OK;
×
723
#else
724
    (void) encoder;
725
    sixel_helper_set_additional_message(
726
        "drcs option is not supported on this platform.");
727
    return SIXEL_NOT_IMPLEMENTED;
728
#endif
729
}
730

731

732
/* returns monochrome dithering context object */
733
static SIXELSTATUS
734
sixel_prepare_monochrome_palette(
12✔
735
    sixel_dither_t  /* out */ **dither,
736
     int            /* in */  finvert)
737
{
738
    SIXELSTATUS status = SIXEL_FALSE;
12✔
739

740
    if (finvert) {
12✔
741
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_LIGHT);
3✔
742
    } else {
1✔
743
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_DARK);
9✔
744
    }
745
    if (*dither == NULL) {
12!
746
        sixel_helper_set_additional_message(
×
747
            "sixel_prepare_monochrome_palette: sixel_dither_get() failed.");
748
        status = SIXEL_RUNTIME_ERROR;
×
749
        goto end;
×
750
    }
751

752
    status = SIXEL_OK;
12✔
753

754
end:
8✔
755
    return status;
12✔
756
}
757

758

759
static SIXELSTATUS
760
sixel_encoder_capture_quantized(sixel_encoder_t *encoder,
3✔
761
                                sixel_dither_t *dither,
762
                                unsigned char const *pixels,
763
                                size_t size,
764
                                int width,
765
                                int height,
766
                                int pixelformat,
767
                                int source_colorspace,
768
                                int colorspace)
769
{
770
    SIXELSTATUS status;
771
    int ncolors;
772
    size_t palette_bytes;
773
    unsigned char *new_pixels;
774
    unsigned char *new_palette;
775
    size_t capture_bytes;
776
    unsigned char const *capture_source;
777
    sixel_index_t *paletted_pixels;
778
    size_t quantized_pixels;
779
    sixel_allocator_t *dither_allocator;
780
    int saved_pixelformat;
781
    int restore_pixelformat;
782

783
    /*
784
     * Preserve the quantized frame for assessment observers.
785
     *
786
     *     +-----------------+     +---------------------+
787
     *     | quantized bytes | --> | encoder->capture_*  |
788
     *     +-----------------+     +---------------------+
789
     */
790

791
    status = SIXEL_OK;
3✔
792
    ncolors = 0;
3✔
793
    palette_bytes = 0;
3✔
794
    new_pixels = NULL;
3✔
795
    new_palette = NULL;
3✔
796
    capture_bytes = size;
3✔
797
    capture_source = pixels;
3✔
798
    paletted_pixels = NULL;
3✔
799
    quantized_pixels = 0;
3✔
800
    dither_allocator = NULL;
3✔
801

802
    if (encoder == NULL || pixels == NULL ||
3!
803
            (dither == NULL && size == 0)) {
1!
804
        sixel_helper_set_additional_message(
×
805
            "sixel_encoder_capture_quantized: invalid capture request.");
806
        return SIXEL_BAD_ARGUMENT;
×
807
    }
808

809
    if (!encoder->capture_quantized) {
3!
810
        return SIXEL_OK;
×
811
    }
812

813
    saved_pixelformat = SIXEL_PIXELFORMAT_RGB888;
3✔
814
    restore_pixelformat = 0;
3✔
815
    if (dither != NULL) {
3!
816
        dither_allocator = dither->allocator;
3✔
817
        saved_pixelformat = dither->pixelformat;
3✔
818
        restore_pixelformat = 1;
3✔
819
        if (width <= 0 || height <= 0) {
3!
820
            sixel_helper_set_additional_message(
×
821
                "sixel_encoder_capture_quantized: invalid dimensions.");
822
            status = SIXEL_BAD_ARGUMENT;
×
823
            goto cleanup;
×
824
        }
825
        quantized_pixels = (size_t)width * (size_t)height;
3✔
826
        if (height != 0 &&
3!
827
                quantized_pixels / (size_t)height != (size_t)width) {
3!
828
            sixel_helper_set_additional_message(
×
829
                "sixel_encoder_capture_quantized: image too large.");
830
            status = SIXEL_RUNTIME_ERROR;
×
831
            goto cleanup;
×
832
        }
833
        paletted_pixels = sixel_dither_apply_palette(
3✔
834
            dither, (unsigned char *)pixels, width, height);
1✔
835
        if (paletted_pixels == NULL) {
3!
836
            sixel_helper_set_additional_message(
×
837
                "sixel_encoder_capture_quantized: palette conversion failed.");
838
            status = SIXEL_RUNTIME_ERROR;
×
839
            goto cleanup;
×
840
        }
841
        capture_source = (unsigned char const *)paletted_pixels;
3✔
842
        capture_bytes = quantized_pixels;
3✔
843
    }
1✔
844

845
    if (capture_bytes > 0) {
3!
846
        if (encoder->capture_pixels == NULL ||
3!
847
                encoder->capture_pixels_size < capture_bytes) {
×
848
            new_pixels = (unsigned char *)sixel_allocator_malloc(
3✔
849
                encoder->allocator, capture_bytes);
1✔
850
            if (new_pixels == NULL) {
3!
851
                sixel_helper_set_additional_message(
×
852
                    "sixel_encoder_capture_quantized: "
853
                    "sixel_allocator_malloc() failed.");
854
                status = SIXEL_BAD_ALLOCATION;
×
855
                goto cleanup;
×
856
            }
857
            sixel_allocator_free(encoder->allocator, encoder->capture_pixels);
3✔
858
            encoder->capture_pixels = new_pixels;
3✔
859
            encoder->capture_pixels_size = capture_bytes;
3✔
860
        }
1✔
861
        memcpy(encoder->capture_pixels, capture_source, capture_bytes);
3✔
862
    }
1✔
863
    encoder->capture_pixel_bytes = capture_bytes;
3✔
864

865
    ncolors = 0;
3✔
866
    palette_bytes = 0;
3✔
867
    if (dither != NULL) {
3!
868
        sixel_palette_t *palette_obj = NULL;
3✔
869
        unsigned char *palette_copy = NULL;
3✔
870
        size_t palette_count = 0U;
3✔
871

872
        status = sixel_dither_get_quantized_palette(dither, &palette_obj);
3✔
873
        if (SIXEL_SUCCEEDED(status) && palette_obj != NULL) {
3!
874
            status = sixel_palette_copy_entries_8bit(
3✔
875
                palette_obj,
1✔
876
                &palette_copy,
877
                &palette_count,
878
                SIXEL_PIXELFORMAT_RGB888,
879
                encoder->allocator);
1✔
880
            sixel_palette_unref(palette_obj);
3✔
881
            palette_obj = NULL;
3✔
882
            if (SIXEL_SUCCEEDED(status)
4!
883
                    && palette_copy != NULL
3!
884
                    && palette_count > 0U) {
3!
885
                palette_bytes = palette_count * 3U;
3✔
886
                ncolors = (int)palette_count;
3✔
887
                if (encoder->capture_palette == NULL
3!
888
                        || encoder->capture_palette_size < palette_bytes) {
1!
889
                    new_palette = (unsigned char *)sixel_allocator_malloc(
3✔
890
                        encoder->allocator, palette_bytes);
1✔
891
                    if (new_palette == NULL) {
3!
892
                        sixel_helper_set_additional_message(
×
893
                            "sixel_encoder_capture_quantized: "
894
                            "sixel_allocator_malloc() failed.");
895
                        status = SIXEL_BAD_ALLOCATION;
×
896
                        sixel_allocator_free(encoder->allocator,
×
897
                                             palette_copy);
898
                        goto cleanup;
×
899
                    }
900
                    sixel_allocator_free(encoder->allocator,
4✔
901
                                         encoder->capture_palette);
3✔
902
                    encoder->capture_palette = new_palette;
3✔
903
                    encoder->capture_palette_size = palette_bytes;
3✔
904
                }
1✔
905
                memcpy(encoder->capture_palette,
3✔
906
                       palette_copy,
907
                       palette_bytes);
908
                if (source_colorspace != colorspace) {
3!
NEW
909
                    (void)sixel_helper_convert_colorspace(
×
910
                        encoder->capture_palette,
911
                        palette_bytes,
912
                        SIXEL_PIXELFORMAT_RGB888,
913
                        source_colorspace,
914
                        colorspace);
915
                }
916
            }
1✔
917
            if (palette_copy != NULL) {
3!
918
                sixel_allocator_free(encoder->allocator, palette_copy);
3✔
919
            }
1✔
920
        }
1✔
921
    }
1✔
922

923
    encoder->capture_width = width;
3✔
924
    encoder->capture_height = height;
3✔
925
    if (dither != NULL) {
3!
926
        encoder->capture_pixelformat = SIXEL_PIXELFORMAT_PAL8;
3✔
927
    } else {
1✔
928
        encoder->capture_pixelformat = pixelformat;
×
929
    }
930
    encoder->capture_colorspace = colorspace;
3✔
931
    encoder->capture_palette_size = palette_bytes;
3✔
932
    encoder->capture_ncolors = ncolors;
3✔
933
    encoder->capture_valid = 1;
3✔
934

935
cleanup:
2✔
936
    if (restore_pixelformat && dither != NULL) {
3!
937
        /*
938
         * Undo the normalization performed by sixel_dither_apply_palette().
939
         *
940
         *     RGBA8888 --capture--> RGB888 (temporary)
941
         *          \______________________________/
942
         *                          |
943
         *                 restore original state for
944
         *                 the real encoder execution.
945
         */
946
        sixel_dither_set_pixelformat(dither, saved_pixelformat);
3✔
947
    }
1✔
948
    if (paletted_pixels != NULL && dither_allocator != NULL) {
3!
949
        sixel_allocator_free(dither_allocator, paletted_pixels);
3✔
950
    }
1✔
951

952
    return status;
3✔
953
}
1✔
954

955
static SIXELSTATUS
956
sixel_prepare_builtin_palette(
27✔
957
    sixel_dither_t /* out */ **dither,
958
    int            /* in */  builtin_palette)
959
{
960
    SIXELSTATUS status = SIXEL_FALSE;
27✔
961

962
    *dither = sixel_dither_get(builtin_palette);
27✔
963
    if (*dither == NULL) {
27!
964
        sixel_helper_set_additional_message(
×
965
            "sixel_prepare_builtin_palette: sixel_dither_get() failed.");
966
        status = SIXEL_RUNTIME_ERROR;
×
967
        goto end;
×
968
    }
969

970
    status = SIXEL_OK;
27✔
971

972
end:
18✔
973
    return status;
27✔
974
}
975

976
static int
977
sixel_encoder_thumbnail_hint(sixel_encoder_t *encoder)
457✔
978
{
979
    int width_hint;
980
    int height_hint;
981
    long base;
982
    long size;
983

984
    width_hint = 0;
457✔
985
    height_hint = 0;
457✔
986
    base = 0;
457✔
987
    size = 0;
457✔
988

989
    if (encoder == NULL) {
457!
990
        return 0;
×
991
    }
992

993
    width_hint = encoder->pixelwidth;
457✔
994
    height_hint = encoder->pixelheight;
457✔
995

996
    /* Request extra resolution for downscaling to preserve detail. */
997
    if (width_hint > 0 && height_hint > 0) {
457✔
998
        /* Follow the CLI rule: double the larger axis before doubling
999
         * again for the final request size. */
1000
        if (width_hint >= height_hint) {
9!
1001
            base = (long)width_hint;
9✔
1002
        } else {
3✔
1003
            base = (long)height_hint;
×
1004
        }
1005
        base *= 2L;
9✔
1006
    } else if (width_hint > 0) {
451✔
1007
        base = (long)width_hint;
48✔
1008
    } else if (height_hint > 0) {
416✔
1009
        base = (long)height_hint;
36✔
1010
    } else {
12✔
1011
        return 0;
364✔
1012
    }
1013

1014
    size = base * 2L;
93✔
1015
    if (size > (long)INT_MAX) {
93!
1016
        size = (long)INT_MAX;
×
1017
    }
1018
    if (size < 1L) {
93!
1019
        size = 1L;
×
1020
    }
1021

1022
    return (int)size;
93✔
1023
}
153✔
1024

1025

1026
typedef struct sixel_callback_context_for_mapfile {
1027
    int reqcolors;
1028
    sixel_dither_t *dither;
1029
    sixel_allocator_t *allocator;
1030
    int working_colorspace;
1031
    int lut_policy;
1032
} sixel_callback_context_for_mapfile_t;
1033

1034

1035
/* callback function for sixel_helper_load_image_file() */
1036
static SIXELSTATUS
1037
load_image_callback_for_palette(
21✔
1038
    sixel_frame_t   /* in */    *frame, /* frame object from image loader */
1039
    void            /* in */    *data)  /* private data */
1040
{
1041
    SIXELSTATUS status = SIXEL_FALSE;
21✔
1042
    sixel_callback_context_for_mapfile_t *callback_context;
1043

1044
    /* get callback context object from the private data */
1045
    callback_context = (sixel_callback_context_for_mapfile_t *)data;
21✔
1046

1047
    status = sixel_frame_ensure_colorspace(frame,
28✔
1048
                                           callback_context->working_colorspace);
7✔
1049
    if (SIXEL_FAILED(status)) {
21!
1050
        goto end;
×
1051
    }
1052

1053
    switch (sixel_frame_get_pixelformat(frame)) {
21!
1054
    case SIXEL_PIXELFORMAT_PAL1:
2✔
1055
    case SIXEL_PIXELFORMAT_PAL2:
1056
    case SIXEL_PIXELFORMAT_PAL4:
1057
    case SIXEL_PIXELFORMAT_PAL8:
1058
        if (sixel_frame_get_palette(frame) == NULL) {
3!
1059
            status = SIXEL_LOGIC_ERROR;
×
1060
            goto end;
×
1061
        }
1062
        /* create new dither object */
1063
        status = sixel_dither_new(
3✔
1064
            &callback_context->dither,
1✔
1065
            sixel_frame_get_ncolors(frame),
1✔
1066
            callback_context->allocator);
1✔
1067
        if (SIXEL_FAILED(status)) {
3!
1068
            goto end;
×
1069
        }
1070

1071
        sixel_dither_set_lut_policy(callback_context->dither,
4✔
1072
                                    callback_context->lut_policy);
1✔
1073

1074
        /* use palette which is extracted from the image */
1075
        sixel_dither_set_palette(callback_context->dither,
4✔
1076
                                 sixel_frame_get_palette(frame));
1✔
1077
        /* success */
1078
        status = SIXEL_OK;
3✔
1079
        break;
3✔
1080
    case SIXEL_PIXELFORMAT_G1:
1081
        /* use 1bpp grayscale builtin palette */
1082
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1083
        /* success */
1084
        status = SIXEL_OK;
×
1085
        break;
×
1086
    case SIXEL_PIXELFORMAT_G2:
1087
        /* use 2bpp grayscale builtin palette */
1088
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1089
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
1090
        /* success */
1091
        status = SIXEL_OK;
×
1092
        break;
×
1093
    case SIXEL_PIXELFORMAT_G4:
1094
        /* use 4bpp grayscale builtin palette */
1095
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
1096
        /* success */
1097
        status = SIXEL_OK;
×
1098
        break;
×
1099
    case SIXEL_PIXELFORMAT_G8:
1100
        /* use 8bpp grayscale builtin palette */
1101
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
1102
        /* success */
1103
        status = SIXEL_OK;
×
1104
        break;
×
1105
    default:
12✔
1106
        /* create new dither object */
1107
        status = sixel_dither_new(
18✔
1108
            &callback_context->dither,
6✔
1109
            callback_context->reqcolors,
6✔
1110
            callback_context->allocator);
6✔
1111
        if (SIXEL_FAILED(status)) {
18!
1112
            goto end;
×
1113
        }
1114

1115
        sixel_dither_set_lut_policy(callback_context->dither,
24✔
1116
                                    callback_context->lut_policy);
6✔
1117

1118
        /* create adaptive palette from given frame object */
1119
        status = sixel_dither_initialize(callback_context->dither,
24✔
1120
                                         sixel_frame_get_pixels(frame),
6✔
1121
                                         sixel_frame_get_width(frame),
6✔
1122
                                         sixel_frame_get_height(frame),
6✔
1123
                                         sixel_frame_get_pixelformat(frame),
6✔
1124
                                         SIXEL_LARGE_NORM,
1125
                                         SIXEL_REP_CENTER_BOX,
1126
                                         SIXEL_QUALITY_HIGH);
1127
        if (SIXEL_FAILED(status)) {
18!
1128
            sixel_dither_unref(callback_context->dither);
×
1129
            goto end;
×
1130
        }
1131

1132
        /* success */
1133
        status = SIXEL_OK;
18✔
1134

1135
        break;
18✔
1136
    }
7✔
1137

1138
end:
14✔
1139
    return status;
21✔
1140
}
1141

1142

1143
static SIXELSTATUS
1144
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder);
1145

1146

1147
static int
1148
sixel_path_has_extension(char const *path, char const *extension)
63✔
1149
{
1150
    size_t path_len;
1151
    size_t ext_len;
1152
    size_t index;
1153

1154
    path_len = 0u;
63✔
1155
    ext_len = 0u;
63✔
1156
    index = 0u;
63✔
1157

1158
    if (path == NULL || extension == NULL) {
63!
1159
        return 0;
×
1160
    }
1161

1162
    path_len = strlen(path);
63✔
1163
    ext_len = strlen(extension);
63✔
1164
    if (ext_len == 0u || path_len < ext_len) {
63!
1165
        return 0;
×
1166
    }
1167

1168
    for (index = 0u; index < ext_len; ++index) {
144!
1169
        unsigned char path_ch;
1170
        unsigned char ext_ch;
1171

1172
        path_ch = (unsigned char)path[path_len - ext_len + index];
144✔
1173
        ext_ch = (unsigned char)extension[index];
144✔
1174
        if (tolower(path_ch) != tolower(ext_ch)) {
144✔
1175
            return 0;
63✔
1176
        }
1177
    }
27✔
1178

1179
    return 1;
×
1180
}
21✔
1181

1182
typedef enum sixel_palette_format {
1183
    SIXEL_PALETTE_FORMAT_NONE = 0,
1184
    SIXEL_PALETTE_FORMAT_ACT,
1185
    SIXEL_PALETTE_FORMAT_PAL_JASC,
1186
    SIXEL_PALETTE_FORMAT_PAL_RIFF,
1187
    SIXEL_PALETTE_FORMAT_PAL_AUTO,
1188
    SIXEL_PALETTE_FORMAT_GPL
1189
} sixel_palette_format_t;
1190

1191
/*
1192
 * Palette specification parser
1193
 *
1194
 *   TYPE:PATH  -> explicit format prefix
1195
 *   PATH       -> rely on extension or heuristics
1196
 *
1197
 * The ASCII diagram below shows how the prefix is peeled:
1198
 *
1199
 *   [type] : [path]
1200
 *    ^-- left part selects decoder/encoder when present.
1201
 */
1202
static char const *
1203
sixel_palette_strip_prefix(char const *spec,
48✔
1204
                           sixel_palette_format_t *format_hint)
1205
{
1206
    char const *colon;
1207
    size_t type_len;
1208
    size_t index;
1209
    char lowered[16];
1210

1211
    colon = NULL;
48✔
1212
    type_len = 0u;
48✔
1213
    index = 0u;
48✔
1214

1215
    if (format_hint != NULL) {
48✔
1216
        *format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
1217
    }
7✔
1218
    if (spec == NULL) {
48!
1219
        return NULL;
×
1220
    }
1221

1222
    colon = strchr(spec, ':');
48✔
1223
    if (colon == NULL) {
48!
1224
        return spec;
48✔
1225
    }
1226

1227
    type_len = (size_t)(colon - spec);
×
1228
    if (type_len == 0u || type_len >= sizeof(lowered)) {
×
1229
        return spec;
×
1230
    }
1231

1232
    for (index = 0u; index < type_len; ++index) {
×
1233
        lowered[index] = (char)tolower((unsigned char)spec[index]);
×
1234
    }
1235
    lowered[type_len] = '\0';
×
1236

1237
    if (strcmp(lowered, "act") == 0) {
×
1238
        if (format_hint != NULL) {
×
1239
            *format_hint = SIXEL_PALETTE_FORMAT_ACT;
×
1240
        }
1241
        return colon + 1;
×
1242
    }
1243
    if (strcmp(lowered, "pal") == 0) {
×
1244
        if (format_hint != NULL) {
×
1245
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1246
        }
1247
        return colon + 1;
×
1248
    }
1249
    if (strcmp(lowered, "pal-jasc") == 0) {
×
1250
        if (format_hint != NULL) {
×
1251
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1252
        }
1253
        return colon + 1;
×
1254
    }
1255
    if (strcmp(lowered, "pal-riff") == 0) {
×
1256
        if (format_hint != NULL) {
×
1257
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1258
        }
1259
        return colon + 1;
×
1260
    }
1261
    if (strcmp(lowered, "gpl") == 0) {
×
1262
        if (format_hint != NULL) {
×
1263
            *format_hint = SIXEL_PALETTE_FORMAT_GPL;
×
1264
        }
1265
        return colon + 1;
×
1266
    }
1267

1268
    return spec;
×
1269
}
16✔
1270

1271
static sixel_palette_format_t
1272
sixel_palette_format_from_extension(char const *path)
21✔
1273
{
1274
    if (path == NULL) {
21!
1275
        return SIXEL_PALETTE_FORMAT_NONE;
×
1276
    }
1277

1278
    if (sixel_path_has_extension(path, ".act")) {
21!
1279
        return SIXEL_PALETTE_FORMAT_ACT;
×
1280
    }
1281
    if (sixel_path_has_extension(path, ".pal")) {
21!
1282
        return SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1283
    }
1284
    if (sixel_path_has_extension(path, ".gpl")) {
21!
1285
        return SIXEL_PALETTE_FORMAT_GPL;
×
1286
    }
1287

1288
    return SIXEL_PALETTE_FORMAT_NONE;
21✔
1289
}
7✔
1290

1291
static int
1292
sixel_path_has_any_extension(char const *path)
21✔
1293
{
1294
    char const *slash_forward;
1295
#if defined(_WIN32)
1296
    char const *slash_backward;
1297
#endif
1298
    char const *start;
1299
    char const *dot;
1300

1301
    slash_forward = NULL;
21✔
1302
#if defined(_WIN32)
1303
    slash_backward = NULL;
1304
#endif
1305
    start = path;
21✔
1306
    dot = NULL;
21✔
1307

1308
    if (path == NULL) {
21!
1309
        return 0;
×
1310
    }
1311

1312
    slash_forward = strrchr(path, '/');
21✔
1313
#if defined(_WIN32)
1314
    slash_backward = strrchr(path, '\\');
1315
    if (slash_backward != NULL &&
1316
            (slash_forward == NULL || slash_backward > slash_forward)) {
1317
        slash_forward = slash_backward;
1318
    }
1319
#endif
1320
    if (slash_forward == NULL) {
21!
1321
        start = path;
×
1322
    } else {
1323
        start = slash_forward + 1;
21✔
1324
    }
1325

1326
    dot = strrchr(start, '.');
21✔
1327
    if (dot == NULL) {
21!
1328
        return 0;
×
1329
    }
1330

1331
    if (dot[1] == '\0') {
21!
1332
        return 0;
×
1333
    }
1334

1335
    return 1;
21✔
1336
}
7✔
1337

1338
static int
1339
sixel_palette_has_utf8_bom(unsigned char const *data, size_t size)
×
1340
{
1341
    if (data == NULL || size < 3u) {
×
1342
        return 0;
×
1343
    }
1344
    if (data[0] == 0xefu && data[1] == 0xbbu && data[2] == 0xbfu) {
×
1345
        return 1;
×
1346
    }
1347
    return 0;
×
1348
}
1349

1350

1351
/*
1352
 * Materialize palette bytes from a stream.
1353
 *
1354
 * The flow looks like:
1355
 *
1356
 *   stream --> [scratch buffer] --> [resizable heap buffer]
1357
 *                  ^ looped read        ^ returned payload
1358
 */
1359
static SIXELSTATUS
1360
sixel_palette_read_stream(FILE *stream,
×
1361
                          sixel_allocator_t *allocator,
1362
                          unsigned char **pdata,
1363
                          size_t *psize)
1364
{
1365
    SIXELSTATUS status;
1366
    unsigned char *buffer;
1367
    unsigned char *grown;
1368
    size_t capacity;
1369
    size_t used;
1370
    size_t read_bytes;
1371
    size_t needed;
1372
    size_t new_capacity;
1373
    unsigned char scratch[4096];
1374

1375
    status = SIXEL_FALSE;
×
1376
    buffer = NULL;
×
1377
    grown = NULL;
×
1378
    capacity = 0u;
×
1379
    used = 0u;
×
1380
    read_bytes = 0u;
×
1381
    needed = 0u;
×
1382
    new_capacity = 0u;
×
1383

1384
    if (pdata == NULL || psize == NULL || stream == NULL || allocator == NULL) {
×
1385
        sixel_helper_set_additional_message(
×
1386
            "sixel_palette_read_stream: invalid argument.");
1387
        return SIXEL_BAD_ARGUMENT;
×
1388
    }
1389

1390
    *pdata = NULL;
×
1391
    *psize = 0u;
×
1392

1393
    while (1) {
1394
        read_bytes = fread(scratch, 1, sizeof(scratch), stream);
×
1395
        if (read_bytes == 0u) {
×
1396
            if (ferror(stream)) {
×
1397
                sixel_helper_set_additional_message(
×
1398
                    "sixel_palette_read_stream: fread() failed.");
1399
                status = SIXEL_LIBC_ERROR;
×
1400
                goto cleanup;
×
1401
            }
1402
            break;
×
1403
        }
1404

1405
        if (used > SIZE_MAX - read_bytes) {
×
1406
            sixel_helper_set_additional_message(
×
1407
                "sixel_palette_read_stream: size overflow.");
1408
            status = SIXEL_BAD_ALLOCATION;
×
1409
            goto cleanup;
×
1410
        }
1411
        needed = used + read_bytes;
×
1412

1413
        if (needed > capacity) {
×
1414
            new_capacity = capacity;
×
1415
            if (new_capacity == 0u) {
×
1416
                new_capacity = 4096u;
×
1417
            }
1418
            while (needed > new_capacity) {
×
1419
                if (new_capacity > SIZE_MAX / 2u) {
×
1420
                    sixel_helper_set_additional_message(
×
1421
                        "sixel_palette_read_stream: size overflow.");
1422
                    status = SIXEL_BAD_ALLOCATION;
×
1423
                    goto cleanup;
×
1424
                }
1425
                new_capacity *= 2u;
×
1426
            }
1427

1428
            grown = (unsigned char *)sixel_allocator_malloc(allocator,
×
1429
                                                             new_capacity);
1430
            if (grown == NULL) {
×
1431
                sixel_helper_set_additional_message(
×
1432
                    "sixel_palette_read_stream: allocation failed.");
1433
                status = SIXEL_BAD_ALLOCATION;
×
1434
                goto cleanup;
×
1435
            }
1436

1437
            if (buffer != NULL) {
×
1438
                memcpy(grown, buffer, used);
×
1439
                sixel_allocator_free(allocator, buffer);
×
1440
            }
1441

1442
            buffer = grown;
×
1443
            grown = NULL;
×
1444
            capacity = new_capacity;
×
1445
        }
1446

1447
        memcpy(buffer + used, scratch, read_bytes);
×
1448
        used += read_bytes;
×
1449
    }
1450

1451
    *pdata = buffer;
×
1452
    *psize = used;
×
1453
    status = SIXEL_OK;
×
1454
    return status;
×
1455

1456
cleanup:
1457
    if (grown != NULL) {
×
1458
        sixel_allocator_free(allocator, grown);
×
1459
    }
1460
    if (buffer != NULL) {
×
1461
        sixel_allocator_free(allocator, buffer);
×
1462
    }
1463
    return status;
×
1464
}
1465

1466

1467
static SIXELSTATUS
1468
sixel_palette_open_read(char const *path, FILE **pstream, int *pclose)
×
1469
{
1470
    int error_value;
1471
    char error_message[256];
1472
#if HAVE_SYS_STAT_H
1473
    struct stat path_stat;
1474
#endif
1475

1476
    if (pstream == NULL || pclose == NULL || path == NULL) {
×
1477
        sixel_helper_set_additional_message(
×
1478
            "sixel_palette_open_read: invalid argument.");
1479
        return SIXEL_BAD_ARGUMENT;
×
1480
    }
1481

1482
    error_value = 0;
×
1483
    error_message[0] = '\0';
×
1484

1485
    if (strcmp(path, "-") == 0) {
×
1486
        *pstream = stdin;
×
1487
        *pclose = 0;
×
1488
        return SIXEL_OK;
×
1489
    }
1490

1491
#if HAVE_SYS_STAT_H
1492
    if (stat(path, &path_stat) == 0 && S_ISDIR(path_stat.st_mode)) {
×
1493
        sixel_compat_snprintf(error_message,
×
1494
                              sizeof(error_message),
1495
                              "sixel_palette_open_read: mapfile \"%s\" "
1496
                              "is a directory.",
1497
                              path);
1498
        sixel_helper_set_additional_message(error_message);
×
1499
        return SIXEL_BAD_INPUT;
×
1500
    }
1501
#endif
1502

1503
    errno = 0;
×
1504
    *pstream = fopen(path, "rb");
×
1505
    if (*pstream == NULL) {
×
1506
        error_value = errno;
×
1507
        sixel_compat_snprintf(error_message,
×
1508
                              sizeof(error_message),
1509
                              "sixel_palette_open_read: failed to open "
1510
                              "\"%s\": %s.",
1511
                              path,
1512
                              strerror(error_value));
1513
        sixel_helper_set_additional_message(error_message);
×
1514
        return SIXEL_LIBC_ERROR;
×
1515
    }
1516

1517
    *pclose = 1;
×
1518
    return SIXEL_OK;
×
1519
}
1520

1521

1522
static void
1523
sixel_palette_close_stream(FILE *stream, int close_stream)
×
1524
{
1525
    if (close_stream && stream != NULL) {
×
1526
        (void) fclose(stream);
×
1527
    }
1528
}
×
1529

1530

1531
static sixel_palette_format_t
1532
sixel_palette_guess_format(unsigned char const *data, size_t size)
×
1533
{
1534
    size_t offset;
1535
    size_t data_size;
1536

1537
    offset = 0u;
×
1538
    data_size = size;
×
1539

1540
    if (data == NULL || size == 0u) {
×
1541
        return SIXEL_PALETTE_FORMAT_NONE;
×
1542
    }
1543

1544
    if (size == 256u * 3u || size == 256u * 3u + 4u) {
×
1545
        return SIXEL_PALETTE_FORMAT_ACT;
×
1546
    }
1547

1548
    if (size >= 12u && memcmp(data, "RIFF", 4) == 0
×
1549
            && memcmp(data + 8, "PAL ", 4) == 0) {
×
1550
        return SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1551
    }
1552

1553
    if (sixel_palette_has_utf8_bom(data, size)) {
×
1554
        offset = 3u;
×
1555
        data_size = size - 3u;
×
1556
    }
1557

1558
    if (data_size >= 8u && memcmp(data + offset, "JASC-PAL", 8) == 0) {
×
1559
        return SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1560
    }
1561
    if (data_size >= 12u && memcmp(data + offset, "GIMP Palette", 12) == 0) {
×
1562
        return SIXEL_PALETTE_FORMAT_GPL;
×
1563
    }
1564

1565
    return SIXEL_PALETTE_FORMAT_NONE;
×
1566
}
1567

1568

1569
static unsigned int
1570
sixel_palette_read_le16(unsigned char const *ptr)
×
1571
{
1572
    if (ptr == NULL) {
×
1573
        return 0u;
×
1574
    }
1575
    return (unsigned int)ptr[0] | ((unsigned int)ptr[1] << 8);
×
1576
}
1577

1578

1579
static unsigned int
1580
sixel_palette_read_le32(unsigned char const *ptr)
×
1581
{
1582
    if (ptr == NULL) {
×
1583
        return 0u;
×
1584
    }
1585
    return ((unsigned int)ptr[0])
×
1586
        | ((unsigned int)ptr[1] << 8)
×
1587
        | ((unsigned int)ptr[2] << 16)
×
1588
        | ((unsigned int)ptr[3] << 24);
×
1589
}
1590

1591

1592
/*
1593
 * Adobe Color Table (*.act) reader
1594
 *
1595
 *   +-----------+---------------------------+
1596
 *   | section   | bytes                     |
1597
 *   +-----------+---------------------------+
1598
 *   | palette   | 256 entries * 3 RGB bytes |
1599
 *   | trailer   | optional count/start pair |
1600
 *   +-----------+---------------------------+
1601
 */
1602
static SIXELSTATUS
1603
sixel_palette_parse_act(unsigned char const *data,
×
1604
                        size_t size,
1605
                        sixel_encoder_t *encoder,
1606
                        sixel_dither_t **dither)
1607
{
1608
    SIXELSTATUS status;
1609
    sixel_dither_t *local;
1610
    unsigned char const *palette_start;
1611
    unsigned char const *trailer;
1612
    sixel_palette_t *palette_obj;
1613
    int exported_colors;
1614
    int start_index;
1615

1616
    status = SIXEL_FALSE;
×
1617
    local = NULL;
×
1618
    palette_start = data;
×
1619
    trailer = NULL;
×
1620
    palette_obj = NULL;
×
1621
    exported_colors = 0;
×
1622
    start_index = 0;
×
1623

1624
    if (encoder == NULL || dither == NULL) {
×
1625
        sixel_helper_set_additional_message(
×
1626
            "sixel_palette_parse_act: invalid argument.");
1627
        return SIXEL_BAD_ARGUMENT;
×
1628
    }
1629
    if (data == NULL || size < 256u * 3u) {
×
1630
        sixel_helper_set_additional_message(
×
1631
            "sixel_palette_parse_act: truncated ACT palette.");
1632
        return SIXEL_BAD_INPUT;
×
1633
    }
1634

1635
    if (size == 256u * 3u) {
×
1636
        exported_colors = 256;
×
1637
        start_index = 0;
×
1638
    } else if (size == 256u * 3u + 4u) {
×
1639
        trailer = data + 256u * 3u;
×
1640
        exported_colors = (int)(((unsigned int)trailer[0] << 8)
×
1641
                                | (unsigned int)trailer[1]);
×
1642
        start_index = (int)(((unsigned int)trailer[2] << 8)
×
1643
                            | (unsigned int)trailer[3]);
×
1644
    } else {
1645
        sixel_helper_set_additional_message(
×
1646
            "sixel_palette_parse_act: invalid ACT length.");
1647
        return SIXEL_BAD_INPUT;
×
1648
    }
1649

1650
    if (start_index < 0 || start_index >= 256) {
×
1651
        sixel_helper_set_additional_message(
×
1652
            "sixel_palette_parse_act: ACT start index out of range.");
1653
        return SIXEL_BAD_INPUT;
×
1654
    }
1655
    if (exported_colors <= 0 || exported_colors > 256) {
×
1656
        exported_colors = 256;
×
1657
    }
1658
    if (start_index + exported_colors > 256) {
×
1659
        sixel_helper_set_additional_message(
×
1660
            "sixel_palette_parse_act: ACT palette exceeds 256 slots.");
1661
        return SIXEL_BAD_INPUT;
×
1662
    }
1663

1664
    status = sixel_dither_new(&local, exported_colors, encoder->allocator);
×
1665
    if (SIXEL_FAILED(status)) {
×
1666
        return status;
×
1667
    }
1668

1669
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1670

1671
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
1672
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
1673
        sixel_dither_unref(local);
×
1674
        return status;
×
1675
    }
1676
    status = sixel_palette_set_entries(
×
1677
        palette_obj,
1678
        palette_start + (size_t)start_index * 3u,
×
1679
        (unsigned int)exported_colors,
1680
        3,
1681
        encoder->allocator);
1682
    sixel_palette_unref(palette_obj);
×
1683
    if (SIXEL_FAILED(status)) {
×
1684
        sixel_dither_unref(local);
×
1685
        return status;
×
1686
    }
1687

1688
    *dither = local;
×
1689
    return SIXEL_OK;
×
1690
}
1691

1692

1693
static SIXELSTATUS
1694
sixel_palette_parse_pal_jasc(unsigned char const *data,
×
1695
                             size_t size,
1696
                             sixel_encoder_t *encoder,
1697
                             sixel_dither_t **dither)
1698
{
1699
    SIXELSTATUS status;
1700
    char *text;
1701
    size_t index;
1702
    size_t offset;
1703
    char *cursor;
1704
    char *line;
1705
    char *line_end;
1706
    int stage;
1707
    int exported_colors;
1708
    int parsed_colors;
1709
    sixel_dither_t *local;
1710
    sixel_palette_t *palette_obj;
1711
    unsigned char *palette_buffer;
1712
    long component;
1713
    char *parse_end;
1714
    int value_index;
1715
    int values[3];
1716
    char tail;
1717

1718
    status = SIXEL_FALSE;
×
1719
    text = NULL;
×
1720
    index = 0u;
×
1721
    offset = 0u;
×
1722
    cursor = NULL;
×
1723
    line = NULL;
×
1724
    line_end = NULL;
×
1725
    stage = 0;
×
1726
    exported_colors = 0;
×
1727
    parsed_colors = 0;
×
1728
    local = NULL;
×
1729
    palette_obj = NULL;
×
1730
    palette_buffer = NULL;
×
1731
    component = 0;
×
1732
    parse_end = NULL;
×
1733
    value_index = 0;
×
1734
    values[0] = 0;
×
1735
    values[1] = 0;
×
1736
    values[2] = 0;
×
1737

1738
    if (encoder == NULL || dither == NULL) {
×
1739
        sixel_helper_set_additional_message(
×
1740
            "sixel_palette_parse_pal_jasc: invalid argument.");
1741
        return SIXEL_BAD_ARGUMENT;
×
1742
    }
1743
    if (data == NULL || size == 0u) {
×
1744
        sixel_helper_set_additional_message(
×
1745
            "sixel_palette_parse_pal_jasc: empty palette.");
1746
        return SIXEL_BAD_INPUT;
×
1747
    }
1748

1749
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
1750
    if (text == NULL) {
×
1751
        sixel_helper_set_additional_message(
×
1752
            "sixel_palette_parse_pal_jasc: allocation failed.");
1753
        return SIXEL_BAD_ALLOCATION;
×
1754
    }
1755
    memcpy(text, data, size);
×
1756
    text[size] = '\0';
×
1757

1758
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
1759
        offset = 3u;
×
1760
    }
1761
    cursor = text + offset;
×
1762

1763
    while (*cursor != '\0') {
×
1764
        line = cursor;
×
1765
        line_end = cursor;
×
1766
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
1767
            ++line_end;
×
1768
        }
1769
        if (*line_end != '\0') {
×
1770
            *line_end = '\0';
×
1771
            cursor = line_end + 1;
×
1772
        } else {
1773
            cursor = line_end;
×
1774
        }
1775
        while (*cursor == '\n' || *cursor == '\r') {
×
1776
            ++cursor;
×
1777
        }
1778

1779
        while (*line == ' ' || *line == '\t') {
×
1780
            ++line;
×
1781
        }
1782
        index = strlen(line);
×
1783
        while (index > 0u) {
×
1784
            tail = line[index - 1];
×
1785
            if (tail != ' ' && tail != '\t') {
×
1786
                break;
×
1787
            }
1788
            line[index - 1] = '\0';
×
1789
            --index;
×
1790
        }
1791
        if (*line == '\0') {
×
1792
            continue;
×
1793
        }
1794
        if (*line == '#') {
×
1795
            continue;
×
1796
        }
1797

1798
        if (stage == 0) {
×
1799
            if (strcmp(line, "JASC-PAL") != 0) {
×
1800
                sixel_helper_set_additional_message(
×
1801
                    "sixel_palette_parse_pal_jasc: missing header.");
1802
                status = SIXEL_BAD_INPUT;
×
1803
                goto cleanup;
×
1804
            }
1805
            stage = 1;
×
1806
            continue;
×
1807
        }
1808
        if (stage == 1) {
×
1809
            stage = 2;
×
1810
            continue;
×
1811
        }
1812
        if (stage == 2) {
×
1813
            component = strtol(line, &parse_end, 10);
×
1814
            if (parse_end == line || component <= 0L || component > 256L) {
×
1815
                sixel_helper_set_additional_message(
×
1816
                    "sixel_palette_parse_pal_jasc: invalid color count.");
1817
                status = SIXEL_BAD_INPUT;
×
1818
                goto cleanup;
×
1819
            }
1820
            exported_colors = (int)component;
×
1821
            if (exported_colors <= 0) {
×
1822
                sixel_helper_set_additional_message(
×
1823
                    "sixel_palette_parse_pal_jasc: invalid color count.");
1824
                status = SIXEL_BAD_INPUT;
×
1825
                goto cleanup;
×
1826
            }
1827
            palette_buffer = (unsigned char *)sixel_allocator_malloc(
×
1828
                encoder->allocator,
1829
                (size_t)exported_colors * 3u);
×
1830
            if (palette_buffer == NULL) {
×
1831
                sixel_helper_set_additional_message(
×
1832
                    "sixel_palette_parse_pal_jasc: allocation failed.");
1833
                status = SIXEL_BAD_ALLOCATION;
×
1834
                goto cleanup;
×
1835
            }
1836
            status = sixel_dither_new(&local, exported_colors,
×
1837
                                      encoder->allocator);
1838
            if (SIXEL_FAILED(status)) {
×
1839
                goto cleanup;
×
1840
            }
1841
            sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1842
            stage = 3;
×
1843
            continue;
×
1844
        }
1845

1846
        value_index = 0;
×
1847
        while (value_index < 3) {
×
1848
            component = strtol(line, &parse_end, 10);
×
1849
            if (parse_end == line || component < 0L || component > 255L) {
×
1850
                sixel_helper_set_additional_message(
×
1851
                    "sixel_palette_parse_pal_jasc: invalid component.");
1852
                status = SIXEL_BAD_INPUT;
×
1853
                goto cleanup;
×
1854
            }
1855
            values[value_index] = (int)component;
×
1856
            ++value_index;
×
1857
            line = parse_end;
×
1858
            while (*line == ' ' || *line == '\t') {
×
1859
                ++line;
×
1860
            }
1861
        }
1862

1863
        if (parsed_colors >= exported_colors) {
×
1864
            sixel_helper_set_additional_message(
×
1865
                "sixel_palette_parse_pal_jasc: excess entries.");
1866
            status = SIXEL_BAD_INPUT;
×
1867
            goto cleanup;
×
1868
        }
1869

1870
        palette_buffer[parsed_colors * 3 + 0] =
×
1871
            (unsigned char)values[0];
×
1872
        palette_buffer[parsed_colors * 3 + 1] =
×
1873
            (unsigned char)values[1];
×
1874
        palette_buffer[parsed_colors * 3 + 2] =
×
1875
            (unsigned char)values[2];
×
1876
        ++parsed_colors;
×
1877
    }
1878

1879
    if (stage < 3) {
×
1880
        sixel_helper_set_additional_message(
×
1881
            "sixel_palette_parse_pal_jasc: incomplete header.");
1882
        status = SIXEL_BAD_INPUT;
×
1883
        goto cleanup;
×
1884
    }
1885
    if (parsed_colors != exported_colors) {
×
1886
        sixel_helper_set_additional_message(
×
1887
            "sixel_palette_parse_pal_jasc: color count mismatch.");
1888
        status = SIXEL_BAD_INPUT;
×
1889
        goto cleanup;
×
1890
    }
1891

1892
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
1893
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
1894
        goto cleanup;
×
1895
    }
1896
    status = sixel_palette_set_entries(palette_obj,
×
1897
                                       palette_buffer,
1898
                                       (unsigned int)exported_colors,
1899
                                       3,
1900
                                       encoder->allocator);
1901
    sixel_palette_unref(palette_obj);
×
1902
    palette_obj = NULL;
×
1903
    if (SIXEL_FAILED(status)) {
×
1904
        goto cleanup;
×
1905
    }
1906

1907
    *dither = local;
×
1908
    status = SIXEL_OK;
×
1909

1910
cleanup:
1911
    if (palette_obj != NULL) {
×
1912
        sixel_palette_unref(palette_obj);
×
1913
    }
1914
    if (SIXEL_FAILED(status) && local != NULL) {
×
1915
        sixel_dither_unref(local);
×
1916
    }
1917
    if (palette_buffer != NULL) {
×
1918
        sixel_allocator_free(encoder->allocator, palette_buffer);
×
1919
    }
1920
    if (text != NULL) {
×
1921
        sixel_allocator_free(encoder->allocator, text);
×
1922
    }
1923
    return status;
×
1924
}
1925

1926

1927
static SIXELSTATUS
1928
sixel_palette_parse_pal_riff(unsigned char const *data,
×
1929
                             size_t size,
1930
                             sixel_encoder_t *encoder,
1931
                             sixel_dither_t **dither)
1932
{
1933
    SIXELSTATUS status;
1934
    size_t offset;
1935
    size_t chunk_size;
1936
    sixel_dither_t *local;
1937
    sixel_palette_t *palette_obj;
1938
    unsigned char const *chunk;
1939
    unsigned char *palette_buffer;
1940
    unsigned int entry_count;
1941
    unsigned int version;
1942
    unsigned int index;
1943
    size_t palette_offset;
1944

1945
    status = SIXEL_FALSE;
×
1946
    offset = 0u;
×
1947
    chunk_size = 0u;
×
1948
    local = NULL;
×
1949
    chunk = NULL;
×
1950
    palette_obj = NULL;
×
1951
    palette_buffer = NULL;
×
1952
    entry_count = 0u;
×
1953
    version = 0u;
×
1954
    index = 0u;
×
1955
    palette_offset = 0u;
×
1956

1957
    if (encoder == NULL || dither == NULL) {
×
1958
        sixel_helper_set_additional_message(
×
1959
            "sixel_palette_parse_pal_riff: invalid argument.");
1960
        return SIXEL_BAD_ARGUMENT;
×
1961
    }
1962
    if (data == NULL || size < 12u) {
×
1963
        sixel_helper_set_additional_message(
×
1964
            "sixel_palette_parse_pal_riff: truncated palette.");
1965
        return SIXEL_BAD_INPUT;
×
1966
    }
1967
    if (memcmp(data, "RIFF", 4) != 0 || memcmp(data + 8, "PAL ", 4) != 0) {
×
1968
        sixel_helper_set_additional_message(
×
1969
            "sixel_palette_parse_pal_riff: missing RIFF header.");
1970
        return SIXEL_BAD_INPUT;
×
1971
    }
1972

1973
    offset = 12u;
×
1974
    while (offset + 8u <= size) {
×
1975
        chunk = data + offset;
×
1976
        chunk_size = (size_t)sixel_palette_read_le32(chunk + 4);
×
1977
        if (offset + 8u + chunk_size > size) {
×
1978
            sixel_helper_set_additional_message(
×
1979
                "sixel_palette_parse_pal_riff: chunk extends past end.");
1980
            return SIXEL_BAD_INPUT;
×
1981
        }
1982
        if (memcmp(chunk, "data", 4) == 0) {
×
1983
            break;
×
1984
        }
1985
        offset += 8u + ((chunk_size + 1u) & ~1u);
×
1986
    }
1987

1988
    if (offset + 8u > size || memcmp(chunk, "data", 4) != 0) {
×
1989
        sixel_helper_set_additional_message(
×
1990
            "sixel_palette_parse_pal_riff: missing data chunk.");
1991
        return SIXEL_BAD_INPUT;
×
1992
    }
1993

1994
    if (chunk_size < 4u) {
×
1995
        sixel_helper_set_additional_message(
×
1996
            "sixel_palette_parse_pal_riff: data chunk too small.");
1997
        return SIXEL_BAD_INPUT;
×
1998
    }
1999
    version = sixel_palette_read_le16(chunk + 8);
×
2000
    (void)version;
2001
    entry_count = sixel_palette_read_le16(chunk + 10);
×
2002
    if (entry_count == 0u || entry_count > 256u) {
×
2003
        sixel_helper_set_additional_message(
×
2004
            "sixel_palette_parse_pal_riff: invalid entry count.");
2005
        return SIXEL_BAD_INPUT;
×
2006
    }
2007
    if (chunk_size != 4u + (size_t)entry_count * 4u) {
×
2008
        sixel_helper_set_additional_message(
×
2009
            "sixel_palette_parse_pal_riff: unexpected chunk size.");
2010
        return SIXEL_BAD_INPUT;
×
2011
    }
2012

2013
    status = sixel_dither_new(&local, (int)entry_count, encoder->allocator);
×
2014
    if (SIXEL_FAILED(status)) {
×
2015
        return status;
×
2016
    }
2017
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2018
    palette_buffer = (unsigned char *)sixel_allocator_malloc(
×
2019
        encoder->allocator,
2020
        (size_t)entry_count * 3u);
×
2021
    if (palette_buffer == NULL) {
×
2022
        sixel_helper_set_additional_message(
×
2023
            "sixel_palette_parse_pal_riff: allocation failed.");
2024
        sixel_dither_unref(local);
×
2025
        return SIXEL_BAD_ALLOCATION;
×
2026
    }
2027
    palette_offset = 12u;
×
2028
    for (index = 0u; index < entry_count; ++index) {
×
2029
        palette_buffer[index * 3u + 0u] =
×
2030
            chunk[palette_offset + index * 4u + 0u];
×
2031
        palette_buffer[index * 3u + 1u] =
×
2032
            chunk[palette_offset + index * 4u + 1u];
×
2033
        palette_buffer[index * 3u + 2u] =
×
2034
            chunk[palette_offset + index * 4u + 2u];
×
2035
    }
2036

2037
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
2038
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
2039
        sixel_allocator_free(encoder->allocator, palette_buffer);
×
2040
        sixel_dither_unref(local);
×
2041
        return status;
×
2042
    }
2043
    status = sixel_palette_set_entries(palette_obj,
×
2044
                                       palette_buffer,
2045
                                       (unsigned int)entry_count,
2046
                                       3,
2047
                                       encoder->allocator);
2048
    sixel_palette_unref(palette_obj);
×
2049
    palette_obj = NULL;
×
2050
    sixel_allocator_free(encoder->allocator, palette_buffer);
×
2051
    palette_buffer = NULL;
×
2052
    if (SIXEL_FAILED(status)) {
×
2053
        sixel_dither_unref(local);
×
2054
        return status;
×
2055
    }
2056

2057
    *dither = local;
×
2058
    return SIXEL_OK;
×
2059
}
2060

2061

2062
static SIXELSTATUS
2063
sixel_palette_parse_gpl(unsigned char const *data,
×
2064
                        size_t size,
2065
                        sixel_encoder_t *encoder,
2066
                        sixel_dither_t **dither)
2067
{
2068
    SIXELSTATUS status;
2069
    char *text;
2070
    size_t offset;
2071
    char *cursor;
2072
    char *line;
2073
    char *line_end;
2074
    size_t index;
2075
    int header_seen;
2076
    int parsed_colors;
2077
    unsigned char palette_bytes[256 * 3];
2078
    long component;
2079
    char *parse_end;
2080
    int value_index;
2081
    int values[3];
2082
    sixel_dither_t *local;
2083
    sixel_palette_t *palette_obj;
2084
    char tail;
2085

2086
    status = SIXEL_FALSE;
×
2087
    text = NULL;
×
2088
    offset = 0u;
×
2089
    cursor = NULL;
×
2090
    line = NULL;
×
2091
    line_end = NULL;
×
2092
    index = 0u;
×
2093
    header_seen = 0;
×
2094
    parsed_colors = 0;
×
2095
    component = 0;
×
2096
    parse_end = NULL;
×
2097
    value_index = 0;
×
2098
    values[0] = 0;
×
2099
    values[1] = 0;
×
2100
    values[2] = 0;
×
2101
    local = NULL;
×
2102
    palette_obj = NULL;
×
2103

2104
    if (encoder == NULL || dither == NULL) {
×
2105
        sixel_helper_set_additional_message(
×
2106
            "sixel_palette_parse_gpl: invalid argument.");
2107
        return SIXEL_BAD_ARGUMENT;
×
2108
    }
2109
    if (data == NULL || size == 0u) {
×
2110
        sixel_helper_set_additional_message(
×
2111
            "sixel_palette_parse_gpl: empty palette.");
2112
        return SIXEL_BAD_INPUT;
×
2113
    }
2114

2115
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
2116
    if (text == NULL) {
×
2117
        sixel_helper_set_additional_message(
×
2118
            "sixel_palette_parse_gpl: allocation failed.");
2119
        return SIXEL_BAD_ALLOCATION;
×
2120
    }
2121
    memcpy(text, data, size);
×
2122
    text[size] = '\0';
×
2123

2124
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
2125
        offset = 3u;
×
2126
    }
2127
    cursor = text + offset;
×
2128

2129
    while (*cursor != '\0') {
×
2130
        line = cursor;
×
2131
        line_end = cursor;
×
2132
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
2133
            ++line_end;
×
2134
        }
2135
        if (*line_end != '\0') {
×
2136
            *line_end = '\0';
×
2137
            cursor = line_end + 1;
×
2138
        } else {
2139
            cursor = line_end;
×
2140
        }
2141
        while (*cursor == '\n' || *cursor == '\r') {
×
2142
            ++cursor;
×
2143
        }
2144

2145
        while (*line == ' ' || *line == '\t') {
×
2146
            ++line;
×
2147
        }
2148
        index = strlen(line);
×
2149
        while (index > 0u) {
×
2150
            tail = line[index - 1];
×
2151
            if (tail != ' ' && tail != '\t') {
×
2152
                break;
×
2153
            }
2154
            line[index - 1] = '\0';
×
2155
            --index;
×
2156
        }
2157
        if (*line == '\0') {
×
2158
            continue;
×
2159
        }
2160
        if (*line == '#') {
×
2161
            continue;
×
2162
        }
2163
        if (strncmp(line, "Name:", 5) == 0) {
×
2164
            continue;
×
2165
        }
2166
        if (strncmp(line, "Columns:", 8) == 0) {
×
2167
            continue;
×
2168
        }
2169

2170
        if (!header_seen) {
×
2171
            if (strcmp(line, "GIMP Palette") != 0) {
×
2172
                sixel_helper_set_additional_message(
×
2173
                    "sixel_palette_parse_gpl: missing header.");
2174
                status = SIXEL_BAD_INPUT;
×
2175
                goto cleanup;
×
2176
            }
2177
            header_seen = 1;
×
2178
            continue;
×
2179
        }
2180

2181
        if (parsed_colors >= 256) {
×
2182
            sixel_helper_set_additional_message(
×
2183
                "sixel_palette_parse_gpl: too many colors.");
2184
            status = SIXEL_BAD_INPUT;
×
2185
            goto cleanup;
×
2186
        }
2187

2188
        value_index = 0;
×
2189
        while (value_index < 3) {
×
2190
            component = strtol(line, &parse_end, 10);
×
2191
            if (parse_end == line || component < 0L || component > 255L) {
×
2192
                sixel_helper_set_additional_message(
×
2193
                    "sixel_palette_parse_gpl: invalid component.");
2194
                status = SIXEL_BAD_INPUT;
×
2195
                goto cleanup;
×
2196
            }
2197
            values[value_index] = (int)component;
×
2198
            ++value_index;
×
2199
            line = parse_end;
×
2200
            while (*line == ' ' || *line == '\t') {
×
2201
                ++line;
×
2202
            }
2203
        }
2204

2205
        palette_bytes[parsed_colors * 3 + 0] =
×
2206
            (unsigned char)values[0];
×
2207
        palette_bytes[parsed_colors * 3 + 1] =
×
2208
            (unsigned char)values[1];
×
2209
        palette_bytes[parsed_colors * 3 + 2] =
×
2210
            (unsigned char)values[2];
×
2211
        ++parsed_colors;
×
2212
    }
2213

2214
    if (!header_seen) {
×
2215
        sixel_helper_set_additional_message(
×
2216
            "sixel_palette_parse_gpl: header missing.");
2217
        status = SIXEL_BAD_INPUT;
×
2218
        goto cleanup;
×
2219
    }
2220
    if (parsed_colors <= 0) {
×
2221
        sixel_helper_set_additional_message(
×
2222
            "sixel_palette_parse_gpl: no colors parsed.");
2223
        status = SIXEL_BAD_INPUT;
×
2224
        goto cleanup;
×
2225
    }
2226

2227
    status = sixel_dither_new(&local, parsed_colors, encoder->allocator);
×
2228
    if (SIXEL_FAILED(status)) {
×
2229
        goto cleanup;
×
2230
    }
2231
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2232
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
2233
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
2234
        goto cleanup;
×
2235
    }
2236
    status = sixel_palette_set_entries(palette_obj,
×
2237
                                       palette_bytes,
2238
                                       (unsigned int)parsed_colors,
2239
                                       3,
2240
                                       encoder->allocator);
2241
    sixel_palette_unref(palette_obj);
×
2242
    palette_obj = NULL;
×
2243
    if (SIXEL_FAILED(status)) {
×
2244
        goto cleanup;
×
2245
    }
2246

2247
    *dither = local;
×
2248
    status = SIXEL_OK;
×
2249

2250
cleanup:
2251
    if (palette_obj != NULL) {
×
2252
        sixel_palette_unref(palette_obj);
×
2253
    }
2254
    if (SIXEL_FAILED(status) && local != NULL) {
×
2255
        sixel_dither_unref(local);
×
2256
    }
2257
    if (text != NULL) {
×
2258
        sixel_allocator_free(encoder->allocator, text);
×
2259
    }
2260
    return status;
×
2261
}
2262

2263

2264
/*
2265
 * Palette exporters
2266
 *
2267
 *   +----------+-------------------------+
2268
 *   | format   | emission strategy       |
2269
 *   +----------+-------------------------+
2270
 *   | ACT      | fixed 256 entries + EOF |
2271
 *   | PAL JASC | textual lines           |
2272
 *   | PAL RIFF | RIFF container          |
2273
 *   | GPL      | textual lines           |
2274
 *   +----------+-------------------------+
2275
 */
2276
static SIXELSTATUS
2277
sixel_palette_write_act(FILE *stream,
×
2278
                        unsigned char const *palette,
2279
                        int exported_colors)
2280
{
2281
    SIXELSTATUS status;
2282
    unsigned char act_table[256 * 3];
2283
    unsigned char trailer[4];
2284
    size_t exported_bytes;
2285

2286
    status = SIXEL_FALSE;
×
2287
    exported_bytes = 0u;
×
2288

2289
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2290
        return SIXEL_BAD_ARGUMENT;
×
2291
    }
2292
    if (exported_colors > 256) {
×
2293
        exported_colors = 256;
×
2294
    }
2295

2296
    memset(act_table, 0, sizeof(act_table));
×
2297
    exported_bytes = (size_t)exported_colors * 3u;
×
2298
    memcpy(act_table, palette, exported_bytes);
×
2299

2300
    trailer[0] = (unsigned char)(((unsigned int)exported_colors >> 8)
×
2301
                                 & 0xffu);
2302
    trailer[1] = (unsigned char)((unsigned int)exported_colors & 0xffu);
×
2303
    trailer[2] = 0u;
×
2304
    trailer[3] = 0u;
×
2305

2306
    if (fwrite(act_table, 1, sizeof(act_table), stream)
×
2307
            != sizeof(act_table)) {
2308
        status = SIXEL_LIBC_ERROR;
×
2309
        return status;
×
2310
    }
2311
    if (fwrite(trailer, 1, sizeof(trailer), stream)
×
2312
            != sizeof(trailer)) {
2313
        status = SIXEL_LIBC_ERROR;
×
2314
        return status;
×
2315
    }
2316

2317
    return SIXEL_OK;
×
2318
}
2319

2320

2321
static SIXELSTATUS
2322
sixel_palette_write_pal_jasc(FILE *stream,
×
2323
                             unsigned char const *palette,
2324
                             int exported_colors)
2325
{
2326
    int index;
2327

2328
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2329
        return SIXEL_BAD_ARGUMENT;
×
2330
    }
2331
    if (fprintf(stream, "JASC-PAL\n0100\n%d\n", exported_colors) < 0) {
×
2332
        return SIXEL_LIBC_ERROR;
×
2333
    }
2334
    for (index = 0; index < exported_colors; ++index) {
×
2335
        if (fprintf(stream, "%d %d %d\n",
×
2336
                    (int)palette[index * 3 + 0],
×
2337
                    (int)palette[index * 3 + 1],
×
2338
                    (int)palette[index * 3 + 2]) < 0) {
×
2339
            return SIXEL_LIBC_ERROR;
×
2340
        }
2341
    }
2342
    return SIXEL_OK;
×
2343
}
2344

2345

2346
static SIXELSTATUS
2347
sixel_palette_write_pal_riff(FILE *stream,
×
2348
                             unsigned char const *palette,
2349
                             int exported_colors)
2350
{
2351
    unsigned char header[12];
2352
    unsigned char chunk[8];
2353
    unsigned char log_palette[4 + 256 * 4];
2354
    unsigned int data_size;
2355
    unsigned int riff_size;
2356
    int index;
2357

2358
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2359
        return SIXEL_BAD_ARGUMENT;
×
2360
    }
2361
    if (exported_colors > 256) {
×
2362
        exported_colors = 256;
×
2363
    }
2364

2365
    data_size = 4u + (unsigned int)exported_colors * 4u;
×
2366
    riff_size = 4u + 8u + data_size;
×
2367

2368
    memcpy(header, "RIFF", 4);
×
2369
    header[4] = (unsigned char)(riff_size & 0xffu);
×
2370
    header[5] = (unsigned char)((riff_size >> 8) & 0xffu);
×
2371
    header[6] = (unsigned char)((riff_size >> 16) & 0xffu);
×
2372
    header[7] = (unsigned char)((riff_size >> 24) & 0xffu);
×
2373
    memcpy(header + 8, "PAL ", 4);
×
2374

2375
    memcpy(chunk, "data", 4);
×
2376
    chunk[4] = (unsigned char)(data_size & 0xffu);
×
2377
    chunk[5] = (unsigned char)((data_size >> 8) & 0xffu);
×
2378
    chunk[6] = (unsigned char)((data_size >> 16) & 0xffu);
×
2379
    chunk[7] = (unsigned char)((data_size >> 24) & 0xffu);
×
2380

2381
    memset(log_palette, 0, sizeof(log_palette));
×
2382
    log_palette[0] = 0x00;
×
2383
    log_palette[1] = 0x03;
×
2384
    log_palette[2] = (unsigned char)(exported_colors & 0xff);
×
2385
    log_palette[3] = (unsigned char)((exported_colors >> 8) & 0xff);
×
2386
    for (index = 0; index < exported_colors; ++index) {
×
2387
        log_palette[4 + index * 4 + 0] = palette[index * 3 + 0];
×
2388
        log_palette[4 + index * 4 + 1] = palette[index * 3 + 1];
×
2389
        log_palette[4 + index * 4 + 2] = palette[index * 3 + 2];
×
2390
        log_palette[4 + index * 4 + 3] = 0u;
×
2391
    }
2392

2393
    if (fwrite(header, 1, sizeof(header), stream)
×
2394
            != sizeof(header)) {
2395
        return SIXEL_LIBC_ERROR;
×
2396
    }
2397
    if (fwrite(chunk, 1, sizeof(chunk), stream) != sizeof(chunk)) {
×
2398
        return SIXEL_LIBC_ERROR;
×
2399
    }
2400
    if (fwrite(log_palette, 1, (size_t)data_size, stream)
×
2401
            != (size_t)data_size) {
×
2402
        return SIXEL_LIBC_ERROR;
×
2403
    }
2404
    return SIXEL_OK;
×
2405
}
2406

2407

2408
static SIXELSTATUS
2409
sixel_palette_write_gpl(FILE *stream,
×
2410
                        unsigned char const *palette,
2411
                        int exported_colors)
2412
{
2413
    int index;
2414

2415
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2416
        return SIXEL_BAD_ARGUMENT;
×
2417
    }
2418
    if (fprintf(stream, "GIMP Palette\n") < 0) {
×
2419
        return SIXEL_LIBC_ERROR;
×
2420
    }
2421
    if (fprintf(stream, "Name: libsixel export\n") < 0) {
×
2422
        return SIXEL_LIBC_ERROR;
×
2423
    }
2424
    if (fprintf(stream, "Columns: 16\n") < 0) {
×
2425
        return SIXEL_LIBC_ERROR;
×
2426
    }
2427
    if (fprintf(stream, "# Exported by libsixel\n") < 0) {
×
2428
        return SIXEL_LIBC_ERROR;
×
2429
    }
2430
    for (index = 0; index < exported_colors; ++index) {
×
2431
        if (fprintf(stream, "%3d %3d %3d\tIndex %d\n",
×
2432
                    (int)palette[index * 3 + 0],
×
2433
                    (int)palette[index * 3 + 1],
×
2434
                    (int)palette[index * 3 + 2],
×
2435
                    index) < 0) {
2436
            return SIXEL_LIBC_ERROR;
×
2437
        }
2438
    }
2439
    return SIXEL_OK;
×
2440
}
2441

2442

2443
/* create palette from specified map file */
2444
static SIXELSTATUS
2445
sixel_prepare_specified_palette(
21✔
2446
    sixel_dither_t  /* out */   **dither,
2447
    sixel_encoder_t /* in */    *encoder)
2448
{
2449
    SIXELSTATUS status;
2450
    sixel_callback_context_for_mapfile_t callback_context;
2451
    sixel_loader_t *loader;
2452
    int fstatic;
2453
    int fuse_palette;
2454
    int reqcolors;
2455
    int loop_override;
2456
    char const *path;
2457
    sixel_palette_format_t format_hint;
2458
    sixel_palette_format_t format_ext;
2459
    sixel_palette_format_t format_final;
2460
    sixel_palette_format_t format_detected;
2461
    FILE *stream;
2462
    int close_stream;
2463
    unsigned char *buffer;
2464
    size_t buffer_size;
2465
    int palette_request;
2466
    int need_detection;
2467
    int treat_as_image;
2468
    int path_has_extension;
2469
    char mapfile_message[256];
2470

2471
    status = SIXEL_FALSE;
21✔
2472
    loader = NULL;
21✔
2473
    fstatic = 1;
21✔
2474
    fuse_palette = 1;
21✔
2475
    reqcolors = SIXEL_PALETTE_MAX;
21✔
2476
    loop_override = SIXEL_LOOP_DISABLE;
21✔
2477
    path = NULL;
21✔
2478
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
2479
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
21✔
2480
    format_final = SIXEL_PALETTE_FORMAT_NONE;
21✔
2481
    format_detected = SIXEL_PALETTE_FORMAT_NONE;
21✔
2482
    stream = NULL;
21✔
2483
    close_stream = 0;
21✔
2484
    buffer = NULL;
21✔
2485
    buffer_size = 0u;
21✔
2486
    palette_request = 0;
21✔
2487
    need_detection = 0;
21✔
2488
    treat_as_image = 0;
21✔
2489
    path_has_extension = 0;
21✔
2490
    mapfile_message[0] = '\0';
21✔
2491

2492
    if (dither == NULL || encoder == NULL || encoder->mapfile == NULL) {
21!
2493
        sixel_helper_set_additional_message(
×
2494
            "sixel_prepare_specified_palette: invalid mapfile path.");
2495
        return SIXEL_BAD_ARGUMENT;
×
2496
    }
2497

2498
    path = sixel_palette_strip_prefix(encoder->mapfile, &format_hint);
21✔
2499
    if (path == NULL || *path == '\0') {
21!
2500
        sixel_helper_set_additional_message(
×
2501
            "sixel_prepare_specified_palette: empty mapfile path.");
2502
        return SIXEL_BAD_ARGUMENT;
×
2503
    }
2504

2505
    format_ext = sixel_palette_format_from_extension(path);
21✔
2506
    path_has_extension = sixel_path_has_any_extension(path);
21✔
2507

2508
    if (format_hint != SIXEL_PALETTE_FORMAT_NONE) {
21!
2509
        palette_request = 1;
×
2510
        format_final = format_hint;
×
2511
    } else if (format_ext != SIXEL_PALETTE_FORMAT_NONE) {
21!
2512
        palette_request = 1;
×
2513
        format_final = format_ext;
×
2514
    } else if (!path_has_extension) {
21!
2515
        palette_request = 1;
×
2516
        need_detection = 1;
×
2517
    } else {
2518
        treat_as_image = 1;
21✔
2519
    }
2520

2521
    if (palette_request) {
21!
2522
        status = sixel_palette_open_read(path, &stream, &close_stream);
×
2523
        if (SIXEL_FAILED(status)) {
×
2524
            goto palette_cleanup;
×
2525
        }
2526
        status = sixel_palette_read_stream(stream,
×
2527
                                           encoder->allocator,
2528
                                           &buffer,
2529
                                           &buffer_size);
2530
        if (close_stream) {
×
2531
            sixel_palette_close_stream(stream, close_stream);
×
2532
            stream = NULL;
×
2533
            close_stream = 0;
×
2534
        }
2535
        if (SIXEL_FAILED(status)) {
×
2536
            goto palette_cleanup;
×
2537
        }
2538
        if (buffer_size == 0u) {
×
2539
            sixel_compat_snprintf(mapfile_message,
×
2540
                                  sizeof(mapfile_message),
2541
                                  "sixel_prepare_specified_palette: "
2542
                                  "mapfile \"%s\" is empty.",
2543
                                  path != NULL ? path : "");
×
2544
            sixel_helper_set_additional_message(mapfile_message);
×
2545
            status = SIXEL_BAD_INPUT;
×
2546
            goto palette_cleanup;
×
2547
        }
2548

2549
        if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
2550
            format_detected = sixel_palette_guess_format(buffer,
×
2551
                                                         buffer_size);
2552
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
2553
                sixel_helper_set_additional_message(
×
2554
                    "sixel_prepare_specified_palette: "
2555
                    "unable to detect palette format.");
2556
                status = SIXEL_BAD_INPUT;
×
2557
                goto palette_cleanup;
×
2558
            }
2559
            format_final = format_detected;
×
2560
        } else if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
2561
            format_detected = sixel_palette_guess_format(buffer,
×
2562
                                                         buffer_size);
2563
            if (format_detected == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
2564
                    format_detected == SIXEL_PALETTE_FORMAT_PAL_RIFF) {
2565
                format_final = format_detected;
×
2566
            } else {
2567
                sixel_helper_set_additional_message(
×
2568
                    "sixel_prepare_specified_palette: "
2569
                    "ambiguous .pal content.");
2570
                status = SIXEL_BAD_INPUT;
×
2571
                goto palette_cleanup;
×
2572
            }
2573
        } else if (need_detection) {
×
2574
            format_detected = sixel_palette_guess_format(buffer,
×
2575
                                                         buffer_size);
2576
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
2577
                sixel_helper_set_additional_message(
×
2578
                    "sixel_prepare_specified_palette: "
2579
                    "unable to detect palette format.");
2580
                status = SIXEL_BAD_INPUT;
×
2581
                goto palette_cleanup;
×
2582
            }
2583
            format_final = format_detected;
×
2584
        }
2585

2586
        switch (format_final) {
×
2587
        case SIXEL_PALETTE_FORMAT_ACT:
2588
            status = sixel_palette_parse_act(buffer,
×
2589
                                             buffer_size,
2590
                                             encoder,
2591
                                             dither);
2592
            break;
×
2593
        case SIXEL_PALETTE_FORMAT_PAL_JASC:
2594
            status = sixel_palette_parse_pal_jasc(buffer,
×
2595
                                                  buffer_size,
2596
                                                  encoder,
2597
                                                  dither);
2598
            break;
×
2599
        case SIXEL_PALETTE_FORMAT_PAL_RIFF:
2600
            status = sixel_palette_parse_pal_riff(buffer,
×
2601
                                                  buffer_size,
2602
                                                  encoder,
2603
                                                  dither);
2604
            break;
×
2605
        case SIXEL_PALETTE_FORMAT_GPL:
2606
            status = sixel_palette_parse_gpl(buffer,
×
2607
                                             buffer_size,
2608
                                             encoder,
2609
                                             dither);
2610
            break;
×
2611
        default:
2612
            sixel_helper_set_additional_message(
×
2613
                "sixel_prepare_specified_palette: "
2614
                "unsupported palette format.");
2615
            status = SIXEL_BAD_INPUT;
×
2616
            break;
×
2617
        }
2618

2619
palette_cleanup:
2620
        if (buffer != NULL) {
×
2621
            sixel_allocator_free(encoder->allocator, buffer);
×
2622
            buffer = NULL;
×
2623
        }
2624
        if (stream != NULL) {
×
2625
            sixel_palette_close_stream(stream, close_stream);
×
2626
            stream = NULL;
×
2627
        }
2628
        if (SIXEL_SUCCEEDED(status)) {
×
2629
            return status;
×
2630
        }
2631
        if (!treat_as_image) {
×
2632
            return status;
×
2633
        }
2634
    }
2635

2636
    callback_context.reqcolors = encoder->reqcolors;
21✔
2637
    callback_context.dither = NULL;
21✔
2638
    callback_context.allocator = encoder->allocator;
21✔
2639
    callback_context.working_colorspace = encoder->working_colorspace;
21✔
2640
    callback_context.lut_policy = encoder->lut_policy;
21✔
2641

2642
    sixel_helper_set_loader_trace(encoder->verbose);
21✔
2643
    sixel_helper_set_thumbnail_size_hint(
21✔
2644
        sixel_encoder_thumbnail_hint(encoder));
7✔
2645
    status = sixel_loader_new(&loader, encoder->allocator);
21✔
2646
    if (SIXEL_FAILED(status)) {
21!
2647
        goto end_loader;
×
2648
    }
2649

2650
    status = sixel_loader_setopt(loader,
21✔
2651
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
2652
                                 &fstatic);
2653
    if (SIXEL_FAILED(status)) {
21!
2654
        goto end_loader;
×
2655
    }
2656

2657
    status = sixel_loader_setopt(loader,
21✔
2658
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
2659
                                 &fuse_palette);
2660
    if (SIXEL_FAILED(status)) {
21!
2661
        goto end_loader;
×
2662
    }
2663

2664
    status = sixel_loader_setopt(loader,
21✔
2665
                                 SIXEL_LOADER_OPTION_REQCOLORS,
2666
                                 &reqcolors);
2667
    if (SIXEL_FAILED(status)) {
21!
2668
        goto end_loader;
×
2669
    }
2670

2671
    status = sixel_loader_setopt(loader,
28✔
2672
                                 SIXEL_LOADER_OPTION_BGCOLOR,
2673
                                 encoder->bgcolor);
21✔
2674
    if (SIXEL_FAILED(status)) {
21!
2675
        goto end_loader;
×
2676
    }
2677

2678
    status = sixel_loader_setopt(loader,
21✔
2679
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
2680
                                 &loop_override);
2681
    if (SIXEL_FAILED(status)) {
21!
2682
        goto end_loader;
×
2683
    }
2684

2685
    status = sixel_loader_setopt(loader,
28✔
2686
                                 SIXEL_LOADER_OPTION_INSECURE,
2687
                                 &encoder->finsecure);
21✔
2688
    if (SIXEL_FAILED(status)) {
21!
2689
        goto end_loader;
×
2690
    }
2691

2692
    status = sixel_loader_setopt(loader,
28✔
2693
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
2694
                                 encoder->cancel_flag);
21✔
2695
    if (SIXEL_FAILED(status)) {
21!
2696
        goto end_loader;
×
2697
    }
2698

2699
    status = sixel_loader_setopt(loader,
28✔
2700
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
2701
                                 encoder->loader_order);
21✔
2702
    if (SIXEL_FAILED(status)) {
21!
2703
        goto end_loader;
×
2704
    }
2705

2706
    status = sixel_loader_setopt(loader,
21✔
2707
                                 SIXEL_LOADER_OPTION_CONTEXT,
2708
                                 &callback_context);
2709
    if (SIXEL_FAILED(status)) {
21!
2710
        goto end_loader;
×
2711
    }
2712

2713
    status = sixel_loader_load_file(loader,
28✔
2714
                                    encoder->mapfile,
21✔
2715
                                    load_image_callback_for_palette);
2716
    if (status != SIXEL_OK) {
21!
2717
        goto end_loader;
×
2718
    }
2719

2720
end_loader:
14✔
2721
    sixel_loader_unref(loader);
21✔
2722

2723
    if (status != SIXEL_OK) {
21!
2724
        return status;
×
2725
    }
2726

2727
    if (! callback_context.dither) {
21!
2728
        sixel_compat_snprintf(mapfile_message,
×
2729
                              sizeof(mapfile_message),
2730
                              "sixel_prepare_specified_palette() failed.\n"
2731
                              "reason: mapfile \"%s\" is empty.",
2732
                              encoder->mapfile != NULL
×
2733
                                ? encoder->mapfile
2734
                                : "");
2735
        sixel_helper_set_additional_message(mapfile_message);
×
2736
        return SIXEL_BAD_INPUT;
×
2737
    }
2738

2739
    *dither = callback_context.dither;
21✔
2740

2741
    return status;
21✔
2742
}
7✔
2743

2744

2745
/* create dither object from a frame */
2746
static SIXELSTATUS
2747
sixel_encoder_prepare_palette(
520✔
2748
    sixel_encoder_t *encoder,  /* encoder object */
2749
    sixel_frame_t   *frame,    /* input frame object */
2750
    sixel_dither_t  **dither)  /* dither object to be created from the frame */
2751
{
2752
    SIXELSTATUS status = SIXEL_FALSE;
520✔
2753
    int histogram_colors;
2754
    sixel_assessment_t *assessment;
2755
    int promoted_stage;
2756

2757
    assessment = NULL;
520✔
2758
    promoted_stage = 0;
520✔
2759
    if (encoder != NULL) {
520!
2760
        assessment = encoder->assessment_observer;
520✔
2761
    }
174✔
2762

2763
    switch (encoder->color_option) {
520!
2764
    case SIXEL_COLOR_OPTION_HIGHCOLOR:
24✔
2765
        if (encoder->dither_cache) {
36!
2766
            *dither = encoder->dither_cache;
×
2767
            status = SIXEL_OK;
×
2768
        } else {
2769
            status = sixel_dither_new(dither, (-1), encoder->allocator);
36✔
2770
            sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
36✔
2771
        }
2772
        goto end;
36✔
2773
    case SIXEL_COLOR_OPTION_MONOCHROME:
8✔
2774
        if (encoder->dither_cache) {
12!
2775
            *dither = encoder->dither_cache;
×
2776
            status = SIXEL_OK;
×
2777
        } else {
2778
            status = sixel_prepare_monochrome_palette(dither, encoder->finvert);
12✔
2779
        }
2780
        goto end;
12✔
2781
    case SIXEL_COLOR_OPTION_MAPFILE:
14✔
2782
        if (encoder->dither_cache) {
21!
2783
            *dither = encoder->dither_cache;
×
2784
            status = SIXEL_OK;
×
2785
        } else {
2786
            status = sixel_prepare_specified_palette(dither, encoder);
21✔
2787
        }
2788
        goto end;
21✔
2789
    case SIXEL_COLOR_OPTION_BUILTIN:
18✔
2790
        if (encoder->dither_cache) {
27!
2791
            *dither = encoder->dither_cache;
×
2792
            status = SIXEL_OK;
×
2793
        } else {
2794
            status = sixel_prepare_builtin_palette(dither, encoder->builtin_palette);
27✔
2795
        }
2796
        goto end;
27✔
2797
    case SIXEL_COLOR_OPTION_DEFAULT:
424✔
2798
    default:
2799
        break;
424✔
2800
    }
2801

2802
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE) {
424✔
2803
        if (!sixel_frame_get_palette(frame)) {
228!
2804
            status = SIXEL_LOGIC_ERROR;
×
2805
            goto end;
×
2806
        }
2807
        status = sixel_dither_new(dither, sixel_frame_get_ncolors(frame),
304✔
2808
                                  encoder->allocator);
76✔
2809
        if (SIXEL_FAILED(status)) {
228!
2810
            goto end;
×
2811
        }
2812
        sixel_dither_set_palette(*dither, sixel_frame_get_palette(frame));
228✔
2813
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
228✔
2814
        if (sixel_frame_get_transparent(frame) != (-1)) {
228!
2815
            sixel_dither_set_transparent(*dither, sixel_frame_get_transparent(frame));
×
2816
        }
2817
        if (*dither && encoder->dither_cache) {
228!
2818
            sixel_dither_unref(encoder->dither_cache);
×
2819
        }
2820
        goto end;
228✔
2821
    }
2822

2823
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_GRAYSCALE) {
196!
2824
        switch (sixel_frame_get_pixelformat(frame)) {
×
2825
        case SIXEL_PIXELFORMAT_G1:
2826
            *dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
2827
            break;
×
2828
        case SIXEL_PIXELFORMAT_G2:
2829
            *dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
2830
            break;
×
2831
        case SIXEL_PIXELFORMAT_G4:
2832
            *dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
2833
            break;
×
2834
        case SIXEL_PIXELFORMAT_G8:
2835
            *dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
2836
            break;
×
2837
        default:
2838
            *dither = NULL;
×
2839
            status = SIXEL_LOGIC_ERROR;
×
2840
            goto end;
×
2841
        }
2842
        if (*dither && encoder->dither_cache) {
×
2843
            sixel_dither_unref(encoder->dither_cache);
×
2844
        }
2845
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
×
2846
        status = SIXEL_OK;
×
2847
        goto end;
×
2848
    }
2849

2850
    if (encoder->dither_cache) {
196!
2851
        sixel_dither_unref(encoder->dither_cache);
×
2852
    }
2853
    status = sixel_dither_new(dither, encoder->reqcolors, encoder->allocator);
196✔
2854
    if (SIXEL_FAILED(status)) {
196!
2855
        goto end;
×
2856
    }
2857

2858
    sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
196✔
2859
    sixel_dither_set_sixel_reversible(*dither,
262✔
2860
                                      encoder->sixel_reversible);
66✔
2861
    sixel_dither_set_final_merge(*dither, encoder->final_merge_mode);
196✔
2862
    (*dither)->quantize_model = encoder->quantize_model;
196✔
2863

2864
    status = sixel_dither_initialize(*dither,
262✔
2865
                                     sixel_frame_get_pixels(frame),
66✔
2866
                                     sixel_frame_get_width(frame),
66✔
2867
                                     sixel_frame_get_height(frame),
66✔
2868
                                     sixel_frame_get_pixelformat(frame),
66✔
2869
                                     encoder->method_for_largest,
66✔
2870
                                     encoder->method_for_rep,
66✔
2871
                                     encoder->quality_mode);
66✔
2872
    if (SIXEL_FAILED(status)) {
196!
2873
        sixel_dither_unref(*dither);
×
2874
        goto end;
×
2875
    }
2876

2877
    if (assessment != NULL && promoted_stage == 0) {
196!
2878
        sixel_assessment_stage_transition(
3✔
2879
            assessment,
1✔
2880
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2881
        promoted_stage = 1;
3✔
2882
    }
1✔
2883

2884
    histogram_colors = sixel_dither_get_num_of_histogram_colors(*dither);
196✔
2885
    if (histogram_colors <= encoder->reqcolors) {
196✔
2886
        encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
127✔
2887
    }
43✔
2888
    sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
196✔
2889

2890
    status = SIXEL_OK;
196✔
2891

2892
end:
346✔
2893
    if (assessment != NULL && promoted_stage == 0) {
520!
2894
        sixel_assessment_stage_transition(
×
2895
            assessment,
2896
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2897
        promoted_stage = 1;
×
2898
    }
2899
    if (SIXEL_SUCCEEDED(status) && dither != NULL && *dither != NULL) {
520!
2900
        sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
520✔
2901
        /* pass down the user's demand for an exact palette size */
2902
        (*dither)->force_palette = encoder->force_palette;
520✔
2903
    }
174✔
2904
    return status;
520✔
2905
}
2906

2907

2908
/* resize a frame with settings of specified encoder object */
2909
static SIXELSTATUS
2910
sixel_encoder_do_resize(
526✔
2911
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
2912
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
2913
{
2914
    SIXELSTATUS status = SIXEL_FALSE;
526✔
2915
    int src_width;
2916
    int src_height;
2917
    int dst_width;
2918
    int dst_height;
2919

2920
    /* get frame width and height */
2921
    src_width = sixel_frame_get_width(frame);
526✔
2922
    src_height = sixel_frame_get_height(frame);
526✔
2923

2924
    if (src_width < 1) {
526✔
2925
         sixel_helper_set_additional_message(
6✔
2926
             "sixel_encoder_do_resize: "
2927
             "detected a frame with a non-positive width.");
2928
        return SIXEL_BAD_ARGUMENT;
6✔
2929
    }
2930

2931
    if (src_height < 1) {
520!
2932
         sixel_helper_set_additional_message(
×
2933
             "sixel_encoder_do_resize: "
2934
             "detected a frame with a non-positive height.");
2935
        return SIXEL_BAD_ARGUMENT;
×
2936
    }
2937

2938
    /* settings around scaling */
2939
    dst_width = encoder->pixelwidth;    /* may be -1 (default) */
520✔
2940
    dst_height = encoder->pixelheight;  /* may be -1 (default) */
520✔
2941

2942
    /* if the encoder has percentwidth or percentheight property,
2943
       convert them to pixelwidth / pixelheight */
2944
    if (encoder->percentwidth > 0) {
520✔
2945
        dst_width = src_width * encoder->percentwidth / 100;
12✔
2946
    }
4✔
2947
    if (encoder->percentheight > 0) {
520✔
2948
        dst_height = src_height * encoder->percentheight / 100;
9✔
2949
    }
3✔
2950

2951
    /* if only either width or height is set, set also the other
2952
       to retain frame aspect ratio */
2953
    if (dst_width > 0 && dst_height <= 0) {
520✔
2954
        dst_height = src_height * dst_width / src_width;
42✔
2955
    }
14✔
2956
    if (dst_height > 0 && dst_width <= 0) {
520✔
2957
        dst_width = src_width * dst_height / src_height;
33✔
2958
    }
11✔
2959

2960
    /* do resize */
2961
    if (dst_width > 0 && dst_height > 0) {
520!
2962
        status = sixel_frame_resize(frame, dst_width, dst_height,
124✔
2963
                                    encoder->method_for_resampling);
31✔
2964
        if (SIXEL_FAILED(status)) {
93!
2965
            goto end;
×
2966
        }
2967
    }
31✔
2968

2969
    /* success */
2970
    status = SIXEL_OK;
520✔
2971

2972
end:
346✔
2973
    return status;
520✔
2974
}
176✔
2975

2976

2977
/* clip a frame with settings of specified encoder object */
2978
static SIXELSTATUS
2979
sixel_encoder_do_clip(
520✔
2980
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
2981
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
2982
{
2983
    SIXELSTATUS status = SIXEL_FALSE;
520✔
2984
    int src_width;
2985
    int src_height;
2986
    int clip_x;
2987
    int clip_y;
2988
    int clip_w;
2989
    int clip_h;
2990

2991
    /* get frame width and height */
2992
    src_width = sixel_frame_get_width(frame);
520✔
2993
    src_height = sixel_frame_get_height(frame);
520✔
2994

2995
    /* settings around clipping */
2996
    clip_x = encoder->clipx;
520✔
2997
    clip_y = encoder->clipy;
520✔
2998
    clip_w = encoder->clipwidth;
520✔
2999
    clip_h = encoder->clipheight;
520✔
3000

3001
    /* adjust clipping width with comparing it to frame width */
3002
    if (clip_w + clip_x > src_width) {
520✔
3003
        if (clip_x > src_width) {
6✔
3004
            clip_w = 0;
3✔
3005
        } else {
1✔
3006
            clip_w = src_width - clip_x;
3✔
3007
        }
3008
    }
2✔
3009

3010
    /* adjust clipping height with comparing it to frame height */
3011
    if (clip_h + clip_y > src_height) {
520✔
3012
        if (clip_y > src_height) {
6✔
3013
            clip_h = 0;
3✔
3014
        } else {
1✔
3015
            clip_h = src_height - clip_y;
3✔
3016
        }
3017
    }
2✔
3018

3019
    /* do clipping */
3020
    if (clip_w > 0 && clip_h > 0) {
520!
3021
        status = sixel_frame_clip(frame, clip_x, clip_y, clip_w, clip_h);
12✔
3022
        if (SIXEL_FAILED(status)) {
12!
3023
            goto end;
×
3024
        }
3025
    }
4✔
3026

3027
    /* success */
3028
    status = SIXEL_OK;
520✔
3029

3030
end:
346✔
3031
    return status;
520✔
3032
}
3033

3034

3035
static void
3036
sixel_debug_print_palette(
3✔
3037
    sixel_dither_t /* in */ *dither /* dithering object */
3038
)
3039
{
3040
    sixel_palette_t *palette_obj;
3041
    unsigned char *palette_copy;
3042
    size_t palette_count;
3043
    int i;
3044

3045
    palette_obj = NULL;
3✔
3046
    palette_copy = NULL;
3✔
3047
    palette_count = 0U;
3✔
3048
    if (dither == NULL) {
3!
3049
        return;
×
3050
    }
3051

3052
    if (SIXEL_FAILED(
3!
3053
            sixel_dither_get_quantized_palette(dither, &palette_obj))
3054
            || palette_obj == NULL) {
3!
3055
        return;
×
3056
    }
3057
    if (SIXEL_FAILED(sixel_palette_copy_entries_8bit(
3!
3058
            palette_obj,
3059
            &palette_copy,
3060
            &palette_count,
3061
            SIXEL_PIXELFORMAT_RGB888,
3062
            dither->allocator))
3063
            || palette_copy == NULL) {
3!
3064
        sixel_palette_unref(palette_obj);
×
3065
        return;
×
3066
    }
3067
    sixel_palette_unref(palette_obj);
3✔
3068

3069
    fprintf(stderr, "palette:\n");
3✔
3070
    for (i = 0; i < (int)palette_count;
51✔
3071
            ++i) {
48✔
3072
        fprintf(stderr, "%d: #%02x%02x%02x\n", i,
64✔
3073
                palette_copy[i * 3 + 0],
48✔
3074
                palette_copy[i * 3 + 1],
48✔
3075
                palette_copy[i * 3 + 2]);
48✔
3076
    }
16✔
3077
    sixel_allocator_free(dither->allocator, palette_copy);
3✔
3078
}
1✔
3079

3080

3081
static SIXELSTATUS
3082
sixel_encoder_output_without_macro(
436✔
3083
    sixel_frame_t       /* in */ *frame,
3084
    sixel_dither_t      /* in */ *dither,
3085
    sixel_output_t      /* in */ *output,
3086
    sixel_encoder_t     /* in */ *encoder)
3087
{
3088
    SIXELSTATUS status = SIXEL_OK;
436✔
3089
    static unsigned char *p;
3090
    int depth;
3091
    enum { message_buffer_size = 2048 };
3092
    char message[message_buffer_size];
3093
    int nwrite;
3094
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3095
    int dulation;
3096
    int delay;
3097
    struct timespec tv;
3098
#endif
3099
    unsigned char *pixbuf;
3100
    int width;
3101
    int height;
3102
    int pixelformat = 0;
436✔
3103
    size_t size;
3104
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
436✔
3105
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3106
    sixel_clock_t last_clock;
3107
#endif
3108

3109
    if (encoder == NULL) {
436!
3110
        sixel_helper_set_additional_message(
×
3111
            "sixel_encoder_output_without_macro: encoder object is null.");
3112
        status = SIXEL_BAD_ARGUMENT;
×
3113
        goto end;
×
3114
    }
3115

3116
    if (encoder->assessment_observer != NULL) {
436✔
3117
        sixel_assessment_stage_transition(
3✔
3118
            encoder->assessment_observer,
3✔
3119
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3120
    }
1✔
3121

3122
    if (encoder->color_option == SIXEL_COLOR_OPTION_DEFAULT) {
436✔
3123
        if (encoder->force_palette) {
340!
3124
            /* keep every slot when the user forced the palette size */
3125
            sixel_dither_set_optimize_palette(dither, 0);
×
3126
        } else {
3127
            sixel_dither_set_optimize_palette(dither, 1);
340✔
3128
        }
3129
    }
114✔
3130

3131
    pixelformat = sixel_frame_get_pixelformat(frame);
436✔
3132
    frame_colorspace = sixel_frame_get_colorspace(frame);
436✔
3133
    output->pixelformat = pixelformat;
436✔
3134
    output->source_colorspace = frame_colorspace;
436✔
3135
    output->colorspace = encoder->output_colorspace;
436✔
3136
    sixel_dither_set_pixelformat(dither, pixelformat);
436✔
3137
    depth = sixel_helper_compute_depth(pixelformat);
436✔
3138
    if (depth < 0) {
436!
3139
        status = SIXEL_LOGIC_ERROR;
×
3140
        nwrite = sixel_compat_snprintf(
×
3141
            message,
3142
            sizeof(message),
3143
            "sixel_encoder_output_without_macro: "
3144
            "sixel_helper_compute_depth(%08x) failed.",
3145
            pixelformat);
3146
        if (nwrite > 0) {
×
3147
            sixel_helper_set_additional_message(message);
×
3148
        }
3149
        goto end;
×
3150
    }
3151

3152
    width = sixel_frame_get_width(frame);
436✔
3153
    height = sixel_frame_get_height(frame);
436✔
3154
    size = (size_t)(width * height * depth);
436✔
3155
    p = (unsigned char *)sixel_allocator_malloc(encoder->allocator, size);
436✔
3156
    if (p == NULL) {
436!
3157
        sixel_helper_set_additional_message(
×
3158
            "sixel_encoder_output_without_macro: sixel_allocator_malloc() failed.");
3159
        status = SIXEL_BAD_ALLOCATION;
×
3160
        goto end;
×
3161
    }
3162
#if defined(HAVE_CLOCK)
3163
    if (output->last_clock == 0) {
436!
3164
        output->last_clock = clock();
436✔
3165
    }
146✔
3166
#elif defined(HAVE_CLOCK_WIN)
3167
    if (output->last_clock == 0) {
3168
        output->last_clock = clock_win();
3169
    }
3170
#endif
3171
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3172
    delay = sixel_frame_get_delay(frame);
436✔
3173
    if (delay > 0 && !encoder->fignore_delay) {
436✔
3174
# if defined(HAVE_CLOCK)
3175
        last_clock = clock();
3✔
3176
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
3177
#  if defined(__APPLE__)
3178
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
2✔
3179
                          / 100000);
1✔
3180
#  else
3181
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
2✔
3182
                          / CLOCKS_PER_SEC);
3183
#  endif
3184
        output->last_clock = last_clock;
3✔
3185
# elif defined(HAVE_CLOCK_WIN)
3186
        last_clock = clock_win();
3187
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3188
                          / CLOCKS_PER_SEC);
3189
        output->last_clock = last_clock;
3190
# else
3191
        dulation = 0;
3192
# endif
3193
        if (dulation < 1000 * 10 * delay) {
3!
3194
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3195
            tv.tv_sec = 0;
3✔
3196
            tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
3✔
3197
#  if defined(HAVE_NANOSLEEP)
3198
            nanosleep(&tv, NULL);
3✔
3199
#  else
3200
            nanosleep_win(&tv, NULL);
3201
#  endif
3202
# endif
3203
        }
1✔
3204
    }
1✔
3205
#endif
3206

3207
    pixbuf = sixel_frame_get_pixels(frame);
436✔
3208
    memcpy(p, pixbuf, (size_t)(width * height * depth));
436✔
3209

3210
    if (encoder->cancel_flag && *encoder->cancel_flag) {
436!
UNCOV
3211
        goto end;
×
3212
    }
3213

3214
    if (encoder->capture_quantized) {
436✔
3215
        status = sixel_encoder_capture_quantized(encoder,
4✔
3216
                                                 dither,
1✔
3217
                                                 p,
1✔
3218
                                                 size,
1✔
3219
                                                 width,
1✔
3220
                                                 height,
1✔
3221
                                                 pixelformat,
1✔
3222
                                                 frame_colorspace,
1✔
3223
                                                 output->colorspace);
1✔
3224
        if (SIXEL_FAILED(status)) {
3!
3225
            goto end;
×
3226
        }
3227
    }
1✔
3228

3229
    if (encoder->assessment_observer != NULL) {
436✔
3230
        sixel_assessment_stage_transition(
3✔
3231
            encoder->assessment_observer,
3✔
3232
            SIXEL_ASSESSMENT_STAGE_ENCODE);
3233
    }
1✔
3234
    status = sixel_encode(p, width, height, depth, dither, output);
436✔
3235
    if (encoder->assessment_observer != NULL) {
436✔
3236
        sixel_assessment_stage_finish(encoder->assessment_observer);
3✔
3237
    }
1✔
3238
    if (status != SIXEL_OK) {
436!
3239
        goto end;
×
3240
    }
3241

3242
end:
290✔
3243
    output->pixelformat = pixelformat;
436✔
3244
    output->source_colorspace = frame_colorspace;
436✔
3245
    sixel_allocator_free(encoder->allocator, p);
436✔
3246

3247
    return status;
436✔
3248
}
3249

3250

3251
static SIXELSTATUS
3252
sixel_encoder_output_with_macro(
84✔
3253
    sixel_frame_t   /* in */ *frame,
3254
    sixel_dither_t  /* in */ *dither,
3255
    sixel_output_t  /* in */ *output,
3256
    sixel_encoder_t /* in */ *encoder)
3257
{
3258
    SIXELSTATUS status = SIXEL_OK;
84✔
3259
    enum { message_buffer_size = 256 };
3260
    char buffer[message_buffer_size];
3261
    int nwrite;
3262
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3263
    int dulation;
3264
    struct timespec tv;
3265
#endif
3266
    int width;
3267
    int height;
3268
    int pixelformat;
3269
    int depth;
3270
    size_t size = 0;
84✔
3271
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
84✔
3272
    unsigned char *converted = NULL;
84✔
3273
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3274
    int delay;
3275
#endif
3276
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3277
    sixel_clock_t last_clock;
3278
#endif
3279
    double write_started_at;
3280
    double write_finished_at;
3281
    double write_duration;
3282

3283
    if (encoder != NULL && encoder->assessment_observer != NULL) {
84!
UNCOV
3284
        sixel_assessment_stage_transition(
×
3285
            encoder->assessment_observer,
×
3286
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3287
    }
3288

3289
#if defined(HAVE_CLOCK)
3290
    if (output->last_clock == 0) {
84!
3291
        output->last_clock = clock();
84✔
3292
    }
28✔
3293
#elif defined(HAVE_CLOCK_WIN)
3294
    if (output->last_clock == 0) {
3295
        output->last_clock = clock_win();
3296
    }
3297
#endif
3298

3299
    width = sixel_frame_get_width(frame);
84✔
3300
    height = sixel_frame_get_height(frame);
84✔
3301
    pixelformat = sixel_frame_get_pixelformat(frame);
84✔
3302
    depth = sixel_helper_compute_depth(pixelformat);
84✔
3303
    if (depth < 0) {
84!
3304
        status = SIXEL_LOGIC_ERROR;
×
3305
        sixel_helper_set_additional_message(
×
3306
            "sixel_encoder_output_with_macro: "
3307
            "sixel_helper_compute_depth() failed.");
3308
        goto end;
×
3309
    }
3310

3311
    frame_colorspace = sixel_frame_get_colorspace(frame);
84✔
3312
    size = (size_t)width * (size_t)height * (size_t)depth;
84✔
3313
    converted = (unsigned char *)sixel_allocator_malloc(
84✔
3314
        encoder->allocator, size);
28✔
3315
    if (converted == NULL) {
84!
3316
        sixel_helper_set_additional_message(
×
3317
            "sixel_encoder_output_with_macro: "
3318
            "sixel_allocator_malloc() failed.");
3319
        status = SIXEL_BAD_ALLOCATION;
×
3320
        goto end;
×
3321
    }
3322

3323
    memcpy(converted, sixel_frame_get_pixels(frame), size);
84✔
3324
    output->pixelformat = pixelformat;
84✔
3325
    output->source_colorspace = frame_colorspace;
84✔
3326
    output->colorspace = encoder->output_colorspace;
84✔
3327

3328
    if (sixel_frame_get_loop_no(frame) == 0) {
84✔
3329
        if (encoder->macro_number >= 0) {
54!
3330
            nwrite = sixel_compat_snprintf(
×
3331
                buffer,
3332
                sizeof(buffer),
3333
                "\033P%d;0;1!z",
3334
                encoder->macro_number);
3335
        } else {
3336
            nwrite = sixel_compat_snprintf(
54✔
3337
                buffer,
18✔
3338
                sizeof(buffer),
3339
                "\033P%d;0;1!z",
3340
                sixel_frame_get_frame_no(frame));
18✔
3341
        }
3342
        if (nwrite < 0) {
54!
3343
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3344
            sixel_helper_set_additional_message(
×
3345
                "sixel_encoder_output_with_macro: command format failed.");
3346
            goto end;
×
3347
        }
3348
        write_started_at = 0.0;
54✔
3349
        write_finished_at = 0.0;
54✔
3350
        write_duration = 0.0;
54✔
3351
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3352
            write_started_at = sixel_assessment_timer_now();
×
3353
        }
3354
        nwrite = sixel_write_callback(buffer,
72✔
3355
                                      (int)strlen(buffer),
54✔
3356
                                      &encoder->outfd);
54✔
3357
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3358
            write_finished_at = sixel_assessment_timer_now();
×
3359
            write_duration = write_finished_at - write_started_at;
×
3360
            if (write_duration < 0.0) {
×
3361
                write_duration = 0.0;
×
3362
            }
3363
        }
3364
        if (nwrite < 0) {
54!
3365
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3366
            sixel_helper_set_additional_message(
×
3367
                "sixel_encoder_output_with_macro: "
3368
                "sixel_write_callback() failed.");
3369
            goto end;
×
3370
        }
3371
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3372
            sixel_assessment_record_output_write(
×
3373
                encoder->assessment_observer,
×
3374
                (size_t)nwrite,
3375
                write_duration);
3376
        }
3377

3378
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3379
            sixel_assessment_stage_transition(
×
3380
                encoder->assessment_observer,
×
3381
                SIXEL_ASSESSMENT_STAGE_ENCODE);
3382
        }
3383
        status = sixel_encode(converted,
72✔
3384
                              width,
18✔
3385
                              height,
18✔
3386
                              depth,
18✔
3387
                              dither,
18✔
3388
                              output);
18✔
3389
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3390
            sixel_assessment_stage_finish(
×
3391
                encoder->assessment_observer);
×
3392
        }
3393
        if (SIXEL_FAILED(status)) {
54!
3394
            goto end;
×
3395
        }
3396

3397
        write_started_at = 0.0;
54✔
3398
        write_finished_at = 0.0;
54✔
3399
        write_duration = 0.0;
54✔
3400
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3401
            write_started_at = sixel_assessment_timer_now();
×
3402
        }
3403
        nwrite = sixel_write_callback("\033\\", 2, &encoder->outfd);
54✔
3404
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3405
            write_finished_at = sixel_assessment_timer_now();
×
3406
            write_duration = write_finished_at - write_started_at;
×
3407
            if (write_duration < 0.0) {
×
3408
                write_duration = 0.0;
×
3409
            }
3410
        }
3411
        if (nwrite < 0) {
54!
3412
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3413
            sixel_helper_set_additional_message(
×
3414
                "sixel_encoder_output_with_macro: "
3415
                "sixel_write_callback() failed.");
3416
            goto end;
×
3417
        }
3418
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3419
            sixel_assessment_record_output_write(
×
3420
                encoder->assessment_observer,
×
3421
                (size_t)nwrite,
3422
                write_duration);
3423
        }
3424
    }
18✔
3425
    if (encoder->macro_number < 0) {
111✔
3426
        nwrite = sixel_compat_snprintf(
81✔
3427
            buffer,
27✔
3428
            sizeof(buffer),
3429
            "\033[%d*z",
3430
            sixel_frame_get_frame_no(frame));
27✔
3431
        if (nwrite < 0) {
81!
3432
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3433
            sixel_helper_set_additional_message(
×
3434
                "sixel_encoder_output_with_macro: command format failed.");
3435
        }
3436
        write_started_at = 0.0;
81✔
3437
        write_finished_at = 0.0;
81✔
3438
        write_duration = 0.0;
81✔
3439
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
3440
            write_started_at = sixel_assessment_timer_now();
×
3441
        }
3442
        nwrite = sixel_write_callback(buffer,
108✔
3443
                                      (int)strlen(buffer),
81✔
3444
                                      &encoder->outfd);
81✔
3445
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
3446
            write_finished_at = sixel_assessment_timer_now();
×
3447
            write_duration = write_finished_at - write_started_at;
×
3448
            if (write_duration < 0.0) {
×
3449
                write_duration = 0.0;
×
3450
            }
3451
        }
3452
        if (nwrite < 0) {
81!
3453
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3454
            sixel_helper_set_additional_message(
×
3455
                "sixel_encoder_output_with_macro: "
3456
                "sixel_write_callback() failed.");
3457
            goto end;
×
3458
        }
3459
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
3460
            sixel_assessment_record_output_write(
×
3461
                encoder->assessment_observer,
×
3462
                (size_t)nwrite,
3463
                write_duration);
3464
        }
3465
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3466
        delay = sixel_frame_get_delay(frame);
81✔
3467
        if (delay > 0 && !encoder->fignore_delay) {
81!
3468
# if defined(HAVE_CLOCK)
3469
            last_clock = clock();
54✔
3470
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
3471
#  if defined(__APPLE__)
3472
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
36✔
3473
                             / 100000);
18✔
3474
#  else
3475
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
36✔
3476
                             / CLOCKS_PER_SEC);
3477
#  endif
3478
            output->last_clock = last_clock;
54✔
3479
# elif defined(HAVE_CLOCK_WIN)
3480
            last_clock = clock_win();
3481
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3482
                             / CLOCKS_PER_SEC);
3483
            output->last_clock = last_clock;
3484
# else
3485
            dulation = 0;
3486
# endif
3487
            if (dulation < 1000 * 10 * delay) {
54!
3488
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3489
                tv.tv_sec = 0;
54✔
3490
                tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
54✔
3491
#  if defined(HAVE_NANOSLEEP)
3492
                nanosleep(&tv, NULL);
54✔
3493
#  else
3494
                nanosleep_win(&tv, NULL);
3495
#  endif
3496
# endif
3497
            }
18✔
3498
        }
18✔
3499
#endif
3500
    }
27✔
3501

3502
end:
20✔
3503
    output->pixelformat = pixelformat;
84✔
3504
    output->source_colorspace = frame_colorspace;
84✔
3505
    sixel_allocator_free(encoder->allocator, converted);
84✔
3506

3507
    return status;
84✔
3508
}
3509

3510

3511
static SIXELSTATUS
3512
sixel_encoder_emit_iso2022_chars(
×
3513
    sixel_encoder_t *encoder,
3514
    sixel_frame_t *frame
3515
)
3516
{
3517
    char *buf_p, *buf;
3518
    int col, row;
3519
    int charset;
3520
    int is_96cs;
3521
    unsigned int charset_no;
3522
    unsigned int code;
3523
    int num_cols, num_rows;
3524
    SIXELSTATUS status;
3525
    size_t alloc_size;
3526
    int nwrite;
3527
    int target_fd;
3528
    int chunk_size;
3529

3530
    charset_no = encoder->drcs_charset_no;
×
3531
    if (charset_no == 0u) {
×
3532
        charset_no = 1u;
×
3533
    }
3534
    if (encoder->drcs_mmv == 0) {
×
3535
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3536
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3537
    } else if (encoder->drcs_mmv == 1) {
×
3538
        is_96cs = 0;
×
3539
        charset = (int)(charset_no + 0x3fu);
×
3540
    } else {
3541
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3542
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3543
    }
3544
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3545
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3546
             / encoder->cell_width;
×
3547
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3548
             / encoder->cell_height;
×
3549

3550
    /* cols x rows + designation(4 chars) + SI + SO + LFs */
3551
    alloc_size = num_cols * num_rows + (num_cols * num_rows / 96 + 1) * 4 + 2 + num_rows;
×
3552
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3553
    if (buf == NULL) {
×
3554
        sixel_helper_set_additional_message(
×
3555
            "sixel_encoder_emit_iso2022_chars: sixel_allocator_malloc() failed.");
3556
        status = SIXEL_BAD_ALLOCATION;
×
3557
        goto end;
×
3558
    }
3559

3560
    code = 0x20;
×
3561
    *(buf_p++) = '\016';  /* SI */
×
3562
    *(buf_p++) = '\033';
×
3563
    *(buf_p++) = ')';
×
3564
    *(buf_p++) = ' ';
×
3565
    *(buf_p++) = charset;
×
3566
    for(row = 0; row < num_rows; row++) {
×
3567
        for(col = 0; col < num_cols; col++) {
×
3568
            if ((code & 0x7f) == 0x0) {
×
3569
                if (charset == 0x7e) {
×
3570
                    is_96cs = 1 - is_96cs;
×
3571
                    charset = '0';
×
3572
                } else {
3573
                    charset++;
×
3574
                }
3575
                code = 0x20;
×
3576
                *(buf_p++) = '\033';
×
3577
                *(buf_p++) = is_96cs ? '-': ')';
×
3578
                *(buf_p++) = ' ';
×
3579
                *(buf_p++) = charset;
×
3580
            }
3581
            *(buf_p++) = code++;
×
3582
        }
3583
        *(buf_p++) = '\n';
×
3584
    }
3585
    *(buf_p++) = '\017';  /* SO */
×
3586

3587
    if (encoder->tile_outfd >= 0) {
×
3588
        target_fd = encoder->tile_outfd;
×
3589
    } else {
3590
        target_fd = encoder->outfd;
×
3591
    }
3592

3593
    chunk_size = (int)(buf_p - buf);
×
3594
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3595
                                          buf,
3596
                                          chunk_size,
3597
                                          target_fd);
3598
    if (nwrite != chunk_size) {
×
3599
        sixel_helper_set_additional_message(
×
3600
            "sixel_encoder_emit_iso2022_chars: write() failed.");
3601
        status = SIXEL_RUNTIME_ERROR;
×
3602
        goto end;
×
3603
    }
3604

3605
    sixel_allocator_free(encoder->allocator, buf);
×
3606

3607
    status = SIXEL_OK;
×
3608

3609
end:
3610
    return status;
×
3611
}
3612

3613

3614
/*
3615
 * This routine is derived from mlterm's drcssixel.c
3616
 * (https://raw.githubusercontent.com/arakiken/mlterm/master/drcssixel/drcssixel.c).
3617
 * The original implementation is credited to Araki Ken.
3618
 * Adjusted here to integrate with libsixel's encoder pipeline.
3619
 */
3620
static SIXELSTATUS
3621
sixel_encoder_emit_drcsmmv2_chars(
×
3622
    sixel_encoder_t *encoder,
3623
    sixel_frame_t *frame
3624
)
3625
{
3626
    char *buf_p, *buf;
3627
    int col, row;
3628
    int charset;
3629
    int is_96cs;
3630
    unsigned int charset_no;
3631
    unsigned int code;
3632
    int num_cols, num_rows;
3633
    SIXELSTATUS status;
3634
    size_t alloc_size;
3635
    int nwrite;
3636
    int target_fd;
3637
    int chunk_size;
3638

3639
    charset_no = encoder->drcs_charset_no;
×
3640
    if (charset_no == 0u) {
×
3641
        charset_no = 1u;
×
3642
    }
3643
    if (encoder->drcs_mmv == 0) {
×
3644
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3645
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3646
    } else if (encoder->drcs_mmv == 1) {
×
3647
        is_96cs = 0;
×
3648
        charset = (int)(charset_no + 0x3fu);
×
3649
    } else {
3650
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3651
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3652
    }
3653
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3654
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3655
             / encoder->cell_width;
×
3656
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3657
             / encoder->cell_height;
×
3658

3659
    /* cols x rows x 4(out of BMP) + rows(LFs) */
3660
    alloc_size = num_cols * num_rows * 4 + num_rows;
×
3661
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3662
    if (buf == NULL) {
×
3663
        sixel_helper_set_additional_message(
×
3664
            "sixel_encoder_emit_drcsmmv2_chars: sixel_allocator_malloc() failed.");
3665
        status = SIXEL_BAD_ALLOCATION;
×
3666
        goto end;
×
3667
    }
3668

3669
    for(row = 0; row < num_rows; row++) {
×
3670
        for(col = 0; col < num_cols; col++) {
×
3671
            *(buf_p++) = ((code >> 18) & 0x07) | 0xf0;
×
3672
            *(buf_p++) = ((code >> 12) & 0x3f) | 0x80;
×
3673
            *(buf_p++) = ((code >> 6) & 0x3f) | 0x80;
×
3674
            *(buf_p++) = (code & 0x3f) | 0x80;
×
3675
            code++;
×
3676
            if ((code & 0x7f) == 0x0) {
×
3677
                if (charset == 0x7e) {
×
3678
                    is_96cs = 1 - is_96cs;
×
3679
                    charset = '0';
×
3680
                } else {
3681
                    charset++;
×
3682
                }
3683
                code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3684
            }
3685
        }
3686
        *(buf_p++) = '\n';
×
3687
    }
3688

3689
    if (charset == 0x7e) {
×
3690
        is_96cs = 1 - is_96cs;
×
3691
    } else {
3692
        charset = '0';
×
3693
        charset++;
×
3694
    }
3695
    if (encoder->tile_outfd >= 0) {
×
3696
        target_fd = encoder->tile_outfd;
×
3697
    } else {
3698
        target_fd = encoder->outfd;
×
3699
    }
3700

3701
    chunk_size = (int)(buf_p - buf);
×
3702
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3703
                                          buf,
3704
                                          chunk_size,
3705
                                          target_fd);
3706
    if (nwrite != chunk_size) {
×
3707
        sixel_helper_set_additional_message(
×
3708
            "sixel_encoder_emit_drcsmmv2_chars: write() failed.");
3709
        status = SIXEL_RUNTIME_ERROR;
×
3710
        goto end;
×
3711
    }
3712

3713
    sixel_allocator_free(encoder->allocator, buf);
×
3714

3715
    status = SIXEL_OK;
×
3716

3717
end:
3718
    return status;
×
3719
}
3720

3721
static SIXELSTATUS
3722
sixel_encoder_encode_frame(
526✔
3723
    sixel_encoder_t *encoder,
3724
    sixel_frame_t   *frame,
3725
    sixel_output_t  *output)
3726
{
3727
    SIXELSTATUS status = SIXEL_FALSE;
526✔
3728
    sixel_dither_t *dither = NULL;
526✔
3729
    int height;
3730
    int is_animation = 0;
526✔
3731
    int nwrite;
3732
    int drcs_is_96cs_param;
3733
    int drcs_designate_char;
3734
    char buf[256];
3735
    sixel_write_function fn_write;
3736
    sixel_write_function write_callback;
3737
    sixel_write_function scroll_callback;
3738
    void *write_priv;
3739
    void *scroll_priv;
3740
    sixel_encoder_output_probe_t probe;
3741
    sixel_encoder_output_probe_t scroll_probe;
3742
    sixel_assessment_t *assessment;
3743

3744
    fn_write = sixel_write_callback;
526✔
3745
    write_callback = sixel_write_callback;
526✔
3746
    scroll_callback = sixel_write_callback;
526✔
3747
    write_priv = &encoder->outfd;
526✔
3748
    scroll_priv = &encoder->outfd;
526✔
3749
    probe.encoder = NULL;
526✔
3750
    probe.base_write = NULL;
526✔
3751
    probe.base_priv = NULL;
526✔
3752
    scroll_probe.encoder = NULL;
526✔
3753
    scroll_probe.base_write = NULL;
526✔
3754
    scroll_probe.base_priv = NULL;
526✔
3755
    assessment = NULL;
526✔
3756
    if (encoder != NULL) {
526!
3757
        assessment = encoder->assessment_observer;
526✔
3758
    }
176✔
3759
    if (assessment != NULL) {
526✔
3760
        if (encoder->clipfirst) {
3!
3761
            sixel_assessment_stage_transition(
×
3762
                assessment,
3763
                SIXEL_ASSESSMENT_STAGE_CROP);
3764
        } else {
3765
            sixel_assessment_stage_transition(
3✔
3766
                assessment,
1✔
3767
                SIXEL_ASSESSMENT_STAGE_SCALE);
3768
        }
3769
    }
1✔
3770

3771
    /*
3772
     *  Geometry timeline:
3773
     *
3774
     *      +-------+    +------+    +---------------+
3775
     *      | scale | -> | crop | -> | color convert |
3776
     *      +-------+    +------+    +---------------+
3777
     *
3778
     *  The order of the first two blocks mirrors `-c`, so we hop between
3779
     *  SCALE and CROP depending on `clipfirst`.
3780
     */
3781

3782
    if (encoder->clipfirst) {
526✔
3783
        status = sixel_encoder_do_clip(encoder, frame);
6✔
3784
        if (SIXEL_FAILED(status)) {
6!
3785
            goto end;
×
3786
        }
3787
        if (assessment != NULL) {
6!
3788
            sixel_assessment_stage_transition(
×
3789
                assessment,
3790
                SIXEL_ASSESSMENT_STAGE_SCALE);
3791
        }
3792
        status = sixel_encoder_do_resize(encoder, frame);
6✔
3793
        if (SIXEL_FAILED(status)) {
6!
3794
            goto end;
×
3795
        }
3796
    } else {
2✔
3797
        status = sixel_encoder_do_resize(encoder, frame);
520✔
3798
        if (SIXEL_FAILED(status)) {
520✔
3799
            goto end;
6✔
3800
        }
3801
        if (assessment != NULL) {
514✔
3802
            sixel_assessment_stage_transition(
3✔
3803
                assessment,
1✔
3804
                SIXEL_ASSESSMENT_STAGE_CROP);
3805
        }
1✔
3806
        status = sixel_encoder_do_clip(encoder, frame);
514✔
3807
        if (SIXEL_FAILED(status)) {
514!
3808
            goto end;
×
3809
        }
3810
    }
3811

3812
    if (assessment != NULL) {
520✔
3813
        sixel_assessment_stage_transition(
3✔
3814
            assessment,
1✔
3815
            SIXEL_ASSESSMENT_STAGE_COLORSPACE);
3816
    }
1✔
3817

3818
    status = sixel_frame_ensure_colorspace(frame,
694✔
3819
                                           encoder->working_colorspace);
174✔
3820
    if (SIXEL_FAILED(status)) {
520!
3821
        goto end;
×
3822
    }
3823

3824
    if (assessment != NULL) {
520✔
3825
        sixel_assessment_stage_transition(
3✔
3826
            assessment,
1✔
3827
            SIXEL_ASSESSMENT_STAGE_PALETTE_HISTOGRAM);
3828
    }
1✔
3829

3830
    /* prepare dither context */
3831
    status = sixel_encoder_prepare_palette(encoder, frame, &dither);
520✔
3832
    if (status != SIXEL_OK) {
520!
3833
        dither = NULL;
×
3834
        goto end;
×
3835
    }
3836

3837
    if (encoder->dither_cache != NULL) {
520!
3838
        encoder->dither_cache = dither;
×
3839
        sixel_dither_ref(dither);
×
3840
    }
3841

3842
    if (encoder->fdrcs) {
520!
3843
        status = sixel_encoder_ensure_cell_size(encoder);
×
3844
        if (SIXEL_FAILED(status)) {
×
3845
            goto end;
×
3846
        }
3847
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
×
3848
            sixel_helper_set_additional_message(
×
3849
                "drcs option cannot be used together with macro output.");
3850
            status = SIXEL_BAD_ARGUMENT;
×
3851
            goto end;
×
3852
        }
3853
    }
3854

3855
    /* evaluate -v option: print palette */
3856
    if (encoder->verbose) {
520✔
3857
        if ((sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE)) {
9✔
3858
            sixel_debug_print_palette(dither);
3✔
3859
        }
1✔
3860
    }
3✔
3861

3862
    /* evaluate -d option: set method for diffusion */
3863
    sixel_dither_set_diffusion_type(dither, encoder->method_for_diffuse);
520✔
3864
    sixel_dither_set_diffusion_scan(dither, encoder->method_for_scan);
520✔
3865
    sixel_dither_set_diffusion_carry(dither, encoder->method_for_carry);
520✔
3866

3867
    /* evaluate -C option: set complexion score */
3868
    if (encoder->complexion > 1) {
520✔
3869
        sixel_dither_set_complexion_score(dither, encoder->complexion);
6✔
3870
    }
2✔
3871

3872
    if (output) {
520!
3873
        sixel_output_ref(output);
×
3874
        if (encoder->assessment_observer != NULL) {
×
3875
            probe.encoder = encoder;
×
3876
            probe.base_write = fn_write;
×
3877
            probe.base_priv = &encoder->outfd;
×
3878
            write_callback = sixel_write_with_probe;
×
3879
            write_priv = &probe;
×
3880
        }
3881
    } else {
3882
        /* create output context */
3883
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
520✔
3884
            /* -u or -n option */
3885
            fn_write = sixel_hex_write_callback;
84✔
3886
        } else {
28✔
3887
            fn_write = sixel_write_callback;
436✔
3888
        }
3889
        write_callback = fn_write;
520✔
3890
        write_priv = &encoder->outfd;
520✔
3891
        if (encoder->assessment_observer != NULL) {
520✔
3892
            probe.encoder = encoder;
3✔
3893
            probe.base_write = fn_write;
3✔
3894
            probe.base_priv = &encoder->outfd;
3✔
3895
            write_callback = sixel_write_with_probe;
3✔
3896
            write_priv = &probe;
3✔
3897
        }
1✔
3898
        status = sixel_output_new(&output,
520✔
3899
                                  write_callback,
174✔
3900
                                  write_priv,
174✔
3901
                                  encoder->allocator);
174✔
3902
        if (SIXEL_FAILED(status)) {
520!
3903
            goto end;
×
3904
        }
3905
    }
3906

3907
    if (encoder->fdrcs) {
520!
3908
        sixel_output_set_skip_dcs_envelope(output, 1);
×
3909
        sixel_output_set_skip_header(output, 1);
×
3910
    }
3911

3912
    sixel_output_set_8bit_availability(output, encoder->f8bit);
520✔
3913
    sixel_output_set_gri_arg_limit(output, encoder->has_gri_arg_limit);
520✔
3914
    sixel_output_set_palette_type(output, encoder->palette_type);
520✔
3915
    sixel_output_set_penetrate_multiplexer(
520✔
3916
        output, encoder->penetrate_multiplexer);
174✔
3917
    sixel_output_set_encode_policy(output, encoder->encode_policy);
520✔
3918
    sixel_output_set_ormode(output, encoder->ormode);
520✔
3919

3920
    if (sixel_frame_get_multiframe(frame) && !encoder->fstatic) {
520✔
3921
        if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
108✔
3922
            is_animation = 1;
99✔
3923
        }
33✔
3924
        height = sixel_frame_get_height(frame);
108✔
3925
        if (encoder->assessment_observer != NULL) {
108!
3926
            scroll_probe.encoder = encoder;
×
3927
            scroll_probe.base_write = sixel_write_callback;
×
3928
            scroll_probe.base_priv = &encoder->outfd;
×
3929
            scroll_callback = sixel_write_with_probe;
×
3930
            scroll_priv = &scroll_probe;
×
3931
        } else {
3932
            scroll_callback = sixel_write_callback;
108✔
3933
            scroll_priv = &encoder->outfd;
108✔
3934
        }
3935
        (void) sixel_tty_scroll(scroll_callback,
144✔
3936
                                scroll_priv,
36✔
3937
                                encoder->outfd,
36✔
3938
                                height,
36✔
3939
                                is_animation);
36✔
3940
    }
36✔
3941

3942
    if (encoder->cancel_flag && *encoder->cancel_flag) {
520!
3943
        status = SIXEL_INTERRUPTED;
×
3944
        goto end;
×
3945
    }
3946

3947
    if (encoder->fdrcs) {  /* -@ option */
520!
3948
        if (encoder->drcs_mmv == 0) {
×
3949
            drcs_is_96cs_param =
×
3950
                (encoder->drcs_charset_no > 63u) ? 1 : 0;
×
3951
            drcs_designate_char =
×
3952
                (int)(((encoder->drcs_charset_no - 1u) % 63u) + 0x40u);
×
3953
        } else if (encoder->drcs_mmv == 1) {
×
3954
            drcs_is_96cs_param = 0;
×
3955
            drcs_designate_char =
×
3956
                (int)(encoder->drcs_charset_no + 0x3fu);
×
3957
        } else {
3958
            drcs_is_96cs_param =
×
3959
                (encoder->drcs_charset_no > 79u) ? 1 : 0;
×
3960
            drcs_designate_char =
×
3961
                (int)(((encoder->drcs_charset_no - 1u) % 79u) + 0x30u);
×
3962
        }
3963
        nwrite = sprintf(buf,
×
3964
                         "%s%s1;0;0;%d;1;3;%d;%d{ %c",
3965
                         (encoder->drcs_mmv > 0) ? (
×
3966
                             encoder->f8bit ? "\233?8800h": "\033[?8800h"
×
3967
                         ): "",
3968
                         encoder->f8bit ? "\220": "\033P",
×
3969
                         encoder->cell_width,
3970
                         encoder->cell_height,
3971
                         drcs_is_96cs_param,
3972
                         drcs_designate_char);
3973
        if (nwrite < 0) {
×
3974
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3975
            sixel_helper_set_additional_message(
×
3976
                "sixel_encoder_encode_frame: command format failed.");
3977
            goto end;
×
3978
        }
3979
        nwrite = write_callback(buf, nwrite, write_priv);
×
3980
        if (nwrite < 0) {
×
3981
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3982
            sixel_helper_set_additional_message(
×
3983
                "sixel_encoder_encode_frame: write() failed.");
3984
            goto end;
×
3985
        }
3986
    }
3987

3988
    /* output sixel: junction of multi-frame processing strategy */
3989
    if (encoder->fuse_macro) {  /* -u option */
520✔
3990
        /* use macro */
3991
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
81✔
3992
    } else if (encoder->macro_number >= 0) { /* -n option */
466✔
3993
        /* use macro */
3994
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
3✔
3995
    } else {
1✔
3996
        /* do not use macro */
3997
        status = sixel_encoder_output_without_macro(frame, dither, output, encoder);
436✔
3998
    }
3999
    if (SIXEL_FAILED(status)) {
520!
4000
        goto end;
×
4001
    }
4002

4003
    if (encoder->cancel_flag && *encoder->cancel_flag) {
520!
4004
        nwrite = write_callback("\x18\033\\", 3, write_priv);
×
4005
        if (nwrite < 0) {
×
4006
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4007
            sixel_helper_set_additional_message(
×
4008
                "sixel_encoder_encode_frame: write_callback() failed.");
4009
            goto end;
×
4010
        }
4011
        status = SIXEL_INTERRUPTED;
×
4012
    }
4013
    if (SIXEL_FAILED(status)) {
520!
4014
        goto end;
×
4015
    }
4016

4017
    if (encoder->fdrcs) {  /* -@ option */
520!
4018
        if (encoder->f8bit) {
×
4019
            nwrite = write_callback("\234", 1, write_priv);
×
4020
        } else {
4021
            nwrite = write_callback("\033\\", 2, write_priv);
×
4022
        }
4023
        if (nwrite < 0) {
×
4024
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4025
            sixel_helper_set_additional_message(
×
4026
                "sixel_encoder_encode_frame: write_callback() failed.");
4027
            goto end;
×
4028
        }
4029

4030
        if (encoder->tile_outfd >= 0) {
×
4031
            if (encoder->drcs_mmv == 0) {
×
4032
                status = sixel_encoder_emit_iso2022_chars(encoder, frame);
×
4033
                if (SIXEL_FAILED(status)) {
×
4034
                    goto end;
×
4035
                }
4036
            } else {
4037
                status = sixel_encoder_emit_drcsmmv2_chars(encoder, frame);
×
4038
                if (SIXEL_FAILED(status)) {
×
4039
                    goto end;
×
4040
                }
4041
            }
4042
        }
4043
    }
4044

4045

4046
end:
346✔
4047
    if (output) {
526✔
4048
        sixel_output_unref(output);
520✔
4049
    }
174✔
4050
    if (dither) {
526✔
4051
        sixel_dither_unref(dither);
520✔
4052
    }
174✔
4053

4054
    return status;
526✔
4055
}
4056

4057

4058
/* create encoder object */
4059
SIXELAPI SIXELSTATUS
4060
sixel_encoder_new(
545✔
4061
    sixel_encoder_t     /* out */ **ppencoder, /* encoder object to be created */
4062
    sixel_allocator_t   /* in */  *allocator)  /* allocator, null if you use
4063
                                                  default allocator */
4064
{
4065
    SIXELSTATUS status = SIXEL_FALSE;
545✔
4066
    char const *env_default_bgcolor = NULL;
545✔
4067
    char const *env_default_ncolors = NULL;
545✔
4068
    int ncolors;
4069
#if HAVE__DUPENV_S
4070
    errno_t e;
4071
    size_t len;
4072
#endif  /* HAVE__DUPENV_S */
4073

4074
    if (allocator == NULL) {
545!
4075
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
545✔
4076
        if (SIXEL_FAILED(status)) {
545!
4077
            goto end;
×
4078
        }
4079
    } else {
183✔
4080
        sixel_allocator_ref(allocator);
×
4081
    }
4082

4083
    *ppencoder
183✔
4084
        = (sixel_encoder_t *)sixel_allocator_malloc(allocator,
728✔
4085
                                                    sizeof(sixel_encoder_t));
4086
    if (*ppencoder == NULL) {
545!
4087
        sixel_helper_set_additional_message(
×
4088
            "sixel_encoder_new: sixel_allocator_malloc() failed.");
4089
        status = SIXEL_BAD_ALLOCATION;
×
4090
        sixel_allocator_unref(allocator);
×
4091
        goto end;
×
4092
    }
4093

4094
    (*ppencoder)->ref                   = 1;
545✔
4095
    (*ppencoder)->reqcolors             = (-1);
545✔
4096
    (*ppencoder)->force_palette         = 0;
545✔
4097
    (*ppencoder)->mapfile               = NULL;
545✔
4098
    (*ppencoder)->palette_output        = NULL;
545✔
4099
    (*ppencoder)->loader_order          = NULL;
545✔
4100
    (*ppencoder)->color_option          = SIXEL_COLOR_OPTION_DEFAULT;
545✔
4101
    (*ppencoder)->builtin_palette       = 0;
545✔
4102
    (*ppencoder)->method_for_diffuse    = SIXEL_DIFFUSE_AUTO;
545✔
4103
    (*ppencoder)->method_for_scan       = SIXEL_SCAN_AUTO;
545✔
4104
    (*ppencoder)->method_for_carry      = SIXEL_CARRY_AUTO;
545✔
4105
    (*ppencoder)->method_for_largest    = SIXEL_LARGE_AUTO;
545✔
4106
    (*ppencoder)->method_for_rep        = SIXEL_REP_AUTO;
545✔
4107
    (*ppencoder)->quality_mode          = SIXEL_QUALITY_AUTO;
545✔
4108
    (*ppencoder)->quantize_model        = SIXEL_QUANTIZE_MODEL_AUTO;
545✔
4109
    (*ppencoder)->final_merge_mode      = SIXEL_FINAL_MERGE_AUTO;
545✔
4110
    (*ppencoder)->lut_policy            = SIXEL_LUT_POLICY_AUTO;
545✔
4111
    (*ppencoder)->sixel_reversible      = 0;
545✔
4112
    (*ppencoder)->method_for_resampling = SIXEL_RES_BILINEAR;
545✔
4113
    (*ppencoder)->loop_mode             = SIXEL_LOOP_AUTO;
545✔
4114
    (*ppencoder)->palette_type          = SIXEL_PALETTETYPE_AUTO;
545✔
4115
    (*ppencoder)->f8bit                 = 0;
545✔
4116
    (*ppencoder)->has_gri_arg_limit     = 0;
545✔
4117
    (*ppencoder)->finvert               = 0;
545✔
4118
    (*ppencoder)->fuse_macro            = 0;
545✔
4119
    (*ppencoder)->fdrcs                 = 0;
545✔
4120
    (*ppencoder)->fignore_delay         = 0;
545✔
4121
    (*ppencoder)->complexion            = 1;
545✔
4122
    (*ppencoder)->fstatic               = 0;
545✔
4123
    (*ppencoder)->cell_width            = 0;
545✔
4124
    (*ppencoder)->cell_height           = 0;
545✔
4125
    (*ppencoder)->pixelwidth            = (-1);
545✔
4126
    (*ppencoder)->pixelheight           = (-1);
545✔
4127
    (*ppencoder)->percentwidth          = (-1);
545✔
4128
    (*ppencoder)->percentheight         = (-1);
545✔
4129
    (*ppencoder)->clipx                 = 0;
545✔
4130
    (*ppencoder)->clipy                 = 0;
545✔
4131
    (*ppencoder)->clipwidth             = 0;
545✔
4132
    (*ppencoder)->clipheight            = 0;
545✔
4133
    (*ppencoder)->clipfirst             = 0;
545✔
4134
    (*ppencoder)->macro_number          = (-1);
545✔
4135
    (*ppencoder)->verbose               = 0;
545✔
4136
    (*ppencoder)->penetrate_multiplexer = 0;
545✔
4137
    (*ppencoder)->encode_policy         = SIXEL_ENCODEPOLICY_AUTO;
545✔
4138
    (*ppencoder)->working_colorspace    = SIXEL_COLORSPACE_GAMMA;
545✔
4139
    (*ppencoder)->output_colorspace     = SIXEL_COLORSPACE_GAMMA;
545✔
4140
    (*ppencoder)->ormode                = 0;
545✔
4141
    (*ppencoder)->pipe_mode             = 0;
545✔
4142
    (*ppencoder)->bgcolor               = NULL;
545✔
4143
    (*ppencoder)->outfd                 = STDOUT_FILENO;
545✔
4144
    (*ppencoder)->tile_outfd            = (-1);
545✔
4145
    (*ppencoder)->finsecure             = 0;
545✔
4146
    (*ppencoder)->cancel_flag           = NULL;
545✔
4147
    (*ppencoder)->dither_cache          = NULL;
545✔
4148
    (*ppencoder)->drcs_charset_no       = 1u;
545✔
4149
    (*ppencoder)->drcs_mmv              = 2;
545✔
4150
    (*ppencoder)->capture_quantized     = 0;
545✔
4151
    (*ppencoder)->capture_source        = 0;
545✔
4152
    (*ppencoder)->capture_pixels        = NULL;
545✔
4153
    (*ppencoder)->capture_pixels_size   = 0;
545✔
4154
    (*ppencoder)->capture_palette       = NULL;
545✔
4155
    (*ppencoder)->capture_palette_size  = 0;
545✔
4156
    (*ppencoder)->capture_pixel_bytes   = 0;
545✔
4157
    (*ppencoder)->capture_width         = 0;
545✔
4158
    (*ppencoder)->capture_height        = 0;
545✔
4159
    (*ppencoder)->capture_pixelformat   = SIXEL_PIXELFORMAT_RGB888;
545✔
4160
    (*ppencoder)->capture_colorspace    = SIXEL_COLORSPACE_GAMMA;
545✔
4161
    (*ppencoder)->capture_ncolors       = 0;
545✔
4162
    (*ppencoder)->capture_valid         = 0;
545✔
4163
    (*ppencoder)->capture_source_frame  = NULL;
545✔
4164
    (*ppencoder)->assessment_observer   = NULL;
545✔
4165
    (*ppencoder)->assessment_json_path  = NULL;
545✔
4166
    (*ppencoder)->assessment_sections   = SIXEL_ASSESSMENT_SECTION_NONE;
545✔
4167
    (*ppencoder)->last_loader_name[0]   = '\0';
545✔
4168
    (*ppencoder)->last_source_path[0]   = '\0';
545✔
4169
    (*ppencoder)->last_input_bytes      = 0u;
545✔
4170
    (*ppencoder)->output_is_png         = 0;
545✔
4171
    (*ppencoder)->output_png_to_stdout  = 0;
545✔
4172
    (*ppencoder)->png_output_path       = NULL;
545✔
4173
    (*ppencoder)->sixel_output_path     = NULL;
545✔
4174
    (*ppencoder)->clipboard_output_active = 0;
545✔
4175
    (*ppencoder)->clipboard_output_format[0] = '\0';
545✔
4176
    (*ppencoder)->clipboard_output_path = NULL;
545✔
4177
    (*ppencoder)->allocator             = allocator;
545✔
4178

4179
    /* evaluate environment variable ${SIXEL_BGCOLOR} */
4180
#if HAVE__DUPENV_S
4181
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_BGCOLOR");
4182
    if (e != (0)) {
4183
        sixel_helper_set_additional_message(
4184
            "failed to get environment variable $SIXEL_BGCOLOR.");
4185
        return (SIXEL_LIBC_ERROR | (e & 0xff));
4186
    }
4187
#else
4188
    env_default_bgcolor = sixel_compat_getenv("SIXEL_BGCOLOR");
545✔
4189
#endif  /* HAVE__DUPENV_S */
4190
    if (env_default_bgcolor != NULL) {
545!
4191
        status = sixel_parse_x_colorspec(&(*ppencoder)->bgcolor,
×
4192
                                         env_default_bgcolor,
4193
                                         allocator);
4194
        if (SIXEL_FAILED(status)) {
×
4195
            goto error;
×
4196
        }
4197
    }
4198

4199
    /* evaluate environment variable ${SIXEL_COLORS} */
4200
#if HAVE__DUPENV_S
4201
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_COLORS");
4202
    if (e != (0)) {
4203
        sixel_helper_set_additional_message(
4204
            "failed to get environment variable $SIXEL_COLORS.");
4205
        return (SIXEL_LIBC_ERROR | (e & 0xff));
4206
    }
4207
#else
4208
    env_default_ncolors = sixel_compat_getenv("SIXEL_COLORS");
545✔
4209
#endif  /* HAVE__DUPENV_S */
4210
    if (env_default_ncolors) {
545!
4211
        ncolors = atoi(env_default_ncolors); /* may overflow */
×
4212
        if (ncolors > 1 && ncolors <= SIXEL_PALETTE_MAX) {
×
4213
            (*ppencoder)->reqcolors = ncolors;
×
4214
        }
4215
    }
4216

4217
    /* success */
4218
    status = SIXEL_OK;
545✔
4219

4220
    goto end;
545✔
4221

4222
error:
4223
    sixel_allocator_free(allocator, *ppencoder);
×
4224
    sixel_allocator_unref(allocator);
×
4225
    *ppencoder = NULL;
×
4226

4227
end:
362✔
4228
#if HAVE__DUPENV_S
4229
    free(env_default_bgcolor);
4230
    free(env_default_ncolors);
4231
#endif  /* HAVE__DUPENV_S */
4232
    return status;
545✔
4233
}
4234

4235

4236
/* create encoder object (deprecated version) */
4237
SIXELAPI /* deprecated */ sixel_encoder_t *
4238
sixel_encoder_create(void)
×
4239
{
4240
    SIXELSTATUS status = SIXEL_FALSE;
×
4241
    sixel_encoder_t *encoder = NULL;
×
4242

4243
    status = sixel_encoder_new(&encoder, NULL);
×
4244
    if (SIXEL_FAILED(status)) {
×
4245
        return NULL;
×
4246
    }
4247

4248
    return encoder;
×
4249
}
4250

4251

4252
/* destroy encoder object */
4253
static void
4254
sixel_encoder_destroy(sixel_encoder_t *encoder)
545✔
4255
{
4256
    sixel_allocator_t *allocator;
4257

4258
    if (encoder) {
545!
4259
        allocator = encoder->allocator;
545✔
4260
        sixel_allocator_free(allocator, encoder->mapfile);
545✔
4261
        sixel_allocator_free(allocator, encoder->palette_output);
545✔
4262
        sixel_allocator_free(allocator, encoder->loader_order);
545✔
4263
        sixel_allocator_free(allocator, encoder->bgcolor);
545✔
4264
        sixel_dither_unref(encoder->dither_cache);
545✔
4265
        if (encoder->outfd
555!
4266
            && encoder->outfd != STDOUT_FILENO
545!
4267
            && encoder->outfd != STDERR_FILENO) {
203!
4268
            (void)sixel_compat_close(encoder->outfd);
30✔
4269
        }
10✔
4270
        if (encoder->tile_outfd >= 0
545!
4271
            && encoder->tile_outfd != encoder->outfd
183!
4272
            && encoder->tile_outfd != STDOUT_FILENO
×
4273
            && encoder->tile_outfd != STDERR_FILENO) {
×
4274
            (void)sixel_compat_close(encoder->tile_outfd);
×
4275
        }
4276
        if (encoder->capture_source_frame != NULL) {
545✔
4277
            sixel_frame_unref(encoder->capture_source_frame);
3✔
4278
        }
1✔
4279
        if (encoder->clipboard_output_path != NULL) {
545!
4280
            (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
4281
            encoder->clipboard_output_path = NULL;
×
4282
        }
4283
        encoder->clipboard_output_active = 0;
545✔
4284
        encoder->clipboard_output_format[0] = '\0';
545✔
4285
        sixel_allocator_free(allocator, encoder->capture_pixels);
545✔
4286
        sixel_allocator_free(allocator, encoder->capture_palette);
545✔
4287
        sixel_allocator_free(allocator, encoder->png_output_path);
545✔
4288
        sixel_allocator_free(allocator, encoder->sixel_output_path);
545✔
4289
        sixel_allocator_free(allocator, encoder);
545✔
4290
        sixel_allocator_unref(allocator);
545✔
4291
    }
183✔
4292
}
545✔
4293

4294

4295
/* increase reference count of encoder object (thread-unsafe) */
4296
SIXELAPI void
4297
sixel_encoder_ref(sixel_encoder_t *encoder)
1,154✔
4298
{
4299
    /* TODO: be thread safe */
4300
    ++encoder->ref;
1,154✔
4301
}
1,154✔
4302

4303

4304
/* decrease reference count of encoder object (thread-unsafe) */
4305
SIXELAPI void
4306
sixel_encoder_unref(sixel_encoder_t *encoder)
1,700✔
4307
{
4308
    /* TODO: be thread safe */
4309
    if (encoder != NULL && --encoder->ref == 0) {
1,700!
4310
        sixel_encoder_destroy(encoder);
545✔
4311
    }
183✔
4312
}
1,700✔
4313

4314

4315
/* set cancel state flag to encoder object */
4316
SIXELAPI SIXELSTATUS
4317
sixel_encoder_set_cancel_flag(
440✔
4318
    sixel_encoder_t /* in */ *encoder,
4319
    int             /* in */ *cancel_flag
4320
)
4321
{
4322
    SIXELSTATUS status = SIXEL_OK;
440✔
4323

4324
    encoder->cancel_flag = cancel_flag;
440✔
4325

4326
    return status;
440✔
4327
}
4328

4329

4330
/*
4331
 * parse_assessment_sections() translates a comma-separated section list into
4332
 * the bitmask understood by the assessment back-end.  The accepted grammar is
4333
 * intentionally small so that the CLI contract stays predictable:
4334
 *
4335
 *     list := section {"," section}
4336
 *     section := name | name "@" view
4337
 *
4338
 * Each name maps to a bit flag while the optional view toggles the encoded vs
4339
 * quantized quality comparison.  The helper folds case, trims ASCII
4340
 * whitespace, and rejects mixed encoded/quantized requests so that the caller
4341
 * can rely on a single coherent capture strategy.
4342
 */
4343
static int
4344
parse_assessment_sections(char const *spec,
6✔
4345
                          unsigned int *out_sections)
4346
{
4347
    char *copy;
4348
    char *cursor;
4349
    char *token;
4350
    char *context;
4351
    unsigned int sections;
4352
    unsigned int view;
4353
    int result;
4354
    size_t length;
4355
    size_t spec_len;
4356
    char *at;
4357
    char *base;
4358
    char *variant;
4359
    char *p;
4360

4361
    if (spec == NULL || out_sections == NULL) {
6!
4362
        return -1;
×
4363
    }
4364
    spec_len = strlen(spec);
6✔
4365
    copy = (char *)malloc(spec_len + 1u);
6✔
4366
    if (copy == NULL) {
6!
4367
        return -1;
×
4368
    }
4369
    memcpy(copy, spec, spec_len + 1u);
6✔
4370
    cursor = copy;
6✔
4371
    sections = 0u;
6✔
4372
    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
6✔
4373
    result = 0;
6✔
4374
    context = NULL;
6✔
4375
    while (result == 0) {
12!
4376
        token = sixel_compat_strtok(cursor, ",", &context);
12✔
4377
        if (token == NULL) {
12✔
4378
            break;
6✔
4379
        }
4380
        cursor = NULL;
6✔
4381
        while (*token == ' ' || *token == '\t') {
6!
4382
            token += 1;
×
4383
        }
4384
        length = strlen(token);
6✔
4385
        while (length > 0u &&
8!
4386
               (token[length - 1u] == ' ' || token[length - 1u] == '\t')) {
6!
4387
            token[length - 1u] = '\0';
×
4388
            length -= 1u;
×
4389
        }
4390
        if (*token == '\0') {
6!
4391
            result = -1;
×
4392
            break;
×
4393
        }
4394
        for (p = token; *p != '\0'; ++p) {
42✔
4395
            *p = (char)tolower((unsigned char)*p);
36✔
4396
        }
12✔
4397
        at = strchr(token, '@');
6✔
4398
        if (at != NULL) {
6!
4399
            *at = '\0';
×
4400
            variant = at + 1;
×
4401
            if (*variant == '\0') {
×
4402
                result = -1;
×
4403
                break;
×
4404
            }
4405
        } else {
4406
            variant = NULL;
6✔
4407
        }
4408
        base = token;
6✔
4409
        if (strcmp(base, "all") == 0) {
6!
4410
            sections |= SIXEL_ASSESSMENT_SECTION_ALL;
×
4411
            if (variant != NULL && variant[0] != '\0') {
×
4412
                if (strcmp(variant, "quantized") == 0) {
×
4413
                    if (view != SIXEL_ASSESSMENT_VIEW_ENCODED &&
×
4414
                            view != SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
4415
                        result = -1;
×
4416
                    }
4417
                    view = SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
4418
                } else if (strcmp(variant, "encoded") == 0) {
×
4419
                    if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4420
                        result = -1;
×
4421
                    }
4422
                    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
×
4423
                } else {
4424
                    result = -1;
×
4425
                }
4426
            }
4427
        } else if (strcmp(base, "basic") == 0) {
6✔
4428
            sections |= SIXEL_ASSESSMENT_SECTION_BASIC;
3✔
4429
            if (variant != NULL) {
3!
4430
                result = -1;
×
4431
            }
4432
        } else if (strcmp(base, "performance") == 0) {
4!
4433
            sections |= SIXEL_ASSESSMENT_SECTION_PERFORMANCE;
×
4434
            if (variant != NULL) {
×
4435
                result = -1;
×
4436
            }
4437
        } else if (strcmp(base, "size") == 0) {
3!
4438
            sections |= SIXEL_ASSESSMENT_SECTION_SIZE;
×
4439
            if (variant != NULL) {
×
4440
                result = -1;
×
4441
            }
4442
        } else if (strcmp(base, "quality") == 0) {
3!
4443
            sections |= SIXEL_ASSESSMENT_SECTION_QUALITY;
3✔
4444
            if (variant != NULL && variant[0] != '\0') {
3!
4445
                if (strcmp(variant, "quantized") == 0) {
×
4446
                    if (view != SIXEL_ASSESSMENT_VIEW_ENCODED &&
×
4447
                            view != SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
4448
                        result = -1;
×
4449
                    }
4450
                    view = SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
4451
                } else if (strcmp(variant, "encoded") == 0) {
×
4452
                    if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4453
                        result = -1;
×
4454
                    }
4455
                    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
×
4456
                } else {
4457
                    result = -1;
×
4458
                }
4459
            } else if (variant != NULL) {
3!
4460
                if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4461
                    result = -1;
×
4462
                }
4463
                view = SIXEL_ASSESSMENT_VIEW_ENCODED;
×
4464
            }
4465
        } else {
1✔
4466
            result = -1;
×
4467
        }
4468
    }
4469
    if (result == 0) {
6!
4470
        if (sections == 0u) {
6!
4471
            result = -1;
×
4472
        } else {
4473
            if ((sections & SIXEL_ASSESSMENT_SECTION_QUALITY) != 0u &&
6!
4474
                    view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
1✔
4475
                sections |= SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
4476
            }
4477
            *out_sections = sections;
6✔
4478
        }
4479
    }
2✔
4480
    free(copy);
6✔
4481
    return result;
6✔
4482
}
2✔
4483

4484

4485
static int
4486
is_png_target(char const *path)
31✔
4487
{
4488
    size_t len;
4489
    int matched;
4490

4491
    /*
4492
     * Detect PNG requests from explicit prefixes or a ".png" suffix:
4493
     *
4494
     *   argument
4495
     *   |
4496
     *   v
4497
     *   .............. . p n g
4498
     *   ^             ^^^^^^^^^
4499
     *   |             +-- case-insensitive suffix comparison
4500
     *   +-- accepts the "png:" inline prefix used for stdout capture
4501
     */
4502

4503
    len = 0;
31✔
4504
    matched = 0;
31✔
4505

4506
    if (path == NULL) {
31!
4507
        return 0;
×
4508
    }
4509

4510
    if (strncmp(path, "png:", 4) == 0) {
31✔
4511
        return path[4] != '\0';
6✔
4512
    }
4513

4514
    len = strlen(path);
25✔
4515
    if (len >= 4) {
25✔
4516
        matched = (tolower((unsigned char)path[len - 4]) == '.')
23✔
4517
            && (tolower((unsigned char)path[len - 3]) == 'p')
10!
4518
            && (tolower((unsigned char)path[len - 2]) == 'n')
3!
4519
            && (tolower((unsigned char)path[len - 1]) == 'g');
24!
4520
    }
8✔
4521

4522
    return matched;
25✔
4523
}
11✔
4524

4525

4526
static char const *
4527
png_target_payload_view(char const *argument)
9✔
4528
{
4529
    /*
4530
     * Inline PNG targets split into either a prefix/payload pair or rely on
4531
     * a simple file-name suffix:
4532
     *
4533
     *   +--------------+------------+-------------+
4534
     *   | form         | payload    | destination |
4535
     *   +--------------+------------+-------------+
4536
     *   | png:         | -          | stdout      |
4537
     *   | png:         | filename   | filesystem  |
4538
     *   | *.png        | filename   | filesystem  |
4539
     *   +--------------+------------+-------------+
4540
     *
4541
     * The caller only needs the payload column, so we expose it here.  When
4542
     * the user omits the prefix we simply echo the original pointer so the
4543
     * caller can copy the value verbatim.
4544
     */
4545
    if (argument == NULL) {
9!
4546
        return NULL;
×
4547
    }
4548
    if (strncmp(argument, "png:", 4) == 0) {
9✔
4549
        return argument + 4;
6✔
4550
    }
4551

4552
    return argument;
3✔
4553
}
3✔
4554

4555
static void
4556
normalise_windows_drive_path(char *path)
9✔
4557
{
4558
#if defined(_WIN32)
4559
    size_t length;
4560

4561
    /*
4562
     * MSYS-like environments forward POSIX-looking absolute paths to native
4563
     * binaries.  When a user writes "/d/..." MSYS converts the command line to
4564
     * UTF-16 and preserves the literal bytes.  The Windows CRT, however,
4565
     * expects "d:/..." or "d:\...".  The tiny state machine below rewrites the
4566
     * leading token so the runtime resolves the drive correctly:
4567
     *
4568
     *   input     normalised
4569
     *   |         |
4570
     *   v         v
4571
     *   / d / ... d : / ...
4572
     *
4573
     * The body keeps the rest of the string intact so UNC paths ("//server")
4574
     * and relative references pass through untouched.
4575
     */
4576

4577
    length = 0u;
4578

4579
    if (path == NULL) {
4580
        return;
4581
    }
4582

4583
    length = strlen(path);
4584
    if (length >= 3u
4585
            && path[0] == '/'
4586
            && ((path[1] >= 'A' && path[1] <= 'Z')
4587
                || (path[1] >= 'a' && path[1] <= 'z'))
4588
            && path[2] == '/') {
4589
        path[0] = path[1];
4590
        path[1] = ':';
4591
    }
4592
#else
4593
    (void)path;
3✔
4594
#endif
4595
}
9✔
4596

4597

4598
static int
4599
is_dev_null_path(char const *path)
×
4600
{
4601
    if (path == NULL || path[0] == '\0') {
×
4602
        return 0;
×
4603
    }
4604
#if defined(_WIN32)
4605
    if (_stricmp(path, "nul") == 0) {
4606
        return 1;
4607
    }
4608
#endif
4609
    return strcmp(path, "/dev/null") == 0;
×
4610
}
4611

4612

4613
static int
4614
sixel_encoder_threads_token_is_auto(char const *text)
×
4615
{
4616
    if (text == NULL) {
×
4617
        return 0;
×
4618
    }
4619

4620
    if ((text[0] == 'a' || text[0] == 'A') &&
×
4621
        (text[1] == 'u' || text[1] == 'U') &&
×
4622
        (text[2] == 't' || text[2] == 'T') &&
×
4623
        (text[3] == 'o' || text[3] == 'O') &&
×
4624
        text[4] == '\0') {
×
4625
        return 1;
×
4626
    }
4627

4628
    return 0;
×
4629
}
4630

4631
static int
4632
sixel_encoder_parse_threads_argument(char const *text, int *value)
×
4633
{
4634
    long parsed;
4635
    char *endptr;
4636

4637
    parsed = 0L;
×
4638
    endptr = NULL;
×
4639

4640
    if (text == NULL || value == NULL) {
×
4641
        return 0;
×
4642
    }
4643

4644
    if (sixel_encoder_threads_token_is_auto(text) != 0) {
×
4645
        *value = 0;
×
4646
        return 1;
×
4647
    }
4648

4649
    errno = 0;
×
4650
    parsed = strtol(text, &endptr, 10);
×
4651
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
×
4652
        return 0;
×
4653
    }
4654

4655
    if (parsed < 1L || parsed > (long)INT_MAX) {
×
4656
        return 0;
×
4657
    }
4658

4659
    *value = (int)parsed;
×
4660
    return 1;
×
4661
}
4662

4663
/* set an option flag to encoder object */
4664
SIXELAPI SIXELSTATUS
4665
sixel_encoder_setopt(
718✔
4666
    sixel_encoder_t /* in */ *encoder,
4667
    int             /* in */ arg,
4668
    char const      /* in */ *value)
4669
{
4670
    SIXELSTATUS status = SIXEL_FALSE;
718✔
4671
    int number;
4672
    int parsed;
4673
    char unit[32];
4674
    char lowered[16];
4675
    size_t len;
4676
    size_t i;
4677
    long parsed_reqcolors;
4678
    char *endptr;
4679
    int forced_palette;
4680
    char *opt_copy;
4681
    char const *drcs_arg_delim;
4682
    char const *drcs_arg_charset;
4683
    char const *drcs_arg_second_delim;
4684
    char const *drcs_arg_path;
4685
    size_t drcs_arg_path_length;
4686
    size_t drcs_segment_length;
4687
    char drcs_segment[32];
4688
    int drcs_mmv_value;
4689
    long drcs_charset_value;
4690
    unsigned int drcs_charset_limit;
4691
    sixel_option_choice_result_t match_result;
4692
    int match_value;
4693
    char match_detail[128];
4694
    char match_message[256];
4695
    int png_argument_has_prefix = 0;
718✔
4696
    char const *png_path_view = NULL;
718✔
4697
    size_t png_path_length;
4698
    char cell_message[256];
4699
    char const *cell_detail;
4700
    unsigned int path_flags;
4701
    char const *mapfile_view;
4702
    int path_check;
4703

4704
    sixel_encoder_ref(encoder);
718✔
4705
    opt_copy = NULL;
718✔
4706
    path_flags = 0u;
718✔
4707
    mapfile_view = NULL;
718✔
4708
    path_check = 0;
718✔
4709

4710
    switch(arg) {
718!
4711
    case SIXEL_OPTFLAG_OUTFILE:  /* o */
20✔
4712
        if (*value == '\0') {
31!
4713
            sixel_helper_set_additional_message(
×
4714
                "no file name specified.");
4715
            status = SIXEL_BAD_ARGUMENT;
×
4716
            goto end;
×
4717
        }
4718
        if (is_png_target(value)) {
31✔
4719
            encoder->output_is_png = 1;
9✔
4720
            png_argument_has_prefix =
9✔
4721
                (value != NULL)
3✔
4722
                && (strncmp(value, "png:", 4) == 0);
9!
4723
            png_path_view = png_target_payload_view(value);
9✔
4724
            if (png_argument_has_prefix
11!
4725
                    && (png_path_view == NULL
7!
4726
                        || png_path_view[0] == '\0')) {
6!
4727
                sixel_helper_set_additional_message(
×
4728
                    "sixel_encoder_setopt: missing target after the \"png:\" "
4729
                    "prefix. use png:- or png:<path> with a non-empty payload."
4730
                );
4731
                status = SIXEL_BAD_ARGUMENT;
×
4732
                goto end;
×
4733
            }
4734
            encoder->output_png_to_stdout =
9✔
4735
                (png_path_view != NULL)
3✔
4736
                && (strcmp(png_path_view, "-") == 0);
9!
4737
            sixel_allocator_free(encoder->allocator, encoder->png_output_path);
9✔
4738
            encoder->png_output_path = NULL;
9✔
4739
            sixel_allocator_free(encoder->allocator, encoder->sixel_output_path);
9✔
4740
            encoder->sixel_output_path = NULL;
9✔
4741
            if (! encoder->output_png_to_stdout) {
9!
4742
                /*
4743
                 * +-----------------------------------------+
4744
                 * |  PNG target normalization               |
4745
                 * +-----------------------------------------+
4746
                 * |  Raw input  |  Stored file path         |
4747
                 * |-------------+---------------------------|
4748
                 * |  png:-      |  "-" (stdout sentinel)    |
4749
                 * |  png:/foo   |  "/foo"                   |
4750
                 * +-----------------------------------------+
4751
                 * Strip the "png:" prefix so the decoder can
4752
                 * pass the true filesystem path to libpng
4753
                 * while the CLI retains its shorthand.
4754
                 */
4755
                png_path_view = value;
9✔
4756
                if (strncmp(value, "png:", 4) == 0) {
9✔
4757
                    png_path_view = value + 4;
6✔
4758
                }
2✔
4759
                if (png_path_view[0] == '\0') {
9!
4760
                    sixel_helper_set_additional_message(
×
4761
                        "sixel_encoder_setopt: PNG output path is empty.");
4762
                    status = SIXEL_BAD_ARGUMENT;
×
4763
                    goto end;
×
4764
                }
4765
                png_path_length = strlen(png_path_view);
9✔
4766
                encoder->png_output_path =
9✔
4767
                    (char *)sixel_allocator_malloc(
9✔
4768
                        encoder->allocator, png_path_length + 1u);
3✔
4769
                if (encoder->png_output_path == NULL) {
9!
4770
                    sixel_helper_set_additional_message(
×
4771
                        "sixel_encoder_setopt: sixel_allocator_malloc() "
4772
                        "failed for PNG output path.");
4773
                    status = SIXEL_BAD_ALLOCATION;
×
4774
                    goto end;
×
4775
                }
4776
                if (png_path_view != NULL) {
9!
4777
                    (void)sixel_compat_strcpy(encoder->png_output_path,
12✔
4778
                                              png_path_length + 1u,
3✔
4779
                                              png_path_view);
3✔
4780
                } else {
3✔
4781
                    encoder->png_output_path[0] = '\0';
×
4782
                }
4783
                normalise_windows_drive_path(encoder->png_output_path);
9✔
4784
            }
3✔
4785
        } else {
3✔
4786
            encoder->output_is_png = 0;
22✔
4787
            encoder->output_png_to_stdout = 0;
22✔
4788
            png_argument_has_prefix = 0;
22✔
4789
            png_path_view = NULL;
22✔
4790
            sixel_allocator_free(encoder->allocator, encoder->png_output_path);
22✔
4791
            encoder->png_output_path = NULL;
22✔
4792
            sixel_allocator_free(encoder->allocator, encoder->sixel_output_path);
22✔
4793
            encoder->sixel_output_path = NULL;
22✔
4794
            if (encoder->clipboard_output_path != NULL) {
22!
4795
                (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
4796
                sixel_allocator_free(encoder->allocator,
×
4797
                                     encoder->clipboard_output_path);
×
4798
                encoder->clipboard_output_path = NULL;
×
4799
            }
4800
            encoder->clipboard_output_active = 0;
22✔
4801
            encoder->clipboard_output_format[0] = '\0';
22✔
4802
            {
4803
                sixel_clipboard_spec_t clipboard_spec;
4804
                SIXELSTATUS clip_status;
4805
                char *spool_path;
4806
                int spool_fd;
4807

4808
                clipboard_spec.is_clipboard = 0;
22✔
4809
                clipboard_spec.format[0] = '\0';
22✔
4810
                clip_status = SIXEL_OK;
22✔
4811
                spool_path = NULL;
22✔
4812
                spool_fd = (-1);
22✔
4813

4814
                if (sixel_clipboard_parse_spec(value, &clipboard_spec)
22!
4815
                        && clipboard_spec.is_clipboard) {
8!
4816
                    clip_status = clipboard_create_spool(
1✔
4817
                        encoder->allocator,
1✔
4818
                        "clipboard-out",
4819
                        &spool_path,
4820
                        &spool_fd);
4821
                    if (SIXEL_FAILED(clip_status)) {
1!
4822
                        status = clip_status;
×
4823
                        goto end;
×
4824
                    }
4825
                    clipboard_select_format(
1✔
4826
                        encoder->clipboard_output_format,
1✔
4827
                        sizeof(encoder->clipboard_output_format),
4828
                        clipboard_spec.format,
1✔
4829
                        "sixel");
4830
                    if (encoder->outfd
1!
4831
                            && encoder->outfd != STDOUT_FILENO
1!
4832
                            && encoder->outfd != STDERR_FILENO) {
1!
4833
                        (void)sixel_compat_close(encoder->outfd);
×
4834
                    }
4835
                    encoder->outfd = spool_fd;
1✔
4836
                    spool_fd = (-1);
1✔
4837
                    encoder->sixel_output_path = spool_path;
1✔
4838
                    encoder->clipboard_output_path = spool_path;
1✔
4839
                    spool_path = NULL;
1✔
4840
                    encoder->clipboard_output_active = 1;
1✔
4841
                    break;
1✔
4842
                }
4843

4844
                if (spool_fd >= 0) {
21!
4845
                    (void)sixel_compat_close(spool_fd);
×
4846
                }
4847
                if (spool_path != NULL) {
21!
4848
                    sixel_allocator_free(encoder->allocator, spool_path);
×
4849
                }
4850
            }
4851
            if (strcmp(value, "-") != 0) {
21!
4852
                encoder->sixel_output_path = (char *)sixel_allocator_malloc(
35✔
4853
                    encoder->allocator, strlen(value) + 1);
21✔
4854
                if (encoder->sixel_output_path == NULL) {
21!
4855
                    sixel_helper_set_additional_message(
×
4856
                        "sixel_encoder_setopt: malloc() failed for output path.");
4857
                    status = SIXEL_BAD_ALLOCATION;
×
4858
                    goto end;
×
4859
                }
4860
                (void)sixel_compat_strcpy(encoder->sixel_output_path,
28✔
4861
                                          strlen(value) + 1,
21✔
4862
                                          value);
7✔
4863
            }
7✔
4864
        }
4865

4866
        if (!encoder->clipboard_output_active && strcmp(value, "-") != 0) {
30!
4867
            if (encoder->outfd && encoder->outfd != STDOUT_FILENO) {
30!
4868
                (void)sixel_compat_close(encoder->outfd);
×
4869
            }
4870
            encoder->outfd = sixel_compat_open(value,
30✔
4871
                                               O_RDWR | O_CREAT | O_TRUNC,
4872
                                               S_IRUSR | S_IWUSR);
4873
        }
10✔
4874
        break;
30✔
4875
    case SIXEL_OPTFLAG_ASSESSMENT:  /* a */
4✔
4876
        if (parse_assessment_sections(value,
10!
4877
                                      &encoder->assessment_sections) != 0) {
4✔
4878
            sixel_helper_set_additional_message(
×
4879
                "sixel_encoder_setopt: cannot parse assessment section list");
4880
            status = SIXEL_BAD_ARGUMENT;
×
4881
            goto end;
×
4882
        }
4883
        break;
6✔
4884
    case SIXEL_OPTFLAG_ASSESSMENT_FILE:  /* J */
2✔
4885
        encoder->assessment_json_path = value;
3✔
4886
        break;
3✔
4887
    case SIXEL_OPTFLAG_7BIT_MODE:  /* 7 */
10✔
4888
        encoder->f8bit = 0;
15✔
4889
        break;
15✔
4890
    case SIXEL_OPTFLAG_8BIT_MODE:  /* 8 */
12✔
4891
        encoder->f8bit = 1;
18✔
4892
        break;
18✔
4893
    case SIXEL_OPTFLAG_6REVERSIBLE:  /* 6 */
4894
        encoder->sixel_reversible = 1;
×
4895
        break;
×
4896
    case SIXEL_OPTFLAG_HAS_GRI_ARG_LIMIT:  /* R */
4897
        encoder->has_gri_arg_limit = 1;
×
4898
        break;
×
4899
    case SIXEL_OPTFLAG_PRECISION:  /* . */
4900
        match_result = sixel_option_match_choice(
×
4901
            value,
4902
            g_option_choices_precision,
4903
            sizeof(g_option_choices_precision) /
4904
                sizeof(g_option_choices_precision[0]),
4905
            &match_value,
4906
            match_detail,
4907
            sizeof(match_detail));
4908
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
4909
            status = sixel_encoder_apply_precision_override(
×
4910
                (sixel_encoder_precision_mode_t)match_value);
4911
            if (SIXEL_FAILED(status)) {
×
4912
                goto end;
×
4913
            }
4914
        } else {
4915
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
4916
                sixel_option_report_ambiguous_prefix(
×
4917
                    value,
4918
                    match_detail,
4919
                    match_message,
4920
                    sizeof(match_message));
4921
            } else {
4922
                sixel_option_report_invalid_choice(
×
4923
                    "precision accepts auto, 8bit, or float32.",
4924
                    match_detail,
4925
                    match_message,
4926
                    sizeof(match_message));
4927
            }
4928
            status = SIXEL_BAD_ARGUMENT;
×
4929
            goto end;
×
4930
        }
4931
        break;
×
4932
    case SIXEL_OPTFLAG_THREADS:  /* = */
4933
        if (sixel_encoder_parse_threads_argument(value, &number) == 0) {
×
4934
            sixel_helper_set_additional_message(
×
4935
                "threads accepts positive integers or 'auto'.");
4936
            status = SIXEL_BAD_ARGUMENT;
×
4937
            goto end;
×
4938
        }
4939
        sixel_set_threads(number);
×
4940
        break;
×
4941
    case SIXEL_OPTFLAG_COLORS:  /* p */
18✔
4942
        forced_palette = 0;
27✔
4943
        errno = 0;
27✔
4944
        endptr = NULL;
27✔
4945
        if (*value == '!' && value[1] == '\0') {
27!
4946
            /*
4947
             * Force the default palette size even when the median cut
4948
             * finished early.
4949
             *
4950
             *   requested colors
4951
             *          |
4952
             *          v
4953
             *        [ 256 ]  <--- "-p!" triggers this shortcut
4954
             */
4955
            parsed_reqcolors = SIXEL_PALETTE_MAX;
×
4956
            forced_palette = 1;
×
4957
        } else {
4958
            parsed_reqcolors = strtol(value, &endptr, 10);
27✔
4959
            if (endptr != NULL && *endptr == '!') {
27!
4960
                forced_palette = 1;
×
4961
                ++endptr;
×
4962
            }
4963
            if (errno == ERANGE || endptr == value) {
27!
4964
                sixel_helper_set_additional_message(
×
4965
                    "cannot parse -p/--colors option.");
4966
                status = SIXEL_BAD_ARGUMENT;
×
4967
                goto end;
×
4968
            }
4969
            if (endptr != NULL && *endptr != '\0') {
27!
4970
                sixel_helper_set_additional_message(
×
4971
                    "cannot parse -p/--colors option.");
4972
                status = SIXEL_BAD_ARGUMENT;
×
4973
                goto end;
×
4974
            }
4975
        }
4976
        if (parsed_reqcolors < 1) {
27!
4977
            sixel_helper_set_additional_message(
×
4978
                "-p/--colors parameter must be 1 or more.");
4979
            status = SIXEL_BAD_ARGUMENT;
×
4980
            goto end;
×
4981
        }
4982
        if (parsed_reqcolors > SIXEL_PALETTE_MAX) {
27!
4983
            sixel_helper_set_additional_message(
×
4984
                "-p/--colors parameter must be less then or equal to 256.");
4985
            status = SIXEL_BAD_ARGUMENT;
×
4986
            goto end;
×
4987
        }
4988
        encoder->reqcolors = (int)parsed_reqcolors;
27✔
4989
        encoder->force_palette = forced_palette;
27✔
4990
        break;
27✔
4991
    case SIXEL_OPTFLAG_MAPFILE:  /* m */
18✔
4992
        mapfile_view = sixel_palette_strip_prefix(value, NULL);
27✔
4993
        if (mapfile_view == NULL) {
27!
4994
            mapfile_view = value;
×
4995
        }
4996
        path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
27✔
4997
            SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
4998
            SIXEL_OPTION_PATH_ALLOW_REMOTE |
4999
            SIXEL_OPTION_PATH_ALLOW_EMPTY;
5000
        path_check = sixel_option_validate_filesystem_path(
27✔
5001
            value,
9✔
5002
            mapfile_view,
9✔
5003
            path_flags);
9✔
5004
        if (path_check != 0) {
27✔
5005
            status = SIXEL_BAD_ARGUMENT;
3✔
5006
            goto end;
3✔
5007
        }
5008
        if (encoder->mapfile) {
24✔
5009
            sixel_allocator_free(encoder->allocator, encoder->mapfile);
3✔
5010
        }
1✔
5011
        encoder->mapfile = arg_strdup(value, encoder->allocator);
24✔
5012
        if (encoder->mapfile == NULL) {
24!
5013
            sixel_helper_set_additional_message(
×
5014
                "sixel_encoder_setopt: sixel_allocator_malloc() failed.");
5015
            status = SIXEL_BAD_ALLOCATION;
×
5016
            goto end;
×
5017
        }
5018
        encoder->color_option = SIXEL_COLOR_OPTION_MAPFILE;
24✔
5019
        break;
24✔
5020
    case SIXEL_OPTFLAG_MAPFILE_OUTPUT:  /* M */
5021
        if (value == NULL || *value == '\0') {
×
5022
            sixel_helper_set_additional_message(
×
5023
                "sixel_encoder_setopt: mapfile-output path is empty.");
5024
            status = SIXEL_BAD_ARGUMENT;
×
5025
            goto end;
×
5026
        }
5027
        opt_copy = arg_strdup(value, encoder->allocator);
×
5028
        if (opt_copy == NULL) {
×
5029
            sixel_helper_set_additional_message(
×
5030
                "sixel_encoder_setopt: sixel_allocator_malloc() failed.");
5031
            status = SIXEL_BAD_ALLOCATION;
×
5032
            goto end;
×
5033
        }
5034
        status = sixel_encoder_enable_quantized_capture(encoder, 1);
×
5035
        if (SIXEL_FAILED(status)) {
×
5036
            sixel_allocator_free(encoder->allocator, opt_copy);
×
5037
            goto end;
×
5038
        }
5039
        sixel_allocator_free(encoder->allocator, encoder->palette_output);
×
5040
        encoder->palette_output = opt_copy;
×
5041
        opt_copy = NULL;
×
5042
        break;
×
5043
    case SIXEL_OPTFLAG_MONOCHROME:  /* e */
10✔
5044
        encoder->color_option = SIXEL_COLOR_OPTION_MONOCHROME;
15✔
5045
        break;
15✔
5046
    case SIXEL_OPTFLAG_HIGH_COLOR:  /* I */
28✔
5047
        encoder->color_option = SIXEL_COLOR_OPTION_HIGHCOLOR;
42✔
5048
        break;
42✔
5049
    case SIXEL_OPTFLAG_BUILTIN_PALETTE:  /* b */
22✔
5050
        match_result = sixel_option_match_choice(
33✔
5051
            value,
11✔
5052
            g_option_choices_builtin_palette,
5053
            sizeof(g_option_choices_builtin_palette) /
5054
            sizeof(g_option_choices_builtin_palette[0]),
5055
            &match_value,
5056
            match_detail,
11✔
5057
            sizeof(match_detail));
5058
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
33✔
5059
            encoder->builtin_palette = match_value;
30✔
5060
        } else {
10✔
5061
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
5062
                sixel_option_report_ambiguous_prefix(value,
×
5063
                                              match_detail,
5064
                                              match_message,
5065
                                              sizeof(match_message));
5066
            } else {
5067
                sixel_option_report_invalid_choice(
3✔
5068
                    "cannot parse builtin palette option.",
5069
                    match_detail,
1✔
5070
                    match_message,
1✔
5071
                    sizeof(match_message));
5072
            }
5073
            status = SIXEL_BAD_ARGUMENT;
3✔
5074
            goto end;
3✔
5075
        }
5076
        encoder->color_option = SIXEL_COLOR_OPTION_BUILTIN;
30✔
5077
        break;
30✔
5078
    case SIXEL_OPTFLAG_DIFFUSION:  /* d */
50✔
5079
        match_result = sixel_option_match_choice(
75✔
5080
            value,
25✔
5081
            g_option_choices_diffusion,
5082
            sizeof(g_option_choices_diffusion) /
5083
            sizeof(g_option_choices_diffusion[0]),
5084
            &match_value,
5085
            match_detail,
25✔
5086
            sizeof(match_detail));
5087
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
75✔
5088
            encoder->method_for_diffuse = match_value;
66✔
5089
        } else {
22✔
5090
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
9✔
5091
                sixel_option_report_ambiguous_prefix(value,
4✔
5092
                                              match_detail,
1✔
5093
                                              match_message,
1✔
5094
                                              sizeof(match_message));
5095
            } else {
1✔
5096
                sixel_option_report_invalid_choice(
6✔
5097
                    "specified diffusion method is not supported.",
5098
                    match_detail,
2✔
5099
                    match_message,
2✔
5100
                    sizeof(match_message));
5101
            }
5102
            status = SIXEL_BAD_ARGUMENT;
9✔
5103
            goto end;
9✔
5104
        }
5105
        break;
66✔
5106
    case SIXEL_OPTFLAG_DIFFUSION_SCAN:  /* y */
2✔
5107
        match_result = sixel_option_match_choice(
3✔
5108
            value,
1✔
5109
            g_option_choices_diffusion_scan,
5110
            sizeof(g_option_choices_diffusion_scan) /
5111
            sizeof(g_option_choices_diffusion_scan[0]),
5112
            &match_value,
5113
            match_detail,
1✔
5114
            sizeof(match_detail));
5115
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
3!
5116
            encoder->method_for_scan = match_value;
3✔
5117
        } else {
1✔
5118
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5119
                sixel_option_report_ambiguous_prefix(value,
×
5120
                                              match_detail,
5121
                                              match_message,
5122
                                              sizeof(match_message));
5123
            } else {
5124
                sixel_option_report_invalid_choice(
×
5125
                    "specified diffusion scan is not supported.",
5126
                    match_detail,
5127
                    match_message,
5128
                    sizeof(match_message));
5129
            }
5130
            status = SIXEL_BAD_ARGUMENT;
×
5131
            goto end;
×
5132
        }
5133
        break;
3✔
5134
    case SIXEL_OPTFLAG_DIFFUSION_CARRY:  /* Y */
5135
        match_result = sixel_option_match_choice(
×
5136
            value,
5137
            g_option_choices_diffusion_carry,
5138
            sizeof(g_option_choices_diffusion_carry) /
5139
            sizeof(g_option_choices_diffusion_carry[0]),
5140
            &match_value,
5141
            match_detail,
5142
            sizeof(match_detail));
5143
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5144
            encoder->method_for_carry = match_value;
×
5145
        } else {
5146
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5147
                sixel_option_report_ambiguous_prefix(value,
×
5148
                                              match_detail,
5149
                                              match_message,
5150
                                              sizeof(match_message));
5151
            } else {
5152
                sixel_option_report_invalid_choice(
×
5153
                    "specified diffusion carry mode is not supported.",
5154
                    match_detail,
5155
                    match_message,
5156
                    sizeof(match_message));
5157
            }
5158
            status = SIXEL_BAD_ARGUMENT;
×
5159
            goto end;
×
5160
        }
5161
        break;
×
5162
    case SIXEL_OPTFLAG_FIND_LARGEST:  /* f */
10✔
5163
        if (value != NULL) {
15!
5164
            match_result = sixel_option_match_choice(
15✔
5165
                value,
5✔
5166
                g_option_choices_find_largest,
5167
                sizeof(g_option_choices_find_largest) /
5168
                sizeof(g_option_choices_find_largest[0]),
5169
                &match_value,
5170
                match_detail,
5✔
5171
                sizeof(match_detail));
5172
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
15✔
5173
                encoder->method_for_largest = match_value;
12✔
5174
            } else {
4✔
5175
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
5176
                    sixel_option_report_ambiguous_prefix(value,
×
5177
                                                  match_detail,
5178
                                                  match_message,
5179
                                                  sizeof(match_message));
5180
                } else {
5181
                    sixel_option_report_invalid_choice(
3✔
5182
                        "specified finding method is not supported.",
5183
                        match_detail,
1✔
5184
                        match_message,
1✔
5185
                        sizeof(match_message));
5186
                }
5187
                status = SIXEL_BAD_ARGUMENT;
3✔
5188
                goto end;
3✔
5189
            }
5190
        }
4✔
5191
        break;
12✔
5192
    case SIXEL_OPTFLAG_SELECT_COLOR:  /* s */
14✔
5193
        match_result = sixel_option_match_choice(
21✔
5194
            value,
7✔
5195
            g_option_choices_select_color,
5196
            sizeof(g_option_choices_select_color) /
5197
            sizeof(g_option_choices_select_color[0]),
5198
            &match_value,
5199
            match_detail,
7✔
5200
            sizeof(match_detail));
5201
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
21✔
5202
            encoder->method_for_rep = match_value;
15✔
5203
        } else {
5✔
5204
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
6✔
5205
                sixel_option_report_ambiguous_prefix(value,
4✔
5206
                                              match_detail,
1✔
5207
                                              match_message,
1✔
5208
                                              sizeof(match_message));
5209
            } else {
1✔
5210
                sixel_option_report_invalid_choice(
3✔
5211
                    "specified finding method is not supported.",
5212
                    match_detail,
1✔
5213
                    match_message,
1✔
5214
                    sizeof(match_message));
5215
            }
5216
            status = SIXEL_BAD_ARGUMENT;
6✔
5217
            goto end;
6✔
5218
        }
5219
        break;
15✔
5220
    case SIXEL_OPTFLAG_QUANTIZE_MODEL:  /* Q */
5221
        match_result = sixel_option_match_choice(
×
5222
            value,
5223
            g_option_choices_quantize_model,
5224
            sizeof(g_option_choices_quantize_model) /
5225
            sizeof(g_option_choices_quantize_model[0]),
5226
            &match_value,
5227
            match_detail,
5228
            sizeof(match_detail));
5229
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5230
            encoder->quantize_model = match_value;
×
5231
        } else {
5232
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5233
                sixel_option_report_ambiguous_prefix(value,
×
5234
                                              match_detail,
5235
                                              match_message,
5236
                                              sizeof(match_message));
5237
            } else {
5238
                sixel_option_report_invalid_choice(
×
5239
                    "sixel_encoder_setopt: unsupported quantize model.",
5240
                    match_detail,
5241
                    match_message,
5242
                    sizeof(match_message));
5243
            }
5244
            status = SIXEL_BAD_ARGUMENT;
×
5245
            goto end;
×
5246
        }
5247
        break;
×
5248
    case SIXEL_OPTFLAG_FINAL_MERGE:  /* F */
5249
        match_result = sixel_option_match_choice(
×
5250
            value,
5251
            g_option_choices_final_merge,
5252
            sizeof(g_option_choices_final_merge) /
5253
            sizeof(g_option_choices_final_merge[0]),
5254
            &match_value,
5255
            match_detail,
5256
            sizeof(match_detail));
5257
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5258
            encoder->final_merge_mode = match_value;
×
5259
        } else {
5260
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5261
                sixel_option_report_ambiguous_prefix(value,
×
5262
                                              match_detail,
5263
                                              match_message,
5264
                                              sizeof(match_message));
5265
            } else {
5266
                sixel_option_report_invalid_choice(
×
5267
                    "specified final merge policy is not supported.",
5268
                    match_detail,
5269
                    match_message,
5270
                    sizeof(match_message));
5271
            }
5272
            status = SIXEL_BAD_ARGUMENT;
×
5273
            goto end;
×
5274
        }
5275
        break;
×
5276
    case SIXEL_OPTFLAG_CROP:  /* c */
10✔
5277
#if HAVE_SSCANF_S
5278
        number = sscanf_s(value, "%dx%d+%d+%d",
5279
                          &encoder->clipwidth, &encoder->clipheight,
5280
                          &encoder->clipx, &encoder->clipy);
5281
#else
5282
        number = sscanf(value, "%dx%d+%d+%d",
20✔
5283
                        &encoder->clipwidth, &encoder->clipheight,
5✔
5284
                        &encoder->clipx, &encoder->clipy);
5✔
5285
#endif  /* HAVE_SSCANF_S */
5286
        if (number != 4) {
15!
5287
            status = SIXEL_BAD_ARGUMENT;
×
5288
            goto end;
×
5289
        }
5290
        if (encoder->clipwidth <= 0 || encoder->clipheight <= 0) {
15!
5291
            status = SIXEL_BAD_ARGUMENT;
×
5292
            goto end;
×
5293
        }
5294
        if (encoder->clipx < 0 || encoder->clipy < 0) {
15!
5295
            status = SIXEL_BAD_ARGUMENT;
×
5296
            goto end;
×
5297
        }
5298
        encoder->clipfirst = 0;
15✔
5299
        break;
15✔
5300
    case SIXEL_OPTFLAG_WIDTH:  /* w */
50✔
5301
#if HAVE_SSCANF_S
5302
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
5303
#else
5304
        parsed = sscanf(value, "%d%2s", &number, unit);
75✔
5305
#endif  /* HAVE_SSCANF_S */
5306
        if (parsed == 2 && strcmp(unit, "%") == 0) {
75!
5307
            encoder->pixelwidth = (-1);
12✔
5308
            encoder->percentwidth = number;
12✔
5309
        } else if (parsed == 2 && strcmp(unit, "c") == 0) {
67!
5310
            status = sixel_encoder_ensure_cell_size(encoder);
×
5311
            if (SIXEL_FAILED(status)) {
×
5312
                cell_detail = sixel_helper_get_additional_message();
×
5313
                if (cell_detail != NULL && cell_detail[0] != '\0') {
×
5314
                    (void) snprintf(cell_message,
×
5315
                                    sizeof(cell_message),
5316
                                    "cannot determine terminal cell size for "
5317
                                    "-w/--width option: %s",
5318
                                    cell_detail);
5319
                    sixel_helper_set_additional_message(cell_message);
×
5320
                } else {
5321
                    sixel_helper_set_additional_message(
×
5322
                        "cannot determine terminal cell size for "
5323
                        "-w/--width option.");
5324
                }
5325
                goto end;
×
5326
            }
5327
            /*
5328
             * Terminal cell units map the requested column count to pixels.
5329
             * The cell size probe caches the tty geometry so repeated calls
5330
             * reuse the same measurement.
5331
             */
5332
            encoder->pixelwidth = number * encoder->cell_width;
×
5333
            encoder->percentwidth = (-1);
×
5334
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
63!
5335
            encoder->pixelwidth = number;
51✔
5336
            encoder->percentwidth = (-1);
51✔
5337
        } else if (strcmp(value, "auto") == 0) {
29✔
5338
            encoder->pixelwidth = (-1);
9✔
5339
            encoder->percentwidth = (-1);
9✔
5340
        } else {
3✔
5341
            sixel_helper_set_additional_message(
3✔
5342
                "cannot parse -w/--width option.");
5343
            status = SIXEL_BAD_ARGUMENT;
3✔
5344
            goto end;
3✔
5345
        }
5346
        if (encoder->clipwidth) {
72✔
5347
            encoder->clipfirst = 1;
6✔
5348
        }
2✔
5349
        break;
72✔
5350
    case SIXEL_OPTFLAG_HEIGHT:  /* h */
44✔
5351
#if HAVE_SSCANF_S
5352
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
5353
#else
5354
        parsed = sscanf(value, "%d%2s", &number, unit);
66✔
5355
#endif  /* HAVE_SSCANF_S */
5356
        if (parsed == 2 && strcmp(unit, "%") == 0) {
66!
5357
            encoder->pixelheight = (-1);
9✔
5358
            encoder->percentheight = number;
9✔
5359
        } else if (parsed == 2 && strcmp(unit, "c") == 0) {
60!
5360
            status = sixel_encoder_ensure_cell_size(encoder);
×
5361
            if (SIXEL_FAILED(status)) {
×
5362
                cell_detail = sixel_helper_get_additional_message();
×
5363
                if (cell_detail != NULL && cell_detail[0] != '\0') {
×
5364
                    (void) snprintf(cell_message,
×
5365
                                    sizeof(cell_message),
5366
                                    "cannot determine terminal cell size for "
5367
                                    "-h/--height option: %s",
5368
                                    cell_detail);
5369
                    sixel_helper_set_additional_message(cell_message);
×
5370
                } else {
5371
                    sixel_helper_set_additional_message(
×
5372
                        "cannot determine terminal cell size for "
5373
                        "-h/--height option.");
5374
                }
5375
                goto end;
×
5376
            }
5377
            /*
5378
             * Rows specified in terminal cells use the current tty metrics to
5379
             * translate into pixel counts before scaling.
5380
             */
5381
            encoder->pixelheight = number * encoder->cell_height;
×
5382
            encoder->percentheight = (-1);
×
5383
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
57!
5384
            encoder->pixelheight = number;
45✔
5385
            encoder->percentheight = (-1);
45✔
5386
        } else if (strcmp(value, "auto") == 0) {
27✔
5387
            encoder->pixelheight = (-1);
9✔
5388
            encoder->percentheight = (-1);
9✔
5389
        } else {
3✔
5390
            sixel_helper_set_additional_message(
3✔
5391
                "cannot parse -h/--height option.");
5392
            status = SIXEL_BAD_ARGUMENT;
3✔
5393
            goto end;
3✔
5394
        }
5395
        if (encoder->clipheight) {
63✔
5396
            encoder->clipfirst = 1;
3✔
5397
        }
1✔
5398
        break;
63✔
5399
    case SIXEL_OPTFLAG_RESAMPLING:  /* r */
40✔
5400
        match_result = sixel_option_match_choice(
60✔
5401
            value,
20✔
5402
            g_option_choices_resampling,
5403
            sizeof(g_option_choices_resampling) /
5404
            sizeof(g_option_choices_resampling[0]),
5405
            &match_value,
5406
            match_detail,
20✔
5407
            sizeof(match_detail));
5408
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
60✔
5409
            encoder->method_for_resampling = match_value;
48✔
5410
        } else {
16✔
5411
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
12!
5412
                sixel_option_report_ambiguous_prefix(value,
×
5413
                                              match_detail,
5414
                                              match_message,
5415
                                              sizeof(match_message));
5416
            } else {
5417
                sixel_option_report_invalid_choice(
12✔
5418
                    "specified desampling method is not supported.",
5419
                    match_detail,
4✔
5420
                    match_message,
4✔
5421
                    sizeof(match_message));
5422
            }
5423
            status = SIXEL_BAD_ARGUMENT;
12✔
5424
            goto end;
12✔
5425
        }
5426
        break;
48✔
5427
    case SIXEL_OPTFLAG_QUALITY:  /* q */
12✔
5428
        match_result = sixel_option_match_choice(
18✔
5429
            value,
6✔
5430
            g_option_choices_quality,
5431
            sizeof(g_option_choices_quality) /
5432
            sizeof(g_option_choices_quality[0]),
5433
            &match_value,
5434
            match_detail,
6✔
5435
            sizeof(match_detail));
5436
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
18✔
5437
            encoder->quality_mode = match_value;
15✔
5438
        } else {
5✔
5439
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
5440
                sixel_option_report_ambiguous_prefix(value,
×
5441
                                              match_detail,
5442
                                              match_message,
5443
                                              sizeof(match_message));
5444
            } else {
5445
                sixel_option_report_invalid_choice(
3✔
5446
                    "cannot parse quality option.",
5447
                    match_detail,
1✔
5448
                    match_message,
1✔
5449
                    sizeof(match_message));
5450
            }
5451
            status = SIXEL_BAD_ARGUMENT;
3✔
5452
            goto end;
3✔
5453
        }
5454
        break;
15✔
5455
    case SIXEL_OPTFLAG_LOOPMODE:  /* l */
10✔
5456
        match_result = sixel_option_match_choice(
15✔
5457
            value,
5✔
5458
            g_option_choices_loopmode,
5459
            sizeof(g_option_choices_loopmode) /
5460
            sizeof(g_option_choices_loopmode[0]),
5461
            &match_value,
5462
            match_detail,
5✔
5463
            sizeof(match_detail));
5464
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
15✔
5465
            encoder->loop_mode = match_value;
12✔
5466
        } else {
4✔
5467
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
5468
                sixel_option_report_ambiguous_prefix(value,
×
5469
                                              match_detail,
5470
                                              match_message,
5471
                                              sizeof(match_message));
5472
            } else {
5473
                sixel_option_report_invalid_choice(
3✔
5474
                    "cannot parse loop-control option.",
5475
                    match_detail,
1✔
5476
                    match_message,
1✔
5477
                    sizeof(match_message));
5478
            }
5479
            status = SIXEL_BAD_ARGUMENT;
3✔
5480
            goto end;
3✔
5481
        }
5482
        break;
12✔
5483
    case SIXEL_OPTFLAG_PALETTE_TYPE:  /* t */
18✔
5484
        match_result = sixel_option_match_choice(
27✔
5485
            value,
9✔
5486
            g_option_choices_palette_type,
5487
            sizeof(g_option_choices_palette_type) /
5488
            sizeof(g_option_choices_palette_type[0]),
5489
            &match_value,
5490
            match_detail,
9✔
5491
            sizeof(match_detail));
5492
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
27✔
5493
            encoder->palette_type = match_value;
24✔
5494
        } else {
8✔
5495
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
5496
                sixel_option_report_ambiguous_prefix(value,
×
5497
                                              match_detail,
5498
                                              match_message,
5499
                                              sizeof(match_message));
5500
            } else {
5501
                sixel_option_report_invalid_choice(
3✔
5502
                    "cannot parse palette type option.",
5503
                    match_detail,
1✔
5504
                    match_message,
1✔
5505
                    sizeof(match_message));
5506
            }
5507
            status = SIXEL_BAD_ARGUMENT;
3✔
5508
            goto end;
3✔
5509
        }
5510
        break;
24✔
5511
    case SIXEL_OPTFLAG_BGCOLOR:  /* B */
30✔
5512
        /* parse --bgcolor option */
5513
        if (encoder->bgcolor) {
45✔
5514
            sixel_allocator_free(encoder->allocator, encoder->bgcolor);
6✔
5515
            encoder->bgcolor = NULL;
6✔
5516
        }
2✔
5517
        status = sixel_parse_x_colorspec(&encoder->bgcolor,
60✔
5518
                                         value,
15✔
5519
                                         encoder->allocator);
15✔
5520
        if (SIXEL_FAILED(status)) {
45✔
5521
            sixel_helper_set_additional_message(
21✔
5522
                "cannot parse bgcolor option.");
5523
            status = SIXEL_BAD_ARGUMENT;
21✔
5524
            goto end;
21✔
5525
        }
5526
        break;
24✔
5527
    case SIXEL_OPTFLAG_INSECURE:  /* k */
5528
        encoder->finsecure = 1;
×
5529
        break;
×
5530
    case SIXEL_OPTFLAG_INVERT:  /* i */
4✔
5531
        encoder->finvert = 1;
6✔
5532
        break;
6✔
5533
    case SIXEL_OPTFLAG_USE_MACRO:  /* u */
4✔
5534
        encoder->fuse_macro = 1;
6✔
5535
        break;
6✔
5536
    case SIXEL_OPTFLAG_MACRO_NUMBER:  /* n */
2✔
5537
        encoder->macro_number = atoi(value);
3✔
5538
        if (encoder->macro_number < 0) {
3!
5539
            status = SIXEL_BAD_ARGUMENT;
×
5540
            goto end;
×
5541
        }
5542
        break;
3✔
5543
    case SIXEL_OPTFLAG_IGNORE_DELAY:  /* g */
4✔
5544
        encoder->fignore_delay = 1;
6✔
5545
        break;
6✔
5546
    case SIXEL_OPTFLAG_VERBOSE:  /* v */
6✔
5547
        encoder->verbose = 1;
9✔
5548
        sixel_helper_set_loader_trace(1);
9✔
5549
        break;
9✔
5550
    case SIXEL_OPTFLAG_LOADERS:  /* J */
5551
        if (encoder->loader_order != NULL) {
×
5552
            sixel_allocator_free(encoder->allocator,
×
5553
                                 encoder->loader_order);
×
5554
            encoder->loader_order = NULL;
×
5555
        }
5556
        if (value != NULL && *value != '\0') {
×
5557
            encoder->loader_order = arg_strdup(value,
×
5558
                                               encoder->allocator);
5559
            if (encoder->loader_order == NULL) {
×
5560
                sixel_helper_set_additional_message(
×
5561
                    "sixel_encoder_setopt: "
5562
                    "sixel_allocator_malloc() failed.");
5563
                status = SIXEL_BAD_ALLOCATION;
×
5564
                goto end;
×
5565
            }
5566
        }
5567
        break;
×
5568
    case SIXEL_OPTFLAG_STATIC:  /* S */
4✔
5569
        encoder->fstatic = 1;
6✔
5570
        break;
6✔
5571
    case SIXEL_OPTFLAG_DRCS:  /* @ */
5572
        encoder->fdrcs = 1;
×
5573
        drcs_arg_delim = NULL;
×
5574
        drcs_arg_charset = NULL;
×
5575
        drcs_arg_second_delim = NULL;
×
5576
        drcs_arg_path = NULL;
×
5577
        drcs_arg_path_length = 0u;
×
5578
        drcs_segment_length = 0u;
×
5579
        drcs_mmv_value = 2;
×
5580
        drcs_charset_value = 1L;
×
5581
        drcs_charset_limit = 0u;
×
5582
        if (value != NULL && *value != '\0') {
×
5583
            drcs_arg_delim = strchr(value, ':');
×
5584
            if (drcs_arg_delim == NULL) {
×
5585
                drcs_segment_length = strlen(value);
×
5586
                if (drcs_segment_length >= sizeof(drcs_segment)) {
×
5587
                    sixel_helper_set_additional_message(
×
5588
                        "DRCS mapping revision is too long.");
5589
                    status = SIXEL_BAD_ARGUMENT;
×
5590
                    goto end;
×
5591
                }
5592
                memcpy(drcs_segment, value, drcs_segment_length);
×
5593
                drcs_segment[drcs_segment_length] = '\0';
×
5594
                errno = 0;
×
5595
                endptr = NULL;
×
5596
                drcs_mmv_value = (int)strtol(drcs_segment, &endptr, 10);
×
5597
                if (errno != 0 || endptr == drcs_segment || *endptr != '\0') {
×
5598
                    sixel_helper_set_additional_message(
×
5599
                        "cannot parse DRCS option.");
5600
                    status = SIXEL_BAD_ARGUMENT;
×
5601
                    goto end;
×
5602
                }
5603
            } else {
5604
                if (drcs_arg_delim != value) {
×
5605
                    drcs_segment_length =
×
5606
                        (size_t)(drcs_arg_delim - value);
×
5607
                    if (drcs_segment_length >= sizeof(drcs_segment)) {
×
5608
                        sixel_helper_set_additional_message(
×
5609
                            "DRCS mapping revision is too long.");
5610
                        status = SIXEL_BAD_ARGUMENT;
×
5611
                        goto end;
×
5612
                    }
5613
                    memcpy(drcs_segment, value, drcs_segment_length);
×
5614
                    drcs_segment[drcs_segment_length] = '\0';
×
5615
                    errno = 0;
×
5616
                    endptr = NULL;
×
5617
                    drcs_mmv_value = (int)strtol(drcs_segment, &endptr, 10);
×
5618
                    if (errno != 0 || endptr == drcs_segment || *endptr != '\0') {
×
5619
                        sixel_helper_set_additional_message(
×
5620
                            "cannot parse DRCS option.");
5621
                        status = SIXEL_BAD_ARGUMENT;
×
5622
                        goto end;
×
5623
                    }
5624
                }
5625
                drcs_arg_charset = drcs_arg_delim + 1;
×
5626
                drcs_arg_second_delim = strchr(drcs_arg_charset, ':');
×
5627
                if (drcs_arg_second_delim != NULL) {
×
5628
                    if (drcs_arg_second_delim != drcs_arg_charset) {
×
5629
                        drcs_segment_length =
×
5630
                            (size_t)(drcs_arg_second_delim - drcs_arg_charset);
×
5631
                        if (drcs_segment_length >= sizeof(drcs_segment)) {
×
5632
                            sixel_helper_set_additional_message(
×
5633
                                "DRCS charset number is too long.");
5634
                            status = SIXEL_BAD_ARGUMENT;
×
5635
                            goto end;
×
5636
                        }
5637
                        memcpy(drcs_segment,
×
5638
                               drcs_arg_charset,
5639
                               drcs_segment_length);
5640
                        drcs_segment[drcs_segment_length] = '\0';
×
5641
                        errno = 0;
×
5642
                        endptr = NULL;
×
5643
                        drcs_charset_value = strtol(drcs_segment,
×
5644
                                                    &endptr,
5645
                                                    10);
5646
                        if (errno != 0 || endptr == drcs_segment ||
×
5647
                                *endptr != '\0') {
×
5648
                            sixel_helper_set_additional_message(
×
5649
                                "cannot parse DRCS charset number.");
5650
                            status = SIXEL_BAD_ARGUMENT;
×
5651
                            goto end;
×
5652
                        }
5653
                    }
5654
                    drcs_arg_path = drcs_arg_second_delim + 1;
×
5655
                    drcs_arg_path_length = strlen(drcs_arg_path);
×
5656
                    if (drcs_arg_path_length == 0u) {
×
5657
                        drcs_arg_path = NULL;
×
5658
                    }
5659
                } else if (*drcs_arg_charset != '\0') {
×
5660
                    drcs_segment_length = strlen(drcs_arg_charset);
×
5661
                    if (drcs_segment_length >= sizeof(drcs_segment)) {
×
5662
                        sixel_helper_set_additional_message(
×
5663
                            "DRCS charset number is too long.");
5664
                        status = SIXEL_BAD_ARGUMENT;
×
5665
                        goto end;
×
5666
                    }
5667
                    memcpy(drcs_segment,
×
5668
                           drcs_arg_charset,
5669
                           drcs_segment_length);
5670
                    drcs_segment[drcs_segment_length] = '\0';
×
5671
                    errno = 0;
×
5672
                    endptr = NULL;
×
5673
                    drcs_charset_value = strtol(drcs_segment,
×
5674
                                                &endptr,
5675
                                                10);
5676
                    if (errno != 0 || endptr == drcs_segment ||
×
5677
                            *endptr != '\0') {
×
5678
                        sixel_helper_set_additional_message(
×
5679
                            "cannot parse DRCS charset number.");
5680
                        status = SIXEL_BAD_ARGUMENT;
×
5681
                        goto end;
×
5682
                    }
5683
                }
5684
            }
5685
        }
5686
        /*
5687
         * Layout of the DRCS option value:
5688
         *
5689
         *    value = <mmv>:<charset_no>:<path>
5690
         *          ^        ^                ^
5691
         *          |        |                |
5692
         *          |        |                +-- optional path that may reuse
5693
         *          |        |                    STDOUT when set to "-" or drop
5694
         *          |        |                    tiles when left blank
5695
         *          |        +-- charset number (defaults to 1 when omitted)
5696
         *          +-- mapping revision (defaults to 2 when omitted)
5697
         */
5698
        if (drcs_mmv_value < 0 || drcs_mmv_value > 2) {
×
5699
            sixel_helper_set_additional_message(
×
5700
                "unknown DRCS unicode mapping version.");
5701
            status = SIXEL_BAD_ARGUMENT;
×
5702
            goto end;
×
5703
        }
5704
        if (drcs_mmv_value == 0) {
×
5705
            drcs_charset_limit = 126u;
×
5706
        } else if (drcs_mmv_value == 1) {
×
5707
            drcs_charset_limit = 63u;
×
5708
        } else {
5709
            drcs_charset_limit = 158u;
×
5710
        }
5711
        if (drcs_charset_value < 1 ||
×
5712
            (unsigned long)drcs_charset_value > drcs_charset_limit) {
×
5713
            sixel_helper_set_additional_message(
×
5714
                "DRCS charset number is out of range.");
5715
            status = SIXEL_BAD_ARGUMENT;
×
5716
            goto end;
×
5717
        }
5718
        encoder->drcs_mmv = drcs_mmv_value;
×
5719
        encoder->drcs_charset_no = (unsigned short)drcs_charset_value;
×
5720
        if (encoder->tile_outfd >= 0
×
5721
            && encoder->tile_outfd != encoder->outfd
×
5722
            && encoder->tile_outfd != STDOUT_FILENO
×
5723
            && encoder->tile_outfd != STDERR_FILENO) {
×
5724
#if HAVE__CLOSE
5725
            (void) _close(encoder->tile_outfd);
5726
#else
5727
            (void) close(encoder->tile_outfd);
×
5728
#endif  /* HAVE__CLOSE */
5729
        }
5730
        encoder->tile_outfd = (-1);
×
5731
        if (drcs_arg_path != NULL) {
×
5732
            if (strcmp(drcs_arg_path, "-") == 0) {
×
5733
                encoder->tile_outfd = STDOUT_FILENO;
×
5734
            } else {
5735
#if HAVE__OPEN
5736
                encoder->tile_outfd = _open(drcs_arg_path,
5737
                                            O_RDWR|O_CREAT|O_TRUNC,
5738
                                            S_IRUSR|S_IWUSR);
5739
#else
5740
                encoder->tile_outfd = open(drcs_arg_path,
×
5741
                                           O_RDWR|O_CREAT|O_TRUNC,
5742
                                           S_IRUSR|S_IWUSR);
5743
#endif  /* HAVE__OPEN */
5744
                if (encoder->tile_outfd < 0) {
×
5745
                    sixel_helper_set_additional_message(
×
5746
                        "sixel_encoder_setopt: failed to open tile"
5747
                        " output path.");
5748
                    status = SIXEL_RUNTIME_ERROR;
×
5749
                    goto end;
×
5750
                }
5751
            }
5752
        }
5753
        break;
×
5754
    case SIXEL_OPTFLAG_PENETRATE:  /* P */
6✔
5755
        encoder->penetrate_multiplexer = 1;
9✔
5756
        break;
9✔
5757
    case SIXEL_OPTFLAG_ENCODE_POLICY:  /* E */
8✔
5758
        match_result = sixel_option_match_choice(
12✔
5759
            value,
4✔
5760
            g_option_choices_encode_policy,
5761
            sizeof(g_option_choices_encode_policy) /
5762
            sizeof(g_option_choices_encode_policy[0]),
5763
            &match_value,
5764
            match_detail,
4✔
5765
            sizeof(match_detail));
5766
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
12✔
5767
            encoder->encode_policy = match_value;
9✔
5768
        } else {
3✔
5769
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
5770
                sixel_option_report_ambiguous_prefix(value,
×
5771
                                              match_detail,
5772
                                              match_message,
5773
                                              sizeof(match_message));
5774
            } else {
5775
                sixel_option_report_invalid_choice(
3✔
5776
                    "cannot parse encode policy option.",
5777
                    match_detail,
1✔
5778
                    match_message,
1✔
5779
                    sizeof(match_message));
5780
            }
5781
            status = SIXEL_BAD_ARGUMENT;
3✔
5782
            goto end;
3✔
5783
        }
5784
        break;
9✔
5785
    case SIXEL_OPTFLAG_LUT_POLICY:  /* L */
5786
        match_result = sixel_option_match_choice(
×
5787
            value,
5788
            g_option_choices_lut_policy,
5789
            sizeof(g_option_choices_lut_policy) /
5790
            sizeof(g_option_choices_lut_policy[0]),
5791
            &match_value,
5792
            match_detail,
5793
            sizeof(match_detail));
5794
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5795
            encoder->lut_policy = match_value;
×
5796
        } else {
5797
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5798
                sixel_option_report_ambiguous_prefix(value,
×
5799
                                              match_detail,
5800
                                              match_message,
5801
                                              sizeof(match_message));
5802
            } else {
5803
                sixel_option_report_invalid_choice(
×
5804
                    "cannot parse lut policy option.",
5805
                    match_detail,
5806
                    match_message,
5807
                    sizeof(match_message));
5808
            }
5809
            status = SIXEL_BAD_ARGUMENT;
×
5810
            goto end;
×
5811
        }
5812
        if (encoder->dither_cache != NULL) {
×
5813
            sixel_dither_set_lut_policy(encoder->dither_cache,
×
5814
                                        encoder->lut_policy);
5815
        }
5816
        break;
×
5817
    case SIXEL_OPTFLAG_WORKING_COLORSPACE:  /* W */
5818
        if (value == NULL) {
×
5819
            sixel_helper_set_additional_message(
×
5820
                "working-colorspace requires an argument.");
5821
            status = SIXEL_BAD_ARGUMENT;
×
5822
            goto end;
×
5823
        } else {
5824
            len = strlen(value);
×
5825

5826
            if (len >= sizeof(lowered)) {
×
5827
                sixel_helper_set_additional_message(
×
5828
                    "specified working colorspace name is too long.");
5829
                status = SIXEL_BAD_ARGUMENT;
×
5830
                goto end;
×
5831
            }
5832
            for (i = 0; i < len; ++i) {
×
5833
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
5834
            }
5835
            lowered[len] = '\0';
×
5836

5837
            match_result = sixel_option_match_choice(
×
5838
                lowered,
5839
                g_option_choices_working_colorspace,
5840
                sizeof(g_option_choices_working_colorspace) /
5841
                sizeof(g_option_choices_working_colorspace[0]),
5842
                &match_value,
5843
                match_detail,
5844
                sizeof(match_detail));
5845
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5846
                encoder->working_colorspace = match_value;
×
5847
            } else {
5848
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5849
                    sixel_option_report_ambiguous_prefix(value,
×
5850
                        match_detail,
5851
                        match_message,
5852
                        sizeof(match_message));
5853
                } else {
5854
                    sixel_option_report_invalid_choice(
×
5855
                        "unsupported working colorspace specified.",
5856
                        match_detail,
5857
                        match_message,
5858
                        sizeof(match_message));
5859
                }
5860
                status = SIXEL_BAD_ARGUMENT;
×
5861
                goto end;
×
5862
            }
5863
        }
5864
        break;
×
5865
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
5866
        if (value == NULL) {
×
5867
            sixel_helper_set_additional_message(
×
5868
                "output-colorspace requires an argument.");
5869
            status = SIXEL_BAD_ARGUMENT;
×
5870
            goto end;
×
5871
        } else {
5872
            len = strlen(value);
×
5873

5874
            if (len >= sizeof(lowered)) {
×
5875
                sixel_helper_set_additional_message(
×
5876
                    "specified output colorspace name is too long.");
5877
                status = SIXEL_BAD_ARGUMENT;
×
5878
                goto end;
×
5879
            }
5880
            for (i = 0; i < len; ++i) {
×
5881
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
5882
            }
5883
            lowered[len] = '\0';
×
5884

5885
            match_result = sixel_option_match_choice(
×
5886
                lowered,
5887
                g_option_choices_output_colorspace,
5888
                sizeof(g_option_choices_output_colorspace) /
5889
                sizeof(g_option_choices_output_colorspace[0]),
5890
                &match_value,
5891
                match_detail,
5892
                sizeof(match_detail));
5893
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5894
                encoder->output_colorspace = match_value;
×
5895
            } else {
5896
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5897
                    sixel_option_report_ambiguous_prefix(value,
×
5898
                        match_detail,
5899
                        match_message,
5900
                        sizeof(match_message));
5901
                } else {
5902
                    sixel_option_report_invalid_choice(
×
5903
                        "unsupported output colorspace specified.",
5904
                        match_detail,
5905
                        match_message,
5906
                        sizeof(match_message));
5907
                }
5908
                status = SIXEL_BAD_ARGUMENT;
×
5909
                goto end;
×
5910
            }
5911
        }
5912
        break;
×
5913
    case SIXEL_OPTFLAG_ORMODE:  /* O */
5914
        encoder->ormode = 1;
×
5915
        break;
×
5916
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
6✔
5917
        encoder->complexion = atoi(value);
9✔
5918
        if (encoder->complexion < 1) {
9✔
5919
            sixel_helper_set_additional_message(
3✔
5920
                "complexion parameter must be 1 or more.");
5921
            status = SIXEL_BAD_ARGUMENT;
3✔
5922
            goto end;
3✔
5923
        }
5924
        break;
6✔
5925
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
5926
        encoder->pipe_mode = 1;
×
5927
        break;
×
5928
    case '?':  /* unknown option */
×
5929
    default:
5930
        /* exit if unknown options are specified */
5931
        sixel_helper_set_additional_message(
×
5932
            "unknown option is specified.");
5933
        status = SIXEL_BAD_ARGUMENT;
×
5934
        goto end;
×
5935
    }
5936

5937
    /* detects arguments conflictions */
5938
    if (encoder->reqcolors != (-1)) {
640✔
5939
        switch (encoder->color_option) {
99!
5940
        case SIXEL_COLOR_OPTION_MAPFILE:
5941
            sixel_helper_set_additional_message(
×
5942
                "option -p, --colors conflicts with -m, --mapfile.");
5943
            status = SIXEL_BAD_ARGUMENT;
×
5944
            goto end;
×
5945
        case SIXEL_COLOR_OPTION_MONOCHROME:
2✔
5946
            sixel_helper_set_additional_message(
3✔
5947
                "option -e, --monochrome conflicts with -p, --colors.");
5948
            status = SIXEL_BAD_ARGUMENT;
3✔
5949
            goto end;
3✔
5950
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
2✔
5951
            sixel_helper_set_additional_message(
3✔
5952
                "option -p, --colors conflicts with -I, --high-color.");
5953
            status = SIXEL_BAD_ARGUMENT;
3✔
5954
            goto end;
3✔
5955
        case SIXEL_COLOR_OPTION_BUILTIN:
2✔
5956
            sixel_helper_set_additional_message(
3✔
5957
                "option -p, --colors conflicts with -b, --builtin-palette.");
5958
            status = SIXEL_BAD_ARGUMENT;
3✔
5959
            goto end;
3✔
5960
        default:
60✔
5961
            break;
90✔
5962
        }
5963
    }
30✔
5964

5965
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
5966
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
631✔
5967
        sixel_helper_set_additional_message(
3✔
5968
            "option -8 --8bit-mode conflicts"
5969
            " with -P, --penetrate.");
5970
        status = SIXEL_BAD_ARGUMENT;
3✔
5971
        goto end;
3✔
5972
    }
5973

5974
    status = SIXEL_OK;
628✔
5975

5976
end:
478✔
5977
    if (opt_copy != NULL) {
718!
5978
        sixel_allocator_free(encoder->allocator, opt_copy);
×
5979
    }
5980
    sixel_encoder_unref(encoder);
718✔
5981

5982
    return status;
718✔
5983
}
5984

5985

5986
/* called when image loader component load a image frame */
5987
static SIXELSTATUS
5988
load_image_callback(sixel_frame_t *frame, void *data)
526✔
5989
{
5990
    sixel_encoder_t *encoder;
5991

5992
    encoder = (sixel_encoder_t *)data;
526✔
5993
    if (encoder->capture_source && encoder->capture_source_frame == NULL) {
526!
5994
        sixel_frame_ref(frame);
3✔
5995
        encoder->capture_source_frame = frame;
3✔
5996
    }
1✔
5997

5998
    return sixel_encoder_encode_frame(encoder, frame, NULL);
526✔
5999
}
6000

6001
/*
6002
 * Build a tee for encoded-assessment output:
6003
 *
6004
 *     +-------------+     +-------------------+     +------------+
6005
 *     | encoder FD  | --> | temporary SIXEL   | --> | tee sink   |
6006
 *     +-------------+     +-------------------+     +------------+
6007
 *
6008
 * The tee sink can be stdout or a user-provided file such as /dev/null.
6009
 */
6010
static SIXELSTATUS
6011
copy_file_to_stream(char const *path,
×
6012
                    FILE *stream,
6013
                    sixel_assessment_t *assessment)
6014
{
6015
    FILE *source;
6016
    unsigned char buffer[4096];
6017
    size_t nread;
6018
    size_t nwritten;
6019
    double started_at;
6020
    double finished_at;
6021
    double duration;
6022

6023
    source = NULL;
×
6024
    nread = 0;
×
6025
    nwritten = 0;
×
6026
    started_at = 0.0;
×
6027
    finished_at = 0.0;
×
6028
    duration = 0.0;
×
6029

6030
    source = sixel_compat_fopen(path, "rb");
×
6031
    if (source == NULL) {
×
6032
        sixel_helper_set_additional_message(
×
6033
            "copy_file_to_stream: failed to open assessment staging file.");
6034
        return SIXEL_LIBC_ERROR;
×
6035
    }
6036

6037
    for (;;) {
6038
        nread = fread(buffer, 1, sizeof(buffer), source);
×
6039
        if (nread == 0) {
×
6040
            if (ferror(source)) {
×
6041
                sixel_helper_set_additional_message(
×
6042
                    "copy_file_to_stream: failed while reading assessment staging file.");
6043
                (void) fclose(source);
×
6044
                return SIXEL_LIBC_ERROR;
×
6045
            }
6046
            break;
×
6047
        }
6048
        if (assessment != NULL) {
×
6049
            started_at = sixel_assessment_timer_now();
×
6050
        }
6051
        nwritten = fwrite(buffer, 1, nread, stream);
×
6052
        if (nwritten != nread) {
×
6053
            sixel_helper_set_additional_message(
×
6054
                "img2sixel: failed while copying assessment staging file.");
6055
            (void) fclose(source);
×
6056
            return SIXEL_LIBC_ERROR;
×
6057
        }
6058
        if (assessment != NULL) {
×
6059
            finished_at = sixel_assessment_timer_now();
×
6060
            duration = finished_at - started_at;
×
6061
            if (duration < 0.0) {
×
6062
                duration = 0.0;
×
6063
            }
6064
            sixel_assessment_record_output_write(assessment,
×
6065
                                                 nwritten,
6066
                                                 duration);
6067
        }
6068
    }
6069

6070
    if (fclose(source) != 0) {
×
6071
        sixel_helper_set_additional_message(
×
6072
            "img2sixel: failed to close assessment staging file.");
6073
        return SIXEL_LIBC_ERROR;
×
6074
    }
6075

6076
    return SIXEL_OK;
×
6077
}
6078

6079
typedef struct assessment_json_sink {
6080
    FILE *stream;
6081
    int failed;
6082
} assessment_json_sink_t;
6083

6084
static void
6085
assessment_json_callback(char const *chunk,
42✔
6086
                         size_t length,
6087
                         void *user_data)
6088
{
6089
    assessment_json_sink_t *sink;
6090

6091
    sink = (assessment_json_sink_t *)user_data;
42✔
6092
    if (sink == NULL || sink->stream == NULL) {
42!
6093
        return;
×
6094
    }
6095
    if (sink->failed) {
42!
6096
        return;
×
6097
    }
6098
    if (fwrite(chunk, 1, length, sink->stream) != length) {
42!
6099
        sink->failed = 1;
×
6100
    }
6101
}
14✔
6102

6103
static char *
6104
create_temp_template_with_prefix(sixel_allocator_t *allocator,
11✔
6105
                                 char const *prefix,
6106
                                 size_t *capacity_out)
6107
{
6108
    char const *tmpdir;
6109
    size_t tmpdir_len;
6110
    size_t prefix_len;
6111
    size_t suffix_len;
6112
    size_t template_len;
6113
    char *template_path;
6114
    int needs_separator;
6115
    size_t maximum_tmpdir_len;
6116

6117
    tmpdir = sixel_compat_getenv("TMPDIR");
11✔
6118
#if defined(_WIN32)
6119
    if (tmpdir == NULL || tmpdir[0] == '\0') {
6120
        tmpdir = sixel_compat_getenv("TEMP");
6121
    }
6122
    if (tmpdir == NULL || tmpdir[0] == '\0') {
6123
        tmpdir = sixel_compat_getenv("TMP");
6124
    }
6125
#endif
6126
    if (tmpdir == NULL || tmpdir[0] == '\0') {
11!
6127
#if defined(_WIN32)
6128
        tmpdir = ".";
6129
#else
6130
        tmpdir = "/tmp";
6✔
6131
#endif
6132
    }
6133

6134
    tmpdir_len = strlen(tmpdir);
11✔
6135
    prefix_len = 0u;
11✔
6136
    suffix_len = 0u;
11✔
6137
    if (prefix == NULL) {
11!
6138
        return NULL;
×
6139
    }
6140

6141
    prefix_len = strlen(prefix);
11✔
6142
    suffix_len = prefix_len + strlen("-XXXXXX");
11✔
6143
    maximum_tmpdir_len = (size_t)INT_MAX;
11✔
6144

6145
    if (maximum_tmpdir_len <= suffix_len + 2) {
11!
6146
        return NULL;
×
6147
    }
6148
    if (tmpdir_len > maximum_tmpdir_len - (suffix_len + 2)) {
11!
6149
        return NULL;
×
6150
    }
6151
    needs_separator = 1;
11✔
6152
    if (tmpdir_len > 0) {
11!
6153
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
11!
6154
            needs_separator = 0;
5✔
6155
        }
5✔
6156
    }
5✔
6157

6158
    template_len = tmpdir_len + suffix_len + 2;
11✔
6159
    template_path = (char *)sixel_allocator_malloc(allocator, template_len);
11✔
6160
    if (template_path == NULL) {
11!
6161
        return NULL;
×
6162
    }
6163

6164
    if (needs_separator) {
11!
6165
#if defined(_WIN32)
6166
        (void) snprintf(template_path, template_len,
6167
                        "%s\\%s-XXXXXX", tmpdir, prefix);
6168
#else
6169
        (void) snprintf(template_path, template_len,
6✔
6170
                        "%s/%s-XXXXXX", tmpdir, prefix);
6171
#endif
6172
    } else {
6173
        (void) snprintf(template_path, template_len,
5✔
6174
                        "%s%s-XXXXXX", tmpdir, prefix);
6175
    }
6176

6177
    if (capacity_out != NULL) {
11!
6178
        *capacity_out = template_len;
11✔
6179
    }
5✔
6180

6181
    return template_path;
11✔
6182
}
5✔
6183

6184

6185
static char *
6186
create_temp_template(sixel_allocator_t *allocator,
9✔
6187
                     size_t *capacity_out)
6188
{
6189
    return create_temp_template_with_prefix(allocator,
12✔
6190
                                            "img2sixel",
6191
                                            capacity_out);
3✔
6192
}
6193

6194

6195
static void
6196
clipboard_select_format(char *dest,
2✔
6197
                        size_t dest_size,
6198
                        char const *format,
6199
                        char const *fallback)
6200
{
6201
    char const *source;
6202
    size_t limit;
6203

6204
    if (dest == NULL || dest_size == 0u) {
2!
6205
        return;
×
6206
    }
6207

6208
    source = fallback;
2✔
6209
    if (format != NULL && format[0] != '\0') {
2!
6210
        source = format;
×
6211
    }
6212

6213
    limit = dest_size - 1u;
2✔
6214
    if (limit == 0u) {
2!
6215
        dest[0] = '\0';
×
6216
        return;
×
6217
    }
6218

6219
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
2✔
6220
}
2✔
6221

6222

6223
static SIXELSTATUS
6224
clipboard_create_spool(sixel_allocator_t *allocator,
2✔
6225
                       char const *prefix,
6226
                       char **path_out,
6227
                       int *fd_out)
6228
{
6229
    SIXELSTATUS status;
6230
    char *template_path;
6231
    size_t template_capacity;
6232
    int open_flags;
6233
    int fd;
6234
    char *tmpname_result;
6235

6236
    status = SIXEL_FALSE;
2✔
6237
    template_path = NULL;
2✔
6238
    template_capacity = 0u;
2✔
6239
    open_flags = 0;
2✔
6240
    fd = (-1);
2✔
6241
    tmpname_result = NULL;
2✔
6242

6243
    template_path = create_temp_template_with_prefix(allocator,
4✔
6244
                                                     prefix,
2✔
6245
                                                     &template_capacity);
6246
    if (template_path == NULL) {
2!
6247
        sixel_helper_set_additional_message(
×
6248
            "clipboard: failed to allocate spool template.");
6249
        status = SIXEL_BAD_ALLOCATION;
×
6250
        goto end;
×
6251
    }
6252

6253
    if (sixel_compat_mktemp(template_path, template_capacity) != 0) {
2!
6254
        /* Fall back to tmpnam() when mktemp variants are unavailable. */
6255
        tmpname_result = tmpnam(template_path);
×
6256
        if (tmpname_result == NULL) {
×
6257
            sixel_helper_set_additional_message(
×
6258
                "clipboard: failed to reserve spool template.");
6259
            status = SIXEL_LIBC_ERROR;
×
6260
            goto end;
×
6261
        }
6262
        template_capacity = strlen(template_path) + 1u;
×
6263
    }
6264

6265
    open_flags = O_RDWR | O_CREAT | O_TRUNC;
2✔
6266
#if defined(O_EXCL)
6267
    open_flags |= O_EXCL;
2✔
6268
#endif
6269
    fd = sixel_compat_open(template_path, open_flags, S_IRUSR | S_IWUSR);
2✔
6270
    if (fd < 0) {
2!
6271
        sixel_helper_set_additional_message(
×
6272
            "clipboard: failed to open spool file.");
6273
        status = SIXEL_LIBC_ERROR;
×
6274
        goto end;
×
6275
    }
6276

6277
    *path_out = template_path;
2✔
6278
    if (fd_out != NULL) {
2!
6279
        *fd_out = fd;
1✔
6280
        fd = (-1);
1✔
6281
    }
1✔
6282

6283
    template_path = NULL;
2✔
6284
    status = SIXEL_OK;
2✔
6285

6286
end:
6287
    if (fd >= 0) {
2!
6288
        (void)sixel_compat_close(fd);
1✔
6289
    }
1✔
6290
    if (template_path != NULL) {
2!
6291
        sixel_allocator_free(allocator, template_path);
×
6292
    }
6293

6294
    return status;
2✔
6295
}
6296

6297

6298
static SIXELSTATUS
6299
clipboard_write_file(char const *path,
1✔
6300
                     unsigned char const *data,
6301
                     size_t size)
6302
{
6303
    FILE *stream;
6304
    size_t written;
6305

6306
    if (path == NULL) {
1!
6307
        sixel_helper_set_additional_message(
×
6308
            "clipboard: spool path is null.");
6309
        return SIXEL_BAD_ARGUMENT;
×
6310
    }
6311

6312
    stream = sixel_compat_fopen(path, "wb");
1✔
6313
    if (stream == NULL) {
1!
6314
        sixel_helper_set_additional_message(
×
6315
            "clipboard: failed to open spool file for write.");
6316
        return SIXEL_LIBC_ERROR;
×
6317
    }
6318

6319
    written = 0u;
1✔
6320
    if (size > 0u && data != NULL) {
1!
6321
        written = fwrite(data, 1u, size, stream);
1✔
6322
        if (written != size) {
1!
6323
            (void)fclose(stream);
×
6324
            sixel_helper_set_additional_message(
×
6325
                "clipboard: failed to write spool payload.");
6326
            return SIXEL_LIBC_ERROR;
×
6327
        }
6328
    }
1✔
6329

6330
    if (fclose(stream) != 0) {
1!
6331
        sixel_helper_set_additional_message(
×
6332
            "clipboard: failed to close spool file after write.");
6333
        return SIXEL_LIBC_ERROR;
×
6334
    }
6335

6336
    return SIXEL_OK;
1✔
6337
}
1✔
6338

6339

6340
static SIXELSTATUS
6341
clipboard_read_file(char const *path,
1✔
6342
                    unsigned char **data,
6343
                    size_t *size)
6344
{
6345
    FILE *stream;
6346
    long seek_result;
6347
    long file_size;
6348
    unsigned char *buffer;
6349
    size_t read_size;
6350

6351
    if (data == NULL || size == NULL) {
1!
6352
        sixel_helper_set_additional_message(
×
6353
            "clipboard: read buffer pointers are null.");
6354
        return SIXEL_BAD_ARGUMENT;
×
6355
    }
6356

6357
    *data = NULL;
1✔
6358
    *size = 0u;
1✔
6359

6360
    if (path == NULL) {
1!
6361
        sixel_helper_set_additional_message(
×
6362
            "clipboard: spool path is null.");
6363
        return SIXEL_BAD_ARGUMENT;
×
6364
    }
6365

6366
    stream = sixel_compat_fopen(path, "rb");
1✔
6367
    if (stream == NULL) {
1!
6368
        sixel_helper_set_additional_message(
×
6369
            "clipboard: failed to open spool file for read.");
6370
        return SIXEL_LIBC_ERROR;
×
6371
    }
6372

6373
    seek_result = fseek(stream, 0L, SEEK_END);
1✔
6374
    if (seek_result != 0) {
1!
6375
        (void)fclose(stream);
×
6376
        sixel_helper_set_additional_message(
×
6377
            "clipboard: failed to seek spool file.");
6378
        return SIXEL_LIBC_ERROR;
×
6379
    }
6380

6381
    file_size = ftell(stream);
1✔
6382
    if (file_size < 0) {
1!
6383
        (void)fclose(stream);
×
6384
        sixel_helper_set_additional_message(
×
6385
            "clipboard: failed to determine spool size.");
6386
        return SIXEL_LIBC_ERROR;
×
6387
    }
6388

6389
    seek_result = fseek(stream, 0L, SEEK_SET);
1✔
6390
    if (seek_result != 0) {
1!
6391
        (void)fclose(stream);
×
6392
        sixel_helper_set_additional_message(
×
6393
            "clipboard: failed to rewind spool file.");
6394
        return SIXEL_LIBC_ERROR;
×
6395
    }
6396

6397
    if (file_size == 0) {
1!
6398
        buffer = NULL;
×
6399
        read_size = 0u;
×
6400
    } else {
6401
        buffer = (unsigned char *)malloc((size_t)file_size);
1✔
6402
        if (buffer == NULL) {
1!
6403
            (void)fclose(stream);
×
6404
            sixel_helper_set_additional_message(
×
6405
                "clipboard: malloc() failed for spool payload.");
6406
            return SIXEL_BAD_ALLOCATION;
×
6407
        }
6408
        read_size = fread(buffer, 1u, (size_t)file_size, stream);
1✔
6409
        if (read_size != (size_t)file_size) {
1!
6410
            free(buffer);
×
6411
            (void)fclose(stream);
×
6412
            sixel_helper_set_additional_message(
×
6413
                "clipboard: failed to read spool payload.");
6414
            return SIXEL_LIBC_ERROR;
×
6415
        }
6416
    }
6417

6418
    if (fclose(stream) != 0) {
1!
6419
        if (buffer != NULL) {
×
6420
            free(buffer);
×
6421
        }
6422
        sixel_helper_set_additional_message(
×
6423
            "clipboard: failed to close spool file after read.");
6424
        return SIXEL_LIBC_ERROR;
×
6425
    }
6426

6427
    *data = buffer;
1✔
6428
    *size = read_size;
1✔
6429

6430
    return SIXEL_OK;
1✔
6431
}
1✔
6432

6433

6434
static SIXELSTATUS
6435
write_png_from_sixel(char const *sixel_path, char const *output_path)
9✔
6436
{
6437
    SIXELSTATUS status;
6438
    sixel_decoder_t *decoder;
6439

6440
    status = SIXEL_FALSE;
9✔
6441
    decoder = NULL;
9✔
6442

6443
    status = sixel_decoder_new(&decoder, NULL);
9✔
6444
    if (SIXEL_FAILED(status)) {
9!
6445
        goto end;
×
6446
    }
6447

6448
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_INPUT, sixel_path);
9✔
6449
    if (SIXEL_FAILED(status)) {
9!
6450
        goto end;
×
6451
    }
6452

6453
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_OUTPUT, output_path);
9✔
6454
    if (SIXEL_FAILED(status)) {
9!
6455
        goto end;
×
6456
    }
6457

6458
    status = sixel_decoder_decode(decoder);
9✔
6459

6460
end:
6✔
6461
    sixel_decoder_unref(decoder);
9✔
6462

6463
    return status;
9✔
6464
}
6465

6466

6467
/* load source data from specified file and encode it to SIXEL format
6468
 * output to encoder->outfd */
6469
SIXELAPI SIXELSTATUS
6470
sixel_encoder_encode(
437✔
6471
    sixel_encoder_t *encoder,   /* encoder object */
6472
    char const      *filename)  /* input filename */
6473
{
6474
    SIXELSTATUS status = SIXEL_FALSE;
437✔
6475
    SIXELSTATUS palette_status = SIXEL_OK;
437✔
6476
    int fuse_palette = 1;
437✔
6477
    sixel_loader_t *loader = NULL;
437✔
6478
    sixel_allocator_t *assessment_allocator = NULL;
437✔
6479
    sixel_allocator_t *encode_allocator = NULL;
437✔
6480
    sixel_frame_t *assessment_source_frame = NULL;
437✔
6481
    sixel_frame_t *assessment_target_frame = NULL;
437✔
6482
    sixel_frame_t *assessment_expanded_frame = NULL;
437✔
6483
    unsigned int assessment_section_mask =
437✔
6484
        encoder->assessment_sections & SIXEL_ASSESSMENT_SECTION_MASK;
437✔
6485
    int assessment_need_source_capture = 0;
437✔
6486
    int assessment_need_quantized_capture = 0;
437✔
6487
    int assessment_need_quality = 0;
437✔
6488
    int assessment_quality_quantized = 0;
437✔
6489
    assessment_json_sink_t assessment_sink;
6490
    FILE *assessment_json_file = NULL;
437✔
6491
    FILE *assessment_forward_stream = NULL;
437✔
6492
    int assessment_json_owned = 0;
437✔
6493
    char *assessment_temp_path = NULL;
437✔
6494
    size_t assessment_temp_capacity = 0u;
437✔
6495
    char *assessment_tmpnam_result = NULL;
437✔
6496
    sixel_assessment_spool_mode_t assessment_spool_mode
437✔
6497
        = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
6498
    char *assessment_forward_path = NULL;
437✔
6499
    size_t assessment_output_bytes;
6500
#if HAVE_SYS_STAT_H
6501
    struct stat assessment_stat;
6502
    int assessment_stat_result;
6503
    char const *assessment_size_path = NULL;
437✔
6504
#endif
6505
    char const *png_final_path = NULL;
437✔
6506
    char *png_temp_path = NULL;
437✔
6507
    size_t png_temp_capacity = 0u;
437✔
6508
    char *png_tmpnam_result = NULL;
437✔
6509
    int png_open_flags = 0;
437✔
6510
    int spool_required;
6511
    sixel_clipboard_spec_t clipboard_spec;
6512
    char clipboard_input_format[32];
6513
    char *clipboard_input_path;
6514
    unsigned char *clipboard_blob;
6515
    size_t clipboard_blob_size;
6516
    SIXELSTATUS clipboard_status;
6517
    char const *effective_filename;
6518
    unsigned int path_flags;
6519
    int path_check;
6520

6521
    clipboard_input_format[0] = '\0';
437✔
6522
    clipboard_input_path = NULL;
437✔
6523
    clipboard_blob = NULL;
437✔
6524
    clipboard_blob_size = 0u;
437✔
6525
    clipboard_status = SIXEL_OK;
437✔
6526
    effective_filename = filename;
437✔
6527
    path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
437✔
6528
        SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
6529
        SIXEL_OPTION_PATH_ALLOW_REMOTE;
6530
    path_check = 0;
437✔
6531

6532
    if (filename != NULL) {
437✔
6533
        path_check = sixel_option_validate_filesystem_path(
296✔
6534
            filename,
100✔
6535
            filename,
100✔
6536
            path_flags);
100✔
6537
        if (path_check != 0) {
296!
6538
            status = SIXEL_BAD_ARGUMENT;
1✔
6539
            goto end;
1✔
6540
        }
6541
    }
99✔
6542

6543
    if (encoder != NULL) {
436!
6544
        encode_allocator = encoder->allocator;
436✔
6545
        if (encode_allocator != NULL) {
436!
6546
            /*
6547
             * Hold a reference until cleanup so worker side-effects or loader
6548
             * destruction cannot release the allocator before sequential
6549
             * teardown finishes using it.
6550
             */
6551
            sixel_allocator_ref(encode_allocator);
436✔
6552
        }
146✔
6553
    }
146✔
6554

6555
    clipboard_spec.is_clipboard = 0;
436✔
6556
    clipboard_spec.format[0] = '\0';
436✔
6557
    if (effective_filename != NULL
437!
6558
            && sixel_clipboard_parse_spec(effective_filename,
342!
6559
                                          &clipboard_spec)
6560
            && clipboard_spec.is_clipboard) {
99!
6561
        clipboard_select_format(clipboard_input_format,
2✔
6562
                                sizeof(clipboard_input_format),
6563
                                clipboard_spec.format,
1✔
6564
                                "sixel");
6565
        clipboard_status = sixel_clipboard_read(
1✔
6566
            clipboard_input_format,
1✔
6567
            &clipboard_blob,
6568
            &clipboard_blob_size,
6569
            encoder->allocator);
1✔
6570
        if (SIXEL_FAILED(clipboard_status)) {
1!
6571
            status = clipboard_status;
×
6572
            goto end;
×
6573
        }
6574
        clipboard_status = clipboard_create_spool(
1✔
6575
            encoder->allocator,
1✔
6576
            "clipboard-in",
6577
            &clipboard_input_path,
6578
            NULL);
6579
        if (SIXEL_FAILED(clipboard_status)) {
1!
6580
            status = clipboard_status;
×
6581
            goto end;
×
6582
        }
6583
        clipboard_status = clipboard_write_file(
1✔
6584
            clipboard_input_path,
1✔
6585
            clipboard_blob,
1✔
6586
            clipboard_blob_size);
1✔
6587
        if (SIXEL_FAILED(clipboard_status)) {
1!
6588
            status = clipboard_status;
×
6589
            goto end;
×
6590
        }
6591
        if (clipboard_blob != NULL) {
1!
6592
            free(clipboard_blob);
1✔
6593
            clipboard_blob = NULL;
1✔
6594
        }
1✔
6595
        effective_filename = clipboard_input_path;
1✔
6596
    }
1✔
6597

6598
    if (assessment_section_mask != SIXEL_ASSESSMENT_SECTION_NONE) {
436✔
6599
        status = sixel_allocator_new(&assessment_allocator,
3✔
6600
                                     malloc,
6601
                                     calloc,
6602
                                     realloc,
6603
                                     free);
6604
        if (SIXEL_FAILED(status) || assessment_allocator == NULL) {
3!
6605
            goto end;
×
6606
        }
6607
        status = sixel_assessment_new(&encoder->assessment_observer,
4✔
6608
                                       assessment_allocator);
1✔
6609
        if (SIXEL_FAILED(status) || encoder->assessment_observer == NULL) {
3!
6610
            goto end;
×
6611
        }
6612
        sixel_assessment_select_sections(encoder->assessment_observer,
4✔
6613
                                         encoder->assessment_sections);
1✔
6614
        sixel_assessment_attach_encoder(encoder->assessment_observer,
4✔
6615
                                        encoder);
1✔
6616
        assessment_need_quality =
3✔
6617
            (assessment_section_mask & SIXEL_ASSESSMENT_SECTION_QUALITY) != 0u;
3✔
6618
        assessment_quality_quantized =
3✔
6619
            (encoder->assessment_sections & SIXEL_ASSESSMENT_VIEW_QUANTIZED) != 0u;
3✔
6620
        assessment_need_quantized_capture =
3✔
6621
            ((assessment_section_mask &
3✔
6622
              (SIXEL_ASSESSMENT_SECTION_BASIC |
6623
               SIXEL_ASSESSMENT_SECTION_SIZE)) != 0u) ||
3!
6624
            assessment_quality_quantized;
6625
        assessment_need_source_capture =
3✔
6626
            (assessment_section_mask &
3✔
6627
             (SIXEL_ASSESSMENT_SECTION_BASIC |
6628
              SIXEL_ASSESSMENT_SECTION_PERFORMANCE |
6629
              SIXEL_ASSESSMENT_SECTION_SIZE |
6630
              SIXEL_ASSESSMENT_SECTION_QUALITY)) != 0u;
3✔
6631
        if (assessment_need_quality && !assessment_quality_quantized &&
3!
6632
                encoder->output_is_png) {
×
6633
            sixel_helper_set_additional_message(
×
6634
                "sixel_encoder_setopt: encoded quality assessment requires SIXEL output.");
6635
            status = SIXEL_BAD_ARGUMENT;
×
6636
            goto end;
×
6637
        }
6638
        status = sixel_encoder_enable_source_capture(encoder,
4✔
6639
                                                     assessment_need_source_capture);
1✔
6640
        if (SIXEL_FAILED(status)) {
3!
6641
            goto end;
×
6642
        }
6643
        status = sixel_encoder_setopt(encoder, SIXEL_OPTFLAG_STATIC, NULL);
3✔
6644
        if (SIXEL_FAILED(status)) {
3!
6645
            goto end;
×
6646
        }
6647
        if (assessment_need_quantized_capture) {
3!
6648
            status = sixel_encoder_enable_quantized_capture(encoder, 1);
3✔
6649
            if (SIXEL_FAILED(status)) {
3!
6650
                goto end;
×
6651
            }
6652
        }
1✔
6653
        assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
3✔
6654
        spool_required = 0;
3✔
6655
        if (assessment_need_quality && !assessment_quality_quantized) {
3!
6656
            if (encoder->sixel_output_path == NULL) {
×
6657
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT;
×
6658
                spool_required = 1;
×
6659
            } else if (strcmp(encoder->sixel_output_path, "-") == 0) {
×
6660
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT;
×
6661
                spool_required = 1;
×
6662
                free(encoder->sixel_output_path);
×
6663
                encoder->sixel_output_path = NULL;
×
6664
            } else if (is_dev_null_path(encoder->sixel_output_path)) {
×
6665
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_PATH;
×
6666
                spool_required = 1;
×
6667
                assessment_forward_path = encoder->sixel_output_path;
×
6668
                encoder->sixel_output_path = NULL;
×
6669
            }
6670
        }
6671
        if (spool_required) {
3!
6672
            assessment_temp_capacity = 0u;
×
6673
            assessment_tmpnam_result = NULL;
×
6674
            assessment_temp_path = create_temp_template(encoder->allocator,
×
6675
                                                        &assessment_temp_capacity);
6676
            if (assessment_temp_path == NULL) {
×
6677
                sixel_helper_set_additional_message(
×
6678
                    "sixel_encoder_encode: sixel_allocator_malloc() "
6679
                    "failed for assessment staging path.");
6680
                status = SIXEL_BAD_ALLOCATION;
×
6681
                goto end;
×
6682
            }
6683
            if (sixel_compat_mktemp(assessment_temp_path,
×
6684
                                    assessment_temp_capacity) != 0) {
6685
                /* Fall back to tmpnam() when mktemp variants are unavailable. */
6686
                assessment_tmpnam_result = tmpnam(assessment_temp_path);
×
6687
                if (assessment_tmpnam_result == NULL) {
×
6688
                    sixel_helper_set_additional_message(
×
6689
                        "sixel_encoder_encode: mktemp() failed for assessment staging file.");
6690
                    status = SIXEL_RUNTIME_ERROR;
×
6691
                    goto end;
×
6692
                }
6693
                assessment_temp_capacity = strlen(assessment_temp_path) + 1u;
×
6694
            }
6695
            status = sixel_encoder_setopt(encoder, SIXEL_OPTFLAG_OUTFILE,
×
6696
                                          assessment_temp_path);
6697
            if (SIXEL_FAILED(status)) {
×
6698
                goto end;
×
6699
            }
6700
            encoder->sixel_output_path = (char *)sixel_allocator_malloc(
×
6701
                encoder->allocator, strlen(assessment_temp_path) + 1);
×
6702
            if (encoder->sixel_output_path == NULL) {
×
6703
                sixel_helper_set_additional_message(
×
6704
                    "sixel_encoder_encode: malloc() failed for assessment staging name.");
6705
                status = SIXEL_BAD_ALLOCATION;
×
6706
                goto end;
×
6707
            }
6708
            (void)sixel_compat_strcpy(encoder->sixel_output_path,
×
6709
                                      strlen(assessment_temp_path) + 1,
×
6710
                                      assessment_temp_path);
6711
        }
6712

6713
    }
1✔
6714

6715
    if (encoder->output_is_png) {
436✔
6716
        png_temp_capacity = 0u;
9✔
6717
        png_tmpnam_result = NULL;
9✔
6718
        png_temp_path = create_temp_template(encoder->allocator,
9✔
6719
                                             &png_temp_capacity);
6720
        if (png_temp_path == NULL) {
9!
6721
            sixel_helper_set_additional_message(
×
6722
                "sixel_encoder_encode: malloc() failed for PNG staging path.");
6723
            status = SIXEL_BAD_ALLOCATION;
×
6724
            goto end;
×
6725
        }
6726
        if (sixel_compat_mktemp(png_temp_path, png_temp_capacity) != 0) {
9!
6727
            /* Fall back to tmpnam() when mktemp variants are unavailable. */
6728
            png_tmpnam_result = tmpnam(png_temp_path);
×
6729
            if (png_tmpnam_result == NULL) {
×
6730
                sixel_helper_set_additional_message(
×
6731
                    "sixel_encoder_encode: mktemp() failed for PNG staging file.");
6732
                status = SIXEL_RUNTIME_ERROR;
×
6733
                goto end;
×
6734
            }
6735
            png_temp_capacity = strlen(png_temp_path) + 1u;
×
6736
        }
6737
        if (encoder->outfd >= 0 && encoder->outfd != STDOUT_FILENO) {
9!
6738
            (void)sixel_compat_close(encoder->outfd);
9✔
6739
        }
3✔
6740
        png_open_flags = O_RDWR | O_CREAT | O_TRUNC;
9✔
6741
#if defined(O_EXCL)
6742
        png_open_flags |= O_EXCL;
9✔
6743
#endif
6744
        encoder->outfd = sixel_compat_open(png_temp_path,
12✔
6745
                                           png_open_flags,
3✔
6746
                                           S_IRUSR | S_IWUSR);
6747
        if (encoder->outfd < 0) {
9!
6748
            sixel_helper_set_additional_message(
×
6749
                "sixel_encoder_encode: failed to create the PNG target file.");
6750
            status = SIXEL_LIBC_ERROR;
×
6751
            goto end;
×
6752
        }
6753
    }
3✔
6754

6755
    if (encoder == NULL) {
436!
6756
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6757
#  pragma GCC diagnostic push
6758
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
6759
#endif
6760
        encoder = sixel_encoder_create();
×
6761
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6762
#  pragma GCC diagnostic pop
6763
#endif
6764
        if (encoder == NULL) {
×
6765
            sixel_helper_set_additional_message(
×
6766
                "sixel_encoder_encode: sixel_encoder_create() failed.");
6767
            status = SIXEL_BAD_ALLOCATION;
×
6768
            goto end;
×
6769
        }
6770
    } else {
6771
        sixel_encoder_ref(encoder);
436✔
6772
    }
6773

6774
    if (encode_allocator == NULL && encoder != NULL) {
436!
6775
        encode_allocator = encoder->allocator;
×
6776
        if (encode_allocator != NULL) {
×
6777
            /* Ensure the allocator stays valid after lazy encoder creation. */
6778
            sixel_allocator_ref(encode_allocator);
×
6779
        }
6780
    }
6781

6782
    if (encoder->assessment_observer != NULL) {
436✔
6783
        sixel_assessment_stage_transition(
3✔
6784
            encoder->assessment_observer,
3✔
6785
            SIXEL_ASSESSMENT_STAGE_IMAGE_CHUNK);
6786
    }
1✔
6787
    encoder->last_loader_name[0] = '\0';
436✔
6788
    encoder->last_source_path[0] = '\0';
436✔
6789
    encoder->last_input_bytes = 0u;
436✔
6790

6791
    /* if required color is not set, set the max value */
6792
    if (encoder->reqcolors == (-1)) {
436✔
6793
        encoder->reqcolors = SIXEL_PALETTE_MAX;
418✔
6794
    }
140✔
6795

6796
    if (encoder->capture_source && encoder->capture_source_frame != NULL) {
436!
6797
        sixel_frame_unref(encoder->capture_source_frame);
×
6798
        encoder->capture_source_frame = NULL;
×
6799
    }
6800

6801
    /* if required color is less then 2, set the min value */
6802
    if (encoder->reqcolors < 2) {
436✔
6803
        encoder->reqcolors = SIXEL_PALETTE_MIN;
3✔
6804
    }
1✔
6805

6806
    /* if color space option is not set, choose RGB color space */
6807
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
436✔
6808
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
418✔
6809
    }
140✔
6810

6811
    /* if color option is not default value, prohibit to read
6812
       the file as a paletted image */
6813
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
436✔
6814
        fuse_palette = 0;
96✔
6815
    }
32✔
6816

6817
    /* if scaling options are set, prohibit to read the file as
6818
       a paletted image */
6819
    if (encoder->percentwidth > 0 ||
559✔
6820
        encoder->percentheight > 0 ||
424✔
6821
        encoder->pixelwidth > 0 ||
418✔
6822
        encoder->pixelheight > 0) {
400✔
6823
        fuse_palette = 0;
99✔
6824
    }
33✔
6825

6826
reload:
290✔
6827

6828
    sixel_helper_set_loader_trace(encoder->verbose);
436✔
6829
    sixel_helper_set_thumbnail_size_hint(
436✔
6830
        sixel_encoder_thumbnail_hint(encoder));
146✔
6831

6832
    status = sixel_loader_new(&loader, encoder->allocator);
436✔
6833
    if (SIXEL_FAILED(status)) {
436!
6834
        goto load_end;
×
6835
    }
6836

6837
    status = sixel_loader_setopt(loader,
582✔
6838
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
6839
                                 &encoder->fstatic);
436✔
6840
    if (SIXEL_FAILED(status)) {
436!
6841
        goto load_end;
×
6842
    }
6843

6844
    status = sixel_loader_setopt(loader,
436✔
6845
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
6846
                                 &fuse_palette);
6847
    if (SIXEL_FAILED(status)) {
436!
6848
        goto load_end;
×
6849
    }
6850

6851
    status = sixel_loader_setopt(loader,
582✔
6852
                                 SIXEL_LOADER_OPTION_REQCOLORS,
6853
                                 &encoder->reqcolors);
436✔
6854
    if (SIXEL_FAILED(status)) {
436!
6855
        goto load_end;
×
6856
    }
6857

6858
    status = sixel_loader_setopt(loader,
582✔
6859
                                 SIXEL_LOADER_OPTION_BGCOLOR,
6860
                                 encoder->bgcolor);
436✔
6861
    if (SIXEL_FAILED(status)) {
436!
6862
        goto load_end;
×
6863
    }
6864

6865
    status = sixel_loader_setopt(loader,
582✔
6866
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
6867
                                 &encoder->loop_mode);
436✔
6868
    if (SIXEL_FAILED(status)) {
436!
6869
        goto load_end;
×
6870
    }
6871

6872
    status = sixel_loader_setopt(loader,
582✔
6873
                                 SIXEL_LOADER_OPTION_INSECURE,
6874
                                 &encoder->finsecure);
436✔
6875
    if (SIXEL_FAILED(status)) {
436!
6876
        goto load_end;
×
6877
    }
6878

6879
    status = sixel_loader_setopt(loader,
582✔
6880
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
6881
                                 encoder->cancel_flag);
436✔
6882
    if (SIXEL_FAILED(status)) {
436!
6883
        goto load_end;
×
6884
    }
6885

6886
    status = sixel_loader_setopt(loader,
582✔
6887
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
6888
                                 encoder->loader_order);
436✔
6889
    if (SIXEL_FAILED(status)) {
436!
6890
        goto load_end;
×
6891
    }
6892

6893
    status = sixel_loader_setopt(loader,
582✔
6894
                                 SIXEL_LOADER_OPTION_CONTEXT,
6895
                                 encoder);
146✔
6896
    if (SIXEL_FAILED(status)) {
436!
6897
        goto load_end;
×
6898
    }
6899

6900
    /*
6901
     * Wire the optional assessment observer into the loader.
6902
     *
6903
     * The observer travels separately from the callback context so mapfile
6904
     * palette probes and other callbacks can keep using arbitrary structs.
6905
     */
6906
    status = sixel_loader_setopt(loader,
582✔
6907
                                 SIXEL_LOADER_OPTION_ASSESSMENT,
6908
                                 encoder->assessment_observer);
436✔
6909
    if (SIXEL_FAILED(status)) {
436!
6910
        goto load_end;
×
6911
    }
6912

6913
    status = sixel_loader_load_file(loader,
582✔
6914
                                    effective_filename,
146✔
6915
                                    load_image_callback);
6916
    if (status != SIXEL_OK) {
436✔
6917
        goto load_end;
15✔
6918
    }
6919
    encoder->last_input_bytes = sixel_loader_get_last_input_bytes(loader);
421✔
6920
    if (sixel_loader_get_last_success_name(loader) != NULL) {
421!
6921
        (void)snprintf(encoder->last_loader_name,
421✔
6922
                       sizeof(encoder->last_loader_name),
6923
                       "%s",
6924
                       sixel_loader_get_last_success_name(loader));
6925
    } else {
141✔
6926
        encoder->last_loader_name[0] = '\0';
×
6927
    }
6928
    if (sixel_loader_get_last_source_path(loader) != NULL) {
421✔
6929
        (void)snprintf(encoder->last_source_path,
283✔
6930
                       sizeof(encoder->last_source_path),
6931
                       "%s",
6932
                       sixel_loader_get_last_source_path(loader));
6933
    } else {
95✔
6934
        encoder->last_source_path[0] = '\0';
138✔
6935
    }
6936
    if (encoder->assessment_observer != NULL) {
422✔
6937
        sixel_assessment_record_loader(encoder->assessment_observer,
4✔
6938
                                       encoder->last_source_path,
3✔
6939
                                       encoder->last_loader_name,
3✔
6940
                                       encoder->last_input_bytes);
1✔
6941
    }
1✔
6942

6943
load_end:
278✔
6944
    sixel_loader_unref(loader);
436✔
6945
    loader = NULL;
436✔
6946

6947
    if (status != SIXEL_OK) {
436✔
6948
        goto end;
15✔
6949
    }
6950

6951
    palette_status = sixel_encoder_emit_palette_output(encoder);
421✔
6952
    if (SIXEL_FAILED(palette_status)) {
421!
6953
        status = palette_status;
×
6954
        goto end;
×
6955
    }
6956

6957
    if (encoder->pipe_mode) {
421!
6958
#if HAVE_CLEARERR
6959
        clearerr(stdin);
×
6960
#endif  /* HAVE_FSEEK */
6961
        while (encoder->cancel_flag && !*encoder->cancel_flag) {
×
6962
            status = sixel_tty_wait_stdin(1000000);
×
6963
            if (SIXEL_FAILED(status)) {
×
6964
                goto end;
×
6965
            }
6966
            if (status != SIXEL_OK) {
×
6967
                break;
×
6968
            }
6969
        }
6970
        if (!encoder->cancel_flag || !*encoder->cancel_flag) {
×
6971
            goto reload;
×
6972
        }
6973
    }
6974

6975
    if (encoder->assessment_observer) {
421✔
6976
        if (assessment_allocator == NULL || encoder->assessment_observer == NULL) {
3!
6977
            status = SIXEL_RUNTIME_ERROR;
×
6978
            goto end;
×
6979
        }
6980
        if (assessment_need_source_capture) {
3!
6981
            status = sixel_encoder_copy_source_frame(encoder,
3✔
6982
                                                     &assessment_source_frame);
6983
            if (SIXEL_FAILED(status)) {
3!
6984
                goto end;
×
6985
            }
6986
            sixel_assessment_record_source_frame(encoder->assessment_observer,
4✔
6987
                                                 assessment_source_frame);
1✔
6988
        }
1✔
6989
        if (assessment_need_quality) {
3!
6990
            if (assessment_quality_quantized) {
×
6991
                status = sixel_encoder_copy_quantized_frame(
×
6992
                    encoder, assessment_allocator, &assessment_target_frame);
6993
                if (SIXEL_FAILED(status)) {
×
6994
                    goto end;
×
6995
                }
6996
                status = sixel_assessment_expand_quantized_frame(
×
6997
                    assessment_target_frame,
6998
                    assessment_allocator,
6999
                    &assessment_expanded_frame);
7000
                if (SIXEL_FAILED(status)) {
×
7001
                    goto end;
×
7002
                }
7003
                sixel_frame_unref(assessment_target_frame);
×
7004
                assessment_target_frame = assessment_expanded_frame;
×
7005
                assessment_expanded_frame = NULL;
×
7006
                sixel_assessment_record_quantized_capture(
×
7007
                    encoder->assessment_observer, encoder);
×
7008
            } else {
7009
                status = sixel_assessment_load_single_frame(
×
7010
                    encoder->sixel_output_path,
×
7011
                    assessment_allocator,
7012
                    &assessment_target_frame);
7013
                if (SIXEL_FAILED(status)) {
×
7014
                    goto end;
×
7015
                }
7016
            }
7017
            if (!assessment_quality_quantized &&
×
7018
                    assessment_need_quantized_capture) {
7019
                sixel_assessment_record_quantized_capture(
×
7020
                    encoder->assessment_observer, encoder);
×
7021
            }
7022
        } else if (assessment_need_quantized_capture) {
3!
7023
            sixel_assessment_record_quantized_capture(
3✔
7024
                encoder->assessment_observer, encoder);
3✔
7025
        }
1✔
7026
        if (encoder->assessment_observer != NULL &&
3!
7027
                assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
1✔
7028
            sixel_assessment_stage_transition(
×
7029
                encoder->assessment_observer,
×
7030
                SIXEL_ASSESSMENT_STAGE_OUTPUT);
7031
        }
7032
        if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT) {
3!
7033
            status = copy_file_to_stream(assessment_temp_path,
×
7034
                                         stdout,
7035
                                         encoder->assessment_observer);
×
7036
            if (SIXEL_FAILED(status)) {
×
7037
                goto end;
×
7038
            }
7039
        } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
3!
7040
            if (assessment_forward_path == NULL) {
×
7041
                sixel_helper_set_additional_message(
×
7042
                    "sixel_encoder_encode: missing assessment spool target.");
7043
                status = SIXEL_RUNTIME_ERROR;
×
7044
                goto end;
×
7045
            }
7046
            assessment_forward_stream = sixel_compat_fopen(
×
7047
                assessment_forward_path,
7048
                "wb");
7049
            if (assessment_forward_stream == NULL) {
×
7050
                sixel_helper_set_additional_message(
×
7051
                    "sixel_encoder_encode: failed to open assessment spool sink.");
7052
                status = SIXEL_LIBC_ERROR;
×
7053
                goto end;
×
7054
            }
7055
            status = copy_file_to_stream(assessment_temp_path,
×
7056
                                         assessment_forward_stream,
7057
                                         encoder->assessment_observer);
×
7058
            if (fclose(assessment_forward_stream) != 0) {
×
7059
                if (SIXEL_SUCCEEDED(status)) {
×
7060
                    sixel_helper_set_additional_message(
×
7061
                        "img2sixel: failed to close assessment spool sink.");
7062
                    status = SIXEL_LIBC_ERROR;
×
7063
                }
7064
            }
7065
            assessment_forward_stream = NULL;
×
7066
            if (SIXEL_FAILED(status)) {
×
7067
                goto end;
×
7068
            }
7069
        }
7070
        if (encoder->assessment_observer != NULL &&
3!
7071
                assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
1✔
7072
            sixel_assessment_stage_finish(encoder->assessment_observer);
×
7073
        }
7074
#if HAVE_SYS_STAT_H
7075
        assessment_output_bytes = 0u;
3✔
7076
        assessment_size_path = NULL;
3✔
7077
        if (assessment_need_quality && !assessment_quality_quantized) {
3!
7078
            if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT ||
×
7079
                    assessment_spool_mode ==
7080
                        SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
7081
                assessment_size_path = assessment_temp_path;
×
7082
            } else if (encoder->sixel_output_path != NULL &&
×
7083
                    strcmp(encoder->sixel_output_path, "-") != 0) {
×
7084
                assessment_size_path = encoder->sixel_output_path;
×
7085
            }
7086
        } else {
7087
            if (encoder->sixel_output_path != NULL &&
3!
7088
                    strcmp(encoder->sixel_output_path, "-") != 0) {
×
7089
                assessment_size_path = encoder->sixel_output_path;
×
7090
            } else if (assessment_spool_mode ==
3!
7091
                    SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT ||
3!
7092
                    assessment_spool_mode ==
1✔
7093
                        SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
7094
                assessment_size_path = assessment_temp_path;
×
7095
            }
7096
        }
7097
        if (assessment_size_path != NULL) {
3!
7098
            assessment_stat_result = stat(assessment_size_path,
×
7099
                                          &assessment_stat);
7100
            if (assessment_stat_result == 0 &&
×
7101
                    assessment_stat.st_size >= 0) {
×
7102
                assessment_output_bytes =
×
7103
                    (size_t)assessment_stat.st_size;
×
7104
            }
7105
        }
7106
#else
7107
        assessment_output_bytes = 0u;
7108
#endif
7109
        sixel_assessment_record_output_size(encoder->assessment_observer,
4✔
7110
                                            assessment_output_bytes);
1✔
7111
        if (assessment_need_quality) {
3!
7112
            status = sixel_assessment_analyze(encoder->assessment_observer,
×
7113
                                              assessment_source_frame,
7114
                                              assessment_target_frame);
7115
            if (SIXEL_FAILED(status)) {
×
7116
                goto end;
×
7117
            }
7118
        }
7119
        sixel_assessment_stage_finish(encoder->assessment_observer);
3✔
7120
        if (encoder->assessment_json_path != NULL &&
3!
7121
                strcmp(encoder->assessment_json_path, "-") != 0) {
3!
7122
            assessment_json_file = sixel_compat_fopen(
3✔
7123
                encoder->assessment_json_path,
1✔
7124
                "wb");
7125
            if (assessment_json_file == NULL) {
3!
7126
                sixel_helper_set_additional_message(
×
7127
                    "sixel_encoder_encode: failed to open assessment JSON file.");
7128
                status = SIXEL_LIBC_ERROR;
×
7129
                goto end;
×
7130
            }
7131
            assessment_json_owned = 1;
3✔
7132
            assessment_sink.stream = assessment_json_file;
3✔
7133
        } else {
1✔
7134
            assessment_sink.stream = stdout;
×
7135
        }
7136
        assessment_sink.failed = 0;
3✔
7137
        status = sixel_assessment_get_json(encoder->assessment_observer,
4✔
7138
                                           encoder->assessment_sections,
1✔
7139
                                           assessment_json_callback,
7140
                                           &assessment_sink);
7141
        if (SIXEL_FAILED(status) || assessment_sink.failed) {
3!
7142
            sixel_helper_set_additional_message(
×
7143
                "sixel_encoder_encode: failed to emit assessment JSON.");
7144
            goto end;
×
7145
        }
7146
    } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT) {
419!
7147
        status = copy_file_to_stream(assessment_temp_path,
×
7148
                                     stdout,
7149
                                     encoder->assessment_observer);
×
7150
        if (SIXEL_FAILED(status)) {
×
7151
            goto end;
×
7152
        }
7153
    } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
418!
7154
        if (assessment_forward_path == NULL) {
×
7155
            sixel_helper_set_additional_message(
×
7156
                "img2sixel: missing assessment spool target.");
7157
            status = SIXEL_RUNTIME_ERROR;
×
7158
            goto end;
×
7159
        }
7160
        assessment_forward_stream = sixel_compat_fopen(
×
7161
            assessment_forward_path,
7162
            "wb");
7163
        if (assessment_forward_stream == NULL) {
×
7164
            sixel_helper_set_additional_message(
×
7165
                "img2sixel: failed to open assessment spool sink.");
7166
            status = SIXEL_LIBC_ERROR;
×
7167
            goto end;
×
7168
        }
7169
        status = copy_file_to_stream(assessment_temp_path,
×
7170
                                     assessment_forward_stream,
7171
                                     encoder->assessment_observer);
×
7172
        if (fclose(assessment_forward_stream) != 0) {
×
7173
            if (SIXEL_SUCCEEDED(status)) {
×
7174
                sixel_helper_set_additional_message(
×
7175
                    "img2sixel: failed to close assessment spool sink.");
7176
                status = SIXEL_LIBC_ERROR;
×
7177
            }
7178
        }
7179
        assessment_forward_stream = NULL;
×
7180
        if (SIXEL_FAILED(status)) {
×
7181
            goto end;
×
7182
        }
7183
    }
7184

7185
    if (encoder->output_is_png) {
421✔
7186
        png_final_path = encoder->output_png_to_stdout ? "-" : encoder->png_output_path;
9!
7187
        if (! encoder->output_png_to_stdout && png_final_path == NULL) {
9!
7188
            sixel_helper_set_additional_message(
×
7189
                "sixel_encoder_encode: missing PNG output path.");
7190
            status = SIXEL_RUNTIME_ERROR;
×
7191
            goto end;
×
7192
        }
7193
        status = write_png_from_sixel(png_temp_path, png_final_path);
9✔
7194
        if (SIXEL_FAILED(status)) {
9!
7195
            goto end;
×
7196
        }
7197
    }
3✔
7198

7199
    if (encoder->clipboard_output_active
421!
7200
            && encoder->clipboard_output_path != NULL) {
142!
7201
        unsigned char *clipboard_output_data;
7202
        size_t clipboard_output_size;
7203

7204
        clipboard_output_data = NULL;
1✔
7205
        clipboard_output_size = 0u;
1✔
7206

7207
        if (encoder->outfd
2!
7208
                && encoder->outfd != STDOUT_FILENO
1!
7209
                && encoder->outfd != STDERR_FILENO) {
1!
7210
            (void)sixel_compat_close(encoder->outfd);
1✔
7211
            encoder->outfd = STDOUT_FILENO;
1✔
7212
        }
1✔
7213

7214
        clipboard_status = clipboard_read_file(
1✔
7215
            encoder->clipboard_output_path,
1✔
7216
            &clipboard_output_data,
7217
            &clipboard_output_size);
7218
        if (SIXEL_SUCCEEDED(clipboard_status)) {
1!
7219
            clipboard_status = sixel_clipboard_write(
1✔
7220
                encoder->clipboard_output_format,
1✔
7221
                clipboard_output_data,
1✔
7222
                clipboard_output_size);
1✔
7223
        }
1✔
7224
        if (clipboard_output_data != NULL) {
1!
7225
            free(clipboard_output_data);
1✔
7226
        }
1✔
7227
        if (SIXEL_FAILED(clipboard_status)) {
1!
7228
            status = clipboard_status;
×
7229
            goto end;
×
7230
        }
7231
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
1✔
7232
        sixel_allocator_free(encoder->allocator,
2✔
7233
                             encoder->clipboard_output_path);
1✔
7234
        encoder->clipboard_output_path = NULL;
1✔
7235
        encoder->sixel_output_path = NULL;
1✔
7236
        encoder->clipboard_output_active = 0;
1✔
7237
        encoder->clipboard_output_format[0] = '\0';
1✔
7238
    }
1✔
7239

7240
    /* the status may not be SIXEL_OK */
7241

7242
end:
280✔
7243
    if (png_temp_path != NULL) {
437✔
7244
        (void)sixel_compat_unlink(png_temp_path);
9✔
7245
    }
3✔
7246
    sixel_allocator_free(encoder->allocator, png_temp_path);
437✔
7247
    if (clipboard_input_path != NULL) {
437!
7248
        (void)sixel_compat_unlink(clipboard_input_path);
1✔
7249
        sixel_allocator_free(encoder->allocator, clipboard_input_path);
1✔
7250
    }
1✔
7251
    if (clipboard_blob != NULL) {
437!
7252
        free(clipboard_blob);
×
7253
    }
7254
    if (encoder->clipboard_output_path != NULL) {
437!
7255
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
7256
        sixel_allocator_free(encoder->allocator,
×
7257
                             encoder->clipboard_output_path);
×
7258
        encoder->clipboard_output_path = NULL;
×
7259
        encoder->sixel_output_path = NULL;
×
7260
        encoder->clipboard_output_active = 0;
×
7261
        encoder->clipboard_output_format[0] = '\0';
×
7262
    }
7263
    sixel_allocator_free(encoder->allocator, encoder->png_output_path);
437✔
7264
    encoder->png_output_path = NULL;
437✔
7265
    if (assessment_forward_stream != NULL) {
437!
7266
        (void) fclose(assessment_forward_stream);
×
7267
    }
7268
    if (assessment_temp_path != NULL &&
437!
7269
            assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
7270
        (void)sixel_compat_unlink(assessment_temp_path);
×
7271
    }
7272
    sixel_allocator_free(encoder->allocator, assessment_temp_path);
437✔
7273
    sixel_allocator_free(encoder->allocator, assessment_forward_path);
437✔
7274
    if (assessment_json_owned && assessment_json_file != NULL) {
437!
7275
        (void) fclose(assessment_json_file);
3✔
7276
    }
1✔
7277
    if (assessment_target_frame != NULL) {
437!
7278
        sixel_frame_unref(assessment_target_frame);
×
7279
    }
7280
    if (assessment_expanded_frame != NULL) {
437!
7281
        sixel_frame_unref(assessment_expanded_frame);
×
7282
    }
7283
    if (assessment_source_frame != NULL) {
437✔
7284
        sixel_frame_unref(assessment_source_frame);
3✔
7285
    }
1✔
7286
    if (encoder->assessment_observer != NULL) {
437✔
7287
        sixel_assessment_unref(encoder->assessment_observer);
3✔
7288
        encoder->assessment_observer = NULL;
3✔
7289
    }
1✔
7290
    if (assessment_allocator != NULL) {
437✔
7291
        sixel_allocator_unref(assessment_allocator);
3✔
7292
    }
1✔
7293

7294
    sixel_encoder_unref(encoder);
437✔
7295

7296
    if (encode_allocator != NULL) {
437!
7297
        /*
7298
         * Release the retained allocator reference *after* dropping the
7299
         * encoder reference so that a lazily created encoder can run its
7300
         * destructor while the allocator is still alive.  This ensures that
7301
         * cleanup routines never dereference a freed allocator instance.
7302
         */
7303
        sixel_allocator_unref(encode_allocator);
436✔
7304
        encode_allocator = NULL;
436✔
7305
    }
146✔
7306

7307
    return status;
437✔
7308
}
7309

7310

7311
/* encode specified pixel data to SIXEL format
7312
 * output to encoder->outfd */
7313
SIXELAPI SIXELSTATUS
7314
sixel_encoder_encode_bytes(
×
7315
    sixel_encoder_t     /* in */    *encoder,
7316
    unsigned char       /* in */    *bytes,
7317
    int                 /* in */    width,
7318
    int                 /* in */    height,
7319
    int                 /* in */    pixelformat,
7320
    unsigned char       /* in */    *palette,
7321
    int                 /* in */    ncolors)
7322
{
7323
    SIXELSTATUS status = SIXEL_FALSE;
×
7324
    sixel_frame_t *frame = NULL;
×
7325

7326
    if (encoder == NULL || bytes == NULL) {
×
7327
        status = SIXEL_BAD_ARGUMENT;
×
7328
        goto end;
×
7329
    }
7330

7331
    status = sixel_frame_new(&frame, encoder->allocator);
×
7332
    if (SIXEL_FAILED(status)) {
×
7333
        goto end;
×
7334
    }
7335

7336
    status = sixel_frame_init(frame, bytes, width, height,
×
7337
                              pixelformat, palette, ncolors);
7338
    if (SIXEL_FAILED(status)) {
×
7339
        goto end;
×
7340
    }
7341

7342
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
×
7343
    if (SIXEL_FAILED(status)) {
×
7344
        goto end;
×
7345
    }
7346

7347
    status = SIXEL_OK;
×
7348

7349
end:
7350
    /* we need to free the frame before exiting, but we can't use the
7351
       sixel_frame_destroy function, because that will also attempt to
7352
       free the pixels and palette, which we don't own */
7353
    if (frame != NULL && encoder->allocator != NULL) {
×
7354
        sixel_allocator_free(encoder->allocator, frame);
×
7355
        sixel_allocator_unref(encoder->allocator);
×
7356
    }
7357
    return status;
×
7358
}
7359

7360

7361
/*
7362
 * Toggle source-frame capture for assessment consumers.
7363
 */
7364
SIXELAPI SIXELSTATUS
7365
sixel_encoder_enable_source_capture(
3✔
7366
    sixel_encoder_t *encoder,
7367
    int enable)
7368
{
7369
    if (encoder == NULL) {
3!
7370
        sixel_helper_set_additional_message(
×
7371
            "sixel_encoder_enable_source_capture: encoder is null.");
7372
        return SIXEL_BAD_ARGUMENT;
×
7373
    }
7374

7375
    encoder->capture_source = enable ? 1 : 0;
3✔
7376
    if (!encoder->capture_source && encoder->capture_source_frame != NULL) {
3!
7377
        sixel_frame_unref(encoder->capture_source_frame);
×
7378
        encoder->capture_source_frame = NULL;
×
7379
    }
7380

7381
    return SIXEL_OK;
3✔
7382
}
1✔
7383

7384

7385
/*
7386
 * Enable or disable the quantized-frame capture facility.
7387
 *
7388
 *     capture on --> encoder keeps the latest palette-quantized frame.
7389
 *     capture off --> encoder forgets previously stored frames.
7390
 */
7391
SIXELAPI SIXELSTATUS
7392
sixel_encoder_enable_quantized_capture(
3✔
7393
    sixel_encoder_t *encoder,
7394
    int enable)
7395
{
7396
    if (encoder == NULL) {
3!
7397
        sixel_helper_set_additional_message(
×
7398
            "sixel_encoder_enable_quantized_capture: encoder is null.");
7399
        return SIXEL_BAD_ARGUMENT;
×
7400
    }
7401

7402
    encoder->capture_quantized = enable ? 1 : 0;
3✔
7403
    if (!encoder->capture_quantized) {
3!
7404
        encoder->capture_valid = 0;
×
7405
    }
7406

7407
    return SIXEL_OK;
3✔
7408
}
1✔
7409

7410

7411
/*
7412
 * Materialize the captured quantized frame as a heap-allocated
7413
 * sixel_frame_t instance for assessment consumers.
7414
 */
7415
SIXELAPI SIXELSTATUS
7416
sixel_encoder_copy_quantized_frame(
×
7417
    sixel_encoder_t   *encoder,
7418
    sixel_allocator_t *allocator,
7419
    sixel_frame_t     **ppframe)
7420
{
7421
    SIXELSTATUS status = SIXEL_FALSE;
×
7422
    sixel_frame_t *frame;
7423
    unsigned char *pixels;
7424
    unsigned char *palette;
7425
    size_t palette_bytes;
7426

7427
    if (encoder == NULL || allocator == NULL || ppframe == NULL) {
×
7428
        sixel_helper_set_additional_message(
×
7429
            "sixel_encoder_copy_quantized_frame: invalid argument.");
7430
        return SIXEL_BAD_ARGUMENT;
×
7431
    }
7432

7433
    if (!encoder->capture_quantized || !encoder->capture_valid) {
×
7434
        sixel_helper_set_additional_message(
×
7435
            "sixel_encoder_copy_quantized_frame: no frame captured.");
7436
        return SIXEL_RUNTIME_ERROR;
×
7437
    }
7438

7439
    *ppframe = NULL;
×
7440
    frame = NULL;
×
7441
    pixels = NULL;
×
7442
    palette = NULL;
×
7443

7444
    status = sixel_frame_new(&frame, allocator);
×
7445
    if (SIXEL_FAILED(status)) {
×
7446
        return status;
×
7447
    }
7448

7449
    if (encoder->capture_pixel_bytes > 0) {
×
7450
        pixels = (unsigned char *)sixel_allocator_malloc(
×
7451
            allocator, encoder->capture_pixel_bytes);
7452
        if (pixels == NULL) {
×
7453
            sixel_helper_set_additional_message(
×
7454
                "sixel_encoder_copy_quantized_frame: "
7455
                "sixel_allocator_malloc() failed.");
7456
            status = SIXEL_BAD_ALLOCATION;
×
7457
            goto cleanup;
×
7458
        }
7459
        memcpy(pixels,
×
7460
               encoder->capture_pixels,
7461
               encoder->capture_pixel_bytes);
7462
    }
7463

7464
    palette_bytes = encoder->capture_palette_size;
×
7465
    if (palette_bytes > 0) {
×
7466
        palette = (unsigned char *)sixel_allocator_malloc(allocator,
×
7467
                                                          palette_bytes);
7468
        if (palette == NULL) {
×
7469
            sixel_helper_set_additional_message(
×
7470
                "sixel_encoder_copy_quantized_frame: "
7471
                "sixel_allocator_malloc() failed.");
7472
            status = SIXEL_BAD_ALLOCATION;
×
7473
            goto cleanup;
×
7474
        }
7475
        memcpy(palette,
×
7476
               encoder->capture_palette,
7477
               palette_bytes);
7478
    }
7479

7480
    status = sixel_frame_init(frame,
×
7481
                              pixels,
7482
                              encoder->capture_width,
7483
                              encoder->capture_height,
7484
                              encoder->capture_pixelformat,
7485
                              palette,
7486
                              encoder->capture_ncolors);
7487
    if (SIXEL_FAILED(status)) {
×
7488
        goto cleanup;
×
7489
    }
7490

7491
    pixels = NULL;
×
7492
    palette = NULL;
×
7493
    /*
7494
     * Capture colorspace must be preserved for assessment consumers.
7495
     * Keep access encapsulated via the public setter to avoid
7496
     * depending on frame internals.
7497
     */
7498
    sixel_frame_set_colorspace(frame, encoder->capture_colorspace);
×
7499
    *ppframe = frame;
×
7500
    return SIXEL_OK;
×
7501

7502
cleanup:
7503
    if (palette != NULL) {
×
7504
        sixel_allocator_free(allocator, palette);
×
7505
    }
7506
    if (pixels != NULL) {
×
7507
        sixel_allocator_free(allocator, pixels);
×
7508
    }
7509
    if (frame != NULL) {
×
7510
        sixel_frame_unref(frame);
×
7511
    }
7512
    return status;
×
7513
}
7514

7515

7516
/*
7517
 * Emit the captured palette in the requested format.
7518
 *
7519
 *   palette_output == NULL  -> skip
7520
 *   palette_output != NULL  -> materialize captured palette
7521
 */
7522
static SIXELSTATUS
7523
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder)
421✔
7524
{
7525
    SIXELSTATUS status;
7526
    sixel_frame_t *frame;
7527
    unsigned char const *palette;
7528
    int exported_colors;
7529
    FILE *stream;
7530
    int close_stream;
7531
    char const *path;
7532
    sixel_palette_format_t format_hint;
7533
    sixel_palette_format_t format_ext;
7534
    sixel_palette_format_t format_final;
7535
    char const *mode;
7536

7537
    status = SIXEL_OK;
421✔
7538
    frame = NULL;
421✔
7539
    palette = NULL;
421✔
7540
    exported_colors = 0;
421✔
7541
    stream = NULL;
421✔
7542
    close_stream = 0;
421✔
7543
    path = NULL;
421✔
7544
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
421✔
7545
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
421✔
7546
    format_final = SIXEL_PALETTE_FORMAT_NONE;
421✔
7547
    mode = "wb";
421✔
7548

7549
    if (encoder == NULL || encoder->palette_output == NULL) {
421!
7550
        return SIXEL_OK;
421✔
7551
    }
7552

7553
    status = sixel_encoder_copy_quantized_frame(encoder,
×
7554
                                                encoder->allocator,
7555
                                                &frame);
7556
    if (SIXEL_FAILED(status)) {
×
7557
        return status;
×
7558
    }
7559

7560
    palette = (unsigned char const *)sixel_frame_get_palette(frame);
×
7561
    exported_colors = sixel_frame_get_ncolors(frame);
×
7562
    if (palette == NULL || exported_colors <= 0) {
×
7563
        sixel_helper_set_additional_message(
×
7564
            "sixel_encoder_emit_palette_output: palette unavailable.");
7565
        status = SIXEL_BAD_INPUT;
×
7566
        goto cleanup;
×
7567
    }
7568
    if (exported_colors > 256) {
×
7569
        exported_colors = 256;
×
7570
    }
7571

7572
    path = sixel_palette_strip_prefix(encoder->palette_output, &format_hint);
×
7573
    if (path == NULL || *path == '\0') {
×
7574
        sixel_helper_set_additional_message(
×
7575
            "sixel_encoder_emit_palette_output: invalid path.");
7576
        status = SIXEL_BAD_ARGUMENT;
×
7577
        goto cleanup;
×
7578
    }
7579

7580
    format_ext = sixel_palette_format_from_extension(path);
×
7581
    format_final = format_hint;
×
7582
    if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
7583
        if (format_ext == SIXEL_PALETTE_FORMAT_NONE) {
×
7584
            if (strcmp(path, "-") == 0) {
×
7585
                sixel_helper_set_additional_message(
×
7586
                    "sixel_encoder_emit_palette_output: "
7587
                    "format required for '-'.");
7588
                status = SIXEL_BAD_ARGUMENT;
×
7589
                goto cleanup;
×
7590
            }
7591
            sixel_helper_set_additional_message(
×
7592
                "sixel_encoder_emit_palette_output: "
7593
                "unknown palette file extension.");
7594
            status = SIXEL_BAD_ARGUMENT;
×
7595
            goto cleanup;
×
7596
        }
7597
        format_final = format_ext;
×
7598
    }
7599
    if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
7600
        format_final = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
7601
    }
7602

7603
    if (strcmp(path, "-") == 0) {
×
7604
        stream = stdout;
×
7605
    } else {
7606
        if (format_final == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
7607
                format_final == SIXEL_PALETTE_FORMAT_GPL) {
7608
            mode = "w";
×
7609
        } else {
7610
            mode = "wb";
×
7611
        }
7612
        stream = fopen(path, mode);
×
7613
        if (stream == NULL) {
×
7614
            sixel_helper_set_additional_message(
×
7615
                "sixel_encoder_emit_palette_output: failed to open file.");
7616
            status = SIXEL_LIBC_ERROR;
×
7617
            goto cleanup;
×
7618
        }
7619
        close_stream = 1;
×
7620
    }
7621

7622
    switch (format_final) {
×
7623
    case SIXEL_PALETTE_FORMAT_ACT:
7624
        status = sixel_palette_write_act(stream, palette, exported_colors);
×
7625
        if (SIXEL_FAILED(status)) {
×
7626
            sixel_helper_set_additional_message(
×
7627
                "sixel_encoder_emit_palette_output: failed to write ACT.");
7628
        }
7629
        break;
×
7630
    case SIXEL_PALETTE_FORMAT_PAL_JASC:
7631
        status = sixel_palette_write_pal_jasc(stream,
×
7632
                                              palette,
7633
                                              exported_colors);
7634
        if (SIXEL_FAILED(status)) {
×
7635
            sixel_helper_set_additional_message(
×
7636
                "sixel_encoder_emit_palette_output: failed to write JASC.");
7637
        }
7638
        break;
×
7639
    case SIXEL_PALETTE_FORMAT_PAL_RIFF:
7640
        status = sixel_palette_write_pal_riff(stream,
×
7641
                                              palette,
7642
                                              exported_colors);
7643
        if (SIXEL_FAILED(status)) {
×
7644
            sixel_helper_set_additional_message(
×
7645
                "sixel_encoder_emit_palette_output: failed to write RIFF.");
7646
        }
7647
        break;
×
7648
    case SIXEL_PALETTE_FORMAT_GPL:
7649
        status = sixel_palette_write_gpl(stream,
×
7650
                                         palette,
7651
                                         exported_colors);
7652
        if (SIXEL_FAILED(status)) {
×
7653
            sixel_helper_set_additional_message(
×
7654
                "sixel_encoder_emit_palette_output: failed to write GPL.");
7655
        }
7656
        break;
×
7657
    default:
7658
        sixel_helper_set_additional_message(
×
7659
            "sixel_encoder_emit_palette_output: unsupported format.");
7660
        status = SIXEL_BAD_ARGUMENT;
×
7661
        break;
×
7662
    }
7663
    if (SIXEL_FAILED(status)) {
×
7664
        goto cleanup;
×
7665
    }
7666

7667
    if (close_stream) {
×
7668
        if (fclose(stream) != 0) {
×
7669
            sixel_helper_set_additional_message(
×
7670
                "sixel_encoder_emit_palette_output: fclose() failed.");
7671
            status = SIXEL_LIBC_ERROR;
×
7672
            stream = NULL;
×
7673
            goto cleanup;
×
7674
        }
7675
        stream = NULL;
×
7676
    } else {
7677
        if (fflush(stream) != 0) {
×
7678
            sixel_helper_set_additional_message(
×
7679
                "sixel_encoder_emit_palette_output: fflush() failed.");
7680
            status = SIXEL_LIBC_ERROR;
×
7681
            goto cleanup;
×
7682
        }
7683
    }
7684

7685
cleanup:
7686
    if (close_stream && stream != NULL) {
×
7687
        (void) fclose(stream);
×
7688
    }
7689
    if (frame != NULL) {
×
7690
        sixel_frame_unref(frame);
×
7691
    }
7692

7693
    return status;
×
7694
}
141✔
7695

7696

7697
/*
7698
 * Share the captured source frame with assessment consumers.
7699
 */
7700
SIXELAPI SIXELSTATUS
7701
sixel_encoder_copy_source_frame(
3✔
7702
    sixel_encoder_t *encoder,
7703
    sixel_frame_t  **ppframe)
7704
{
7705
    if (encoder == NULL || ppframe == NULL) {
3!
7706
        sixel_helper_set_additional_message(
×
7707
            "sixel_encoder_copy_source_frame: invalid argument.");
7708
        return SIXEL_BAD_ARGUMENT;
×
7709
    }
7710

7711
    if (!encoder->capture_source || encoder->capture_source_frame == NULL) {
3!
7712
        sixel_helper_set_additional_message(
×
7713
            "sixel_encoder_copy_source_frame: no frame captured.");
7714
        return SIXEL_RUNTIME_ERROR;
×
7715
    }
7716

7717
    sixel_frame_ref(encoder->capture_source_frame);
3✔
7718
    *ppframe = encoder->capture_source_frame;
3✔
7719

7720
    return SIXEL_OK;
3✔
7721
}
1✔
7722

7723

7724
#if HAVE_TESTS
7725
static int
7726
test1(void)
×
7727
{
7728
    int nret = EXIT_FAILURE;
×
7729
    sixel_encoder_t *encoder = NULL;
×
7730

7731
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7732
#  pragma GCC diagnostic push
7733
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7734
#endif
7735
    encoder = sixel_encoder_create();
×
7736
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7737
#  pragma GCC diagnostic pop
7738
#endif
7739
    if (encoder == NULL) {
×
7740
        goto error;
×
7741
    }
7742
    sixel_encoder_ref(encoder);
×
7743
    sixel_encoder_unref(encoder);
×
7744
    nret = EXIT_SUCCESS;
×
7745

7746
error:
7747
    sixel_encoder_unref(encoder);
×
7748
    return nret;
×
7749
}
7750

7751

7752
static int
7753
test2(void)
×
7754
{
7755
    int nret = EXIT_FAILURE;
×
7756
    SIXELSTATUS status;
7757
    sixel_encoder_t *encoder = NULL;
×
7758
    sixel_frame_t *frame = NULL;
×
7759
    unsigned char *buffer;
7760
    int height = 0;
×
7761
    int is_animation = 0;
×
7762

7763
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7764
#  pragma GCC diagnostic push
7765
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7766
#endif
7767
    encoder = sixel_encoder_create();
×
7768
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7769
#  pragma GCC diagnostic pop
7770
#endif
7771
    if (encoder == NULL) {
×
7772
        goto error;
×
7773
    }
7774

7775
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7776
#  pragma GCC diagnostic push
7777
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7778
#endif
7779
    frame = sixel_frame_create();
×
7780
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7781
#  pragma GCC diagnostic pop
7782
#endif
7783
    if (encoder == NULL) {
×
7784
        goto error;
×
7785
    }
7786

7787
    buffer = (unsigned char *)sixel_allocator_malloc(encoder->allocator, 3);
×
7788
    if (buffer == NULL) {
×
7789
        goto error;
×
7790
    }
7791
    status = sixel_frame_init(frame, buffer, 1, 1,
×
7792
                              SIXEL_PIXELFORMAT_RGB888,
7793
                              NULL, 0);
7794
    if (SIXEL_FAILED(status)) {
×
7795
        goto error;
×
7796
    }
7797

7798
    if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
×
7799
        is_animation = 1;
×
7800
    }
7801

7802
    height = sixel_frame_get_height(frame);
×
7803

7804
    status = sixel_tty_scroll(sixel_write_callback,
×
7805
                              &encoder->outfd,
×
7806
                              encoder->outfd,
7807
                              height,
7808
                              is_animation);
7809
    if (SIXEL_FAILED(status)) {
×
7810
        goto error;
×
7811
    }
7812

7813
    nret = EXIT_SUCCESS;
×
7814

7815
error:
7816
    sixel_encoder_unref(encoder);
×
7817
    sixel_frame_unref(frame);
×
7818
    return nret;
×
7819
}
7820

7821

7822
static int
7823
test3(void)
×
7824
{
7825
    int nret = EXIT_FAILURE;
×
7826
    int result;
7827

7828
    result = sixel_tty_wait_stdin(1000);
×
7829
    if (result != 0) {
×
7830
        goto error;
×
7831
    }
7832

7833
    nret = EXIT_SUCCESS;
×
7834

7835
error:
7836
    return nret;
×
7837
}
7838

7839

7840
static int
7841
test4(void)
×
7842
{
7843
    int nret = EXIT_FAILURE;
×
7844
    sixel_encoder_t *encoder = NULL;
×
7845
    SIXELSTATUS status;
7846

7847
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7848
# pragma GCC diagnostic push
7849
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7850
#endif
7851
    encoder = sixel_encoder_create();
×
7852
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7853
# pragma GCC diagnostic pop
7854
#endif
7855
    if (encoder == NULL) {
×
7856
        goto error;
×
7857
    }
7858

7859
    status = sixel_encoder_setopt(encoder,
×
7860
                                  SIXEL_OPTFLAG_LOOPMODE,
7861
                                  "force");
7862
    if (SIXEL_FAILED(status)) {
×
7863
        goto error;
×
7864
    }
7865

7866
    status = sixel_encoder_setopt(encoder,
×
7867
                                  SIXEL_OPTFLAG_PIPE_MODE,
7868
                                  "force");
7869
    if (SIXEL_FAILED(status)) {
×
7870
        goto error;
×
7871
    }
7872

7873
    nret = EXIT_SUCCESS;
×
7874

7875
error:
7876
    sixel_encoder_unref(encoder);
×
7877
    return nret;
×
7878
}
7879

7880

7881
static int
7882
test5(void)
×
7883
{
7884
    int nret = EXIT_FAILURE;
×
7885
    sixel_encoder_t *encoder = NULL;
×
7886
    sixel_allocator_t *allocator = NULL;
×
7887
    SIXELSTATUS status;
7888

7889
    status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
7890
    if (SIXEL_FAILED(status)) {
×
7891
        goto error;
×
7892
    }
7893

7894
    status = sixel_encoder_new(&encoder, allocator);
×
7895
    if (SIXEL_FAILED(status)) {
×
7896
        goto error;
×
7897
    }
7898

7899
    sixel_encoder_ref(encoder);
×
7900
    sixel_encoder_unref(encoder);
×
7901
    nret = EXIT_SUCCESS;
×
7902

7903
error:
7904
    sixel_encoder_unref(encoder);
×
7905
    return nret;
×
7906
}
7907

7908

7909
SIXELAPI int
7910
sixel_encoder_tests_main(void)
×
7911
{
7912
    int nret = EXIT_FAILURE;
×
7913
    size_t i;
7914
    typedef int (* testcase)(void);
7915

7916
    static testcase const testcases[] = {
7917
        test1,
7918
        test2,
7919
        test3,
7920
        test4,
7921
        test5
7922
    };
7923

7924
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
7925
        nret = testcases[i]();
×
7926
        if (nret != EXIT_SUCCESS) {
×
7927
            goto error;
×
7928
        }
7929
    }
7930

7931
    nret = EXIT_SUCCESS;
×
7932

7933
error:
7934
    return nret;
×
7935
}
7936
#endif  /* HAVE_TESTS */
7937

7938

7939
/* emacs Local Variables:      */
7940
/* emacs mode: c               */
7941
/* emacs tab-width: 4          */
7942
/* emacs indent-tabs-mode: nil */
7943
/* emacs c-basic-offset: 4     */
7944
/* emacs End:                  */
7945
/* vim: set expandtab ts=4 : */
7946
/* 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