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

saitoha / libsixel / 19383595875

15 Nov 2025 03:16AM UTC coverage: 43.821% (+0.03%) from 43.793%
19383595875

push

github

saitoha
cli: refactor option matching into shared helpers

8455 of 27720 branches covered (30.5%)

478 of 602 new or added lines in 3 files covered. (79.4%)

2 existing lines in 1 file now uncovered.

11723 of 26752 relevant lines covered (43.82%)

1099181.4 hits per line

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

49.7
/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 HAVE_SYS_TIME_H
86
# include <sys/time.h>
87
#elif HAVE_TIME_H
88
# include <time.h>
89
#endif  /* HAVE_SYS_TIME_H HAVE_TIME_H */
90
#if HAVE_SYS_IOCTL_H
91
# include <sys/ioctl.h>
92
#endif  /* HAVE_SYS_IOCTL_H */
93
#if HAVE_FCNTL_H
94
# include <fcntl.h>
95
#endif  /* HAVE_FCNTL_H */
96
#if HAVE_ERRNO_H
97
# include <errno.h>
98
#endif  /* HAVE_ERRNO_H */
99
#if HAVE_CTYPE_H
100
# include <ctype.h>
101
#endif  /* HAVE_CTYPE_H */
102
#if HAVE_LIMITS_H
103
# include <limits.h>
104
#endif  /* HAVE_LIMITS_H */
105

106
#include <sixel.h>
107
#include "loader.h"
108
#include "assessment.h"
109
#include "tty.h"
110
#include "encoder.h"
111
#include "output.h"
112
#include "options.h"
113
#include "dither.h"
114
#include "frame.h"
115
#include "rgblookup.h"
116
#include "clipboard.h"
117
#include "compat_stub.h"
118

119
static void clipboard_select_format(char *dest,
120
                                    size_t dest_size,
121
                                    char const *format,
122
                                    char const *fallback);
123
static SIXELSTATUS clipboard_create_spool(sixel_allocator_t *allocator,
124
                                          char const *prefix,
125
                                          char **path_out,
126
                                          int *fd_out);
127
static SIXELSTATUS clipboard_write_file(char const *path,
128
                                        unsigned char const *data,
129
                                        size_t size);
130
static SIXELSTATUS clipboard_read_file(char const *path,
131
                                       unsigned char **data,
132
                                       size_t *size);
133

134
#if defined(_WIN32)
135

136
# include <windows.h>
137
# if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
138
#  include <io.h>
139
# endif
140
# if defined(_MSC_VER)
141
#   include <time.h>
142
# endif
143

144
# if defined(CLOCKS_PER_SEC)
145
#  undef CLOCKS_PER_SEC
146
# endif
147
# define CLOCKS_PER_SEC 1000
148

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

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

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

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

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

182
    (void) WaitForSingleObject(timer, INFINITE);
183
    (void) CloseHandle(timer);
184

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

191
    return (0);
192
}
193
# endif  /* HAVE_NANOSLEEP */
194

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

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

213
#endif /* _WIN32 */
214

215

216
static sixel_option_choice_t const g_option_choices_builtin_palette[] = {
217
    { "xterm16", SIXEL_BUILTIN_XTERM16 },
218
    { "xterm256", SIXEL_BUILTIN_XTERM256 },
219
    { "vt340mono", SIXEL_BUILTIN_VT340_MONO },
220
    { "vt340color", SIXEL_BUILTIN_VT340_COLOR },
221
    { "gray1", SIXEL_BUILTIN_G1 },
222
    { "gray2", SIXEL_BUILTIN_G2 },
223
    { "gray4", SIXEL_BUILTIN_G4 },
224
    { "gray8", SIXEL_BUILTIN_G8 }
225
};
226

227
static sixel_option_choice_t const g_option_choices_diffusion[] = {
228
    { "auto", SIXEL_DIFFUSE_AUTO },
229
    { "none", SIXEL_DIFFUSE_NONE },
230
    { "fs", SIXEL_DIFFUSE_FS },
231
    { "atkinson", SIXEL_DIFFUSE_ATKINSON },
232
    { "jajuni", SIXEL_DIFFUSE_JAJUNI },
233
    { "stucki", SIXEL_DIFFUSE_STUCKI },
234
    { "burkes", SIXEL_DIFFUSE_BURKES },
235
    { "sierra1", SIXEL_DIFFUSE_SIERRA1 },
236
    { "sierra2", SIXEL_DIFFUSE_SIERRA2 },
237
    { "sierra3", SIXEL_DIFFUSE_SIERRA3 },
238
    { "a_dither", SIXEL_DIFFUSE_A_DITHER },
239
    { "x_dither", SIXEL_DIFFUSE_X_DITHER },
240
    { "lso2", SIXEL_DIFFUSE_LSO2 },
241
};
242

243
static sixel_option_choice_t const g_option_choices_diffusion_scan[] = {
244
    { "auto", SIXEL_SCAN_AUTO },
245
    { "serpentine", SIXEL_SCAN_SERPENTINE },
246
    { "raster", SIXEL_SCAN_RASTER }
247
};
248

249
static sixel_option_choice_t const g_option_choices_diffusion_carry[] = {
250
    { "auto", SIXEL_CARRY_AUTO },
251
    { "direct", SIXEL_CARRY_DISABLE },
252
    { "carry", SIXEL_CARRY_ENABLE }
253
};
254

255
static sixel_option_choice_t const g_option_choices_find_largest[] = {
256
    { "auto", SIXEL_LARGE_AUTO },
257
    { "norm", SIXEL_LARGE_NORM },
258
    { "lum", SIXEL_LARGE_LUM }
259
};
260

261
static sixel_option_choice_t const g_option_choices_select_color[] = {
262
    { "auto", SIXEL_REP_AUTO },
263
    { "center", SIXEL_REP_CENTER_BOX },
264
    { "average", SIXEL_REP_AVERAGE_COLORS },
265
    { "histogram", SIXEL_REP_AVERAGE_PIXELS },
266
    { "histgram", SIXEL_REP_AVERAGE_PIXELS }
267
};
268

269
static sixel_option_choice_t const g_option_choices_quantize_model[] = {
270
    { "auto", SIXEL_QUANTIZE_MODEL_AUTO },
271
    { "heckbert", SIXEL_QUANTIZE_MODEL_MEDIANCUT },
272
    { "kmeans", SIXEL_QUANTIZE_MODEL_KMEANS }
273
};
274

275
static sixel_option_choice_t const g_option_choices_final_merge[] = {
276
    { "auto", SIXEL_FINAL_MERGE_AUTO },
277
    { "none", SIXEL_FINAL_MERGE_NONE },
278
    { "ward", SIXEL_FINAL_MERGE_WARD },
279
    { "hkmeans", SIXEL_FINAL_MERGE_HKMEANS }
280
};
281

282
static sixel_option_choice_t const g_option_choices_resampling[] = {
283
    { "nearest", SIXEL_RES_NEAREST },
284
    { "gaussian", SIXEL_RES_GAUSSIAN },
285
    { "hanning", SIXEL_RES_HANNING },
286
    { "hamming", SIXEL_RES_HAMMING },
287
    { "bilinear", SIXEL_RES_BILINEAR },
288
    { "welsh", SIXEL_RES_WELSH },
289
    { "bicubic", SIXEL_RES_BICUBIC },
290
    { "lanczos2", SIXEL_RES_LANCZOS2 },
291
    { "lanczos3", SIXEL_RES_LANCZOS3 },
292
    { "lanczos4", SIXEL_RES_LANCZOS4 }
293
};
294

295
static sixel_option_choice_t const g_option_choices_quality[] = {
296
    { "auto", SIXEL_QUALITY_AUTO },
297
    { "high", SIXEL_QUALITY_HIGH },
298
    { "low", SIXEL_QUALITY_LOW },
299
    { "full", SIXEL_QUALITY_FULL }
300
};
301

302
static sixel_option_choice_t const g_option_choices_loopmode[] = {
303
    { "auto", SIXEL_LOOP_AUTO },
304
    { "force", SIXEL_LOOP_FORCE },
305
    { "disable", SIXEL_LOOP_DISABLE }
306
};
307

308
static sixel_option_choice_t const g_option_choices_palette_type[] = {
309
    { "auto", SIXEL_PALETTETYPE_AUTO },
310
    { "hls", SIXEL_PALETTETYPE_HLS },
311
    { "rgb", SIXEL_PALETTETYPE_RGB }
312
};
313

314
static sixel_option_choice_t const g_option_choices_encode_policy[] = {
315
    { "auto", SIXEL_ENCODEPOLICY_AUTO },
316
    { "fast", SIXEL_ENCODEPOLICY_FAST },
317
    { "size", SIXEL_ENCODEPOLICY_SIZE }
318
};
319

320
static sixel_option_choice_t const g_option_choices_lut_policy[] = {
321
    { "auto", SIXEL_LUT_POLICY_AUTO },
322
    { "5bit", SIXEL_LUT_POLICY_5BIT },
323
    { "6bit", SIXEL_LUT_POLICY_6BIT },
324
    { "none", SIXEL_LUT_POLICY_NONE },
325
    { "certlut", SIXEL_LUT_POLICY_CERTLUT }
326
};
327

328
static sixel_option_choice_t const g_option_choices_working_colorspace[] = {
329
    { "gamma", SIXEL_COLORSPACE_GAMMA },
330
    { "linear", SIXEL_COLORSPACE_LINEAR },
331
    { "oklab", SIXEL_COLORSPACE_OKLAB }
332
};
333

334
static sixel_option_choice_t const g_option_choices_output_colorspace[] = {
335
    { "gamma", SIXEL_COLORSPACE_GAMMA },
336
    { "linear", SIXEL_COLORSPACE_LINEAR },
337
    { "smpte-c", SIXEL_COLORSPACE_SMPTEC },
338
    { "smptec", SIXEL_COLORSPACE_SMPTEC }
339
};
340

341

342
static char *
343
arg_strdup(
57✔
344
    char const          /* in */ *s,          /* source buffer */
345
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
346
                                                 destination buffer */
347
{
348
    char *p;
349
    size_t len;
350

351
    len = strlen(s);
57✔
352

353
    p = (char *)sixel_allocator_malloc(allocator, len + 1);
57✔
354
    if (p) {
57!
355
        (void)sixel_compat_strcpy(p, len + 1, s);
57✔
356
    }
19✔
357
    return p;
57✔
358
}
359

360

361
/* An clone function of XColorSpec() of xlib */
362
static SIXELSTATUS
363
sixel_parse_x_colorspec(
45✔
364
    unsigned char       /* out */ **bgcolor,     /* destination buffer */
365
    char const          /* in */  *s,            /* source buffer */
366
    sixel_allocator_t   /* in */  *allocator)    /* allocator object for
367
                                                    destination buffer */
368
{
369
    SIXELSTATUS status = SIXEL_FALSE;
45✔
370
    char *p;
371
    unsigned char components[3];
372
    int component_index = 0;
45✔
373
    unsigned long v;
374
    char *endptr;
375
    char *buf = NULL;
45✔
376
    struct color const *pcolor;
377

378
    /* from rgb_lookup.h generated by gpref */
379
    pcolor = lookup_rgb(s, strlen(s));
45✔
380
    if (pcolor) {
45✔
381
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
3✔
382
        if (*bgcolor == NULL) {
3!
383
            sixel_helper_set_additional_message(
×
384
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
385
            status = SIXEL_BAD_ALLOCATION;
×
386
            goto end;
×
387
        }
388
        (*bgcolor)[0] = pcolor->r;
3✔
389
        (*bgcolor)[1] = pcolor->g;
3✔
390
        (*bgcolor)[2] = pcolor->b;
3✔
391
    } else if (s[0] == 'r' && s[1] == 'g' && s[2] == 'b' && s[3] == ':') {
43!
392
        p = buf = arg_strdup(s + 4, allocator);
6✔
393
        if (buf == NULL) {
6!
394
            sixel_helper_set_additional_message(
×
395
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
396
            status = SIXEL_BAD_ALLOCATION;
×
397
            goto end;
×
398
        }
399
        while (*p) {
15!
400
            v = 0;
15✔
401
            for (endptr = p; endptr - p <= 12; ++endptr) {
36!
402
                if (*endptr >= '0' && *endptr <= '9') {
36✔
403
                    v = (v << 4) | (unsigned long)(*endptr - '0');
15✔
404
                } else if (*endptr >= 'a' && *endptr <= 'f') {
26!
405
                    v = (v << 4) | (unsigned long)(*endptr - 'a' + 10);
3✔
406
                } else if (*endptr >= 'A' && *endptr <= 'F') {
19!
407
                    v = (v << 4) | (unsigned long)(*endptr - 'A' + 10);
3✔
408
                } else {
1✔
409
                    break;
5✔
410
                }
411
            }
7✔
412
            if (endptr - p == 0) {
15!
413
                break;
×
414
            }
415
            if (endptr - p > 4) {
15!
416
                break;
×
417
            }
418
            v = v << ((4 - (endptr - p)) * 4) >> 8;
15✔
419
            components[component_index++] = (unsigned char)v;
15✔
420
            p = endptr;
15✔
421
            if (component_index == 3) {
15✔
422
                break;
3✔
423
            }
424
            if (*p == '\0') {
12✔
425
                break;
3✔
426
            }
427
            if (*p != '/') {
9!
428
                break;
×
429
            }
430
            ++p;
9✔
431
        }
432
        if (component_index != 3 || *p != '\0' || *p == '/') {
6!
433
            status = SIXEL_BAD_ARGUMENT;
3✔
434
            goto end;
3✔
435
        }
436
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
3✔
437
        if (*bgcolor == NULL) {
3!
438
            sixel_helper_set_additional_message(
×
439
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
440
            status = SIXEL_BAD_ALLOCATION;
×
441
            goto end;
×
442
        }
443
        (*bgcolor)[0] = components[0];
3✔
444
        (*bgcolor)[1] = components[1];
3✔
445
        (*bgcolor)[2] = components[2];
3✔
446
    } else if (*s == '#') {
37✔
447
        buf = arg_strdup(s + 1, allocator);
27✔
448
        if (buf == NULL) {
27!
449
            sixel_helper_set_additional_message(
×
450
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
451
            status = SIXEL_BAD_ALLOCATION;
×
452
            goto end;
×
453
        }
454
        for (p = endptr = buf; endptr - p <= 12; ++endptr) {
192✔
455
            if (*endptr >= '0' && *endptr <= '9') {
189✔
456
                *endptr -= '0';
99✔
457
            } else if (*endptr >= 'a' && *endptr <= 'f') {
123!
458
                *endptr -= 'a' - 10;
57✔
459
            } else if (*endptr >= 'A' && *endptr <= 'F') {
52✔
460
                *endptr -= 'A' - 10;
9✔
461
            } else if (*endptr == '\0') {
27✔
462
                break;
21✔
463
            } else {
464
                status = SIXEL_BAD_ARGUMENT;
3✔
465
                goto end;
3✔
466
            }
467
        }
55✔
468
        if (endptr - p > 12) {
24✔
469
            status = SIXEL_BAD_ARGUMENT;
3✔
470
            goto end;
3✔
471
        }
472
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
21✔
473
        if (*bgcolor == NULL) {
21!
474
            sixel_helper_set_additional_message(
×
475
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
476
            status = SIXEL_BAD_ALLOCATION;
×
477
            goto end;
×
478
        }
479
        switch (endptr - p) {
21✔
480
        case 3:
6✔
481
            (*bgcolor)[0] = (unsigned char)(p[0] << 4);
9✔
482
            (*bgcolor)[1] = (unsigned char)(p[1] << 4);
9✔
483
            (*bgcolor)[2] = (unsigned char)(p[2] << 4);
9✔
484
            break;
9✔
485
        case 6:
2✔
486
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
487
            (*bgcolor)[1] = (unsigned char)(p[2] << 4 | p[3]);
3✔
488
            (*bgcolor)[2] = (unsigned char)(p[4] << 4 | p[4]);
3✔
489
            break;
3✔
490
        case 9:
2✔
491
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
492
            (*bgcolor)[1] = (unsigned char)(p[3] << 4 | p[4]);
3✔
493
            (*bgcolor)[2] = (unsigned char)(p[6] << 4 | p[7]);
3✔
494
            break;
3✔
495
        case 12:
2✔
496
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
497
            (*bgcolor)[1] = (unsigned char)(p[4] << 4 | p[5]);
3✔
498
            (*bgcolor)[2] = (unsigned char)(p[8] << 4 | p[9]);
3✔
499
            break;
3✔
500
        default:
2✔
501
            status = SIXEL_BAD_ARGUMENT;
3✔
502
            goto end;
3✔
503
        }
504
    } else {
6✔
505
        status = SIXEL_BAD_ARGUMENT;
9✔
506
        goto end;
9✔
507
    }
508

509
    status = SIXEL_OK;
24✔
510

511
end:
30✔
512
    sixel_allocator_free(allocator, buf);
45✔
513

514
    return status;
45✔
515
}
516

517

518
/* generic writer function for passing to sixel_output_new() */
519
static int
520
sixel_write_callback(char *data, int size, void *priv)
5,888✔
521
{
522
    int result;
523

524
    result = (int)sixel_compat_write(*(int *)priv,
8,048✔
525
                                     data,
2,160✔
526
                                     (size_t)size);
2,160✔
527

528
    return result;
5,888✔
529
}
530

531

532
/* the writer function with hex-encoding for passing to sixel_output_new() */
533
static int
534
sixel_hex_write_callback(
72✔
535
    char    /* in */ *data,
536
    int     /* in */ size,
537
    void    /* in */ *priv)
538
{
539
    char hex[SIXEL_OUTPUT_PACKET_SIZE * 2];
540
    int i;
541
    int j;
542
    int result;
543

544
    for (i = j = 0; i < size; ++i, ++j) {
701,274✔
545
        hex[j] = (data[i] >> 4) & 0xf;
701,202✔
546
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
701,202!
547
        hex[++j] = data[i] & 0xf;
701,202✔
548
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
701,202✔
549
    }
233,734✔
550

551
    result = (int)sixel_compat_write(*(int *)priv,
144✔
552
                                     hex,
24✔
553
                                     (size_t)(size * 2));
72✔
554

555
    return result;
72✔
556
}
557

558
typedef struct sixel_encoder_output_probe {
559
    sixel_encoder_t *encoder;
560
    sixel_write_function base_write;
561
    void *base_priv;
562
} sixel_encoder_output_probe_t;
563

564
static int
565
sixel_write_with_probe(char *data, int size, void *priv)
72✔
566
{
567
    sixel_encoder_output_probe_t *probe;
568
    int written;
569
    double started_at;
570
    double finished_at;
571
    double duration;
572

573
    probe = (sixel_encoder_output_probe_t *)priv;
72✔
574
    if (probe == NULL || probe->base_write == NULL) {
72!
575
        return 0;
×
576
    }
577
    started_at = 0.0;
72✔
578
    finished_at = 0.0;
72✔
579
    duration = 0.0;
72✔
580
    if (probe->encoder != NULL &&
72!
581
            probe->encoder->assessment_observer != NULL) {
72!
582
        started_at = sixel_assessment_timer_now();
72✔
583
    }
24✔
584
    written = probe->base_write(data, size, probe->base_priv);
72✔
585
    if (probe->encoder != NULL &&
72!
586
            probe->encoder->assessment_observer != NULL) {
72!
587
        finished_at = sixel_assessment_timer_now();
72✔
588
        duration = finished_at - started_at;
72✔
589
        if (duration < 0.0) {
72!
590
            duration = 0.0;
×
591
        }
592
    }
24✔
593
    if (written > 0 && probe->encoder != NULL &&
72!
594
            probe->encoder->assessment_observer != NULL) {
72!
595
        sixel_assessment_record_output_write(
72✔
596
            probe->encoder->assessment_observer,
72✔
597
            (size_t)written,
24✔
598
            duration);
24✔
599
    }
24✔
600
    return written;
72✔
601
}
24✔
602

603
/*
604
 * Reuse the fn_write probe for raw escape writes so that every
605
 * assessment bucket receives the same accounting.
606
 *
607
 *     encoder        probe wrapper       write(2)
608
 *     +------+    +----------------+    +---------+
609
 *     | data | -> | sixel_write_*  | -> | target  |
610
 *     +------+    +----------------+    +---------+
611
 */
612
static int
613
sixel_encoder_probe_fd_write(sixel_encoder_t *encoder,
×
614
                             char *data,
615
                             int size,
616
                             int fd)
617
{
618
    sixel_encoder_output_probe_t probe;
619
    int written;
620

621
    probe.encoder = encoder;
×
622
    probe.base_write = sixel_write_callback;
×
623
    probe.base_priv = &fd;
×
624
    written = sixel_write_with_probe(data, size, &probe);
×
625

626
    return written;
×
627
}
628

629
static SIXELSTATUS
630
sixel_encoder_ensure_cell_size(sixel_encoder_t *encoder)
×
631
{
632
#if defined(TIOCGWINSZ)
633
    struct winsize ws;
634
    int result;
635
    int fd = 0;
×
636

637
    if (encoder->cell_width > 0 && encoder->cell_height > 0) {
×
638
        return SIXEL_OK;
×
639
    }
640

641
    fd = sixel_compat_open("/dev/tty", O_RDONLY);
×
642
    if (fd >= 0) {
×
643
        result = ioctl(fd, TIOCGWINSZ, &ws);
×
644
        (void)sixel_compat_close(fd);
×
645
    } else {
646
        sixel_helper_set_additional_message(
×
647
            "failed to open /dev/tty");
648
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
649
    }
650
    if (result != 0) {
×
651
        sixel_helper_set_additional_message(
×
652
            "failed to query terminal geometry with ioctl().");
653
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
654
    }
655

656
    if (ws.ws_col <= 0 || ws.ws_row <= 0 ||
×
657
        ws.ws_xpixel <= ws.ws_col || ws.ws_ypixel <= ws.ws_row) {
×
658
        sixel_helper_set_additional_message(
×
659
            "terminal does not report pixel cell size for drcs option.");
660
        return SIXEL_BAD_ARGUMENT;
×
661
    }
662

663
    encoder->cell_width = ws.ws_xpixel / ws.ws_col;
×
664
    encoder->cell_height = ws.ws_ypixel / ws.ws_row;
×
665
    if (encoder->cell_width <= 0 || encoder->cell_height <= 0) {
×
666
        sixel_helper_set_additional_message(
×
667
            "terminal cell size reported zero via ioctl().");
668
        return SIXEL_BAD_ARGUMENT;
×
669
    }
670

671
    return SIXEL_OK;
×
672
#else
673
    (void) encoder;
674
    sixel_helper_set_additional_message(
675
        "drcs option is not supported on this platform.");
676
    return SIXEL_NOT_IMPLEMENTED;
677
#endif
678
}
679

680

681
/* returns monochrome dithering context object */
682
static SIXELSTATUS
683
sixel_prepare_monochrome_palette(
12✔
684
    sixel_dither_t  /* out */ **dither,
685
     int            /* in */  finvert)
686
{
687
    SIXELSTATUS status = SIXEL_FALSE;
12✔
688

689
    if (finvert) {
12✔
690
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_LIGHT);
3✔
691
    } else {
1✔
692
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_DARK);
9✔
693
    }
694
    if (*dither == NULL) {
12!
695
        sixel_helper_set_additional_message(
×
696
            "sixel_prepare_monochrome_palette: sixel_dither_get() failed.");
697
        status = SIXEL_RUNTIME_ERROR;
×
698
        goto end;
×
699
    }
700

701
    status = SIXEL_OK;
12✔
702

703
end:
8✔
704
    return status;
12✔
705
}
706

707

708
/* returns dithering context object with specified builtin palette */
709
typedef struct palette_conversion {
710
    unsigned char *original;
711
    unsigned char *copy;
712
    size_t size;
713
    int convert_inplace;
714
    int converted;
715
    int frame_colorspace;
716
} palette_conversion_t;
717

718
static SIXELSTATUS
719
sixel_encoder_convert_palette(sixel_encoder_t *encoder,
520✔
720
                              sixel_output_t *output,
721
                              sixel_dither_t *dither,
722
                              int frame_colorspace,
723
                              int pixelformat,
724
                              palette_conversion_t *ctx)
725
{
726
    SIXELSTATUS status = SIXEL_OK;
520✔
727
    unsigned char *palette;
728
    int palette_colors;
729

730
    ctx->original = NULL;
520✔
731
    ctx->copy = NULL;
520✔
732
    ctx->size = 0;
520✔
733
    ctx->convert_inplace = 0;
520✔
734
    ctx->converted = 0;
520✔
735
    ctx->frame_colorspace = frame_colorspace;
520✔
736

737
    palette = sixel_dither_get_palette(dither);
520✔
738
    palette_colors = sixel_dither_get_num_of_palette_colors(dither);
520✔
739
    ctx->original = palette;
520✔
740

741
    if (palette == NULL || palette_colors <= 0 ||
520!
742
            frame_colorspace == output->colorspace) {
520!
743
        return SIXEL_OK;
520✔
744
    }
745

746
    ctx->size = (size_t)palette_colors * 3;
×
747

748
    output->pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
749
    output->source_colorspace = frame_colorspace;
×
750

751
    ctx->copy = (unsigned char *)sixel_allocator_malloc(encoder->allocator,
×
752
                                                        ctx->size);
753
    if (ctx->copy == NULL) {
×
754
        sixel_helper_set_additional_message(
×
755
            "sixel_encoder_convert_palette: "
756
            "sixel_allocator_malloc() failed.");
757
        status = SIXEL_BAD_ALLOCATION;
×
758
        goto end;
×
759
    }
760
    memcpy(ctx->copy, palette, ctx->size);
×
761

762
    status = sixel_output_convert_colorspace(output,
×
763
                                             palette,
764
                                             ctx->size);
765
    if (SIXEL_FAILED(status)) {
×
766
        goto end;
×
767
    }
768
    ctx->converted = 1;
×
769

770
end:
771
    output->pixelformat = pixelformat;
×
772
    output->source_colorspace = frame_colorspace;
×
773

774
    return status;
×
775
}
174✔
776

777
static void
778
sixel_encoder_restore_palette(sixel_encoder_t *encoder,
520✔
779
                              sixel_dither_t *dither,
780
                              palette_conversion_t *ctx)
781
{
782
    if (ctx->copy != NULL && ctx->size > 0) {
520!
783
        unsigned char *palette;
784

785
        palette = sixel_dither_get_palette(dither);
×
786
        if (palette != NULL) {
×
787
            memcpy(palette, ctx->copy, ctx->size);
×
788
        }
789
        sixel_allocator_free(encoder->allocator, ctx->copy);
×
790
        ctx->copy = NULL;
×
791
    } else if (ctx->convert_inplace && ctx->converted &&
520!
792
               ctx->original && ctx->size > 0) {
×
793
        (void)sixel_helper_convert_colorspace(ctx->original,
×
794
                                              ctx->size,
795
                                              SIXEL_PIXELFORMAT_RGB888,
796
                                              SIXEL_COLORSPACE_GAMMA,
797
                                              ctx->frame_colorspace);
798
    }
799
}
520✔
800

801
static SIXELSTATUS
802
sixel_encoder_capture_quantized(sixel_encoder_t *encoder,
3✔
803
                                sixel_dither_t *dither,
804
                                unsigned char const *pixels,
805
                                size_t size,
806
                                int width,
807
                                int height,
808
                                int pixelformat,
809
                                int colorspace)
810
{
811
    SIXELSTATUS status;
812
    unsigned char *palette;
813
    int ncolors;
814
    size_t palette_bytes;
815
    unsigned char *new_pixels;
816
    unsigned char *new_palette;
817
    size_t capture_bytes;
818
    unsigned char const *capture_source;
819
    sixel_index_t *paletted_pixels;
820
    size_t quantized_pixels;
821
    sixel_allocator_t *dither_allocator;
822
    int saved_pixelformat;
823
    int restore_pixelformat;
824

825
    /*
826
     * Preserve the quantized frame for assessment observers.
827
     *
828
     *     +-----------------+     +---------------------+
829
     *     | quantized bytes | --> | encoder->capture_*  |
830
     *     +-----------------+     +---------------------+
831
     */
832

833
    status = SIXEL_OK;
3✔
834
    palette = NULL;
3✔
835
    ncolors = 0;
3✔
836
    palette_bytes = 0;
3✔
837
    new_pixels = NULL;
3✔
838
    new_palette = NULL;
3✔
839
    capture_bytes = size;
3✔
840
    capture_source = pixels;
3✔
841
    paletted_pixels = NULL;
3✔
842
    quantized_pixels = 0;
3✔
843
    dither_allocator = NULL;
3✔
844

845
    if (encoder == NULL || pixels == NULL ||
3!
846
            (dither == NULL && size == 0)) {
1!
847
        sixel_helper_set_additional_message(
×
848
            "sixel_encoder_capture_quantized: invalid capture request.");
849
        return SIXEL_BAD_ARGUMENT;
×
850
    }
851

852
    if (!encoder->capture_quantized) {
3!
853
        return SIXEL_OK;
×
854
    }
855

856
    saved_pixelformat = SIXEL_PIXELFORMAT_RGB888;
3✔
857
    restore_pixelformat = 0;
3✔
858
    if (dither != NULL) {
3!
859
        dither_allocator = dither->allocator;
3✔
860
        saved_pixelformat = dither->pixelformat;
3✔
861
        restore_pixelformat = 1;
3✔
862
        if (width <= 0 || height <= 0) {
3!
863
            sixel_helper_set_additional_message(
×
864
                "sixel_encoder_capture_quantized: invalid dimensions.");
865
            status = SIXEL_BAD_ARGUMENT;
×
866
            goto cleanup;
×
867
        }
868
        quantized_pixels = (size_t)width * (size_t)height;
3✔
869
        if (height != 0 &&
3!
870
                quantized_pixels / (size_t)height != (size_t)width) {
3!
871
            sixel_helper_set_additional_message(
×
872
                "sixel_encoder_capture_quantized: image too large.");
873
            status = SIXEL_RUNTIME_ERROR;
×
874
            goto cleanup;
×
875
        }
876
        paletted_pixels = sixel_dither_apply_palette(
3✔
877
            dither, (unsigned char *)pixels, width, height);
1✔
878
        if (paletted_pixels == NULL) {
3!
879
            sixel_helper_set_additional_message(
×
880
                "sixel_encoder_capture_quantized: palette conversion failed.");
881
            status = SIXEL_RUNTIME_ERROR;
×
882
            goto cleanup;
×
883
        }
884
        capture_source = (unsigned char const *)paletted_pixels;
3✔
885
        capture_bytes = quantized_pixels;
3✔
886
    }
1✔
887

888
    if (capture_bytes > 0) {
3!
889
        if (encoder->capture_pixels == NULL ||
3!
890
                encoder->capture_pixels_size < capture_bytes) {
×
891
            new_pixels = (unsigned char *)sixel_allocator_malloc(
3✔
892
                encoder->allocator, capture_bytes);
1✔
893
            if (new_pixels == NULL) {
3!
894
                sixel_helper_set_additional_message(
×
895
                    "sixel_encoder_capture_quantized: "
896
                    "sixel_allocator_malloc() failed.");
897
                status = SIXEL_BAD_ALLOCATION;
×
898
                goto cleanup;
×
899
            }
900
            sixel_allocator_free(encoder->allocator, encoder->capture_pixels);
3✔
901
            encoder->capture_pixels = new_pixels;
3✔
902
            encoder->capture_pixels_size = capture_bytes;
3✔
903
        }
1✔
904
        memcpy(encoder->capture_pixels, capture_source, capture_bytes);
3✔
905
    }
1✔
906
    encoder->capture_pixel_bytes = capture_bytes;
3✔
907

908
    palette = NULL;
3✔
909
    ncolors = 0;
3✔
910
    palette_bytes = 0;
3✔
911
    if (dither != NULL) {
3!
912
        palette = sixel_dither_get_palette(dither);
3✔
913
        ncolors = sixel_dither_get_num_of_palette_colors(dither);
3✔
914
    }
1✔
915
    if (palette != NULL && ncolors > 0) {
3!
916
        palette_bytes = (size_t)ncolors * 3;
3✔
917
        if (encoder->capture_palette == NULL ||
3!
918
                encoder->capture_palette_size < palette_bytes) {
×
919
            new_palette = (unsigned char *)sixel_allocator_malloc(
3✔
920
                encoder->allocator, palette_bytes);
1✔
921
            if (new_palette == NULL) {
3!
922
                sixel_helper_set_additional_message(
×
923
                    "sixel_encoder_capture_quantized: "
924
                    "sixel_allocator_malloc() failed.");
925
                status = SIXEL_BAD_ALLOCATION;
×
926
                goto cleanup;
×
927
            }
928
            sixel_allocator_free(encoder->allocator,
4✔
929
                                 encoder->capture_palette);
3✔
930
            encoder->capture_palette = new_palette;
3✔
931
            encoder->capture_palette_size = palette_bytes;
3✔
932
        }
1✔
933
        memcpy(encoder->capture_palette, palette, palette_bytes);
3✔
934
    }
1✔
935

936
    encoder->capture_width = width;
3✔
937
    encoder->capture_height = height;
3✔
938
    if (dither != NULL) {
3!
939
        encoder->capture_pixelformat = SIXEL_PIXELFORMAT_PAL8;
3✔
940
    } else {
1✔
941
        encoder->capture_pixelformat = pixelformat;
×
942
    }
943
    encoder->capture_colorspace = colorspace;
3✔
944
    encoder->capture_palette_size = palette_bytes;
3✔
945
    encoder->capture_ncolors = ncolors;
3✔
946
    encoder->capture_valid = 1;
3✔
947

948
cleanup:
2✔
949
    if (restore_pixelformat && dither != NULL) {
3!
950
        /*
951
         * Undo the normalization performed by sixel_dither_apply_palette().
952
         *
953
         *     RGBA8888 --capture--> RGB888 (temporary)
954
         *          \______________________________/
955
         *                          |
956
         *                 restore original state for
957
         *                 the real encoder execution.
958
         */
959
        sixel_dither_set_pixelformat(dither, saved_pixelformat);
3✔
960
    }
1✔
961
    if (paletted_pixels != NULL && dither_allocator != NULL) {
3!
962
        sixel_allocator_free(dither_allocator, paletted_pixels);
3✔
963
    }
1✔
964

965
    return status;
3✔
966
}
1✔
967

968
static SIXELSTATUS
969
sixel_prepare_builtin_palette(
27✔
970
    sixel_dither_t /* out */ **dither,
971
    int            /* in */  builtin_palette)
972
{
973
    SIXELSTATUS status = SIXEL_FALSE;
27✔
974

975
    *dither = sixel_dither_get(builtin_palette);
27✔
976
    if (*dither == NULL) {
27!
977
        sixel_helper_set_additional_message(
×
978
            "sixel_prepare_builtin_palette: sixel_dither_get() failed.");
979
        status = SIXEL_RUNTIME_ERROR;
×
980
        goto end;
×
981
    }
982

983
    status = SIXEL_OK;
27✔
984

985
end:
18✔
986
    return status;
27✔
987
}
988

989
static int
990
sixel_encoder_thumbnail_hint(sixel_encoder_t *encoder)
457✔
991
{
992
    int width_hint;
993
    int height_hint;
994
    long base;
995
    long size;
996

997
    width_hint = 0;
457✔
998
    height_hint = 0;
457✔
999
    base = 0;
457✔
1000
    size = 0;
457✔
1001

1002
    if (encoder == NULL) {
457!
1003
        return 0;
×
1004
    }
1005

1006
    width_hint = encoder->pixelwidth;
457✔
1007
    height_hint = encoder->pixelheight;
457✔
1008

1009
    /* Request extra resolution for downscaling to preserve detail. */
1010
    if (width_hint > 0 && height_hint > 0) {
457✔
1011
        /* Follow the CLI rule: double the larger axis before doubling
1012
         * again for the final request size. */
1013
        if (width_hint >= height_hint) {
9!
1014
            base = (long)width_hint;
9✔
1015
        } else {
3✔
1016
            base = (long)height_hint;
×
1017
        }
1018
        base *= 2L;
9✔
1019
    } else if (width_hint > 0) {
451✔
1020
        base = (long)width_hint;
48✔
1021
    } else if (height_hint > 0) {
416✔
1022
        base = (long)height_hint;
36✔
1023
    } else {
12✔
1024
        return 0;
364✔
1025
    }
1026

1027
    size = base * 2L;
93✔
1028
    if (size > (long)INT_MAX) {
93!
1029
        size = (long)INT_MAX;
×
1030
    }
1031
    if (size < 1L) {
93!
1032
        size = 1L;
×
1033
    }
1034

1035
    return (int)size;
93✔
1036
}
153✔
1037

1038

1039
typedef struct sixel_callback_context_for_mapfile {
1040
    int reqcolors;
1041
    sixel_dither_t *dither;
1042
    sixel_allocator_t *allocator;
1043
    int working_colorspace;
1044
    int lut_policy;
1045
} sixel_callback_context_for_mapfile_t;
1046

1047

1048
/* callback function for sixel_helper_load_image_file() */
1049
static SIXELSTATUS
1050
load_image_callback_for_palette(
21✔
1051
    sixel_frame_t   /* in */    *frame, /* frame object from image loader */
1052
    void            /* in */    *data)  /* private data */
1053
{
1054
    SIXELSTATUS status = SIXEL_FALSE;
21✔
1055
    sixel_callback_context_for_mapfile_t *callback_context;
1056

1057
    /* get callback context object from the private data */
1058
    callback_context = (sixel_callback_context_for_mapfile_t *)data;
21✔
1059

1060
    status = sixel_frame_ensure_colorspace(frame,
28✔
1061
                                           callback_context->working_colorspace);
7✔
1062
    if (SIXEL_FAILED(status)) {
21!
1063
        goto end;
×
1064
    }
1065

1066
    switch (sixel_frame_get_pixelformat(frame)) {
21!
1067
    case SIXEL_PIXELFORMAT_PAL1:
2✔
1068
    case SIXEL_PIXELFORMAT_PAL2:
1069
    case SIXEL_PIXELFORMAT_PAL4:
1070
    case SIXEL_PIXELFORMAT_PAL8:
1071
        if (sixel_frame_get_palette(frame) == NULL) {
3!
1072
            status = SIXEL_LOGIC_ERROR;
×
1073
            goto end;
×
1074
        }
1075
        /* create new dither object */
1076
        status = sixel_dither_new(
3✔
1077
            &callback_context->dither,
1✔
1078
            sixel_frame_get_ncolors(frame),
1✔
1079
            callback_context->allocator);
1✔
1080
        if (SIXEL_FAILED(status)) {
3!
1081
            goto end;
×
1082
        }
1083

1084
        sixel_dither_set_lut_policy(callback_context->dither,
4✔
1085
                                    callback_context->lut_policy);
1✔
1086

1087
        /* use palette which is extracted from the image */
1088
        sixel_dither_set_palette(callback_context->dither,
4✔
1089
                                 sixel_frame_get_palette(frame));
1✔
1090
        /* success */
1091
        status = SIXEL_OK;
3✔
1092
        break;
3✔
1093
    case SIXEL_PIXELFORMAT_G1:
1094
        /* use 1bpp grayscale builtin palette */
1095
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1096
        /* success */
1097
        status = SIXEL_OK;
×
1098
        break;
×
1099
    case SIXEL_PIXELFORMAT_G2:
1100
        /* use 2bpp grayscale builtin palette */
1101
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1102
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
1103
        /* success */
1104
        status = SIXEL_OK;
×
1105
        break;
×
1106
    case SIXEL_PIXELFORMAT_G4:
1107
        /* use 4bpp grayscale builtin palette */
1108
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
1109
        /* success */
1110
        status = SIXEL_OK;
×
1111
        break;
×
1112
    case SIXEL_PIXELFORMAT_G8:
1113
        /* use 8bpp grayscale builtin palette */
1114
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
1115
        /* success */
1116
        status = SIXEL_OK;
×
1117
        break;
×
1118
    default:
12✔
1119
        /* create new dither object */
1120
        status = sixel_dither_new(
18✔
1121
            &callback_context->dither,
6✔
1122
            callback_context->reqcolors,
6✔
1123
            callback_context->allocator);
6✔
1124
        if (SIXEL_FAILED(status)) {
18!
1125
            goto end;
×
1126
        }
1127

1128
        sixel_dither_set_lut_policy(callback_context->dither,
24✔
1129
                                    callback_context->lut_policy);
6✔
1130

1131
        /* create adaptive palette from given frame object */
1132
        status = sixel_dither_initialize(callback_context->dither,
24✔
1133
                                         sixel_frame_get_pixels(frame),
6✔
1134
                                         sixel_frame_get_width(frame),
6✔
1135
                                         sixel_frame_get_height(frame),
6✔
1136
                                         sixel_frame_get_pixelformat(frame),
6✔
1137
                                         SIXEL_LARGE_NORM,
1138
                                         SIXEL_REP_CENTER_BOX,
1139
                                         SIXEL_QUALITY_HIGH);
1140
        if (SIXEL_FAILED(status)) {
18!
1141
            sixel_dither_unref(callback_context->dither);
×
1142
            goto end;
×
1143
        }
1144

1145
        /* success */
1146
        status = SIXEL_OK;
18✔
1147

1148
        break;
18✔
1149
    }
7✔
1150

1151
end:
14✔
1152
    return status;
21✔
1153
}
1154

1155

1156
static SIXELSTATUS
1157
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder);
1158

1159

1160
static int
1161
sixel_path_has_extension(char const *path, char const *extension)
63✔
1162
{
1163
    size_t path_len;
1164
    size_t ext_len;
1165
    size_t index;
1166

1167
    path_len = 0u;
63✔
1168
    ext_len = 0u;
63✔
1169
    index = 0u;
63✔
1170

1171
    if (path == NULL || extension == NULL) {
63!
1172
        return 0;
×
1173
    }
1174

1175
    path_len = strlen(path);
63✔
1176
    ext_len = strlen(extension);
63✔
1177
    if (ext_len == 0u || path_len < ext_len) {
63!
1178
        return 0;
×
1179
    }
1180

1181
    for (index = 0u; index < ext_len; ++index) {
144!
1182
        unsigned char path_ch;
1183
        unsigned char ext_ch;
1184

1185
        path_ch = (unsigned char)path[path_len - ext_len + index];
144✔
1186
        ext_ch = (unsigned char)extension[index];
144✔
1187
        if (tolower(path_ch) != tolower(ext_ch)) {
144✔
1188
            return 0;
63✔
1189
        }
1190
    }
27✔
1191

1192
    return 1;
×
1193
}
21✔
1194

1195
typedef enum sixel_palette_format {
1196
    SIXEL_PALETTE_FORMAT_NONE = 0,
1197
    SIXEL_PALETTE_FORMAT_ACT,
1198
    SIXEL_PALETTE_FORMAT_PAL_JASC,
1199
    SIXEL_PALETTE_FORMAT_PAL_RIFF,
1200
    SIXEL_PALETTE_FORMAT_PAL_AUTO,
1201
    SIXEL_PALETTE_FORMAT_GPL
1202
} sixel_palette_format_t;
1203

1204
/*
1205
 * Palette specification parser
1206
 *
1207
 *   TYPE:PATH  -> explicit format prefix
1208
 *   PATH       -> rely on extension or heuristics
1209
 *
1210
 * The ASCII diagram below shows how the prefix is peeled:
1211
 *
1212
 *   [type] : [path]
1213
 *    ^-- left part selects decoder/encoder when present.
1214
 */
1215
static char const *
1216
sixel_palette_strip_prefix(char const *spec,
48✔
1217
                           sixel_palette_format_t *format_hint)
1218
{
1219
    char const *colon;
1220
    size_t type_len;
1221
    size_t index;
1222
    char lowered[16];
1223

1224
    colon = NULL;
48✔
1225
    type_len = 0u;
48✔
1226
    index = 0u;
48✔
1227

1228
    if (format_hint != NULL) {
48✔
1229
        *format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
1230
    }
7✔
1231
    if (spec == NULL) {
48!
1232
        return NULL;
×
1233
    }
1234

1235
    colon = strchr(spec, ':');
48✔
1236
    if (colon == NULL) {
48!
1237
        return spec;
48✔
1238
    }
1239

1240
    type_len = (size_t)(colon - spec);
×
1241
    if (type_len == 0u || type_len >= sizeof(lowered)) {
×
1242
        return spec;
×
1243
    }
1244

1245
    for (index = 0u; index < type_len; ++index) {
×
1246
        lowered[index] = (char)tolower((unsigned char)spec[index]);
×
1247
    }
1248
    lowered[type_len] = '\0';
×
1249

1250
    if (strcmp(lowered, "act") == 0) {
×
1251
        if (format_hint != NULL) {
×
1252
            *format_hint = SIXEL_PALETTE_FORMAT_ACT;
×
1253
        }
1254
        return colon + 1;
×
1255
    }
1256
    if (strcmp(lowered, "pal") == 0) {
×
1257
        if (format_hint != NULL) {
×
1258
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1259
        }
1260
        return colon + 1;
×
1261
    }
1262
    if (strcmp(lowered, "pal-jasc") == 0) {
×
1263
        if (format_hint != NULL) {
×
1264
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1265
        }
1266
        return colon + 1;
×
1267
    }
1268
    if (strcmp(lowered, "pal-riff") == 0) {
×
1269
        if (format_hint != NULL) {
×
1270
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1271
        }
1272
        return colon + 1;
×
1273
    }
1274
    if (strcmp(lowered, "gpl") == 0) {
×
1275
        if (format_hint != NULL) {
×
1276
            *format_hint = SIXEL_PALETTE_FORMAT_GPL;
×
1277
        }
1278
        return colon + 1;
×
1279
    }
1280

1281
    return spec;
×
1282
}
16✔
1283

1284
static sixel_palette_format_t
1285
sixel_palette_format_from_extension(char const *path)
21✔
1286
{
1287
    if (path == NULL) {
21!
1288
        return SIXEL_PALETTE_FORMAT_NONE;
×
1289
    }
1290

1291
    if (sixel_path_has_extension(path, ".act")) {
21!
1292
        return SIXEL_PALETTE_FORMAT_ACT;
×
1293
    }
1294
    if (sixel_path_has_extension(path, ".pal")) {
21!
1295
        return SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1296
    }
1297
    if (sixel_path_has_extension(path, ".gpl")) {
21!
1298
        return SIXEL_PALETTE_FORMAT_GPL;
×
1299
    }
1300

1301
    return SIXEL_PALETTE_FORMAT_NONE;
21✔
1302
}
7✔
1303

1304
static int
1305
sixel_path_has_any_extension(char const *path)
21✔
1306
{
1307
    char const *slash_forward;
1308
#if defined(_WIN32)
1309
    char const *slash_backward;
1310
#endif
1311
    char const *start;
1312
    char const *dot;
1313

1314
    slash_forward = NULL;
21✔
1315
#if defined(_WIN32)
1316
    slash_backward = NULL;
1317
#endif
1318
    start = path;
21✔
1319
    dot = NULL;
21✔
1320

1321
    if (path == NULL) {
21!
1322
        return 0;
×
1323
    }
1324

1325
    slash_forward = strrchr(path, '/');
21✔
1326
#if defined(_WIN32)
1327
    slash_backward = strrchr(path, '\\');
1328
    if (slash_backward != NULL &&
1329
            (slash_forward == NULL || slash_backward > slash_forward)) {
1330
        slash_forward = slash_backward;
1331
    }
1332
#endif
1333
    if (slash_forward == NULL) {
21!
1334
        start = path;
×
1335
    } else {
1336
        start = slash_forward + 1;
21✔
1337
    }
1338

1339
    dot = strrchr(start, '.');
21✔
1340
    if (dot == NULL) {
21!
1341
        return 0;
×
1342
    }
1343

1344
    if (dot[1] == '\0') {
21!
1345
        return 0;
×
1346
    }
1347

1348
    return 1;
21✔
1349
}
7✔
1350

1351
static int
1352
sixel_palette_has_utf8_bom(unsigned char const *data, size_t size)
×
1353
{
1354
    if (data == NULL || size < 3u) {
×
1355
        return 0;
×
1356
    }
1357
    if (data[0] == 0xefu && data[1] == 0xbbu && data[2] == 0xbfu) {
×
1358
        return 1;
×
1359
    }
1360
    return 0;
×
1361
}
1362

1363

1364
/*
1365
 * Materialize palette bytes from a stream.
1366
 *
1367
 * The flow looks like:
1368
 *
1369
 *   stream --> [scratch buffer] --> [resizable heap buffer]
1370
 *                  ^ looped read        ^ returned payload
1371
 */
1372
static SIXELSTATUS
1373
sixel_palette_read_stream(FILE *stream,
×
1374
                          sixel_allocator_t *allocator,
1375
                          unsigned char **pdata,
1376
                          size_t *psize)
1377
{
1378
    SIXELSTATUS status;
1379
    unsigned char *buffer;
1380
    unsigned char *grown;
1381
    size_t capacity;
1382
    size_t used;
1383
    size_t read_bytes;
1384
    size_t needed;
1385
    size_t new_capacity;
1386
    unsigned char scratch[4096];
1387

1388
    status = SIXEL_FALSE;
×
1389
    buffer = NULL;
×
1390
    grown = NULL;
×
1391
    capacity = 0u;
×
1392
    used = 0u;
×
1393
    read_bytes = 0u;
×
1394
    needed = 0u;
×
1395
    new_capacity = 0u;
×
1396

1397
    if (pdata == NULL || psize == NULL || stream == NULL || allocator == NULL) {
×
1398
        sixel_helper_set_additional_message(
×
1399
            "sixel_palette_read_stream: invalid argument.");
1400
        return SIXEL_BAD_ARGUMENT;
×
1401
    }
1402

1403
    *pdata = NULL;
×
1404
    *psize = 0u;
×
1405

1406
    while (1) {
1407
        read_bytes = fread(scratch, 1, sizeof(scratch), stream);
×
1408
        if (read_bytes == 0u) {
×
1409
            if (ferror(stream)) {
×
1410
                sixel_helper_set_additional_message(
×
1411
                    "sixel_palette_read_stream: fread() failed.");
1412
                status = SIXEL_LIBC_ERROR;
×
1413
                goto cleanup;
×
1414
            }
1415
            break;
×
1416
        }
1417

1418
        if (used > SIZE_MAX - read_bytes) {
×
1419
            sixel_helper_set_additional_message(
×
1420
                "sixel_palette_read_stream: size overflow.");
1421
            status = SIXEL_BAD_ALLOCATION;
×
1422
            goto cleanup;
×
1423
        }
1424
        needed = used + read_bytes;
×
1425

1426
        if (needed > capacity) {
×
1427
            new_capacity = capacity;
×
1428
            if (new_capacity == 0u) {
×
1429
                new_capacity = 4096u;
×
1430
            }
1431
            while (needed > new_capacity) {
×
1432
                if (new_capacity > SIZE_MAX / 2u) {
×
1433
                    sixel_helper_set_additional_message(
×
1434
                        "sixel_palette_read_stream: size overflow.");
1435
                    status = SIXEL_BAD_ALLOCATION;
×
1436
                    goto cleanup;
×
1437
                }
1438
                new_capacity *= 2u;
×
1439
            }
1440

1441
            grown = (unsigned char *)sixel_allocator_malloc(allocator,
×
1442
                                                             new_capacity);
1443
            if (grown == NULL) {
×
1444
                sixel_helper_set_additional_message(
×
1445
                    "sixel_palette_read_stream: allocation failed.");
1446
                status = SIXEL_BAD_ALLOCATION;
×
1447
                goto cleanup;
×
1448
            }
1449

1450
            if (buffer != NULL) {
×
1451
                memcpy(grown, buffer, used);
×
1452
                sixel_allocator_free(allocator, buffer);
×
1453
            }
1454

1455
            buffer = grown;
×
1456
            grown = NULL;
×
1457
            capacity = new_capacity;
×
1458
        }
1459

1460
        memcpy(buffer + used, scratch, read_bytes);
×
1461
        used += read_bytes;
×
1462
    }
1463

1464
    *pdata = buffer;
×
1465
    *psize = used;
×
1466
    status = SIXEL_OK;
×
1467
    return status;
×
1468

1469
cleanup:
1470
    if (grown != NULL) {
×
1471
        sixel_allocator_free(allocator, grown);
×
1472
    }
1473
    if (buffer != NULL) {
×
1474
        sixel_allocator_free(allocator, buffer);
×
1475
    }
1476
    return status;
×
1477
}
1478

1479

1480
static SIXELSTATUS
1481
sixel_palette_open_read(char const *path, FILE **pstream, int *pclose)
×
1482
{
1483
    if (pstream == NULL || pclose == NULL || path == NULL) {
×
1484
        sixel_helper_set_additional_message(
×
1485
            "sixel_palette_open_read: invalid argument.");
1486
        return SIXEL_BAD_ARGUMENT;
×
1487
    }
1488

1489
    if (strcmp(path, "-") == 0) {
×
1490
        *pstream = stdin;
×
1491
        *pclose = 0;
×
1492
        return SIXEL_OK;
×
1493
    }
1494

1495
    *pstream = fopen(path, "rb");
×
1496
    if (*pstream == NULL) {
×
1497
        sixel_helper_set_additional_message(
×
1498
            "sixel_palette_open_read: failed to open file.");
1499
        return SIXEL_LIBC_ERROR;
×
1500
    }
1501

1502
    *pclose = 1;
×
1503
    return SIXEL_OK;
×
1504
}
1505

1506

1507
static void
1508
sixel_palette_close_stream(FILE *stream, int close_stream)
×
1509
{
1510
    if (close_stream && stream != NULL) {
×
1511
        (void) fclose(stream);
×
1512
    }
1513
}
×
1514

1515

1516
static sixel_palette_format_t
1517
sixel_palette_guess_format(unsigned char const *data, size_t size)
×
1518
{
1519
    size_t offset;
1520
    size_t data_size;
1521

1522
    offset = 0u;
×
1523
    data_size = size;
×
1524

1525
    if (data == NULL || size == 0u) {
×
1526
        return SIXEL_PALETTE_FORMAT_NONE;
×
1527
    }
1528

1529
    if (size == 256u * 3u || size == 256u * 3u + 4u) {
×
1530
        return SIXEL_PALETTE_FORMAT_ACT;
×
1531
    }
1532

1533
    if (size >= 12u && memcmp(data, "RIFF", 4) == 0
×
1534
            && memcmp(data + 8, "PAL ", 4) == 0) {
×
1535
        return SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1536
    }
1537

1538
    if (sixel_palette_has_utf8_bom(data, size)) {
×
1539
        offset = 3u;
×
1540
        data_size = size - 3u;
×
1541
    }
1542

1543
    if (data_size >= 8u && memcmp(data + offset, "JASC-PAL", 8) == 0) {
×
1544
        return SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1545
    }
1546
    if (data_size >= 12u && memcmp(data + offset, "GIMP Palette", 12) == 0) {
×
1547
        return SIXEL_PALETTE_FORMAT_GPL;
×
1548
    }
1549

1550
    return SIXEL_PALETTE_FORMAT_NONE;
×
1551
}
1552

1553

1554
static unsigned int
1555
sixel_palette_read_le16(unsigned char const *ptr)
×
1556
{
1557
    if (ptr == NULL) {
×
1558
        return 0u;
×
1559
    }
1560
    return (unsigned int)ptr[0] | ((unsigned int)ptr[1] << 8);
×
1561
}
1562

1563

1564
static unsigned int
1565
sixel_palette_read_le32(unsigned char const *ptr)
×
1566
{
1567
    if (ptr == NULL) {
×
1568
        return 0u;
×
1569
    }
1570
    return ((unsigned int)ptr[0])
×
1571
        | ((unsigned int)ptr[1] << 8)
×
1572
        | ((unsigned int)ptr[2] << 16)
×
1573
        | ((unsigned int)ptr[3] << 24);
×
1574
}
1575

1576

1577
/*
1578
 * Adobe Color Table (*.act) reader
1579
 *
1580
 *   +-----------+---------------------------+
1581
 *   | section   | bytes                     |
1582
 *   +-----------+---------------------------+
1583
 *   | palette   | 256 entries * 3 RGB bytes |
1584
 *   | trailer   | optional count/start pair |
1585
 *   +-----------+---------------------------+
1586
 */
1587
static SIXELSTATUS
1588
sixel_palette_parse_act(unsigned char const *data,
×
1589
                        size_t size,
1590
                        sixel_encoder_t *encoder,
1591
                        sixel_dither_t **dither)
1592
{
1593
    SIXELSTATUS status;
1594
    sixel_dither_t *local;
1595
    unsigned char const *palette_start;
1596
    unsigned char const *trailer;
1597
    unsigned char *target;
1598
    size_t copy_bytes;
1599
    int exported_colors;
1600
    int start_index;
1601

1602
    status = SIXEL_FALSE;
×
1603
    local = NULL;
×
1604
    palette_start = data;
×
1605
    trailer = NULL;
×
1606
    target = NULL;
×
1607
    copy_bytes = 0u;
×
1608
    exported_colors = 0;
×
1609
    start_index = 0;
×
1610

1611
    if (encoder == NULL || dither == NULL) {
×
1612
        sixel_helper_set_additional_message(
×
1613
            "sixel_palette_parse_act: invalid argument.");
1614
        return SIXEL_BAD_ARGUMENT;
×
1615
    }
1616
    if (data == NULL || size < 256u * 3u) {
×
1617
        sixel_helper_set_additional_message(
×
1618
            "sixel_palette_parse_act: truncated ACT palette.");
1619
        return SIXEL_BAD_INPUT;
×
1620
    }
1621

1622
    if (size == 256u * 3u) {
×
1623
        exported_colors = 256;
×
1624
        start_index = 0;
×
1625
    } else if (size == 256u * 3u + 4u) {
×
1626
        trailer = data + 256u * 3u;
×
1627
        exported_colors = (int)(((unsigned int)trailer[0] << 8)
×
1628
                                | (unsigned int)trailer[1]);
×
1629
        start_index = (int)(((unsigned int)trailer[2] << 8)
×
1630
                            | (unsigned int)trailer[3]);
×
1631
    } else {
1632
        sixel_helper_set_additional_message(
×
1633
            "sixel_palette_parse_act: invalid ACT length.");
1634
        return SIXEL_BAD_INPUT;
×
1635
    }
1636

1637
    if (start_index < 0 || start_index >= 256) {
×
1638
        sixel_helper_set_additional_message(
×
1639
            "sixel_palette_parse_act: ACT start index out of range.");
1640
        return SIXEL_BAD_INPUT;
×
1641
    }
1642
    if (exported_colors <= 0 || exported_colors > 256) {
×
1643
        exported_colors = 256;
×
1644
    }
1645
    if (start_index + exported_colors > 256) {
×
1646
        sixel_helper_set_additional_message(
×
1647
            "sixel_palette_parse_act: ACT palette exceeds 256 slots.");
1648
        return SIXEL_BAD_INPUT;
×
1649
    }
1650

1651
    status = sixel_dither_new(&local, exported_colors, encoder->allocator);
×
1652
    if (SIXEL_FAILED(status)) {
×
1653
        return status;
×
1654
    }
1655

1656
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1657

1658
    target = sixel_dither_get_palette(local);
×
1659
    copy_bytes = (size_t)exported_colors * 3u;
×
1660
    memcpy(target, palette_start + (size_t)start_index * 3u, copy_bytes);
×
1661

1662
    *dither = local;
×
1663
    return SIXEL_OK;
×
1664
}
1665

1666

1667
static SIXELSTATUS
1668
sixel_palette_parse_pal_jasc(unsigned char const *data,
×
1669
                             size_t size,
1670
                             sixel_encoder_t *encoder,
1671
                             sixel_dither_t **dither)
1672
{
1673
    SIXELSTATUS status;
1674
    char *text;
1675
    size_t index;
1676
    size_t offset;
1677
    char *cursor;
1678
    char *line;
1679
    char *line_end;
1680
    int stage;
1681
    int exported_colors;
1682
    int parsed_colors;
1683
    sixel_dither_t *local;
1684
    unsigned char *target;
1685
    long component;
1686
    char *parse_end;
1687
    int value_index;
1688
    int values[3];
1689
    char tail;
1690

1691
    status = SIXEL_FALSE;
×
1692
    text = NULL;
×
1693
    index = 0u;
×
1694
    offset = 0u;
×
1695
    cursor = NULL;
×
1696
    line = NULL;
×
1697
    line_end = NULL;
×
1698
    stage = 0;
×
1699
    exported_colors = 0;
×
1700
    parsed_colors = 0;
×
1701
    local = NULL;
×
1702
    target = NULL;
×
1703
    component = 0;
×
1704
    parse_end = NULL;
×
1705
    value_index = 0;
×
1706
    values[0] = 0;
×
1707
    values[1] = 0;
×
1708
    values[2] = 0;
×
1709

1710
    if (encoder == NULL || dither == NULL) {
×
1711
        sixel_helper_set_additional_message(
×
1712
            "sixel_palette_parse_pal_jasc: invalid argument.");
1713
        return SIXEL_BAD_ARGUMENT;
×
1714
    }
1715
    if (data == NULL || size == 0u) {
×
1716
        sixel_helper_set_additional_message(
×
1717
            "sixel_palette_parse_pal_jasc: empty palette.");
1718
        return SIXEL_BAD_INPUT;
×
1719
    }
1720

1721
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
1722
    if (text == NULL) {
×
1723
        sixel_helper_set_additional_message(
×
1724
            "sixel_palette_parse_pal_jasc: allocation failed.");
1725
        return SIXEL_BAD_ALLOCATION;
×
1726
    }
1727
    memcpy(text, data, size);
×
1728
    text[size] = '\0';
×
1729

1730
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
1731
        offset = 3u;
×
1732
    }
1733
    cursor = text + offset;
×
1734

1735
    while (*cursor != '\0') {
×
1736
        line = cursor;
×
1737
        line_end = cursor;
×
1738
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
1739
            ++line_end;
×
1740
        }
1741
        if (*line_end != '\0') {
×
1742
            *line_end = '\0';
×
1743
            cursor = line_end + 1;
×
1744
        } else {
1745
            cursor = line_end;
×
1746
        }
1747
        while (*cursor == '\n' || *cursor == '\r') {
×
1748
            ++cursor;
×
1749
        }
1750

1751
        while (*line == ' ' || *line == '\t') {
×
1752
            ++line;
×
1753
        }
1754
        index = strlen(line);
×
1755
        while (index > 0u) {
×
1756
            tail = line[index - 1];
×
1757
            if (tail != ' ' && tail != '\t') {
×
1758
                break;
×
1759
            }
1760
            line[index - 1] = '\0';
×
1761
            --index;
×
1762
        }
1763
        if (*line == '\0') {
×
1764
            continue;
×
1765
        }
1766
        if (*line == '#') {
×
1767
            continue;
×
1768
        }
1769

1770
        if (stage == 0) {
×
1771
            if (strcmp(line, "JASC-PAL") != 0) {
×
1772
                sixel_helper_set_additional_message(
×
1773
                    "sixel_palette_parse_pal_jasc: missing header.");
1774
                status = SIXEL_BAD_INPUT;
×
1775
                goto cleanup;
×
1776
            }
1777
            stage = 1;
×
1778
            continue;
×
1779
        }
1780
        if (stage == 1) {
×
1781
            stage = 2;
×
1782
            continue;
×
1783
        }
1784
        if (stage == 2) {
×
1785
            component = strtol(line, &parse_end, 10);
×
1786
            if (parse_end == line || component <= 0L || component > 256L) {
×
1787
                sixel_helper_set_additional_message(
×
1788
                    "sixel_palette_parse_pal_jasc: invalid color count.");
1789
                status = SIXEL_BAD_INPUT;
×
1790
                goto cleanup;
×
1791
            }
1792
            exported_colors = (int)component;
×
1793
            status = sixel_dither_new(&local, exported_colors,
×
1794
                                      encoder->allocator);
1795
            if (SIXEL_FAILED(status)) {
×
1796
                goto cleanup;
×
1797
            }
1798
            sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1799
            target = sixel_dither_get_palette(local);
×
1800
            stage = 3;
×
1801
            continue;
×
1802
        }
1803

1804
        value_index = 0;
×
1805
        while (value_index < 3) {
×
1806
            component = strtol(line, &parse_end, 10);
×
1807
            if (parse_end == line || component < 0L || component > 255L) {
×
1808
                sixel_helper_set_additional_message(
×
1809
                    "sixel_palette_parse_pal_jasc: invalid component.");
1810
                status = SIXEL_BAD_INPUT;
×
1811
                goto cleanup;
×
1812
            }
1813
            values[value_index] = (int)component;
×
1814
            ++value_index;
×
1815
            line = parse_end;
×
1816
            while (*line == ' ' || *line == '\t') {
×
1817
                ++line;
×
1818
            }
1819
        }
1820

1821
        if (parsed_colors >= exported_colors) {
×
1822
            sixel_helper_set_additional_message(
×
1823
                "sixel_palette_parse_pal_jasc: excess entries.");
1824
            status = SIXEL_BAD_INPUT;
×
1825
            goto cleanup;
×
1826
        }
1827

1828
        target[parsed_colors * 3 + 0] =
×
1829
            (unsigned char)values[0];
×
1830
        target[parsed_colors * 3 + 1] =
×
1831
            (unsigned char)values[1];
×
1832
        target[parsed_colors * 3 + 2] =
×
1833
            (unsigned char)values[2];
×
1834
        ++parsed_colors;
×
1835
    }
1836

1837
    if (stage < 3) {
×
1838
        sixel_helper_set_additional_message(
×
1839
            "sixel_palette_parse_pal_jasc: incomplete header.");
1840
        status = SIXEL_BAD_INPUT;
×
1841
        goto cleanup;
×
1842
    }
1843
    if (parsed_colors != exported_colors) {
×
1844
        sixel_helper_set_additional_message(
×
1845
            "sixel_palette_parse_pal_jasc: color count mismatch.");
1846
        status = SIXEL_BAD_INPUT;
×
1847
        goto cleanup;
×
1848
    }
1849

1850
    *dither = local;
×
1851
    status = SIXEL_OK;
×
1852

1853
cleanup:
1854
    if (SIXEL_FAILED(status) && local != NULL) {
×
1855
        sixel_dither_unref(local);
×
1856
    }
1857
    if (text != NULL) {
×
1858
        sixel_allocator_free(encoder->allocator, text);
×
1859
    }
1860
    return status;
×
1861
}
1862

1863

1864
static SIXELSTATUS
1865
sixel_palette_parse_pal_riff(unsigned char const *data,
×
1866
                             size_t size,
1867
                             sixel_encoder_t *encoder,
1868
                             sixel_dither_t **dither)
1869
{
1870
    SIXELSTATUS status;
1871
    size_t offset;
1872
    size_t chunk_size;
1873
    sixel_dither_t *local;
1874
    unsigned char const *chunk;
1875
    unsigned char *target;
1876
    unsigned int entry_count;
1877
    unsigned int version;
1878
    unsigned int index;
1879
    size_t palette_offset;
1880

1881
    status = SIXEL_FALSE;
×
1882
    offset = 0u;
×
1883
    chunk_size = 0u;
×
1884
    local = NULL;
×
1885
    chunk = NULL;
×
1886
    target = NULL;
×
1887
    entry_count = 0u;
×
1888
    version = 0u;
×
1889
    index = 0u;
×
1890
    palette_offset = 0u;
×
1891

1892
    if (encoder == NULL || dither == NULL) {
×
1893
        sixel_helper_set_additional_message(
×
1894
            "sixel_palette_parse_pal_riff: invalid argument.");
1895
        return SIXEL_BAD_ARGUMENT;
×
1896
    }
1897
    if (data == NULL || size < 12u) {
×
1898
        sixel_helper_set_additional_message(
×
1899
            "sixel_palette_parse_pal_riff: truncated palette.");
1900
        return SIXEL_BAD_INPUT;
×
1901
    }
1902
    if (memcmp(data, "RIFF", 4) != 0 || memcmp(data + 8, "PAL ", 4) != 0) {
×
1903
        sixel_helper_set_additional_message(
×
1904
            "sixel_palette_parse_pal_riff: missing RIFF header.");
1905
        return SIXEL_BAD_INPUT;
×
1906
    }
1907

1908
    offset = 12u;
×
1909
    while (offset + 8u <= size) {
×
1910
        chunk = data + offset;
×
1911
        chunk_size = (size_t)sixel_palette_read_le32(chunk + 4);
×
1912
        if (offset + 8u + chunk_size > size) {
×
1913
            sixel_helper_set_additional_message(
×
1914
                "sixel_palette_parse_pal_riff: chunk extends past end.");
1915
            return SIXEL_BAD_INPUT;
×
1916
        }
1917
        if (memcmp(chunk, "data", 4) == 0) {
×
1918
            break;
×
1919
        }
1920
        offset += 8u + ((chunk_size + 1u) & ~1u);
×
1921
    }
1922

1923
    if (offset + 8u > size || memcmp(chunk, "data", 4) != 0) {
×
1924
        sixel_helper_set_additional_message(
×
1925
            "sixel_palette_parse_pal_riff: missing data chunk.");
1926
        return SIXEL_BAD_INPUT;
×
1927
    }
1928

1929
    if (chunk_size < 4u) {
×
1930
        sixel_helper_set_additional_message(
×
1931
            "sixel_palette_parse_pal_riff: data chunk too small.");
1932
        return SIXEL_BAD_INPUT;
×
1933
    }
1934
    version = sixel_palette_read_le16(chunk + 8);
×
1935
    (void)version;
1936
    entry_count = sixel_palette_read_le16(chunk + 10);
×
1937
    if (entry_count == 0u || entry_count > 256u) {
×
1938
        sixel_helper_set_additional_message(
×
1939
            "sixel_palette_parse_pal_riff: invalid entry count.");
1940
        return SIXEL_BAD_INPUT;
×
1941
    }
1942
    if (chunk_size != 4u + (size_t)entry_count * 4u) {
×
1943
        sixel_helper_set_additional_message(
×
1944
            "sixel_palette_parse_pal_riff: unexpected chunk size.");
1945
        return SIXEL_BAD_INPUT;
×
1946
    }
1947

1948
    status = sixel_dither_new(&local, (int)entry_count, encoder->allocator);
×
1949
    if (SIXEL_FAILED(status)) {
×
1950
        return status;
×
1951
    }
1952
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1953
    target = sixel_dither_get_palette(local);
×
1954
    palette_offset = 12u;
×
1955
    for (index = 0u; index < entry_count; ++index) {
×
1956
        target[index * 3u + 0u] =
×
1957
            chunk[palette_offset + index * 4u + 0u];
×
1958
        target[index * 3u + 1u] =
×
1959
            chunk[palette_offset + index * 4u + 1u];
×
1960
        target[index * 3u + 2u] =
×
1961
            chunk[palette_offset + index * 4u + 2u];
×
1962
    }
1963

1964
    *dither = local;
×
1965
    return SIXEL_OK;
×
1966
}
1967

1968

1969
static SIXELSTATUS
1970
sixel_palette_parse_gpl(unsigned char const *data,
×
1971
                        size_t size,
1972
                        sixel_encoder_t *encoder,
1973
                        sixel_dither_t **dither)
1974
{
1975
    SIXELSTATUS status;
1976
    char *text;
1977
    size_t offset;
1978
    char *cursor;
1979
    char *line;
1980
    char *line_end;
1981
    size_t index;
1982
    int header_seen;
1983
    int parsed_colors;
1984
    unsigned char palette_bytes[256 * 3];
1985
    long component;
1986
    char *parse_end;
1987
    int value_index;
1988
    int values[3];
1989
    sixel_dither_t *local;
1990
    unsigned char *target;
1991
    char tail;
1992

1993
    status = SIXEL_FALSE;
×
1994
    text = NULL;
×
1995
    offset = 0u;
×
1996
    cursor = NULL;
×
1997
    line = NULL;
×
1998
    line_end = NULL;
×
1999
    index = 0u;
×
2000
    header_seen = 0;
×
2001
    parsed_colors = 0;
×
2002
    component = 0;
×
2003
    parse_end = NULL;
×
2004
    value_index = 0;
×
2005
    values[0] = 0;
×
2006
    values[1] = 0;
×
2007
    values[2] = 0;
×
2008
    local = NULL;
×
2009
    target = NULL;
×
2010

2011
    if (encoder == NULL || dither == NULL) {
×
2012
        sixel_helper_set_additional_message(
×
2013
            "sixel_palette_parse_gpl: invalid argument.");
2014
        return SIXEL_BAD_ARGUMENT;
×
2015
    }
2016
    if (data == NULL || size == 0u) {
×
2017
        sixel_helper_set_additional_message(
×
2018
            "sixel_palette_parse_gpl: empty palette.");
2019
        return SIXEL_BAD_INPUT;
×
2020
    }
2021

2022
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
2023
    if (text == NULL) {
×
2024
        sixel_helper_set_additional_message(
×
2025
            "sixel_palette_parse_gpl: allocation failed.");
2026
        return SIXEL_BAD_ALLOCATION;
×
2027
    }
2028
    memcpy(text, data, size);
×
2029
    text[size] = '\0';
×
2030

2031
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
2032
        offset = 3u;
×
2033
    }
2034
    cursor = text + offset;
×
2035

2036
    while (*cursor != '\0') {
×
2037
        line = cursor;
×
2038
        line_end = cursor;
×
2039
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
2040
            ++line_end;
×
2041
        }
2042
        if (*line_end != '\0') {
×
2043
            *line_end = '\0';
×
2044
            cursor = line_end + 1;
×
2045
        } else {
2046
            cursor = line_end;
×
2047
        }
2048
        while (*cursor == '\n' || *cursor == '\r') {
×
2049
            ++cursor;
×
2050
        }
2051

2052
        while (*line == ' ' || *line == '\t') {
×
2053
            ++line;
×
2054
        }
2055
        index = strlen(line);
×
2056
        while (index > 0u) {
×
2057
            tail = line[index - 1];
×
2058
            if (tail != ' ' && tail != '\t') {
×
2059
                break;
×
2060
            }
2061
            line[index - 1] = '\0';
×
2062
            --index;
×
2063
        }
2064
        if (*line == '\0') {
×
2065
            continue;
×
2066
        }
2067
        if (*line == '#') {
×
2068
            continue;
×
2069
        }
2070
        if (strncmp(line, "Name:", 5) == 0) {
×
2071
            continue;
×
2072
        }
2073
        if (strncmp(line, "Columns:", 8) == 0) {
×
2074
            continue;
×
2075
        }
2076

2077
        if (!header_seen) {
×
2078
            if (strcmp(line, "GIMP Palette") != 0) {
×
2079
                sixel_helper_set_additional_message(
×
2080
                    "sixel_palette_parse_gpl: missing header.");
2081
                status = SIXEL_BAD_INPUT;
×
2082
                goto cleanup;
×
2083
            }
2084
            header_seen = 1;
×
2085
            continue;
×
2086
        }
2087

2088
        if (parsed_colors >= 256) {
×
2089
            sixel_helper_set_additional_message(
×
2090
                "sixel_palette_parse_gpl: too many colors.");
2091
            status = SIXEL_BAD_INPUT;
×
2092
            goto cleanup;
×
2093
        }
2094

2095
        value_index = 0;
×
2096
        while (value_index < 3) {
×
2097
            component = strtol(line, &parse_end, 10);
×
2098
            if (parse_end == line || component < 0L || component > 255L) {
×
2099
                sixel_helper_set_additional_message(
×
2100
                    "sixel_palette_parse_gpl: invalid component.");
2101
                status = SIXEL_BAD_INPUT;
×
2102
                goto cleanup;
×
2103
            }
2104
            values[value_index] = (int)component;
×
2105
            ++value_index;
×
2106
            line = parse_end;
×
2107
            while (*line == ' ' || *line == '\t') {
×
2108
                ++line;
×
2109
            }
2110
        }
2111

2112
        palette_bytes[parsed_colors * 3 + 0] =
×
2113
            (unsigned char)values[0];
×
2114
        palette_bytes[parsed_colors * 3 + 1] =
×
2115
            (unsigned char)values[1];
×
2116
        palette_bytes[parsed_colors * 3 + 2] =
×
2117
            (unsigned char)values[2];
×
2118
        ++parsed_colors;
×
2119
    }
2120

2121
    if (!header_seen) {
×
2122
        sixel_helper_set_additional_message(
×
2123
            "sixel_palette_parse_gpl: header missing.");
2124
        status = SIXEL_BAD_INPUT;
×
2125
        goto cleanup;
×
2126
    }
2127
    if (parsed_colors <= 0) {
×
2128
        sixel_helper_set_additional_message(
×
2129
            "sixel_palette_parse_gpl: no colors parsed.");
2130
        status = SIXEL_BAD_INPUT;
×
2131
        goto cleanup;
×
2132
    }
2133

2134
    status = sixel_dither_new(&local, parsed_colors, encoder->allocator);
×
2135
    if (SIXEL_FAILED(status)) {
×
2136
        goto cleanup;
×
2137
    }
2138
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2139
    target = sixel_dither_get_palette(local);
×
2140
    memcpy(target, palette_bytes, (size_t)parsed_colors * 3u);
×
2141

2142
    *dither = local;
×
2143
    status = SIXEL_OK;
×
2144

2145
cleanup:
2146
    if (SIXEL_FAILED(status) && local != NULL) {
×
2147
        sixel_dither_unref(local);
×
2148
    }
2149
    if (text != NULL) {
×
2150
        sixel_allocator_free(encoder->allocator, text);
×
2151
    }
2152
    return status;
×
2153
}
2154

2155

2156
/*
2157
 * Palette exporters
2158
 *
2159
 *   +----------+-------------------------+
2160
 *   | format   | emission strategy       |
2161
 *   +----------+-------------------------+
2162
 *   | ACT      | fixed 256 entries + EOF |
2163
 *   | PAL JASC | textual lines           |
2164
 *   | PAL RIFF | RIFF container          |
2165
 *   | GPL      | textual lines           |
2166
 *   +----------+-------------------------+
2167
 */
2168
static SIXELSTATUS
2169
sixel_palette_write_act(FILE *stream,
×
2170
                        unsigned char const *palette,
2171
                        int exported_colors)
2172
{
2173
    SIXELSTATUS status;
2174
    unsigned char act_table[256 * 3];
2175
    unsigned char trailer[4];
2176
    size_t exported_bytes;
2177

2178
    status = SIXEL_FALSE;
×
2179
    exported_bytes = 0u;
×
2180

2181
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2182
        return SIXEL_BAD_ARGUMENT;
×
2183
    }
2184
    if (exported_colors > 256) {
×
2185
        exported_colors = 256;
×
2186
    }
2187

2188
    memset(act_table, 0, sizeof(act_table));
×
2189
    exported_bytes = (size_t)exported_colors * 3u;
×
2190
    memcpy(act_table, palette, exported_bytes);
×
2191

2192
    trailer[0] = (unsigned char)(((unsigned int)exported_colors >> 8)
×
2193
                                 & 0xffu);
2194
    trailer[1] = (unsigned char)((unsigned int)exported_colors & 0xffu);
×
2195
    trailer[2] = 0u;
×
2196
    trailer[3] = 0u;
×
2197

2198
    if (fwrite(act_table, 1, sizeof(act_table), stream)
×
2199
            != sizeof(act_table)) {
2200
        status = SIXEL_LIBC_ERROR;
×
2201
        return status;
×
2202
    }
2203
    if (fwrite(trailer, 1, sizeof(trailer), stream)
×
2204
            != sizeof(trailer)) {
2205
        status = SIXEL_LIBC_ERROR;
×
2206
        return status;
×
2207
    }
2208

2209
    return SIXEL_OK;
×
2210
}
2211

2212

2213
static SIXELSTATUS
2214
sixel_palette_write_pal_jasc(FILE *stream,
×
2215
                             unsigned char const *palette,
2216
                             int exported_colors)
2217
{
2218
    int index;
2219

2220
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2221
        return SIXEL_BAD_ARGUMENT;
×
2222
    }
2223
    if (fprintf(stream, "JASC-PAL\n0100\n%d\n", exported_colors) < 0) {
×
2224
        return SIXEL_LIBC_ERROR;
×
2225
    }
2226
    for (index = 0; index < exported_colors; ++index) {
×
2227
        if (fprintf(stream, "%d %d %d\n",
×
2228
                    (int)palette[index * 3 + 0],
×
2229
                    (int)palette[index * 3 + 1],
×
2230
                    (int)palette[index * 3 + 2]) < 0) {
×
2231
            return SIXEL_LIBC_ERROR;
×
2232
        }
2233
    }
2234
    return SIXEL_OK;
×
2235
}
2236

2237

2238
static SIXELSTATUS
2239
sixel_palette_write_pal_riff(FILE *stream,
×
2240
                             unsigned char const *palette,
2241
                             int exported_colors)
2242
{
2243
    unsigned char header[12];
2244
    unsigned char chunk[8];
2245
    unsigned char log_palette[4 + 256 * 4];
2246
    unsigned int data_size;
2247
    unsigned int riff_size;
2248
    int index;
2249

2250
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2251
        return SIXEL_BAD_ARGUMENT;
×
2252
    }
2253
    if (exported_colors > 256) {
×
2254
        exported_colors = 256;
×
2255
    }
2256

2257
    data_size = 4u + (unsigned int)exported_colors * 4u;
×
2258
    riff_size = 4u + 8u + data_size;
×
2259

2260
    memcpy(header, "RIFF", 4);
×
2261
    header[4] = (unsigned char)(riff_size & 0xffu);
×
2262
    header[5] = (unsigned char)((riff_size >> 8) & 0xffu);
×
2263
    header[6] = (unsigned char)((riff_size >> 16) & 0xffu);
×
2264
    header[7] = (unsigned char)((riff_size >> 24) & 0xffu);
×
2265
    memcpy(header + 8, "PAL ", 4);
×
2266

2267
    memcpy(chunk, "data", 4);
×
2268
    chunk[4] = (unsigned char)(data_size & 0xffu);
×
2269
    chunk[5] = (unsigned char)((data_size >> 8) & 0xffu);
×
2270
    chunk[6] = (unsigned char)((data_size >> 16) & 0xffu);
×
2271
    chunk[7] = (unsigned char)((data_size >> 24) & 0xffu);
×
2272

2273
    memset(log_palette, 0, sizeof(log_palette));
×
2274
    log_palette[0] = 0x00;
×
2275
    log_palette[1] = 0x03;
×
2276
    log_palette[2] = (unsigned char)(exported_colors & 0xff);
×
2277
    log_palette[3] = (unsigned char)((exported_colors >> 8) & 0xff);
×
2278
    for (index = 0; index < exported_colors; ++index) {
×
2279
        log_palette[4 + index * 4 + 0] = palette[index * 3 + 0];
×
2280
        log_palette[4 + index * 4 + 1] = palette[index * 3 + 1];
×
2281
        log_palette[4 + index * 4 + 2] = palette[index * 3 + 2];
×
2282
        log_palette[4 + index * 4 + 3] = 0u;
×
2283
    }
2284

2285
    if (fwrite(header, 1, sizeof(header), stream)
×
2286
            != sizeof(header)) {
2287
        return SIXEL_LIBC_ERROR;
×
2288
    }
2289
    if (fwrite(chunk, 1, sizeof(chunk), stream) != sizeof(chunk)) {
×
2290
        return SIXEL_LIBC_ERROR;
×
2291
    }
2292
    if (fwrite(log_palette, 1, (size_t)data_size, stream)
×
2293
            != (size_t)data_size) {
×
2294
        return SIXEL_LIBC_ERROR;
×
2295
    }
2296
    return SIXEL_OK;
×
2297
}
2298

2299

2300
static SIXELSTATUS
2301
sixel_palette_write_gpl(FILE *stream,
×
2302
                        unsigned char const *palette,
2303
                        int exported_colors)
2304
{
2305
    int index;
2306

2307
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2308
        return SIXEL_BAD_ARGUMENT;
×
2309
    }
2310
    if (fprintf(stream, "GIMP Palette\n") < 0) {
×
2311
        return SIXEL_LIBC_ERROR;
×
2312
    }
2313
    if (fprintf(stream, "Name: libsixel export\n") < 0) {
×
2314
        return SIXEL_LIBC_ERROR;
×
2315
    }
2316
    if (fprintf(stream, "Columns: 16\n") < 0) {
×
2317
        return SIXEL_LIBC_ERROR;
×
2318
    }
2319
    if (fprintf(stream, "# Exported by libsixel\n") < 0) {
×
2320
        return SIXEL_LIBC_ERROR;
×
2321
    }
2322
    for (index = 0; index < exported_colors; ++index) {
×
2323
        if (fprintf(stream, "%3d %3d %3d\tIndex %d\n",
×
2324
                    (int)palette[index * 3 + 0],
×
2325
                    (int)palette[index * 3 + 1],
×
2326
                    (int)palette[index * 3 + 2],
×
2327
                    index) < 0) {
2328
            return SIXEL_LIBC_ERROR;
×
2329
        }
2330
    }
2331
    return SIXEL_OK;
×
2332
}
2333

2334

2335
/* create palette from specified map file */
2336
static SIXELSTATUS
2337
sixel_prepare_specified_palette(
21✔
2338
    sixel_dither_t  /* out */   **dither,
2339
    sixel_encoder_t /* in */    *encoder)
2340
{
2341
    SIXELSTATUS status;
2342
    sixel_callback_context_for_mapfile_t callback_context;
2343
    sixel_loader_t *loader;
2344
    int fstatic;
2345
    int fuse_palette;
2346
    int reqcolors;
2347
    int loop_override;
2348
    char const *path;
2349
    sixel_palette_format_t format_hint;
2350
    sixel_palette_format_t format_ext;
2351
    sixel_palette_format_t format_final;
2352
    sixel_palette_format_t format_detected;
2353
    FILE *stream;
2354
    int close_stream;
2355
    unsigned char *buffer;
2356
    size_t buffer_size;
2357
    int palette_request;
2358
    int need_detection;
2359
    int treat_as_image;
2360
    int path_has_extension;
2361

2362
    status = SIXEL_FALSE;
21✔
2363
    loader = NULL;
21✔
2364
    fstatic = 1;
21✔
2365
    fuse_palette = 1;
21✔
2366
    reqcolors = SIXEL_PALETTE_MAX;
21✔
2367
    loop_override = SIXEL_LOOP_DISABLE;
21✔
2368
    path = NULL;
21✔
2369
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
2370
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
21✔
2371
    format_final = SIXEL_PALETTE_FORMAT_NONE;
21✔
2372
    format_detected = SIXEL_PALETTE_FORMAT_NONE;
21✔
2373
    stream = NULL;
21✔
2374
    close_stream = 0;
21✔
2375
    buffer = NULL;
21✔
2376
    buffer_size = 0u;
21✔
2377
    palette_request = 0;
21✔
2378
    need_detection = 0;
21✔
2379
    treat_as_image = 0;
21✔
2380
    path_has_extension = 0;
21✔
2381

2382
    if (dither == NULL || encoder == NULL || encoder->mapfile == NULL) {
21!
2383
        sixel_helper_set_additional_message(
×
2384
            "sixel_prepare_specified_palette: invalid mapfile path.");
2385
        return SIXEL_BAD_ARGUMENT;
×
2386
    }
2387

2388
    path = sixel_palette_strip_prefix(encoder->mapfile, &format_hint);
21✔
2389
    if (path == NULL || *path == '\0') {
21!
2390
        sixel_helper_set_additional_message(
×
2391
            "sixel_prepare_specified_palette: empty mapfile path.");
2392
        return SIXEL_BAD_ARGUMENT;
×
2393
    }
2394

2395
    format_ext = sixel_palette_format_from_extension(path);
21✔
2396
    path_has_extension = sixel_path_has_any_extension(path);
21✔
2397

2398
    if (format_hint != SIXEL_PALETTE_FORMAT_NONE) {
21!
2399
        palette_request = 1;
×
2400
        format_final = format_hint;
×
2401
    } else if (format_ext != SIXEL_PALETTE_FORMAT_NONE) {
21!
2402
        palette_request = 1;
×
2403
        format_final = format_ext;
×
2404
    } else if (!path_has_extension) {
21!
2405
        palette_request = 1;
×
2406
        need_detection = 1;
×
2407
    } else {
2408
        treat_as_image = 1;
21✔
2409
    }
2410

2411
    if (palette_request) {
21!
2412
        status = sixel_palette_open_read(path, &stream, &close_stream);
×
2413
        if (SIXEL_FAILED(status)) {
×
2414
            goto palette_cleanup;
×
2415
        }
2416
        status = sixel_palette_read_stream(stream,
×
2417
                                           encoder->allocator,
2418
                                           &buffer,
2419
                                           &buffer_size);
2420
        if (close_stream) {
×
2421
            sixel_palette_close_stream(stream, close_stream);
×
2422
            stream = NULL;
×
2423
            close_stream = 0;
×
2424
        }
2425
        if (SIXEL_FAILED(status)) {
×
2426
            goto palette_cleanup;
×
2427
        }
2428
        if (buffer_size == 0u) {
×
2429
            sixel_helper_set_additional_message(
×
2430
                "sixel_prepare_specified_palette: mapfile is empty.");
2431
            status = SIXEL_BAD_INPUT;
×
2432
            goto palette_cleanup;
×
2433
        }
2434

2435
        if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
2436
            format_detected = sixel_palette_guess_format(buffer,
×
2437
                                                         buffer_size);
2438
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
2439
                sixel_helper_set_additional_message(
×
2440
                    "sixel_prepare_specified_palette: "
2441
                    "unable to detect palette format.");
2442
                status = SIXEL_BAD_INPUT;
×
2443
                goto palette_cleanup;
×
2444
            }
2445
            format_final = format_detected;
×
2446
        } else if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
2447
            format_detected = sixel_palette_guess_format(buffer,
×
2448
                                                         buffer_size);
2449
            if (format_detected == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
2450
                    format_detected == SIXEL_PALETTE_FORMAT_PAL_RIFF) {
2451
                format_final = format_detected;
×
2452
            } else {
2453
                sixel_helper_set_additional_message(
×
2454
                    "sixel_prepare_specified_palette: "
2455
                    "ambiguous .pal content.");
2456
                status = SIXEL_BAD_INPUT;
×
2457
                goto palette_cleanup;
×
2458
            }
2459
        } else if (need_detection) {
×
2460
            format_detected = sixel_palette_guess_format(buffer,
×
2461
                                                         buffer_size);
2462
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
2463
                sixel_helper_set_additional_message(
×
2464
                    "sixel_prepare_specified_palette: "
2465
                    "unable to detect palette format.");
2466
                status = SIXEL_BAD_INPUT;
×
2467
                goto palette_cleanup;
×
2468
            }
2469
            format_final = format_detected;
×
2470
        }
2471

2472
        switch (format_final) {
×
2473
        case SIXEL_PALETTE_FORMAT_ACT:
2474
            status = sixel_palette_parse_act(buffer,
×
2475
                                             buffer_size,
2476
                                             encoder,
2477
                                             dither);
2478
            break;
×
2479
        case SIXEL_PALETTE_FORMAT_PAL_JASC:
2480
            status = sixel_palette_parse_pal_jasc(buffer,
×
2481
                                                  buffer_size,
2482
                                                  encoder,
2483
                                                  dither);
2484
            break;
×
2485
        case SIXEL_PALETTE_FORMAT_PAL_RIFF:
2486
            status = sixel_palette_parse_pal_riff(buffer,
×
2487
                                                  buffer_size,
2488
                                                  encoder,
2489
                                                  dither);
2490
            break;
×
2491
        case SIXEL_PALETTE_FORMAT_GPL:
2492
            status = sixel_palette_parse_gpl(buffer,
×
2493
                                             buffer_size,
2494
                                             encoder,
2495
                                             dither);
2496
            break;
×
2497
        default:
2498
            sixel_helper_set_additional_message(
×
2499
                "sixel_prepare_specified_palette: "
2500
                "unsupported palette format.");
2501
            status = SIXEL_BAD_INPUT;
×
2502
            break;
×
2503
        }
2504

2505
palette_cleanup:
2506
        if (buffer != NULL) {
×
2507
            sixel_allocator_free(encoder->allocator, buffer);
×
2508
            buffer = NULL;
×
2509
        }
2510
        if (stream != NULL) {
×
2511
            sixel_palette_close_stream(stream, close_stream);
×
2512
            stream = NULL;
×
2513
        }
2514
        if (SIXEL_SUCCEEDED(status)) {
×
2515
            return status;
×
2516
        }
2517
        if (!treat_as_image) {
×
2518
            return status;
×
2519
        }
2520
    }
2521

2522
    callback_context.reqcolors = encoder->reqcolors;
21✔
2523
    callback_context.dither = NULL;
21✔
2524
    callback_context.allocator = encoder->allocator;
21✔
2525
    callback_context.working_colorspace = encoder->working_colorspace;
21✔
2526
    callback_context.lut_policy = encoder->lut_policy;
21✔
2527

2528
    sixel_helper_set_loader_trace(encoder->verbose);
21✔
2529
    sixel_helper_set_thumbnail_size_hint(
21✔
2530
        sixel_encoder_thumbnail_hint(encoder));
7✔
2531
    status = sixel_loader_new(&loader, encoder->allocator);
21✔
2532
    if (SIXEL_FAILED(status)) {
21!
2533
        goto end_loader;
×
2534
    }
2535

2536
    status = sixel_loader_setopt(loader,
21✔
2537
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
2538
                                 &fstatic);
2539
    if (SIXEL_FAILED(status)) {
21!
2540
        goto end_loader;
×
2541
    }
2542

2543
    status = sixel_loader_setopt(loader,
21✔
2544
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
2545
                                 &fuse_palette);
2546
    if (SIXEL_FAILED(status)) {
21!
2547
        goto end_loader;
×
2548
    }
2549

2550
    status = sixel_loader_setopt(loader,
21✔
2551
                                 SIXEL_LOADER_OPTION_REQCOLORS,
2552
                                 &reqcolors);
2553
    if (SIXEL_FAILED(status)) {
21!
2554
        goto end_loader;
×
2555
    }
2556

2557
    status = sixel_loader_setopt(loader,
28✔
2558
                                 SIXEL_LOADER_OPTION_BGCOLOR,
2559
                                 encoder->bgcolor);
21✔
2560
    if (SIXEL_FAILED(status)) {
21!
2561
        goto end_loader;
×
2562
    }
2563

2564
    status = sixel_loader_setopt(loader,
21✔
2565
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
2566
                                 &loop_override);
2567
    if (SIXEL_FAILED(status)) {
21!
2568
        goto end_loader;
×
2569
    }
2570

2571
    status = sixel_loader_setopt(loader,
28✔
2572
                                 SIXEL_LOADER_OPTION_INSECURE,
2573
                                 &encoder->finsecure);
21✔
2574
    if (SIXEL_FAILED(status)) {
21!
2575
        goto end_loader;
×
2576
    }
2577

2578
    status = sixel_loader_setopt(loader,
28✔
2579
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
2580
                                 encoder->cancel_flag);
21✔
2581
    if (SIXEL_FAILED(status)) {
21!
2582
        goto end_loader;
×
2583
    }
2584

2585
    status = sixel_loader_setopt(loader,
28✔
2586
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
2587
                                 encoder->loader_order);
21✔
2588
    if (SIXEL_FAILED(status)) {
21!
2589
        goto end_loader;
×
2590
    }
2591

2592
    status = sixel_loader_setopt(loader,
21✔
2593
                                 SIXEL_LOADER_OPTION_CONTEXT,
2594
                                 &callback_context);
2595
    if (SIXEL_FAILED(status)) {
21!
2596
        goto end_loader;
×
2597
    }
2598

2599
    status = sixel_loader_load_file(loader,
28✔
2600
                                    encoder->mapfile,
21✔
2601
                                    load_image_callback_for_palette);
2602
    if (status != SIXEL_OK) {
21!
2603
        goto end_loader;
×
2604
    }
2605

2606
end_loader:
14✔
2607
    sixel_loader_unref(loader);
21✔
2608

2609
    if (status != SIXEL_OK) {
21!
2610
        return status;
×
2611
    }
2612

2613
    if (! callback_context.dither) {
21!
2614
        sixel_helper_set_additional_message(
×
2615
            "sixel_prepare_specified_palette() failed.\n"
2616
            "reason: mapfile is empty.");
2617
        return SIXEL_BAD_INPUT;
×
2618
    }
2619

2620
    *dither = callback_context.dither;
21✔
2621

2622
    return status;
21✔
2623
}
7✔
2624

2625

2626
/* create dither object from a frame */
2627
static SIXELSTATUS
2628
sixel_encoder_prepare_palette(
520✔
2629
    sixel_encoder_t *encoder,  /* encoder object */
2630
    sixel_frame_t   *frame,    /* input frame object */
2631
    sixel_dither_t  **dither)  /* dither object to be created from the frame */
2632
{
2633
    SIXELSTATUS status = SIXEL_FALSE;
520✔
2634
    int histogram_colors;
2635
    sixel_assessment_t *assessment;
2636
    int promoted_stage;
2637

2638
    assessment = NULL;
520✔
2639
    promoted_stage = 0;
520✔
2640
    if (encoder != NULL) {
520!
2641
        assessment = encoder->assessment_observer;
520✔
2642
    }
174✔
2643

2644
    switch (encoder->color_option) {
520!
2645
    case SIXEL_COLOR_OPTION_HIGHCOLOR:
24✔
2646
        if (encoder->dither_cache) {
36!
2647
            *dither = encoder->dither_cache;
×
2648
            status = SIXEL_OK;
×
2649
        } else {
2650
            status = sixel_dither_new(dither, (-1), encoder->allocator);
36✔
2651
            sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
36✔
2652
        }
2653
        goto end;
36✔
2654
    case SIXEL_COLOR_OPTION_MONOCHROME:
8✔
2655
        if (encoder->dither_cache) {
12!
2656
            *dither = encoder->dither_cache;
×
2657
            status = SIXEL_OK;
×
2658
        } else {
2659
            status = sixel_prepare_monochrome_palette(dither, encoder->finvert);
12✔
2660
        }
2661
        goto end;
12✔
2662
    case SIXEL_COLOR_OPTION_MAPFILE:
14✔
2663
        if (encoder->dither_cache) {
21!
2664
            *dither = encoder->dither_cache;
×
2665
            status = SIXEL_OK;
×
2666
        } else {
2667
            status = sixel_prepare_specified_palette(dither, encoder);
21✔
2668
        }
2669
        goto end;
21✔
2670
    case SIXEL_COLOR_OPTION_BUILTIN:
18✔
2671
        if (encoder->dither_cache) {
27!
2672
            *dither = encoder->dither_cache;
×
2673
            status = SIXEL_OK;
×
2674
        } else {
2675
            status = sixel_prepare_builtin_palette(dither, encoder->builtin_palette);
27✔
2676
        }
2677
        goto end;
27✔
2678
    case SIXEL_COLOR_OPTION_DEFAULT:
424✔
2679
    default:
2680
        break;
424✔
2681
    }
2682

2683
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE) {
424✔
2684
        if (!sixel_frame_get_palette(frame)) {
228!
2685
            status = SIXEL_LOGIC_ERROR;
×
2686
            goto end;
×
2687
        }
2688
        status = sixel_dither_new(dither, sixel_frame_get_ncolors(frame),
304✔
2689
                                  encoder->allocator);
76✔
2690
        if (SIXEL_FAILED(status)) {
228!
2691
            goto end;
×
2692
        }
2693
        sixel_dither_set_palette(*dither, sixel_frame_get_palette(frame));
228✔
2694
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
228✔
2695
        if (sixel_frame_get_transparent(frame) != (-1)) {
228!
2696
            sixel_dither_set_transparent(*dither, sixel_frame_get_transparent(frame));
×
2697
        }
2698
        if (*dither && encoder->dither_cache) {
228!
2699
            sixel_dither_unref(encoder->dither_cache);
×
2700
        }
2701
        goto end;
228✔
2702
    }
2703

2704
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_GRAYSCALE) {
196!
2705
        switch (sixel_frame_get_pixelformat(frame)) {
×
2706
        case SIXEL_PIXELFORMAT_G1:
2707
            *dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
2708
            break;
×
2709
        case SIXEL_PIXELFORMAT_G2:
2710
            *dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
2711
            break;
×
2712
        case SIXEL_PIXELFORMAT_G4:
2713
            *dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
2714
            break;
×
2715
        case SIXEL_PIXELFORMAT_G8:
2716
            *dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
2717
            break;
×
2718
        default:
2719
            *dither = NULL;
×
2720
            status = SIXEL_LOGIC_ERROR;
×
2721
            goto end;
×
2722
        }
2723
        if (*dither && encoder->dither_cache) {
×
2724
            sixel_dither_unref(encoder->dither_cache);
×
2725
        }
2726
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
×
2727
        status = SIXEL_OK;
×
2728
        goto end;
×
2729
    }
2730

2731
    if (encoder->dither_cache) {
196!
2732
        sixel_dither_unref(encoder->dither_cache);
×
2733
    }
2734
    status = sixel_dither_new(dither, encoder->reqcolors, encoder->allocator);
196✔
2735
    if (SIXEL_FAILED(status)) {
196!
2736
        goto end;
×
2737
    }
2738

2739
    sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
196✔
2740
    sixel_dither_set_sixel_reversible(*dither,
262✔
2741
                                      encoder->sixel_reversible);
66✔
2742
    sixel_dither_set_final_merge(*dither, encoder->final_merge_mode);
196✔
2743
    (*dither)->quantize_model = encoder->quantize_model;
196✔
2744

2745
    status = sixel_dither_initialize(*dither,
262✔
2746
                                     sixel_frame_get_pixels(frame),
66✔
2747
                                     sixel_frame_get_width(frame),
66✔
2748
                                     sixel_frame_get_height(frame),
66✔
2749
                                     sixel_frame_get_pixelformat(frame),
66✔
2750
                                     encoder->method_for_largest,
66✔
2751
                                     encoder->method_for_rep,
66✔
2752
                                     encoder->quality_mode);
66✔
2753
    if (SIXEL_FAILED(status)) {
196!
2754
        sixel_dither_unref(*dither);
×
2755
        goto end;
×
2756
    }
2757

2758
    if (assessment != NULL && promoted_stage == 0) {
196!
2759
        sixel_assessment_stage_transition(
3✔
2760
            assessment,
1✔
2761
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2762
        promoted_stage = 1;
3✔
2763
    }
1✔
2764

2765
    histogram_colors = sixel_dither_get_num_of_histogram_colors(*dither);
196✔
2766
    if (histogram_colors <= encoder->reqcolors) {
196✔
2767
        encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
127✔
2768
    }
43✔
2769
    sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
196✔
2770

2771
    status = SIXEL_OK;
196✔
2772

2773
end:
346✔
2774
    if (assessment != NULL && promoted_stage == 0) {
520!
2775
        sixel_assessment_stage_transition(
×
2776
            assessment,
2777
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2778
        promoted_stage = 1;
×
2779
    }
2780
    if (SIXEL_SUCCEEDED(status) && dither != NULL && *dither != NULL) {
520!
2781
        sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
520✔
2782
        /* pass down the user's demand for an exact palette size */
2783
        (*dither)->force_palette = encoder->force_palette;
520✔
2784
    }
174✔
2785
    return status;
520✔
2786
}
2787

2788

2789
/* resize a frame with settings of specified encoder object */
2790
static SIXELSTATUS
2791
sixel_encoder_do_resize(
526✔
2792
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
2793
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
2794
{
2795
    SIXELSTATUS status = SIXEL_FALSE;
526✔
2796
    int src_width;
2797
    int src_height;
2798
    int dst_width;
2799
    int dst_height;
2800

2801
    /* get frame width and height */
2802
    src_width = sixel_frame_get_width(frame);
526✔
2803
    src_height = sixel_frame_get_height(frame);
526✔
2804

2805
    if (src_width < 1) {
526✔
2806
         sixel_helper_set_additional_message(
6✔
2807
             "sixel_encoder_do_resize: "
2808
             "detected a frame with a non-positive width.");
2809
        return SIXEL_BAD_ARGUMENT;
6✔
2810
    }
2811

2812
    if (src_height < 1) {
520!
2813
         sixel_helper_set_additional_message(
×
2814
             "sixel_encoder_do_resize: "
2815
             "detected a frame with a non-positive height.");
2816
        return SIXEL_BAD_ARGUMENT;
×
2817
    }
2818

2819
    /* settings around scaling */
2820
    dst_width = encoder->pixelwidth;    /* may be -1 (default) */
520✔
2821
    dst_height = encoder->pixelheight;  /* may be -1 (default) */
520✔
2822

2823
    /* if the encoder has percentwidth or percentheight property,
2824
       convert them to pixelwidth / pixelheight */
2825
    if (encoder->percentwidth > 0) {
520✔
2826
        dst_width = src_width * encoder->percentwidth / 100;
12✔
2827
    }
4✔
2828
    if (encoder->percentheight > 0) {
520✔
2829
        dst_height = src_height * encoder->percentheight / 100;
9✔
2830
    }
3✔
2831

2832
    /* if only either width or height is set, set also the other
2833
       to retain frame aspect ratio */
2834
    if (dst_width > 0 && dst_height <= 0) {
520✔
2835
        dst_height = src_height * dst_width / src_width;
42✔
2836
    }
14✔
2837
    if (dst_height > 0 && dst_width <= 0) {
520✔
2838
        dst_width = src_width * dst_height / src_height;
33✔
2839
    }
11✔
2840

2841
    /* do resize */
2842
    if (dst_width > 0 && dst_height > 0) {
520!
2843
        status = sixel_frame_resize(frame, dst_width, dst_height,
124✔
2844
                                    encoder->method_for_resampling);
31✔
2845
        if (SIXEL_FAILED(status)) {
93!
2846
            goto end;
×
2847
        }
2848
    }
31✔
2849

2850
    /* success */
2851
    status = SIXEL_OK;
520✔
2852

2853
end:
346✔
2854
    return status;
520✔
2855
}
176✔
2856

2857

2858
/* clip a frame with settings of specified encoder object */
2859
static SIXELSTATUS
2860
sixel_encoder_do_clip(
520✔
2861
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
2862
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
2863
{
2864
    SIXELSTATUS status = SIXEL_FALSE;
520✔
2865
    int src_width;
2866
    int src_height;
2867
    int clip_x;
2868
    int clip_y;
2869
    int clip_w;
2870
    int clip_h;
2871

2872
    /* get frame width and height */
2873
    src_width = sixel_frame_get_width(frame);
520✔
2874
    src_height = sixel_frame_get_height(frame);
520✔
2875

2876
    /* settings around clipping */
2877
    clip_x = encoder->clipx;
520✔
2878
    clip_y = encoder->clipy;
520✔
2879
    clip_w = encoder->clipwidth;
520✔
2880
    clip_h = encoder->clipheight;
520✔
2881

2882
    /* adjust clipping width with comparing it to frame width */
2883
    if (clip_w + clip_x > src_width) {
520✔
2884
        if (clip_x > src_width) {
6✔
2885
            clip_w = 0;
3✔
2886
        } else {
1✔
2887
            clip_w = src_width - clip_x;
3✔
2888
        }
2889
    }
2✔
2890

2891
    /* adjust clipping height with comparing it to frame height */
2892
    if (clip_h + clip_y > src_height) {
520✔
2893
        if (clip_y > src_height) {
6✔
2894
            clip_h = 0;
3✔
2895
        } else {
1✔
2896
            clip_h = src_height - clip_y;
3✔
2897
        }
2898
    }
2✔
2899

2900
    /* do clipping */
2901
    if (clip_w > 0 && clip_h > 0) {
520!
2902
        status = sixel_frame_clip(frame, clip_x, clip_y, clip_w, clip_h);
12✔
2903
        if (SIXEL_FAILED(status)) {
12!
2904
            goto end;
×
2905
        }
2906
    }
4✔
2907

2908
    /* success */
2909
    status = SIXEL_OK;
520✔
2910

2911
end:
346✔
2912
    return status;
520✔
2913
}
2914

2915

2916
static void
2917
sixel_debug_print_palette(
3✔
2918
    sixel_dither_t /* in */ *dither /* dithering object */
2919
)
2920
{
2921
    unsigned char *palette;
2922
    int i;
2923

2924
    palette = sixel_dither_get_palette(dither);
3✔
2925
    fprintf(stderr, "palette:\n");
3✔
2926
    for (i = 0; i < sixel_dither_get_num_of_palette_colors(dither); ++i) {
51✔
2927
        fprintf(stderr, "%d: #%02x%02x%02x\n", i,
64✔
2928
                palette[i * 3 + 0],
48✔
2929
                palette[i * 3 + 1],
48✔
2930
                palette[i * 3 + 2]);
48✔
2931
    }
16✔
2932
}
3✔
2933

2934

2935
static SIXELSTATUS
2936
sixel_encoder_output_without_macro(
436✔
2937
    sixel_frame_t       /* in */ *frame,
2938
    sixel_dither_t      /* in */ *dither,
2939
    sixel_output_t      /* in */ *output,
2940
    sixel_encoder_t     /* in */ *encoder)
2941
{
2942
    SIXELSTATUS status = SIXEL_OK;
436✔
2943
    static unsigned char *p;
2944
    int depth;
2945
    enum { message_buffer_size = 2048 };
2946
    char message[message_buffer_size];
2947
    int nwrite;
2948
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
2949
    int dulation;
2950
    int delay;
2951
    struct timespec tv;
2952
#endif
2953
    unsigned char *pixbuf;
2954
    int width;
2955
    int height;
2956
    int pixelformat = 0;
436✔
2957
    size_t size;
2958
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
436✔
2959
    palette_conversion_t palette_ctx;
2960

2961
    memset(&palette_ctx, 0, sizeof(palette_ctx));
436✔
2962
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
2963
    sixel_clock_t last_clock;
2964
#endif
2965

2966
    if (encoder == NULL) {
436!
2967
        sixel_helper_set_additional_message(
×
2968
            "sixel_encoder_output_without_macro: encoder object is null.");
2969
        status = SIXEL_BAD_ARGUMENT;
×
2970
        goto end;
×
2971
    }
2972

2973
    if (encoder->assessment_observer != NULL) {
436✔
2974
        sixel_assessment_stage_transition(
3✔
2975
            encoder->assessment_observer,
3✔
2976
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
2977
    }
1✔
2978

2979
    if (encoder->color_option == SIXEL_COLOR_OPTION_DEFAULT) {
436✔
2980
        if (encoder->force_palette) {
340!
2981
            /* keep every slot when the user forced the palette size */
2982
            sixel_dither_set_optimize_palette(dither, 0);
×
2983
        } else {
2984
            sixel_dither_set_optimize_palette(dither, 1);
340✔
2985
        }
2986
    }
114✔
2987

2988
    pixelformat = sixel_frame_get_pixelformat(frame);
436✔
2989
    frame_colorspace = sixel_frame_get_colorspace(frame);
436✔
2990
    output->pixelformat = pixelformat;
436✔
2991
    output->source_colorspace = frame_colorspace;
436✔
2992
    output->colorspace = encoder->output_colorspace;
436✔
2993
    sixel_dither_set_pixelformat(dither, pixelformat);
436✔
2994
    depth = sixel_helper_compute_depth(pixelformat);
436✔
2995
    if (depth < 0) {
436!
2996
        status = SIXEL_LOGIC_ERROR;
×
2997
        nwrite = sixel_compat_snprintf(
×
2998
            message,
2999
            sizeof(message),
3000
            "sixel_encoder_output_without_macro: "
3001
            "sixel_helper_compute_depth(%08x) failed.",
3002
            pixelformat);
3003
        if (nwrite > 0) {
×
3004
            sixel_helper_set_additional_message(message);
×
3005
        }
3006
        goto end;
×
3007
    }
3008

3009
    width = sixel_frame_get_width(frame);
436✔
3010
    height = sixel_frame_get_height(frame);
436✔
3011
    size = (size_t)(width * height * depth);
436✔
3012
    p = (unsigned char *)sixel_allocator_malloc(encoder->allocator, size);
436✔
3013
    if (p == NULL) {
436!
3014
        sixel_helper_set_additional_message(
×
3015
            "sixel_encoder_output_without_macro: sixel_allocator_malloc() failed.");
3016
        status = SIXEL_BAD_ALLOCATION;
×
3017
        goto end;
×
3018
    }
3019
#if defined(HAVE_CLOCK)
3020
    if (output->last_clock == 0) {
436!
3021
        output->last_clock = clock();
436✔
3022
    }
146✔
3023
#elif defined(HAVE_CLOCK_WIN)
3024
    if (output->last_clock == 0) {
3025
        output->last_clock = clock_win();
3026
    }
3027
#endif
3028
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3029
    delay = sixel_frame_get_delay(frame);
436✔
3030
    if (delay > 0 && !encoder->fignore_delay) {
436✔
3031
# if defined(HAVE_CLOCK)
3032
        last_clock = clock();
3✔
3033
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
3034
#  if defined(__APPLE__)
3035
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
2✔
3036
                          / 100000);
1✔
3037
#  else
3038
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
2✔
3039
                          / CLOCKS_PER_SEC);
3040
#  endif
3041
        output->last_clock = last_clock;
3✔
3042
# elif defined(HAVE_CLOCK_WIN)
3043
        last_clock = clock_win();
3044
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3045
                          / CLOCKS_PER_SEC);
3046
        output->last_clock = last_clock;
3047
# else
3048
        dulation = 0;
3049
# endif
3050
        if (dulation < 1000 * 10 * delay) {
3!
3051
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3052
            tv.tv_sec = 0;
3✔
3053
            tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
3✔
3054
#  if defined(HAVE_NANOSLEEP)
3055
            nanosleep(&tv, NULL);
3✔
3056
#  else
3057
            nanosleep_win(&tv, NULL);
3058
#  endif
3059
# endif
3060
        }
1✔
3061
    }
1✔
3062
#endif
3063

3064
    pixbuf = sixel_frame_get_pixels(frame);
436✔
3065
    memcpy(p, pixbuf, (size_t)(width * height * depth));
436✔
3066

3067
    status = sixel_output_convert_colorspace(output, p, size);
436✔
3068
    if (SIXEL_FAILED(status)) {
436!
3069
        goto end;
×
3070
    }
3071

3072
    if (encoder->cancel_flag && *encoder->cancel_flag) {
436!
3073
        goto end;
×
3074
    }
3075

3076
    status = sixel_encoder_convert_palette(encoder,
582✔
3077
                                           output,
146✔
3078
                                           dither,
146✔
3079
                                           frame_colorspace,
146✔
3080
                                           pixelformat,
146✔
3081
                                           &palette_ctx);
3082
    if (SIXEL_FAILED(status)) {
436!
3083
        goto end;
×
3084
    }
3085

3086
    if (encoder->capture_quantized) {
436✔
3087
        status = sixel_encoder_capture_quantized(encoder,
4✔
3088
                                                 dither,
1✔
3089
                                                 p,
1✔
3090
                                                 size,
1✔
3091
                                                 width,
1✔
3092
                                                 height,
1✔
3093
                                                 pixelformat,
1✔
3094
                                                 output->colorspace);
1✔
3095
        if (SIXEL_FAILED(status)) {
3!
3096
            goto end;
×
3097
        }
3098
    }
1✔
3099

3100
    if (encoder->assessment_observer != NULL) {
436✔
3101
        sixel_assessment_stage_transition(
3✔
3102
            encoder->assessment_observer,
3✔
3103
            SIXEL_ASSESSMENT_STAGE_ENCODE);
3104
    }
1✔
3105
    status = sixel_encode(p, width, height, depth, dither, output);
436✔
3106
    if (encoder->assessment_observer != NULL) {
436✔
3107
        sixel_assessment_stage_finish(encoder->assessment_observer);
3✔
3108
    }
1✔
3109
    if (status != SIXEL_OK) {
436!
3110
        goto end;
×
3111
    }
3112

3113
end:
290✔
3114
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
436✔
3115
    output->pixelformat = pixelformat;
436✔
3116
    output->source_colorspace = frame_colorspace;
436✔
3117
    sixel_allocator_free(encoder->allocator, p);
436✔
3118

3119
    return status;
436✔
3120
}
3121

3122

3123
static SIXELSTATUS
3124
sixel_encoder_output_with_macro(
84✔
3125
    sixel_frame_t   /* in */ *frame,
3126
    sixel_dither_t  /* in */ *dither,
3127
    sixel_output_t  /* in */ *output,
3128
    sixel_encoder_t /* in */ *encoder)
3129
{
3130
    SIXELSTATUS status = SIXEL_OK;
84✔
3131
    enum { message_buffer_size = 256 };
3132
    char buffer[message_buffer_size];
3133
    int nwrite;
3134
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3135
    int dulation;
3136
    struct timespec tv;
3137
#endif
3138
    int width;
3139
    int height;
3140
    int pixelformat;
3141
    int depth;
3142
    size_t size = 0;
84✔
3143
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
84✔
3144
    unsigned char *converted = NULL;
84✔
3145
    palette_conversion_t palette_ctx;
3146
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3147
    int delay;
3148
#endif
3149
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3150
    sixel_clock_t last_clock;
3151
#endif
3152
    double write_started_at;
3153
    double write_finished_at;
3154
    double write_duration;
3155

3156
    memset(&palette_ctx, 0, sizeof(palette_ctx));
84✔
3157

3158
    if (encoder != NULL && encoder->assessment_observer != NULL) {
84!
3159
        sixel_assessment_stage_transition(
×
3160
            encoder->assessment_observer,
×
3161
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3162
    }
3163

3164
#if defined(HAVE_CLOCK)
3165
    if (output->last_clock == 0) {
84!
3166
        output->last_clock = clock();
84✔
3167
    }
28✔
3168
#elif defined(HAVE_CLOCK_WIN)
3169
    if (output->last_clock == 0) {
3170
        output->last_clock = clock_win();
3171
    }
3172
#endif
3173

3174
    width = sixel_frame_get_width(frame);
84✔
3175
    height = sixel_frame_get_height(frame);
84✔
3176
    pixelformat = sixel_frame_get_pixelformat(frame);
84✔
3177
    depth = sixel_helper_compute_depth(pixelformat);
84✔
3178
    if (depth < 0) {
84!
3179
        status = SIXEL_LOGIC_ERROR;
×
3180
        sixel_helper_set_additional_message(
×
3181
            "sixel_encoder_output_with_macro: "
3182
            "sixel_helper_compute_depth() failed.");
3183
        goto end;
×
3184
    }
3185

3186
    frame_colorspace = sixel_frame_get_colorspace(frame);
84✔
3187
    size = (size_t)width * (size_t)height * (size_t)depth;
84✔
3188
    converted = (unsigned char *)sixel_allocator_malloc(
84✔
3189
        encoder->allocator, size);
28✔
3190
    if (converted == NULL) {
84!
3191
        sixel_helper_set_additional_message(
×
3192
            "sixel_encoder_output_with_macro: "
3193
            "sixel_allocator_malloc() failed.");
3194
        status = SIXEL_BAD_ALLOCATION;
×
3195
        goto end;
×
3196
    }
3197

3198
    memcpy(converted, sixel_frame_get_pixels(frame), size);
84✔
3199
    output->pixelformat = pixelformat;
84✔
3200
    output->source_colorspace = frame_colorspace;
84✔
3201
    output->colorspace = encoder->output_colorspace;
84✔
3202
    status = sixel_output_convert_colorspace(output, converted, size);
84✔
3203
    if (SIXEL_FAILED(status)) {
84!
3204
        goto end;
×
3205
    }
3206

3207
    status = sixel_encoder_convert_palette(encoder,
112✔
3208
                                           output,
28✔
3209
                                           dither,
28✔
3210
                                           frame_colorspace,
28✔
3211
                                           pixelformat,
28✔
3212
                                           &palette_ctx);
3213
    if (SIXEL_FAILED(status)) {
84!
3214
        goto end;
×
3215
    }
3216

3217
    if (sixel_frame_get_loop_no(frame) == 0) {
84✔
3218
        if (encoder->macro_number >= 0) {
54!
3219
            nwrite = sixel_compat_snprintf(
×
3220
                buffer,
3221
                sizeof(buffer),
3222
                "\033P%d;0;1!z",
3223
                encoder->macro_number);
3224
        } else {
3225
            nwrite = sixel_compat_snprintf(
54✔
3226
                buffer,
18✔
3227
                sizeof(buffer),
3228
                "\033P%d;0;1!z",
3229
                sixel_frame_get_frame_no(frame));
18✔
3230
        }
3231
        if (nwrite < 0) {
54!
3232
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3233
            sixel_helper_set_additional_message(
×
3234
                "sixel_encoder_output_with_macro: command format failed.");
3235
            goto end;
×
3236
        }
3237
        write_started_at = 0.0;
54✔
3238
        write_finished_at = 0.0;
54✔
3239
        write_duration = 0.0;
54✔
3240
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3241
            write_started_at = sixel_assessment_timer_now();
×
3242
        }
3243
        nwrite = sixel_write_callback(buffer,
72✔
3244
                                      (int)strlen(buffer),
54✔
3245
                                      &encoder->outfd);
54✔
3246
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3247
            write_finished_at = sixel_assessment_timer_now();
×
3248
            write_duration = write_finished_at - write_started_at;
×
3249
            if (write_duration < 0.0) {
×
3250
                write_duration = 0.0;
×
3251
            }
3252
        }
3253
        if (nwrite < 0) {
54!
3254
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3255
            sixel_helper_set_additional_message(
×
3256
                "sixel_encoder_output_with_macro: "
3257
                "sixel_write_callback() failed.");
3258
            goto end;
×
3259
        }
3260
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3261
            sixel_assessment_record_output_write(
×
3262
                encoder->assessment_observer,
×
3263
                (size_t)nwrite,
3264
                write_duration);
3265
        }
3266

3267
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3268
            sixel_assessment_stage_transition(
×
3269
                encoder->assessment_observer,
×
3270
                SIXEL_ASSESSMENT_STAGE_ENCODE);
3271
        }
3272
        status = sixel_encode(converted,
72✔
3273
                              width,
18✔
3274
                              height,
18✔
3275
                              depth,
18✔
3276
                              dither,
18✔
3277
                              output);
18✔
3278
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3279
            sixel_assessment_stage_finish(
×
3280
                encoder->assessment_observer);
×
3281
        }
3282
        if (SIXEL_FAILED(status)) {
54!
3283
            goto end;
×
3284
        }
3285

3286
        write_started_at = 0.0;
54✔
3287
        write_finished_at = 0.0;
54✔
3288
        write_duration = 0.0;
54✔
3289
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3290
            write_started_at = sixel_assessment_timer_now();
×
3291
        }
3292
        nwrite = sixel_write_callback("\033\\", 2, &encoder->outfd);
54✔
3293
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3294
            write_finished_at = sixel_assessment_timer_now();
×
3295
            write_duration = write_finished_at - write_started_at;
×
3296
            if (write_duration < 0.0) {
×
3297
                write_duration = 0.0;
×
3298
            }
3299
        }
3300
        if (nwrite < 0) {
54!
3301
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3302
            sixel_helper_set_additional_message(
×
3303
                "sixel_encoder_output_with_macro: "
3304
                "sixel_write_callback() failed.");
3305
            goto end;
×
3306
        }
3307
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3308
            sixel_assessment_record_output_write(
×
3309
                encoder->assessment_observer,
×
3310
                (size_t)nwrite,
3311
                write_duration);
3312
        }
3313
    }
18✔
3314
    if (encoder->macro_number < 0) {
111✔
3315
        nwrite = sixel_compat_snprintf(
81✔
3316
            buffer,
27✔
3317
            sizeof(buffer),
3318
            "\033[%d*z",
3319
            sixel_frame_get_frame_no(frame));
27✔
3320
        if (nwrite < 0) {
81!
3321
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3322
            sixel_helper_set_additional_message(
×
3323
                "sixel_encoder_output_with_macro: command format failed.");
3324
        }
3325
        write_started_at = 0.0;
81✔
3326
        write_finished_at = 0.0;
81✔
3327
        write_duration = 0.0;
81✔
3328
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
3329
            write_started_at = sixel_assessment_timer_now();
×
3330
        }
3331
        nwrite = sixel_write_callback(buffer,
108✔
3332
                                      (int)strlen(buffer),
81✔
3333
                                      &encoder->outfd);
81✔
3334
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
3335
            write_finished_at = sixel_assessment_timer_now();
×
3336
            write_duration = write_finished_at - write_started_at;
×
3337
            if (write_duration < 0.0) {
×
3338
                write_duration = 0.0;
×
3339
            }
3340
        }
3341
        if (nwrite < 0) {
81!
3342
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3343
            sixel_helper_set_additional_message(
×
3344
                "sixel_encoder_output_with_macro: "
3345
                "sixel_write_callback() failed.");
3346
            goto end;
×
3347
        }
3348
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
3349
            sixel_assessment_record_output_write(
×
3350
                encoder->assessment_observer,
×
3351
                (size_t)nwrite,
3352
                write_duration);
3353
        }
3354
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3355
        delay = sixel_frame_get_delay(frame);
81✔
3356
        if (delay > 0 && !encoder->fignore_delay) {
81!
3357
# if defined(HAVE_CLOCK)
3358
            last_clock = clock();
54✔
3359
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
3360
#  if defined(__APPLE__)
3361
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
36✔
3362
                             / 100000);
18✔
3363
#  else
3364
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
36✔
3365
                             / CLOCKS_PER_SEC);
3366
#  endif
3367
            output->last_clock = last_clock;
54✔
3368
# elif defined(HAVE_CLOCK_WIN)
3369
            last_clock = clock_win();
3370
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3371
                             / CLOCKS_PER_SEC);
3372
            output->last_clock = last_clock;
3373
# else
3374
            dulation = 0;
3375
# endif
3376
            if (dulation < 1000 * 10 * delay) {
54!
3377
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3378
                tv.tv_sec = 0;
53✔
3379
                tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
53✔
3380
#  if defined(HAVE_NANOSLEEP)
3381
                nanosleep(&tv, NULL);
53✔
3382
#  else
3383
                nanosleep_win(&tv, NULL);
3384
#  endif
3385
# endif
3386
            }
17✔
3387
        }
18✔
3388
#endif
3389
    }
27✔
3390

3391
end:
20✔
3392
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
84✔
3393
    output->pixelformat = pixelformat;
84✔
3394
    output->source_colorspace = frame_colorspace;
84✔
3395
    sixel_allocator_free(encoder->allocator, converted);
84✔
3396

3397
    return status;
84✔
3398
}
3399

3400

3401
static SIXELSTATUS
3402
sixel_encoder_emit_iso2022_chars(
×
3403
    sixel_encoder_t *encoder,
3404
    sixel_frame_t *frame
3405
)
3406
{
3407
    char *buf_p, *buf;
3408
    int col, row;
3409
    int charset;
3410
    int is_96cs;
3411
    unsigned int charset_no;
3412
    unsigned int code;
3413
    int num_cols, num_rows;
3414
    SIXELSTATUS status;
3415
    size_t alloc_size;
3416
    int nwrite;
3417
    int target_fd;
3418
    int chunk_size;
3419

3420
    charset_no = encoder->drcs_charset_no;
×
3421
    if (charset_no == 0u) {
×
3422
        charset_no = 1u;
×
3423
    }
3424
    if (encoder->drcs_mmv == 0) {
×
3425
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3426
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3427
    } else if (encoder->drcs_mmv == 1) {
×
3428
        is_96cs = 0;
×
3429
        charset = (int)(charset_no + 0x3fu);
×
3430
    } else {
3431
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3432
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3433
    }
3434
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3435
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3436
             / encoder->cell_width;
×
3437
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3438
             / encoder->cell_height;
×
3439

3440
    /* cols x rows + designation(4 chars) + SI + SO + LFs */
3441
    alloc_size = num_cols * num_rows + (num_cols * num_rows / 96 + 1) * 4 + 2 + num_rows;
×
3442
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3443
    if (buf == NULL) {
×
3444
        sixel_helper_set_additional_message(
×
3445
            "sixel_encoder_emit_iso2022_chars: sixel_allocator_malloc() failed.");
3446
        status = SIXEL_BAD_ALLOCATION;
×
3447
        goto end;
×
3448
    }
3449

3450
    code = 0x20;
×
3451
    *(buf_p++) = '\016';  /* SI */
×
3452
    *(buf_p++) = '\033';
×
3453
    *(buf_p++) = ')';
×
3454
    *(buf_p++) = ' ';
×
3455
    *(buf_p++) = charset;
×
3456
    for(row = 0; row < num_rows; row++) {
×
3457
        for(col = 0; col < num_cols; col++) {
×
3458
            if ((code & 0x7f) == 0x0) {
×
3459
                if (charset == 0x7e) {
×
3460
                    is_96cs = 1 - is_96cs;
×
3461
                    charset = '0';
×
3462
                } else {
3463
                    charset++;
×
3464
                }
3465
                code = 0x20;
×
3466
                *(buf_p++) = '\033';
×
3467
                *(buf_p++) = is_96cs ? '-': ')';
×
3468
                *(buf_p++) = ' ';
×
3469
                *(buf_p++) = charset;
×
3470
            }
3471
            *(buf_p++) = code++;
×
3472
        }
3473
        *(buf_p++) = '\n';
×
3474
    }
3475
    *(buf_p++) = '\017';  /* SO */
×
3476

3477
    if (encoder->tile_outfd >= 0) {
×
3478
        target_fd = encoder->tile_outfd;
×
3479
    } else {
3480
        target_fd = encoder->outfd;
×
3481
    }
3482

3483
    chunk_size = (int)(buf_p - buf);
×
3484
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3485
                                          buf,
3486
                                          chunk_size,
3487
                                          target_fd);
3488
    if (nwrite != chunk_size) {
×
3489
        sixel_helper_set_additional_message(
×
3490
            "sixel_encoder_emit_iso2022_chars: write() failed.");
3491
        status = SIXEL_RUNTIME_ERROR;
×
3492
        goto end;
×
3493
    }
3494

3495
    sixel_allocator_free(encoder->allocator, buf);
×
3496

3497
    status = SIXEL_OK;
×
3498

3499
end:
3500
    return status;
×
3501
}
3502

3503

3504
/*
3505
 * This routine is derived from mlterm's drcssixel.c
3506
 * (https://raw.githubusercontent.com/arakiken/mlterm/master/drcssixel/drcssixel.c).
3507
 * The original implementation is credited to Araki Ken.
3508
 * Adjusted here to integrate with libsixel's encoder pipeline.
3509
 */
3510
static SIXELSTATUS
3511
sixel_encoder_emit_drcsmmv2_chars(
×
3512
    sixel_encoder_t *encoder,
3513
    sixel_frame_t *frame
3514
)
3515
{
3516
    char *buf_p, *buf;
3517
    int col, row;
3518
    int charset;
3519
    int is_96cs;
3520
    unsigned int charset_no;
3521
    unsigned int code;
3522
    int num_cols, num_rows;
3523
    SIXELSTATUS status;
3524
    size_t alloc_size;
3525
    int nwrite;
3526
    int target_fd;
3527
    int chunk_size;
3528

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

3549
    /* cols x rows x 4(out of BMP) + rows(LFs) */
3550
    alloc_size = num_cols * num_rows * 4 + num_rows;
×
3551
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3552
    if (buf == NULL) {
×
3553
        sixel_helper_set_additional_message(
×
3554
            "sixel_encoder_emit_drcsmmv2_chars: sixel_allocator_malloc() failed.");
3555
        status = SIXEL_BAD_ALLOCATION;
×
3556
        goto end;
×
3557
    }
3558

3559
    for(row = 0; row < num_rows; row++) {
×
3560
        for(col = 0; col < num_cols; col++) {
×
3561
            *(buf_p++) = ((code >> 18) & 0x07) | 0xf0;
×
3562
            *(buf_p++) = ((code >> 12) & 0x3f) | 0x80;
×
3563
            *(buf_p++) = ((code >> 6) & 0x3f) | 0x80;
×
3564
            *(buf_p++) = (code & 0x3f) | 0x80;
×
3565
            code++;
×
3566
            if ((code & 0x7f) == 0x0) {
×
3567
                if (charset == 0x7e) {
×
3568
                    is_96cs = 1 - is_96cs;
×
3569
                    charset = '0';
×
3570
                } else {
3571
                    charset++;
×
3572
                }
3573
                code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3574
            }
3575
        }
3576
        *(buf_p++) = '\n';
×
3577
    }
3578

3579
    if (charset == 0x7e) {
×
3580
        is_96cs = 1 - is_96cs;
×
3581
    } else {
3582
        charset = '0';
×
3583
        charset++;
×
3584
    }
3585
    if (encoder->tile_outfd >= 0) {
×
3586
        target_fd = encoder->tile_outfd;
×
3587
    } else {
3588
        target_fd = encoder->outfd;
×
3589
    }
3590

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

3603
    sixel_allocator_free(encoder->allocator, buf);
×
3604

3605
    status = SIXEL_OK;
×
3606

3607
end:
3608
    return status;
×
3609
}
3610

3611
static SIXELSTATUS
3612
sixel_encoder_encode_frame(
526✔
3613
    sixel_encoder_t *encoder,
3614
    sixel_frame_t   *frame,
3615
    sixel_output_t  *output)
3616
{
3617
    SIXELSTATUS status = SIXEL_FALSE;
526✔
3618
    sixel_dither_t *dither = NULL;
526✔
3619
    int height;
3620
    int is_animation = 0;
526✔
3621
    int nwrite;
3622
    int drcs_is_96cs_param;
3623
    int drcs_designate_char;
3624
    char buf[256];
3625
    sixel_write_function fn_write;
3626
    sixel_write_function write_callback;
3627
    sixel_write_function scroll_callback;
3628
    void *write_priv;
3629
    void *scroll_priv;
3630
    sixel_encoder_output_probe_t probe;
3631
    sixel_encoder_output_probe_t scroll_probe;
3632
    sixel_assessment_t *assessment;
3633

3634
    fn_write = sixel_write_callback;
526✔
3635
    write_callback = sixel_write_callback;
526✔
3636
    scroll_callback = sixel_write_callback;
526✔
3637
    write_priv = &encoder->outfd;
526✔
3638
    scroll_priv = &encoder->outfd;
526✔
3639
    probe.encoder = NULL;
526✔
3640
    probe.base_write = NULL;
526✔
3641
    probe.base_priv = NULL;
526✔
3642
    scroll_probe.encoder = NULL;
526✔
3643
    scroll_probe.base_write = NULL;
526✔
3644
    scroll_probe.base_priv = NULL;
526✔
3645
    assessment = NULL;
526✔
3646
    if (encoder != NULL) {
526!
3647
        assessment = encoder->assessment_observer;
526✔
3648
    }
176✔
3649
    if (assessment != NULL) {
526✔
3650
        if (encoder->clipfirst) {
3!
3651
            sixel_assessment_stage_transition(
×
3652
                assessment,
3653
                SIXEL_ASSESSMENT_STAGE_CROP);
3654
        } else {
3655
            sixel_assessment_stage_transition(
3✔
3656
                assessment,
1✔
3657
                SIXEL_ASSESSMENT_STAGE_SCALE);
3658
        }
3659
    }
1✔
3660

3661
    /*
3662
     *  Geometry timeline:
3663
     *
3664
     *      +-------+    +------+    +---------------+
3665
     *      | scale | -> | crop | -> | color convert |
3666
     *      +-------+    +------+    +---------------+
3667
     *
3668
     *  The order of the first two blocks mirrors `-c`, so we hop between
3669
     *  SCALE and CROP depending on `clipfirst`.
3670
     */
3671

3672
    if (encoder->clipfirst) {
526✔
3673
        status = sixel_encoder_do_clip(encoder, frame);
6✔
3674
        if (SIXEL_FAILED(status)) {
6!
3675
            goto end;
×
3676
        }
3677
        if (assessment != NULL) {
6!
3678
            sixel_assessment_stage_transition(
×
3679
                assessment,
3680
                SIXEL_ASSESSMENT_STAGE_SCALE);
3681
        }
3682
        status = sixel_encoder_do_resize(encoder, frame);
6✔
3683
        if (SIXEL_FAILED(status)) {
6!
3684
            goto end;
×
3685
        }
3686
    } else {
2✔
3687
        status = sixel_encoder_do_resize(encoder, frame);
520✔
3688
        if (SIXEL_FAILED(status)) {
520✔
3689
            goto end;
6✔
3690
        }
3691
        if (assessment != NULL) {
514✔
3692
            sixel_assessment_stage_transition(
3✔
3693
                assessment,
1✔
3694
                SIXEL_ASSESSMENT_STAGE_CROP);
3695
        }
1✔
3696
        status = sixel_encoder_do_clip(encoder, frame);
514✔
3697
        if (SIXEL_FAILED(status)) {
514!
3698
            goto end;
×
3699
        }
3700
    }
3701

3702
    if (assessment != NULL) {
520✔
3703
        sixel_assessment_stage_transition(
3✔
3704
            assessment,
1✔
3705
            SIXEL_ASSESSMENT_STAGE_COLORSPACE);
3706
    }
1✔
3707

3708
    status = sixel_frame_ensure_colorspace(frame,
694✔
3709
                                           encoder->working_colorspace);
174✔
3710
    if (SIXEL_FAILED(status)) {
520!
3711
        goto end;
×
3712
    }
3713

3714
    if (assessment != NULL) {
520✔
3715
        sixel_assessment_stage_transition(
3✔
3716
            assessment,
1✔
3717
            SIXEL_ASSESSMENT_STAGE_PALETTE_HISTOGRAM);
3718
    }
1✔
3719

3720
    /* prepare dither context */
3721
    status = sixel_encoder_prepare_palette(encoder, frame, &dither);
520✔
3722
    if (status != SIXEL_OK) {
520!
3723
        dither = NULL;
×
3724
        goto end;
×
3725
    }
3726

3727
    if (encoder->dither_cache != NULL) {
520!
3728
        encoder->dither_cache = dither;
×
3729
        sixel_dither_ref(dither);
×
3730
    }
3731

3732
    if (encoder->fdrcs) {
520!
3733
        status = sixel_encoder_ensure_cell_size(encoder);
×
3734
        if (SIXEL_FAILED(status)) {
×
3735
            goto end;
×
3736
        }
3737
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
×
3738
            sixel_helper_set_additional_message(
×
3739
                "drcs option cannot be used together with macro output.");
3740
            status = SIXEL_BAD_ARGUMENT;
×
3741
            goto end;
×
3742
        }
3743
    }
3744

3745
    /* evaluate -v option: print palette */
3746
    if (encoder->verbose) {
520✔
3747
        if ((sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE)) {
9✔
3748
            sixel_debug_print_palette(dither);
3✔
3749
        }
1✔
3750
    }
3✔
3751

3752
    /* evaluate -d option: set method for diffusion */
3753
    sixel_dither_set_diffusion_type(dither, encoder->method_for_diffuse);
520✔
3754
    sixel_dither_set_diffusion_scan(dither, encoder->method_for_scan);
520✔
3755
    sixel_dither_set_diffusion_carry(dither, encoder->method_for_carry);
520✔
3756

3757
    /* evaluate -C option: set complexion score */
3758
    if (encoder->complexion > 1) {
520✔
3759
        sixel_dither_set_complexion_score(dither, encoder->complexion);
6✔
3760
    }
2✔
3761

3762
    if (output) {
520!
3763
        sixel_output_ref(output);
×
3764
        if (encoder->assessment_observer != NULL) {
×
3765
            probe.encoder = encoder;
×
3766
            probe.base_write = fn_write;
×
3767
            probe.base_priv = &encoder->outfd;
×
3768
            write_callback = sixel_write_with_probe;
×
3769
            write_priv = &probe;
×
3770
        }
3771
    } else {
3772
        /* create output context */
3773
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
520✔
3774
            /* -u or -n option */
3775
            fn_write = sixel_hex_write_callback;
84✔
3776
        } else {
28✔
3777
            fn_write = sixel_write_callback;
436✔
3778
        }
3779
        write_callback = fn_write;
520✔
3780
        write_priv = &encoder->outfd;
520✔
3781
        if (encoder->assessment_observer != NULL) {
520✔
3782
            probe.encoder = encoder;
3✔
3783
            probe.base_write = fn_write;
3✔
3784
            probe.base_priv = &encoder->outfd;
3✔
3785
            write_callback = sixel_write_with_probe;
3✔
3786
            write_priv = &probe;
3✔
3787
        }
1✔
3788
        status = sixel_output_new(&output,
520✔
3789
                                  write_callback,
174✔
3790
                                  write_priv,
174✔
3791
                                  encoder->allocator);
174✔
3792
        if (SIXEL_FAILED(status)) {
520!
3793
            goto end;
×
3794
        }
3795
    }
3796

3797
    if (encoder->fdrcs) {
520!
3798
        sixel_output_set_skip_dcs_envelope(output, 1);
×
3799
        sixel_output_set_skip_header(output, 1);
×
3800
    }
3801

3802
    sixel_output_set_8bit_availability(output, encoder->f8bit);
520✔
3803
    sixel_output_set_gri_arg_limit(output, encoder->has_gri_arg_limit);
520✔
3804
    sixel_output_set_palette_type(output, encoder->palette_type);
520✔
3805
    sixel_output_set_penetrate_multiplexer(
520✔
3806
        output, encoder->penetrate_multiplexer);
174✔
3807
    sixel_output_set_encode_policy(output, encoder->encode_policy);
520✔
3808
    sixel_output_set_ormode(output, encoder->ormode);
520✔
3809

3810
    if (sixel_frame_get_multiframe(frame) && !encoder->fstatic) {
520✔
3811
        if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
108✔
3812
            is_animation = 1;
99✔
3813
        }
33✔
3814
        height = sixel_frame_get_height(frame);
108✔
3815
        if (encoder->assessment_observer != NULL) {
108!
3816
            scroll_probe.encoder = encoder;
×
3817
            scroll_probe.base_write = sixel_write_callback;
×
3818
            scroll_probe.base_priv = &encoder->outfd;
×
3819
            scroll_callback = sixel_write_with_probe;
×
3820
            scroll_priv = &scroll_probe;
×
3821
        } else {
3822
            scroll_callback = sixel_write_callback;
108✔
3823
            scroll_priv = &encoder->outfd;
108✔
3824
        }
3825
        (void) sixel_tty_scroll(scroll_callback,
144✔
3826
                                scroll_priv,
36✔
3827
                                encoder->outfd,
36✔
3828
                                height,
36✔
3829
                                is_animation);
36✔
3830
    }
36✔
3831

3832
    if (encoder->cancel_flag && *encoder->cancel_flag) {
520!
3833
        status = SIXEL_INTERRUPTED;
×
3834
        goto end;
×
3835
    }
3836

3837
    if (encoder->fdrcs) {  /* -@ option */
520!
3838
        if (encoder->drcs_mmv == 0) {
×
3839
            drcs_is_96cs_param =
×
3840
                (encoder->drcs_charset_no > 63u) ? 1 : 0;
×
3841
            drcs_designate_char =
×
3842
                (int)(((encoder->drcs_charset_no - 1u) % 63u) + 0x40u);
×
3843
        } else if (encoder->drcs_mmv == 1) {
×
3844
            drcs_is_96cs_param = 0;
×
3845
            drcs_designate_char =
×
3846
                (int)(encoder->drcs_charset_no + 0x3fu);
×
3847
        } else {
3848
            drcs_is_96cs_param =
×
3849
                (encoder->drcs_charset_no > 79u) ? 1 : 0;
×
3850
            drcs_designate_char =
×
3851
                (int)(((encoder->drcs_charset_no - 1u) % 79u) + 0x30u);
×
3852
        }
3853
        nwrite = sprintf(buf,
×
3854
                         "%s%s1;0;0;%d;1;3;%d;%d{ %c",
3855
                         (encoder->drcs_mmv > 0) ? (
×
3856
                             encoder->f8bit ? "\233?8800h": "\033[?8800h"
×
3857
                         ): "",
3858
                         encoder->f8bit ? "\220": "\033P",
×
3859
                         encoder->cell_width,
3860
                         encoder->cell_height,
3861
                         drcs_is_96cs_param,
3862
                         drcs_designate_char);
3863
        if (nwrite < 0) {
×
3864
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3865
            sixel_helper_set_additional_message(
×
3866
                "sixel_encoder_encode_frame: command format failed.");
3867
            goto end;
×
3868
        }
3869
        nwrite = write_callback(buf, nwrite, write_priv);
×
3870
        if (nwrite < 0) {
×
3871
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3872
            sixel_helper_set_additional_message(
×
3873
                "sixel_encoder_encode_frame: write() failed.");
3874
            goto end;
×
3875
        }
3876
    }
3877

3878
    /* output sixel: junction of multi-frame processing strategy */
3879
    if (encoder->fuse_macro) {  /* -u option */
520✔
3880
        /* use macro */
3881
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
81✔
3882
    } else if (encoder->macro_number >= 0) { /* -n option */
466✔
3883
        /* use macro */
3884
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
3✔
3885
    } else {
1✔
3886
        /* do not use macro */
3887
        status = sixel_encoder_output_without_macro(frame, dither, output, encoder);
436✔
3888
    }
3889
    if (SIXEL_FAILED(status)) {
520!
3890
        goto end;
×
3891
    }
3892

3893
    if (encoder->cancel_flag && *encoder->cancel_flag) {
520!
3894
        nwrite = write_callback("\x18\033\\", 3, write_priv);
×
3895
        if (nwrite < 0) {
×
3896
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3897
            sixel_helper_set_additional_message(
×
3898
                "sixel_encoder_encode_frame: write_callback() failed.");
3899
            goto end;
×
3900
        }
3901
        status = SIXEL_INTERRUPTED;
×
3902
    }
3903
    if (SIXEL_FAILED(status)) {
520!
3904
        goto end;
×
3905
    }
3906

3907
    if (encoder->fdrcs) {  /* -@ option */
520!
3908
        if (encoder->f8bit) {
×
3909
            nwrite = write_callback("\234", 1, write_priv);
×
3910
        } else {
3911
            nwrite = write_callback("\033\\", 2, write_priv);
×
3912
        }
3913
        if (nwrite < 0) {
×
3914
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3915
            sixel_helper_set_additional_message(
×
3916
                "sixel_encoder_encode_frame: write_callback() failed.");
3917
            goto end;
×
3918
        }
3919

3920
        if (encoder->tile_outfd >= 0) {
×
3921
            if (encoder->drcs_mmv == 0) {
×
3922
                status = sixel_encoder_emit_iso2022_chars(encoder, frame);
×
3923
                if (SIXEL_FAILED(status)) {
×
3924
                    goto end;
×
3925
                }
3926
            } else {
3927
                status = sixel_encoder_emit_drcsmmv2_chars(encoder, frame);
×
3928
                if (SIXEL_FAILED(status)) {
×
3929
                    goto end;
×
3930
                }
3931
            }
3932
        }
3933
    }
3934

3935

3936
end:
346✔
3937
    if (output) {
526✔
3938
        sixel_output_unref(output);
520✔
3939
    }
174✔
3940
    if (dither) {
526✔
3941
        sixel_dither_unref(dither);
520✔
3942
    }
174✔
3943

3944
    return status;
526✔
3945
}
3946

3947

3948
/* create encoder object */
3949
SIXELAPI SIXELSTATUS
3950
sixel_encoder_new(
545✔
3951
    sixel_encoder_t     /* out */ **ppencoder, /* encoder object to be created */
3952
    sixel_allocator_t   /* in */  *allocator)  /* allocator, null if you use
3953
                                                  default allocator */
3954
{
3955
    SIXELSTATUS status = SIXEL_FALSE;
545✔
3956
    char const *env_default_bgcolor = NULL;
545✔
3957
    char const *env_default_ncolors = NULL;
545✔
3958
    int ncolors;
3959
#if HAVE__DUPENV_S
3960
    errno_t e;
3961
    size_t len;
3962
#endif  /* HAVE__DUPENV_S */
3963

3964
    if (allocator == NULL) {
545!
3965
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
545✔
3966
        if (SIXEL_FAILED(status)) {
545!
3967
            goto end;
×
3968
        }
3969
    } else {
183✔
3970
        sixel_allocator_ref(allocator);
×
3971
    }
3972

3973
    *ppencoder
183✔
3974
        = (sixel_encoder_t *)sixel_allocator_malloc(allocator,
728✔
3975
                                                    sizeof(sixel_encoder_t));
3976
    if (*ppencoder == NULL) {
545!
3977
        sixel_helper_set_additional_message(
×
3978
            "sixel_encoder_new: sixel_allocator_malloc() failed.");
3979
        status = SIXEL_BAD_ALLOCATION;
×
3980
        sixel_allocator_unref(allocator);
×
3981
        goto end;
×
3982
    }
3983

3984
    (*ppencoder)->ref                   = 1;
545✔
3985
    (*ppencoder)->reqcolors             = (-1);
545✔
3986
    (*ppencoder)->force_palette         = 0;
545✔
3987
    (*ppencoder)->mapfile               = NULL;
545✔
3988
    (*ppencoder)->palette_output        = NULL;
545✔
3989
    (*ppencoder)->loader_order          = NULL;
545✔
3990
    (*ppencoder)->color_option          = SIXEL_COLOR_OPTION_DEFAULT;
545✔
3991
    (*ppencoder)->builtin_palette       = 0;
545✔
3992
    (*ppencoder)->method_for_diffuse    = SIXEL_DIFFUSE_AUTO;
545✔
3993
    (*ppencoder)->method_for_scan       = SIXEL_SCAN_AUTO;
545✔
3994
    (*ppencoder)->method_for_carry      = SIXEL_CARRY_AUTO;
545✔
3995
    (*ppencoder)->method_for_largest    = SIXEL_LARGE_AUTO;
545✔
3996
    (*ppencoder)->method_for_rep        = SIXEL_REP_AUTO;
545✔
3997
    (*ppencoder)->quality_mode          = SIXEL_QUALITY_AUTO;
545✔
3998
    (*ppencoder)->quantize_model        = SIXEL_QUANTIZE_MODEL_AUTO;
545✔
3999
    (*ppencoder)->final_merge_mode      = SIXEL_FINAL_MERGE_AUTO;
545✔
4000
    (*ppencoder)->lut_policy            = SIXEL_LUT_POLICY_AUTO;
545✔
4001
    (*ppencoder)->sixel_reversible      = 0;
545✔
4002
    (*ppencoder)->method_for_resampling = SIXEL_RES_BILINEAR;
545✔
4003
    (*ppencoder)->loop_mode             = SIXEL_LOOP_AUTO;
545✔
4004
    (*ppencoder)->palette_type          = SIXEL_PALETTETYPE_AUTO;
545✔
4005
    (*ppencoder)->f8bit                 = 0;
545✔
4006
    (*ppencoder)->has_gri_arg_limit     = 0;
545✔
4007
    (*ppencoder)->finvert               = 0;
545✔
4008
    (*ppencoder)->fuse_macro            = 0;
545✔
4009
    (*ppencoder)->fdrcs                 = 0;
545✔
4010
    (*ppencoder)->fignore_delay         = 0;
545✔
4011
    (*ppencoder)->complexion            = 1;
545✔
4012
    (*ppencoder)->fstatic               = 0;
545✔
4013
    (*ppencoder)->cell_width            = 0;
545✔
4014
    (*ppencoder)->cell_height           = 0;
545✔
4015
    (*ppencoder)->pixelwidth            = (-1);
545✔
4016
    (*ppencoder)->pixelheight           = (-1);
545✔
4017
    (*ppencoder)->percentwidth          = (-1);
545✔
4018
    (*ppencoder)->percentheight         = (-1);
545✔
4019
    (*ppencoder)->clipx                 = 0;
545✔
4020
    (*ppencoder)->clipy                 = 0;
545✔
4021
    (*ppencoder)->clipwidth             = 0;
545✔
4022
    (*ppencoder)->clipheight            = 0;
545✔
4023
    (*ppencoder)->clipfirst             = 0;
545✔
4024
    (*ppencoder)->macro_number          = (-1);
545✔
4025
    (*ppencoder)->verbose               = 0;
545✔
4026
    (*ppencoder)->penetrate_multiplexer = 0;
545✔
4027
    (*ppencoder)->encode_policy         = SIXEL_ENCODEPOLICY_AUTO;
545✔
4028
    (*ppencoder)->working_colorspace    = SIXEL_COLORSPACE_GAMMA;
545✔
4029
    (*ppencoder)->output_colorspace     = SIXEL_COLORSPACE_GAMMA;
545✔
4030
    (*ppencoder)->ormode                = 0;
545✔
4031
    (*ppencoder)->pipe_mode             = 0;
545✔
4032
    (*ppencoder)->bgcolor               = NULL;
545✔
4033
    (*ppencoder)->outfd                 = STDOUT_FILENO;
545✔
4034
    (*ppencoder)->tile_outfd            = (-1);
545✔
4035
    (*ppencoder)->finsecure             = 0;
545✔
4036
    (*ppencoder)->cancel_flag           = NULL;
545✔
4037
    (*ppencoder)->dither_cache          = NULL;
545✔
4038
    (*ppencoder)->drcs_charset_no       = 1u;
545✔
4039
    (*ppencoder)->drcs_mmv              = 2;
545✔
4040
    (*ppencoder)->capture_quantized     = 0;
545✔
4041
    (*ppencoder)->capture_source        = 0;
545✔
4042
    (*ppencoder)->capture_pixels        = NULL;
545✔
4043
    (*ppencoder)->capture_pixels_size   = 0;
545✔
4044
    (*ppencoder)->capture_palette       = NULL;
545✔
4045
    (*ppencoder)->capture_palette_size  = 0;
545✔
4046
    (*ppencoder)->capture_pixel_bytes   = 0;
545✔
4047
    (*ppencoder)->capture_width         = 0;
545✔
4048
    (*ppencoder)->capture_height        = 0;
545✔
4049
    (*ppencoder)->capture_pixelformat   = SIXEL_PIXELFORMAT_RGB888;
545✔
4050
    (*ppencoder)->capture_colorspace    = SIXEL_COLORSPACE_GAMMA;
545✔
4051
    (*ppencoder)->capture_ncolors       = 0;
545✔
4052
    (*ppencoder)->capture_valid         = 0;
545✔
4053
    (*ppencoder)->capture_source_frame  = NULL;
545✔
4054
    (*ppencoder)->assessment_observer   = NULL;
545✔
4055
    (*ppencoder)->assessment_json_path  = NULL;
545✔
4056
    (*ppencoder)->assessment_sections   = SIXEL_ASSESSMENT_SECTION_NONE;
545✔
4057
    (*ppencoder)->last_loader_name[0]   = '\0';
545✔
4058
    (*ppencoder)->last_source_path[0]   = '\0';
545✔
4059
    (*ppencoder)->last_input_bytes      = 0u;
545✔
4060
    (*ppencoder)->output_is_png         = 0;
545✔
4061
    (*ppencoder)->output_png_to_stdout  = 0;
545✔
4062
    (*ppencoder)->png_output_path       = NULL;
545✔
4063
    (*ppencoder)->sixel_output_path     = NULL;
545✔
4064
    (*ppencoder)->clipboard_output_active = 0;
545✔
4065
    (*ppencoder)->clipboard_output_format[0] = '\0';
545✔
4066
    (*ppencoder)->clipboard_output_path = NULL;
545✔
4067
    (*ppencoder)->allocator             = allocator;
545✔
4068

4069
    /* evaluate environment variable ${SIXEL_BGCOLOR} */
4070
#if HAVE__DUPENV_S
4071
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_BGCOLOR");
4072
    if (e != (0)) {
4073
        sixel_helper_set_additional_message(
4074
            "failed to get environment variable $SIXEL_BGCOLOR.");
4075
        return (SIXEL_LIBC_ERROR | (e & 0xff));
4076
    }
4077
#else
4078
    env_default_bgcolor = sixel_compat_getenv("SIXEL_BGCOLOR");
545✔
4079
#endif  /* HAVE__DUPENV_S */
4080
    if (env_default_bgcolor != NULL) {
545!
4081
        status = sixel_parse_x_colorspec(&(*ppencoder)->bgcolor,
×
4082
                                         env_default_bgcolor,
4083
                                         allocator);
4084
        if (SIXEL_FAILED(status)) {
×
4085
            goto error;
×
4086
        }
4087
    }
4088

4089
    /* evaluate environment variable ${SIXEL_COLORS} */
4090
#if HAVE__DUPENV_S
4091
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_COLORS");
4092
    if (e != (0)) {
4093
        sixel_helper_set_additional_message(
4094
            "failed to get environment variable $SIXEL_COLORS.");
4095
        return (SIXEL_LIBC_ERROR | (e & 0xff));
4096
    }
4097
#else
4098
    env_default_ncolors = sixel_compat_getenv("SIXEL_COLORS");
545✔
4099
#endif  /* HAVE__DUPENV_S */
4100
    if (env_default_ncolors) {
545!
4101
        ncolors = atoi(env_default_ncolors); /* may overflow */
×
4102
        if (ncolors > 1 && ncolors <= SIXEL_PALETTE_MAX) {
×
4103
            (*ppencoder)->reqcolors = ncolors;
×
4104
        }
4105
    }
4106

4107
    /* success */
4108
    status = SIXEL_OK;
545✔
4109

4110
    goto end;
545✔
4111

4112
error:
4113
    sixel_allocator_free(allocator, *ppencoder);
×
4114
    sixel_allocator_unref(allocator);
×
4115
    *ppencoder = NULL;
×
4116

4117
end:
362✔
4118
#if HAVE__DUPENV_S
4119
    free(env_default_bgcolor);
4120
    free(env_default_ncolors);
4121
#endif  /* HAVE__DUPENV_S */
4122
    return status;
545✔
4123
}
4124

4125

4126
/* create encoder object (deprecated version) */
4127
SIXELAPI /* deprecated */ sixel_encoder_t *
4128
sixel_encoder_create(void)
×
4129
{
4130
    SIXELSTATUS status = SIXEL_FALSE;
×
4131
    sixel_encoder_t *encoder = NULL;
×
4132

4133
    status = sixel_encoder_new(&encoder, NULL);
×
4134
    if (SIXEL_FAILED(status)) {
×
4135
        return NULL;
×
4136
    }
4137

4138
    return encoder;
×
4139
}
4140

4141

4142
/* destroy encoder object */
4143
static void
4144
sixel_encoder_destroy(sixel_encoder_t *encoder)
545✔
4145
{
4146
    sixel_allocator_t *allocator;
4147

4148
    if (encoder) {
545!
4149
        allocator = encoder->allocator;
545✔
4150
        sixel_allocator_free(allocator, encoder->mapfile);
545✔
4151
        sixel_allocator_free(allocator, encoder->palette_output);
545✔
4152
        sixel_allocator_free(allocator, encoder->loader_order);
545✔
4153
        sixel_allocator_free(allocator, encoder->bgcolor);
545✔
4154
        sixel_dither_unref(encoder->dither_cache);
545✔
4155
        if (encoder->outfd
555!
4156
            && encoder->outfd != STDOUT_FILENO
545!
4157
            && encoder->outfd != STDERR_FILENO) {
203!
4158
            (void)sixel_compat_close(encoder->outfd);
30✔
4159
        }
10✔
4160
        if (encoder->tile_outfd >= 0
545!
4161
            && encoder->tile_outfd != encoder->outfd
183!
4162
            && encoder->tile_outfd != STDOUT_FILENO
×
4163
            && encoder->tile_outfd != STDERR_FILENO) {
×
4164
            (void)sixel_compat_close(encoder->tile_outfd);
×
4165
        }
4166
        if (encoder->capture_source_frame != NULL) {
545✔
4167
            sixel_frame_unref(encoder->capture_source_frame);
3✔
4168
        }
1✔
4169
        if (encoder->clipboard_output_path != NULL) {
545!
4170
            (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
4171
            encoder->clipboard_output_path = NULL;
×
4172
        }
4173
        encoder->clipboard_output_active = 0;
545✔
4174
        encoder->clipboard_output_format[0] = '\0';
545✔
4175
        sixel_allocator_free(allocator, encoder->capture_pixels);
545✔
4176
        sixel_allocator_free(allocator, encoder->capture_palette);
545✔
4177
        sixel_allocator_free(allocator, encoder->png_output_path);
545✔
4178
        sixel_allocator_free(allocator, encoder->sixel_output_path);
545✔
4179
        sixel_allocator_free(allocator, encoder);
545✔
4180
        sixel_allocator_unref(allocator);
545✔
4181
    }
183✔
4182
}
545✔
4183

4184

4185
/* increase reference count of encoder object (thread-unsafe) */
4186
SIXELAPI void
4187
sixel_encoder_ref(sixel_encoder_t *encoder)
1,154✔
4188
{
4189
    /* TODO: be thread safe */
4190
    ++encoder->ref;
1,154✔
4191
}
1,154✔
4192

4193

4194
/* decrease reference count of encoder object (thread-unsafe) */
4195
SIXELAPI void
4196
sixel_encoder_unref(sixel_encoder_t *encoder)
1,700✔
4197
{
4198
    /* TODO: be thread safe */
4199
    if (encoder != NULL && --encoder->ref == 0) {
1,700!
4200
        sixel_encoder_destroy(encoder);
545✔
4201
    }
183✔
4202
}
1,700✔
4203

4204

4205
/* set cancel state flag to encoder object */
4206
SIXELAPI SIXELSTATUS
4207
sixel_encoder_set_cancel_flag(
440✔
4208
    sixel_encoder_t /* in */ *encoder,
4209
    int             /* in */ *cancel_flag
4210
)
4211
{
4212
    SIXELSTATUS status = SIXEL_OK;
440✔
4213

4214
    encoder->cancel_flag = cancel_flag;
440✔
4215

4216
    return status;
440✔
4217
}
4218

4219

4220
/*
4221
 * parse_assessment_sections() translates a comma-separated section list into
4222
 * the bitmask understood by the assessment back-end.  The accepted grammar is
4223
 * intentionally small so that the CLI contract stays predictable:
4224
 *
4225
 *     list := section {"," section}
4226
 *     section := name | name "@" view
4227
 *
4228
 * Each name maps to a bit flag while the optional view toggles the encoded vs
4229
 * quantized quality comparison.  The helper folds case, trims ASCII
4230
 * whitespace, and rejects mixed encoded/quantized requests so that the caller
4231
 * can rely on a single coherent capture strategy.
4232
 */
4233
static int
4234
parse_assessment_sections(char const *spec,
6✔
4235
                          unsigned int *out_sections)
4236
{
4237
    char *copy;
4238
    char *cursor;
4239
    char *token;
4240
    char *context;
4241
    unsigned int sections;
4242
    unsigned int view;
4243
    int result;
4244
    size_t length;
4245
    size_t spec_len;
4246
    char *at;
4247
    char *base;
4248
    char *variant;
4249
    char *p;
4250

4251
    if (spec == NULL || out_sections == NULL) {
6!
4252
        return -1;
×
4253
    }
4254
    spec_len = strlen(spec);
6✔
4255
    copy = (char *)malloc(spec_len + 1u);
6✔
4256
    if (copy == NULL) {
6!
4257
        return -1;
×
4258
    }
4259
    memcpy(copy, spec, spec_len + 1u);
6✔
4260
    cursor = copy;
6✔
4261
    sections = 0u;
6✔
4262
    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
6✔
4263
    result = 0;
6✔
4264
    context = NULL;
6✔
4265
    while (result == 0) {
12!
4266
        token = sixel_compat_strtok(cursor, ",", &context);
12✔
4267
        if (token == NULL) {
12✔
4268
            break;
6✔
4269
        }
4270
        cursor = NULL;
6✔
4271
        while (*token == ' ' || *token == '\t') {
6!
4272
            token += 1;
×
4273
        }
4274
        length = strlen(token);
6✔
4275
        while (length > 0u &&
8!
4276
               (token[length - 1u] == ' ' || token[length - 1u] == '\t')) {
6!
4277
            token[length - 1u] = '\0';
×
4278
            length -= 1u;
×
4279
        }
4280
        if (*token == '\0') {
6!
4281
            result = -1;
×
4282
            break;
×
4283
        }
4284
        for (p = token; *p != '\0'; ++p) {
42✔
4285
            *p = (char)tolower((unsigned char)*p);
36✔
4286
        }
12✔
4287
        at = strchr(token, '@');
6✔
4288
        if (at != NULL) {
6!
4289
            *at = '\0';
×
4290
            variant = at + 1;
×
4291
            if (*variant == '\0') {
×
4292
                result = -1;
×
4293
                break;
×
4294
            }
4295
        } else {
4296
            variant = NULL;
6✔
4297
        }
4298
        base = token;
6✔
4299
        if (strcmp(base, "all") == 0) {
6!
4300
            sections |= SIXEL_ASSESSMENT_SECTION_ALL;
×
4301
            if (variant != NULL && variant[0] != '\0') {
×
4302
                if (strcmp(variant, "quantized") == 0) {
×
4303
                    if (view != SIXEL_ASSESSMENT_VIEW_ENCODED &&
×
4304
                            view != SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
4305
                        result = -1;
×
4306
                    }
4307
                    view = SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
4308
                } else if (strcmp(variant, "encoded") == 0) {
×
4309
                    if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4310
                        result = -1;
×
4311
                    }
4312
                    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
×
4313
                } else {
4314
                    result = -1;
×
4315
                }
4316
            }
4317
        } else if (strcmp(base, "basic") == 0) {
6✔
4318
            sections |= SIXEL_ASSESSMENT_SECTION_BASIC;
3✔
4319
            if (variant != NULL) {
3!
4320
                result = -1;
×
4321
            }
4322
        } else if (strcmp(base, "performance") == 0) {
4!
4323
            sections |= SIXEL_ASSESSMENT_SECTION_PERFORMANCE;
×
4324
            if (variant != NULL) {
×
4325
                result = -1;
×
4326
            }
4327
        } else if (strcmp(base, "size") == 0) {
3!
4328
            sections |= SIXEL_ASSESSMENT_SECTION_SIZE;
×
4329
            if (variant != NULL) {
×
4330
                result = -1;
×
4331
            }
4332
        } else if (strcmp(base, "quality") == 0) {
3!
4333
            sections |= SIXEL_ASSESSMENT_SECTION_QUALITY;
3✔
4334
            if (variant != NULL && variant[0] != '\0') {
3!
4335
                if (strcmp(variant, "quantized") == 0) {
×
4336
                    if (view != SIXEL_ASSESSMENT_VIEW_ENCODED &&
×
4337
                            view != SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
4338
                        result = -1;
×
4339
                    }
4340
                    view = SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
4341
                } else if (strcmp(variant, "encoded") == 0) {
×
4342
                    if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4343
                        result = -1;
×
4344
                    }
4345
                    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
×
4346
                } else {
4347
                    result = -1;
×
4348
                }
4349
            } else if (variant != NULL) {
3!
4350
                if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4351
                    result = -1;
×
4352
                }
4353
                view = SIXEL_ASSESSMENT_VIEW_ENCODED;
×
4354
            }
4355
        } else {
1✔
4356
            result = -1;
×
4357
        }
4358
    }
4359
    if (result == 0) {
6!
4360
        if (sections == 0u) {
6!
4361
            result = -1;
×
4362
        } else {
4363
            if ((sections & SIXEL_ASSESSMENT_SECTION_QUALITY) != 0u &&
6!
4364
                    view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
1✔
4365
                sections |= SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
4366
            }
4367
            *out_sections = sections;
6✔
4368
        }
4369
    }
2✔
4370
    free(copy);
6✔
4371
    return result;
6✔
4372
}
2✔
4373

4374

4375
static int
4376
is_png_target(char const *path)
31✔
4377
{
4378
    size_t len;
4379
    int matched;
4380

4381
    /*
4382
     * Detect PNG requests from explicit prefixes or a ".png" suffix:
4383
     *
4384
     *   argument
4385
     *   |
4386
     *   v
4387
     *   .............. . p n g
4388
     *   ^             ^^^^^^^^^
4389
     *   |             +-- case-insensitive suffix comparison
4390
     *   +-- accepts the "png:" inline prefix used for stdout capture
4391
     */
4392

4393
    len = 0;
31✔
4394
    matched = 0;
31✔
4395

4396
    if (path == NULL) {
31!
4397
        return 0;
×
4398
    }
4399

4400
    if (strncmp(path, "png:", 4) == 0) {
31✔
4401
        return path[4] != '\0';
6✔
4402
    }
4403

4404
    len = strlen(path);
25✔
4405
    if (len >= 4) {
25✔
4406
        matched = (tolower((unsigned char)path[len - 4]) == '.')
23✔
4407
            && (tolower((unsigned char)path[len - 3]) == 'p')
10!
4408
            && (tolower((unsigned char)path[len - 2]) == 'n')
3!
4409
            && (tolower((unsigned char)path[len - 1]) == 'g');
24!
4410
    }
8✔
4411

4412
    return matched;
25✔
4413
}
11✔
4414

4415

4416
static char const *
4417
png_target_payload_view(char const *argument)
9✔
4418
{
4419
    /*
4420
     * Inline PNG targets split into either a prefix/payload pair or rely on
4421
     * a simple file-name suffix:
4422
     *
4423
     *   +--------------+------------+-------------+
4424
     *   | form         | payload    | destination |
4425
     *   +--------------+------------+-------------+
4426
     *   | png:         | -          | stdout      |
4427
     *   | png:         | filename   | filesystem  |
4428
     *   | *.png        | filename   | filesystem  |
4429
     *   +--------------+------------+-------------+
4430
     *
4431
     * The caller only needs the payload column, so we expose it here.  When
4432
     * the user omits the prefix we simply echo the original pointer so the
4433
     * caller can copy the value verbatim.
4434
     */
4435
    if (argument == NULL) {
9!
4436
        return NULL;
×
4437
    }
4438
    if (strncmp(argument, "png:", 4) == 0) {
9✔
4439
        return argument + 4;
6✔
4440
    }
4441

4442
    return argument;
3✔
4443
}
3✔
4444

4445
static void
4446
normalise_windows_drive_path(char *path)
9✔
4447
{
4448
#if defined(_WIN32)
4449
    size_t length;
4450

4451
    /*
4452
     * MSYS-like environments forward POSIX-looking absolute paths to native
4453
     * binaries.  When a user writes "/d/..." MSYS converts the command line to
4454
     * UTF-16 and preserves the literal bytes.  The Windows CRT, however,
4455
     * expects "d:/..." or "d:\...".  The tiny state machine below rewrites the
4456
     * leading token so the runtime resolves the drive correctly:
4457
     *
4458
     *   input     normalised
4459
     *   |         |
4460
     *   v         v
4461
     *   / d / ... d : / ...
4462
     *
4463
     * The body keeps the rest of the string intact so UNC paths ("//server")
4464
     * and relative references pass through untouched.
4465
     */
4466

4467
    length = 0u;
4468

4469
    if (path == NULL) {
4470
        return;
4471
    }
4472

4473
    length = strlen(path);
4474
    if (length >= 3u
4475
            && path[0] == '/'
4476
            && ((path[1] >= 'A' && path[1] <= 'Z')
4477
                || (path[1] >= 'a' && path[1] <= 'z'))
4478
            && path[2] == '/') {
4479
        path[0] = path[1];
4480
        path[1] = ':';
4481
    }
4482
#else
4483
    (void)path;
3✔
4484
#endif
4485
}
9✔
4486

4487

4488
static int
4489
is_dev_null_path(char const *path)
×
4490
{
4491
    if (path == NULL || path[0] == '\0') {
×
4492
        return 0;
×
4493
    }
4494
#if defined(_WIN32)
4495
    if (_stricmp(path, "nul") == 0) {
4496
        return 1;
4497
    }
4498
#endif
4499
    return strcmp(path, "/dev/null") == 0;
×
4500
}
4501

4502

4503
/* set an option flag to encoder object */
4504
SIXELAPI SIXELSTATUS
4505
sixel_encoder_setopt(
718✔
4506
    sixel_encoder_t /* in */ *encoder,
4507
    int             /* in */ arg,
4508
    char const      /* in */ *value)
4509
{
4510
    SIXELSTATUS status = SIXEL_FALSE;
718✔
4511
    int number;
4512
    int parsed;
4513
    char unit[32];
4514
    char lowered[16];
4515
    size_t len;
4516
    size_t i;
4517
    long parsed_reqcolors;
4518
    char *endptr;
4519
    int forced_palette;
4520
    char *opt_copy;
4521
    char const *drcs_arg_delim;
4522
    char const *drcs_arg_charset;
4523
    char const *drcs_arg_second_delim;
4524
    char const *drcs_arg_path;
4525
    size_t drcs_arg_path_length;
4526
    size_t drcs_segment_length;
4527
    char drcs_segment[32];
4528
    int drcs_mmv_value;
4529
    long drcs_charset_value;
4530
    unsigned int drcs_charset_limit;
4531
    sixel_option_choice_result_t match_result;
4532
    int match_value;
4533
    char match_detail[128];
4534
    char match_message[256];
4535
    int png_argument_has_prefix = 0;
718✔
4536
    char const *png_path_view = NULL;
718✔
4537
    size_t png_path_length;
4538
    char cell_message[256];
4539
    char const *cell_detail;
4540
    unsigned int path_flags;
4541
    char const *mapfile_view;
4542
    int path_check;
4543

4544
    sixel_encoder_ref(encoder);
718✔
4545
    opt_copy = NULL;
718✔
4546
    path_flags = 0u;
718✔
4547
    mapfile_view = NULL;
718✔
4548
    path_check = 0;
718✔
4549

4550
    switch(arg) {
718!
4551
    case SIXEL_OPTFLAG_OUTFILE:  /* o */
20✔
4552
        if (*value == '\0') {
31!
4553
            sixel_helper_set_additional_message(
×
4554
                "no file name specified.");
4555
            status = SIXEL_BAD_ARGUMENT;
×
4556
            goto end;
×
4557
        }
4558
        if (is_png_target(value)) {
31✔
4559
            encoder->output_is_png = 1;
9✔
4560
            png_argument_has_prefix =
9✔
4561
                (value != NULL)
3✔
4562
                && (strncmp(value, "png:", 4) == 0);
9!
4563
            png_path_view = png_target_payload_view(value);
9✔
4564
            if (png_argument_has_prefix
11!
4565
                    && (png_path_view == NULL
7!
4566
                        || png_path_view[0] == '\0')) {
6!
4567
                sixel_helper_set_additional_message(
×
4568
                    "sixel_encoder_setopt: missing target after the \"png:\" "
4569
                    "prefix. use png:- or png:<path> with a non-empty payload."
4570
                );
4571
                status = SIXEL_BAD_ARGUMENT;
×
4572
                goto end;
×
4573
            }
4574
            encoder->output_png_to_stdout =
9✔
4575
                (png_path_view != NULL)
3✔
4576
                && (strcmp(png_path_view, "-") == 0);
9!
4577
            sixel_allocator_free(encoder->allocator, encoder->png_output_path);
9✔
4578
            encoder->png_output_path = NULL;
9✔
4579
            sixel_allocator_free(encoder->allocator, encoder->sixel_output_path);
9✔
4580
            encoder->sixel_output_path = NULL;
9✔
4581
            if (! encoder->output_png_to_stdout) {
9!
4582
                /*
4583
                 * +-----------------------------------------+
4584
                 * |  PNG target normalization               |
4585
                 * +-----------------------------------------+
4586
                 * |  Raw input  |  Stored file path         |
4587
                 * |-------------+---------------------------|
4588
                 * |  png:-      |  "-" (stdout sentinel)    |
4589
                 * |  png:/foo   |  "/foo"                   |
4590
                 * +-----------------------------------------+
4591
                 * Strip the "png:" prefix so the decoder can
4592
                 * pass the true filesystem path to libpng
4593
                 * while the CLI retains its shorthand.
4594
                 */
4595
                png_path_view = value;
9✔
4596
                if (strncmp(value, "png:", 4) == 0) {
9✔
4597
                    png_path_view = value + 4;
6✔
4598
                }
2✔
4599
                if (png_path_view[0] == '\0') {
9!
4600
                    sixel_helper_set_additional_message(
×
4601
                        "sixel_encoder_setopt: PNG output path is empty.");
4602
                    status = SIXEL_BAD_ARGUMENT;
×
4603
                    goto end;
×
4604
                }
4605
                png_path_length = strlen(png_path_view);
9✔
4606
                encoder->png_output_path =
9✔
4607
                    (char *)sixel_allocator_malloc(
9✔
4608
                        encoder->allocator, png_path_length + 1u);
3✔
4609
                if (encoder->png_output_path == NULL) {
9!
4610
                    sixel_helper_set_additional_message(
×
4611
                        "sixel_encoder_setopt: sixel_allocator_malloc() "
4612
                        "failed for PNG output path.");
4613
                    status = SIXEL_BAD_ALLOCATION;
×
4614
                    goto end;
×
4615
                }
4616
                if (png_path_view != NULL) {
9!
4617
                    (void)sixel_compat_strcpy(encoder->png_output_path,
12✔
4618
                                              png_path_length + 1u,
3✔
4619
                                              png_path_view);
3✔
4620
                } else {
3✔
4621
                    encoder->png_output_path[0] = '\0';
×
4622
                }
4623
                normalise_windows_drive_path(encoder->png_output_path);
9✔
4624
            }
3✔
4625
        } else {
3✔
4626
            encoder->output_is_png = 0;
22✔
4627
            encoder->output_png_to_stdout = 0;
22✔
4628
            png_argument_has_prefix = 0;
22✔
4629
            png_path_view = NULL;
22✔
4630
            sixel_allocator_free(encoder->allocator, encoder->png_output_path);
22✔
4631
            encoder->png_output_path = NULL;
22✔
4632
            sixel_allocator_free(encoder->allocator, encoder->sixel_output_path);
22✔
4633
            encoder->sixel_output_path = NULL;
22✔
4634
            if (encoder->clipboard_output_path != NULL) {
22!
4635
                (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
4636
                sixel_allocator_free(encoder->allocator,
×
4637
                                     encoder->clipboard_output_path);
×
4638
                encoder->clipboard_output_path = NULL;
×
4639
            }
4640
            encoder->clipboard_output_active = 0;
22✔
4641
            encoder->clipboard_output_format[0] = '\0';
22✔
4642
            {
4643
                sixel_clipboard_spec_t clipboard_spec;
4644
                SIXELSTATUS clip_status;
4645
                char *spool_path;
4646
                int spool_fd;
4647

4648
                clipboard_spec.is_clipboard = 0;
22✔
4649
                clipboard_spec.format[0] = '\0';
22✔
4650
                clip_status = SIXEL_OK;
22✔
4651
                spool_path = NULL;
22✔
4652
                spool_fd = (-1);
22✔
4653

4654
                if (sixel_clipboard_parse_spec(value, &clipboard_spec)
22!
4655
                        && clipboard_spec.is_clipboard) {
8!
4656
                    clip_status = clipboard_create_spool(
1✔
4657
                        encoder->allocator,
1✔
4658
                        "clipboard-out",
4659
                        &spool_path,
4660
                        &spool_fd);
4661
                    if (SIXEL_FAILED(clip_status)) {
1!
4662
                        status = clip_status;
×
4663
                        goto end;
×
4664
                    }
4665
                    clipboard_select_format(
1✔
4666
                        encoder->clipboard_output_format,
1✔
4667
                        sizeof(encoder->clipboard_output_format),
4668
                        clipboard_spec.format,
1✔
4669
                        "sixel");
4670
                    if (encoder->outfd
1!
4671
                            && encoder->outfd != STDOUT_FILENO
1!
4672
                            && encoder->outfd != STDERR_FILENO) {
1!
4673
                        (void)sixel_compat_close(encoder->outfd);
×
4674
                    }
4675
                    encoder->outfd = spool_fd;
1✔
4676
                    spool_fd = (-1);
1✔
4677
                    encoder->sixel_output_path = spool_path;
1✔
4678
                    encoder->clipboard_output_path = spool_path;
1✔
4679
                    spool_path = NULL;
1✔
4680
                    encoder->clipboard_output_active = 1;
1✔
4681
                    break;
1✔
4682
                }
4683

4684
                if (spool_fd >= 0) {
21!
4685
                    (void)sixel_compat_close(spool_fd);
×
4686
                }
4687
                if (spool_path != NULL) {
21!
4688
                    sixel_allocator_free(encoder->allocator, spool_path);
×
4689
                }
4690
            }
4691
            if (strcmp(value, "-") != 0) {
21!
4692
                encoder->sixel_output_path = (char *)sixel_allocator_malloc(
35✔
4693
                    encoder->allocator, strlen(value) + 1);
21✔
4694
                if (encoder->sixel_output_path == NULL) {
21!
4695
                    sixel_helper_set_additional_message(
×
4696
                        "sixel_encoder_setopt: malloc() failed for output path.");
4697
                    status = SIXEL_BAD_ALLOCATION;
×
4698
                    goto end;
×
4699
                }
4700
                (void)sixel_compat_strcpy(encoder->sixel_output_path,
28✔
4701
                                          strlen(value) + 1,
21✔
4702
                                          value);
7✔
4703
            }
7✔
4704
        }
4705

4706
        if (!encoder->clipboard_output_active && strcmp(value, "-") != 0) {
30!
4707
            if (encoder->outfd && encoder->outfd != STDOUT_FILENO) {
30!
4708
                (void)sixel_compat_close(encoder->outfd);
×
4709
            }
4710
            encoder->outfd = sixel_compat_open(value,
30✔
4711
                                               O_RDWR | O_CREAT | O_TRUNC,
4712
                                               S_IRUSR | S_IWUSR);
4713
        }
10✔
4714
        break;
30✔
4715
    case SIXEL_OPTFLAG_ASSESSMENT:  /* a */
4✔
4716
        if (parse_assessment_sections(value,
10!
4717
                                      &encoder->assessment_sections) != 0) {
4✔
4718
            sixel_helper_set_additional_message(
×
4719
                "sixel_encoder_setopt: cannot parse assessment section list");
4720
            status = SIXEL_BAD_ARGUMENT;
×
4721
            goto end;
×
4722
        }
4723
        break;
6✔
4724
    case SIXEL_OPTFLAG_ASSESSMENT_FILE:  /* J */
2✔
4725
        encoder->assessment_json_path = value;
3✔
4726
        break;
3✔
4727
    case SIXEL_OPTFLAG_7BIT_MODE:  /* 7 */
10✔
4728
        encoder->f8bit = 0;
15✔
4729
        break;
15✔
4730
    case SIXEL_OPTFLAG_8BIT_MODE:  /* 8 */
12✔
4731
        encoder->f8bit = 1;
18✔
4732
        break;
18✔
4733
    case SIXEL_OPTFLAG_6REVERSIBLE:  /* 6 */
4734
        encoder->sixel_reversible = 1;
×
4735
        break;
×
4736
    case SIXEL_OPTFLAG_HAS_GRI_ARG_LIMIT:  /* R */
4737
        encoder->has_gri_arg_limit = 1;
×
4738
        break;
×
4739
    case SIXEL_OPTFLAG_COLORS:  /* p */
18✔
4740
        forced_palette = 0;
27✔
4741
        errno = 0;
27✔
4742
        endptr = NULL;
27✔
4743
        if (*value == '!' && value[1] == '\0') {
27!
4744
            /*
4745
             * Force the default palette size even when the median cut
4746
             * finished early.
4747
             *
4748
             *   requested colors
4749
             *          |
4750
             *          v
4751
             *        [ 256 ]  <--- "-p!" triggers this shortcut
4752
             */
4753
            parsed_reqcolors = SIXEL_PALETTE_MAX;
×
4754
            forced_palette = 1;
×
4755
        } else {
4756
            parsed_reqcolors = strtol(value, &endptr, 10);
27✔
4757
            if (endptr != NULL && *endptr == '!') {
27!
4758
                forced_palette = 1;
×
4759
                ++endptr;
×
4760
            }
4761
            if (errno == ERANGE || endptr == value) {
27!
4762
                sixel_helper_set_additional_message(
×
4763
                    "cannot parse -p/--colors option.");
4764
                status = SIXEL_BAD_ARGUMENT;
×
4765
                goto end;
×
4766
            }
4767
            if (endptr != NULL && *endptr != '\0') {
27!
4768
                sixel_helper_set_additional_message(
×
4769
                    "cannot parse -p/--colors option.");
4770
                status = SIXEL_BAD_ARGUMENT;
×
4771
                goto end;
×
4772
            }
4773
        }
4774
        if (parsed_reqcolors < 1) {
27!
4775
            sixel_helper_set_additional_message(
×
4776
                "-p/--colors parameter must be 1 or more.");
4777
            status = SIXEL_BAD_ARGUMENT;
×
4778
            goto end;
×
4779
        }
4780
        if (parsed_reqcolors > SIXEL_PALETTE_MAX) {
27!
4781
            sixel_helper_set_additional_message(
×
4782
                "-p/--colors parameter must be less then or equal to 256.");
4783
            status = SIXEL_BAD_ARGUMENT;
×
4784
            goto end;
×
4785
        }
4786
        encoder->reqcolors = (int)parsed_reqcolors;
27✔
4787
        encoder->force_palette = forced_palette;
27✔
4788
        break;
27✔
4789
    case SIXEL_OPTFLAG_MAPFILE:  /* m */
18✔
4790
        mapfile_view = sixel_palette_strip_prefix(value, NULL);
27✔
4791
        if (mapfile_view == NULL) {
27!
NEW
4792
            mapfile_view = value;
×
4793
        }
4794
        path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
27✔
4795
            SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
4796
            SIXEL_OPTION_PATH_ALLOW_REMOTE |
4797
            SIXEL_OPTION_PATH_ALLOW_EMPTY;
4798
        path_check = sixel_option_validate_filesystem_path(
27✔
4799
            value,
9✔
4800
            mapfile_view,
9✔
4801
            path_flags);
9✔
4802
        if (path_check != 0) {
27✔
4803
            status = SIXEL_BAD_ARGUMENT;
3✔
4804
            goto end;
3✔
4805
        }
4806
        if (encoder->mapfile) {
24✔
4807
            sixel_allocator_free(encoder->allocator, encoder->mapfile);
3✔
4808
        }
1✔
4809
        encoder->mapfile = arg_strdup(value, encoder->allocator);
24✔
4810
        if (encoder->mapfile == NULL) {
24!
4811
            sixel_helper_set_additional_message(
×
4812
                "sixel_encoder_setopt: sixel_allocator_malloc() failed.");
4813
            status = SIXEL_BAD_ALLOCATION;
×
4814
            goto end;
×
4815
        }
4816
        encoder->color_option = SIXEL_COLOR_OPTION_MAPFILE;
24✔
4817
        break;
24✔
4818
    case SIXEL_OPTFLAG_MAPFILE_OUTPUT:  /* M */
4819
        if (value == NULL || *value == '\0') {
×
4820
            sixel_helper_set_additional_message(
×
4821
                "sixel_encoder_setopt: mapfile-output path is empty.");
4822
            status = SIXEL_BAD_ARGUMENT;
×
4823
            goto end;
×
4824
        }
4825
        opt_copy = arg_strdup(value, encoder->allocator);
×
4826
        if (opt_copy == NULL) {
×
4827
            sixel_helper_set_additional_message(
×
4828
                "sixel_encoder_setopt: sixel_allocator_malloc() failed.");
4829
            status = SIXEL_BAD_ALLOCATION;
×
4830
            goto end;
×
4831
        }
4832
        status = sixel_encoder_enable_quantized_capture(encoder, 1);
×
4833
        if (SIXEL_FAILED(status)) {
×
4834
            sixel_allocator_free(encoder->allocator, opt_copy);
×
4835
            goto end;
×
4836
        }
4837
        sixel_allocator_free(encoder->allocator, encoder->palette_output);
×
4838
        encoder->palette_output = opt_copy;
×
4839
        opt_copy = NULL;
×
4840
        break;
×
4841
    case SIXEL_OPTFLAG_MONOCHROME:  /* e */
10✔
4842
        encoder->color_option = SIXEL_COLOR_OPTION_MONOCHROME;
15✔
4843
        break;
15✔
4844
    case SIXEL_OPTFLAG_HIGH_COLOR:  /* I */
28✔
4845
        encoder->color_option = SIXEL_COLOR_OPTION_HIGHCOLOR;
42✔
4846
        break;
42✔
4847
    case SIXEL_OPTFLAG_BUILTIN_PALETTE:  /* b */
22✔
4848
        match_result = sixel_option_match_choice(
33✔
4849
            value,
11✔
4850
            g_option_choices_builtin_palette,
4851
            sizeof(g_option_choices_builtin_palette) /
4852
            sizeof(g_option_choices_builtin_palette[0]),
4853
            &match_value,
4854
            match_detail,
11✔
4855
            sizeof(match_detail));
4856
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
33✔
4857
            encoder->builtin_palette = match_value;
30✔
4858
        } else {
10✔
4859
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
NEW
4860
                sixel_option_report_ambiguous_prefix(value,
×
4861
                                              match_detail,
4862
                                              match_message,
4863
                                              sizeof(match_message));
4864
            } else {
4865
                sixel_helper_set_additional_message(
3✔
4866
                    "cannot parse builtin palette option.");
4867
            }
4868
            status = SIXEL_BAD_ARGUMENT;
3✔
4869
            goto end;
3✔
4870
        }
4871
        encoder->color_option = SIXEL_COLOR_OPTION_BUILTIN;
30✔
4872
        break;
30✔
4873
    case SIXEL_OPTFLAG_DIFFUSION:  /* d */
50✔
4874
        match_result = sixel_option_match_choice(
75✔
4875
            value,
25✔
4876
            g_option_choices_diffusion,
4877
            sizeof(g_option_choices_diffusion) /
4878
            sizeof(g_option_choices_diffusion[0]),
4879
            &match_value,
4880
            match_detail,
25✔
4881
            sizeof(match_detail));
4882
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
75✔
4883
            encoder->method_for_diffuse = match_value;
66✔
4884
        } else {
22✔
4885
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
9✔
4886
                sixel_option_report_ambiguous_prefix(value,
4✔
4887
                                              match_detail,
1✔
4888
                                              match_message,
1✔
4889
                                              sizeof(match_message));
4890
            } else {
1✔
4891
                sixel_helper_set_additional_message(
6✔
4892
                    "specified diffusion method is not supported.");
4893
            }
4894
            status = SIXEL_BAD_ARGUMENT;
9✔
4895
            goto end;
9✔
4896
        }
4897
        break;
66✔
4898
    case SIXEL_OPTFLAG_DIFFUSION_SCAN:  /* y */
2✔
4899
        match_result = sixel_option_match_choice(
3✔
4900
            value,
1✔
4901
            g_option_choices_diffusion_scan,
4902
            sizeof(g_option_choices_diffusion_scan) /
4903
            sizeof(g_option_choices_diffusion_scan[0]),
4904
            &match_value,
4905
            match_detail,
1✔
4906
            sizeof(match_detail));
4907
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
3!
4908
            encoder->method_for_scan = match_value;
3✔
4909
        } else {
1✔
4910
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
NEW
4911
                sixel_option_report_ambiguous_prefix(value,
×
4912
                                              match_detail,
4913
                                              match_message,
4914
                                              sizeof(match_message));
4915
            } else {
4916
                sixel_helper_set_additional_message(
×
4917
                    "specified diffusion scan is not supported.");
4918
            }
4919
            status = SIXEL_BAD_ARGUMENT;
×
4920
            goto end;
×
4921
        }
4922
        break;
3✔
4923
    case SIXEL_OPTFLAG_DIFFUSION_CARRY:  /* Y */
NEW
4924
        match_result = sixel_option_match_choice(
×
4925
            value,
4926
            g_option_choices_diffusion_carry,
4927
            sizeof(g_option_choices_diffusion_carry) /
4928
            sizeof(g_option_choices_diffusion_carry[0]),
4929
            &match_value,
4930
            match_detail,
4931
            sizeof(match_detail));
4932
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
4933
            encoder->method_for_carry = match_value;
×
4934
        } else {
4935
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
NEW
4936
                sixel_option_report_ambiguous_prefix(value,
×
4937
                                              match_detail,
4938
                                              match_message,
4939
                                              sizeof(match_message));
4940
            } else {
4941
                sixel_helper_set_additional_message(
×
4942
                    "specified diffusion carry mode is not supported.");
4943
            }
4944
            status = SIXEL_BAD_ARGUMENT;
×
4945
            goto end;
×
4946
        }
4947
        break;
×
4948
    case SIXEL_OPTFLAG_FIND_LARGEST:  /* f */
10✔
4949
        if (value != NULL) {
15!
4950
            match_result = sixel_option_match_choice(
15✔
4951
                value,
5✔
4952
                g_option_choices_find_largest,
4953
                sizeof(g_option_choices_find_largest) /
4954
                sizeof(g_option_choices_find_largest[0]),
4955
                &match_value,
4956
                match_detail,
5✔
4957
                sizeof(match_detail));
4958
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
15✔
4959
                encoder->method_for_largest = match_value;
12✔
4960
            } else {
4✔
4961
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
NEW
4962
                    sixel_option_report_ambiguous_prefix(value,
×
4963
                                                  match_detail,
4964
                                                  match_message,
4965
                                                  sizeof(match_message));
4966
                } else {
4967
                    sixel_helper_set_additional_message(
3✔
4968
                        "specified finding method is not supported.");
4969
                }
4970
                status = SIXEL_BAD_ARGUMENT;
3✔
4971
                goto end;
3✔
4972
            }
4973
        }
4✔
4974
        break;
12✔
4975
    case SIXEL_OPTFLAG_SELECT_COLOR:  /* s */
14✔
4976
        match_result = sixel_option_match_choice(
21✔
4977
            value,
7✔
4978
            g_option_choices_select_color,
4979
            sizeof(g_option_choices_select_color) /
4980
            sizeof(g_option_choices_select_color[0]),
4981
            &match_value,
4982
            match_detail,
7✔
4983
            sizeof(match_detail));
4984
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
21✔
4985
            encoder->method_for_rep = match_value;
15✔
4986
        } else {
5✔
4987
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
6✔
4988
                sixel_option_report_ambiguous_prefix(value,
4✔
4989
                                              match_detail,
1✔
4990
                                              match_message,
1✔
4991
                                              sizeof(match_message));
4992
            } else {
1✔
4993
                sixel_helper_set_additional_message(
3✔
4994
                    "specified finding method is not supported.");
4995
            }
4996
            status = SIXEL_BAD_ARGUMENT;
6✔
4997
            goto end;
6✔
4998
        }
4999
        break;
15✔
5000
    case SIXEL_OPTFLAG_QUANTIZE_MODEL:  /* Q */
NEW
5001
        match_result = sixel_option_match_choice(
×
5002
            value,
5003
            g_option_choices_quantize_model,
5004
            sizeof(g_option_choices_quantize_model) /
5005
            sizeof(g_option_choices_quantize_model[0]),
5006
            &match_value,
5007
            match_detail,
5008
            sizeof(match_detail));
5009
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5010
            encoder->quantize_model = match_value;
×
5011
        } else {
5012
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
NEW
5013
                sixel_option_report_ambiguous_prefix(value,
×
5014
                                              match_detail,
5015
                                              match_message,
5016
                                              sizeof(match_message));
5017
            } else {
5018
                sixel_helper_set_additional_message(
×
5019
                    "sixel_encoder_setopt: unsupported quantize model.");
5020
            }
5021
            status = SIXEL_BAD_ARGUMENT;
×
5022
            goto end;
×
5023
        }
5024
        break;
×
5025
    case SIXEL_OPTFLAG_FINAL_MERGE:  /* F */
NEW
5026
        match_result = sixel_option_match_choice(
×
5027
            value,
5028
            g_option_choices_final_merge,
5029
            sizeof(g_option_choices_final_merge) /
5030
            sizeof(g_option_choices_final_merge[0]),
5031
            &match_value,
5032
            match_detail,
5033
            sizeof(match_detail));
5034
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5035
            encoder->final_merge_mode = match_value;
×
5036
        } else {
5037
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
NEW
5038
                sixel_option_report_ambiguous_prefix(value,
×
5039
                                              match_detail,
5040
                                              match_message,
5041
                                              sizeof(match_message));
5042
            } else {
5043
                sixel_helper_set_additional_message(
×
5044
                    "specified final merge policy is not supported.");
5045
            }
5046
            status = SIXEL_BAD_ARGUMENT;
×
5047
            goto end;
×
5048
        }
5049
        break;
×
5050
    case SIXEL_OPTFLAG_CROP:  /* c */
10✔
5051
#if HAVE_SSCANF_S
5052
        number = sscanf_s(value, "%dx%d+%d+%d",
5053
                          &encoder->clipwidth, &encoder->clipheight,
5054
                          &encoder->clipx, &encoder->clipy);
5055
#else
5056
        number = sscanf(value, "%dx%d+%d+%d",
20✔
5057
                        &encoder->clipwidth, &encoder->clipheight,
5✔
5058
                        &encoder->clipx, &encoder->clipy);
5✔
5059
#endif  /* HAVE_SSCANF_S */
5060
        if (number != 4) {
15!
5061
            status = SIXEL_BAD_ARGUMENT;
×
5062
            goto end;
×
5063
        }
5064
        if (encoder->clipwidth <= 0 || encoder->clipheight <= 0) {
15!
5065
            status = SIXEL_BAD_ARGUMENT;
×
5066
            goto end;
×
5067
        }
5068
        if (encoder->clipx < 0 || encoder->clipy < 0) {
15!
5069
            status = SIXEL_BAD_ARGUMENT;
×
5070
            goto end;
×
5071
        }
5072
        encoder->clipfirst = 0;
15✔
5073
        break;
15✔
5074
    case SIXEL_OPTFLAG_WIDTH:  /* w */
50✔
5075
#if HAVE_SSCANF_S
5076
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
5077
#else
5078
        parsed = sscanf(value, "%d%2s", &number, unit);
75✔
5079
#endif  /* HAVE_SSCANF_S */
5080
        if (parsed == 2 && strcmp(unit, "%") == 0) {
75!
5081
            encoder->pixelwidth = (-1);
12✔
5082
            encoder->percentwidth = number;
12✔
5083
        } else if (parsed == 2 && strcmp(unit, "c") == 0) {
67!
5084
            status = sixel_encoder_ensure_cell_size(encoder);
×
5085
            if (SIXEL_FAILED(status)) {
×
5086
                cell_detail = sixel_helper_get_additional_message();
×
5087
                if (cell_detail != NULL && cell_detail[0] != '\0') {
×
5088
                    (void) snprintf(cell_message,
×
5089
                                    sizeof(cell_message),
5090
                                    "cannot determine terminal cell size for "
5091
                                    "-w/--width option: %s",
5092
                                    cell_detail);
5093
                    sixel_helper_set_additional_message(cell_message);
×
5094
                } else {
5095
                    sixel_helper_set_additional_message(
×
5096
                        "cannot determine terminal cell size for "
5097
                        "-w/--width option.");
5098
                }
5099
                goto end;
×
5100
            }
5101
            /*
5102
             * Terminal cell units map the requested column count to pixels.
5103
             * The cell size probe caches the tty geometry so repeated calls
5104
             * reuse the same measurement.
5105
             */
5106
            encoder->pixelwidth = number * encoder->cell_width;
×
5107
            encoder->percentwidth = (-1);
×
5108
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
63!
5109
            encoder->pixelwidth = number;
51✔
5110
            encoder->percentwidth = (-1);
51✔
5111
        } else if (strcmp(value, "auto") == 0) {
29✔
5112
            encoder->pixelwidth = (-1);
9✔
5113
            encoder->percentwidth = (-1);
9✔
5114
        } else {
3✔
5115
            sixel_helper_set_additional_message(
3✔
5116
                "cannot parse -w/--width option.");
5117
            status = SIXEL_BAD_ARGUMENT;
3✔
5118
            goto end;
3✔
5119
        }
5120
        if (encoder->clipwidth) {
72✔
5121
            encoder->clipfirst = 1;
6✔
5122
        }
2✔
5123
        break;
72✔
5124
    case SIXEL_OPTFLAG_HEIGHT:  /* h */
44✔
5125
#if HAVE_SSCANF_S
5126
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
5127
#else
5128
        parsed = sscanf(value, "%d%2s", &number, unit);
66✔
5129
#endif  /* HAVE_SSCANF_S */
5130
        if (parsed == 2 && strcmp(unit, "%") == 0) {
66!
5131
            encoder->pixelheight = (-1);
9✔
5132
            encoder->percentheight = number;
9✔
5133
        } else if (parsed == 2 && strcmp(unit, "c") == 0) {
60!
5134
            status = sixel_encoder_ensure_cell_size(encoder);
×
5135
            if (SIXEL_FAILED(status)) {
×
5136
                cell_detail = sixel_helper_get_additional_message();
×
5137
                if (cell_detail != NULL && cell_detail[0] != '\0') {
×
5138
                    (void) snprintf(cell_message,
×
5139
                                    sizeof(cell_message),
5140
                                    "cannot determine terminal cell size for "
5141
                                    "-h/--height option: %s",
5142
                                    cell_detail);
5143
                    sixel_helper_set_additional_message(cell_message);
×
5144
                } else {
5145
                    sixel_helper_set_additional_message(
×
5146
                        "cannot determine terminal cell size for "
5147
                        "-h/--height option.");
5148
                }
5149
                goto end;
×
5150
            }
5151
            /*
5152
             * Rows specified in terminal cells use the current tty metrics to
5153
             * translate into pixel counts before scaling.
5154
             */
5155
            encoder->pixelheight = number * encoder->cell_height;
×
5156
            encoder->percentheight = (-1);
×
5157
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
57!
5158
            encoder->pixelheight = number;
45✔
5159
            encoder->percentheight = (-1);
45✔
5160
        } else if (strcmp(value, "auto") == 0) {
27✔
5161
            encoder->pixelheight = (-1);
9✔
5162
            encoder->percentheight = (-1);
9✔
5163
        } else {
3✔
5164
            sixel_helper_set_additional_message(
3✔
5165
                "cannot parse -h/--height option.");
5166
            status = SIXEL_BAD_ARGUMENT;
3✔
5167
            goto end;
3✔
5168
        }
5169
        if (encoder->clipheight) {
63✔
5170
            encoder->clipfirst = 1;
3✔
5171
        }
1✔
5172
        break;
63✔
5173
    case SIXEL_OPTFLAG_RESAMPLING:  /* r */
40✔
5174
        match_result = sixel_option_match_choice(
60✔
5175
            value,
20✔
5176
            g_option_choices_resampling,
5177
            sizeof(g_option_choices_resampling) /
5178
            sizeof(g_option_choices_resampling[0]),
5179
            &match_value,
5180
            match_detail,
20✔
5181
            sizeof(match_detail));
5182
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
60✔
5183
            encoder->method_for_resampling = match_value;
48✔
5184
        } else {
16✔
5185
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
12!
NEW
5186
                sixel_option_report_ambiguous_prefix(value,
×
5187
                                              match_detail,
5188
                                              match_message,
5189
                                              sizeof(match_message));
5190
            } else {
5191
                sixel_helper_set_additional_message(
12✔
5192
                    "specified desampling method is not supported.");
5193
            }
5194
            status = SIXEL_BAD_ARGUMENT;
12✔
5195
            goto end;
12✔
5196
        }
5197
        break;
48✔
5198
    case SIXEL_OPTFLAG_QUALITY:  /* q */
12✔
5199
        match_result = sixel_option_match_choice(
18✔
5200
            value,
6✔
5201
            g_option_choices_quality,
5202
            sizeof(g_option_choices_quality) /
5203
            sizeof(g_option_choices_quality[0]),
5204
            &match_value,
5205
            match_detail,
6✔
5206
            sizeof(match_detail));
5207
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
18✔
5208
            encoder->quality_mode = match_value;
15✔
5209
        } else {
5✔
5210
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
NEW
5211
                sixel_option_report_ambiguous_prefix(value,
×
5212
                                              match_detail,
5213
                                              match_message,
5214
                                              sizeof(match_message));
5215
            } else {
5216
                sixel_helper_set_additional_message(
3✔
5217
                    "cannot parse quality option.");
5218
            }
5219
            status = SIXEL_BAD_ARGUMENT;
3✔
5220
            goto end;
3✔
5221
        }
5222
        break;
15✔
5223
    case SIXEL_OPTFLAG_LOOPMODE:  /* l */
10✔
5224
        match_result = sixel_option_match_choice(
15✔
5225
            value,
5✔
5226
            g_option_choices_loopmode,
5227
            sizeof(g_option_choices_loopmode) /
5228
            sizeof(g_option_choices_loopmode[0]),
5229
            &match_value,
5230
            match_detail,
5✔
5231
            sizeof(match_detail));
5232
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
15✔
5233
            encoder->loop_mode = match_value;
12✔
5234
        } else {
4✔
5235
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
NEW
5236
                sixel_option_report_ambiguous_prefix(value,
×
5237
                                              match_detail,
5238
                                              match_message,
5239
                                              sizeof(match_message));
5240
            } else {
5241
                sixel_helper_set_additional_message(
3✔
5242
                    "cannot parse loop-control option.");
5243
            }
5244
            status = SIXEL_BAD_ARGUMENT;
3✔
5245
            goto end;
3✔
5246
        }
5247
        break;
12✔
5248
    case SIXEL_OPTFLAG_PALETTE_TYPE:  /* t */
18✔
5249
        match_result = sixel_option_match_choice(
27✔
5250
            value,
9✔
5251
            g_option_choices_palette_type,
5252
            sizeof(g_option_choices_palette_type) /
5253
            sizeof(g_option_choices_palette_type[0]),
5254
            &match_value,
5255
            match_detail,
9✔
5256
            sizeof(match_detail));
5257
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
27✔
5258
            encoder->palette_type = match_value;
24✔
5259
        } else {
8✔
5260
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
NEW
5261
                sixel_option_report_ambiguous_prefix(value,
×
5262
                                              match_detail,
5263
                                              match_message,
5264
                                              sizeof(match_message));
5265
            } else {
5266
                sixel_helper_set_additional_message(
3✔
5267
                    "cannot parse palette type option.");
5268
            }
5269
            status = SIXEL_BAD_ARGUMENT;
3✔
5270
            goto end;
3✔
5271
        }
5272
        break;
24✔
5273
    case SIXEL_OPTFLAG_BGCOLOR:  /* B */
30✔
5274
        /* parse --bgcolor option */
5275
        if (encoder->bgcolor) {
45✔
5276
            sixel_allocator_free(encoder->allocator, encoder->bgcolor);
6✔
5277
            encoder->bgcolor = NULL;
6✔
5278
        }
2✔
5279
        status = sixel_parse_x_colorspec(&encoder->bgcolor,
60✔
5280
                                         value,
15✔
5281
                                         encoder->allocator);
15✔
5282
        if (SIXEL_FAILED(status)) {
45✔
5283
            sixel_helper_set_additional_message(
21✔
5284
                "cannot parse bgcolor option.");
5285
            status = SIXEL_BAD_ARGUMENT;
21✔
5286
            goto end;
21✔
5287
        }
5288
        break;
24✔
5289
    case SIXEL_OPTFLAG_INSECURE:  /* k */
5290
        encoder->finsecure = 1;
×
5291
        break;
×
5292
    case SIXEL_OPTFLAG_INVERT:  /* i */
4✔
5293
        encoder->finvert = 1;
6✔
5294
        break;
6✔
5295
    case SIXEL_OPTFLAG_USE_MACRO:  /* u */
4✔
5296
        encoder->fuse_macro = 1;
6✔
5297
        break;
6✔
5298
    case SIXEL_OPTFLAG_MACRO_NUMBER:  /* n */
2✔
5299
        encoder->macro_number = atoi(value);
3✔
5300
        if (encoder->macro_number < 0) {
3!
5301
            status = SIXEL_BAD_ARGUMENT;
×
5302
            goto end;
×
5303
        }
5304
        break;
3✔
5305
    case SIXEL_OPTFLAG_IGNORE_DELAY:  /* g */
4✔
5306
        encoder->fignore_delay = 1;
6✔
5307
        break;
6✔
5308
    case SIXEL_OPTFLAG_VERBOSE:  /* v */
6✔
5309
        encoder->verbose = 1;
9✔
5310
        sixel_helper_set_loader_trace(1);
9✔
5311
        break;
9✔
5312
    case SIXEL_OPTFLAG_LOADERS:  /* J */
5313
        if (encoder->loader_order != NULL) {
×
5314
            sixel_allocator_free(encoder->allocator,
×
5315
                                 encoder->loader_order);
×
5316
            encoder->loader_order = NULL;
×
5317
        }
5318
        if (value != NULL && *value != '\0') {
×
5319
            encoder->loader_order = arg_strdup(value,
×
5320
                                               encoder->allocator);
5321
            if (encoder->loader_order == NULL) {
×
5322
                sixel_helper_set_additional_message(
×
5323
                    "sixel_encoder_setopt: "
5324
                    "sixel_allocator_malloc() failed.");
5325
                status = SIXEL_BAD_ALLOCATION;
×
5326
                goto end;
×
5327
            }
5328
        }
5329
        break;
×
5330
    case SIXEL_OPTFLAG_STATIC:  /* S */
4✔
5331
        encoder->fstatic = 1;
6✔
5332
        break;
6✔
5333
    case SIXEL_OPTFLAG_DRCS:  /* @ */
5334
        encoder->fdrcs = 1;
×
5335
        drcs_arg_delim = NULL;
×
5336
        drcs_arg_charset = NULL;
×
5337
        drcs_arg_second_delim = NULL;
×
5338
        drcs_arg_path = NULL;
×
5339
        drcs_arg_path_length = 0u;
×
5340
        drcs_segment_length = 0u;
×
5341
        drcs_mmv_value = 2;
×
5342
        drcs_charset_value = 1L;
×
5343
        drcs_charset_limit = 0u;
×
5344
        if (value != NULL && *value != '\0') {
×
5345
            drcs_arg_delim = strchr(value, ':');
×
5346
            if (drcs_arg_delim == NULL) {
×
5347
                drcs_segment_length = strlen(value);
×
5348
                if (drcs_segment_length >= sizeof(drcs_segment)) {
×
5349
                    sixel_helper_set_additional_message(
×
5350
                        "DRCS mapping revision is too long.");
5351
                    status = SIXEL_BAD_ARGUMENT;
×
5352
                    goto end;
×
5353
                }
5354
                memcpy(drcs_segment, value, drcs_segment_length);
×
5355
                drcs_segment[drcs_segment_length] = '\0';
×
5356
                errno = 0;
×
5357
                endptr = NULL;
×
5358
                drcs_mmv_value = (int)strtol(drcs_segment, &endptr, 10);
×
5359
                if (errno != 0 || endptr == drcs_segment || *endptr != '\0') {
×
5360
                    sixel_helper_set_additional_message(
×
5361
                        "cannot parse DRCS option.");
5362
                    status = SIXEL_BAD_ARGUMENT;
×
5363
                    goto end;
×
5364
                }
5365
            } else {
5366
                if (drcs_arg_delim != value) {
×
5367
                    drcs_segment_length =
×
5368
                        (size_t)(drcs_arg_delim - value);
×
5369
                    if (drcs_segment_length >= sizeof(drcs_segment)) {
×
5370
                        sixel_helper_set_additional_message(
×
5371
                            "DRCS mapping revision is too long.");
5372
                        status = SIXEL_BAD_ARGUMENT;
×
5373
                        goto end;
×
5374
                    }
5375
                    memcpy(drcs_segment, value, drcs_segment_length);
×
5376
                    drcs_segment[drcs_segment_length] = '\0';
×
5377
                    errno = 0;
×
5378
                    endptr = NULL;
×
5379
                    drcs_mmv_value = (int)strtol(drcs_segment, &endptr, 10);
×
5380
                    if (errno != 0 || endptr == drcs_segment || *endptr != '\0') {
×
5381
                        sixel_helper_set_additional_message(
×
5382
                            "cannot parse DRCS option.");
5383
                        status = SIXEL_BAD_ARGUMENT;
×
5384
                        goto end;
×
5385
                    }
5386
                }
5387
                drcs_arg_charset = drcs_arg_delim + 1;
×
5388
                drcs_arg_second_delim = strchr(drcs_arg_charset, ':');
×
5389
                if (drcs_arg_second_delim != NULL) {
×
5390
                    if (drcs_arg_second_delim != drcs_arg_charset) {
×
5391
                        drcs_segment_length =
×
5392
                            (size_t)(drcs_arg_second_delim - drcs_arg_charset);
×
5393
                        if (drcs_segment_length >= sizeof(drcs_segment)) {
×
5394
                            sixel_helper_set_additional_message(
×
5395
                                "DRCS charset number is too long.");
5396
                            status = SIXEL_BAD_ARGUMENT;
×
5397
                            goto end;
×
5398
                        }
5399
                        memcpy(drcs_segment,
×
5400
                               drcs_arg_charset,
5401
                               drcs_segment_length);
5402
                        drcs_segment[drcs_segment_length] = '\0';
×
5403
                        errno = 0;
×
5404
                        endptr = NULL;
×
5405
                        drcs_charset_value = strtol(drcs_segment,
×
5406
                                                    &endptr,
5407
                                                    10);
5408
                        if (errno != 0 || endptr == drcs_segment ||
×
5409
                                *endptr != '\0') {
×
5410
                            sixel_helper_set_additional_message(
×
5411
                                "cannot parse DRCS charset number.");
5412
                            status = SIXEL_BAD_ARGUMENT;
×
5413
                            goto end;
×
5414
                        }
5415
                    }
5416
                    drcs_arg_path = drcs_arg_second_delim + 1;
×
5417
                    drcs_arg_path_length = strlen(drcs_arg_path);
×
5418
                    if (drcs_arg_path_length == 0u) {
×
5419
                        drcs_arg_path = NULL;
×
5420
                    }
5421
                } else if (*drcs_arg_charset != '\0') {
×
5422
                    drcs_segment_length = strlen(drcs_arg_charset);
×
5423
                    if (drcs_segment_length >= sizeof(drcs_segment)) {
×
5424
                        sixel_helper_set_additional_message(
×
5425
                            "DRCS charset number is too long.");
5426
                        status = SIXEL_BAD_ARGUMENT;
×
5427
                        goto end;
×
5428
                    }
5429
                    memcpy(drcs_segment,
×
5430
                           drcs_arg_charset,
5431
                           drcs_segment_length);
5432
                    drcs_segment[drcs_segment_length] = '\0';
×
5433
                    errno = 0;
×
5434
                    endptr = NULL;
×
5435
                    drcs_charset_value = strtol(drcs_segment,
×
5436
                                                &endptr,
5437
                                                10);
5438
                    if (errno != 0 || endptr == drcs_segment ||
×
5439
                            *endptr != '\0') {
×
5440
                        sixel_helper_set_additional_message(
×
5441
                            "cannot parse DRCS charset number.");
5442
                        status = SIXEL_BAD_ARGUMENT;
×
5443
                        goto end;
×
5444
                    }
5445
                }
5446
            }
5447
        }
5448
        /*
5449
         * Layout of the DRCS option value:
5450
         *
5451
         *    value = <mmv>:<charset_no>:<path>
5452
         *          ^        ^                ^
5453
         *          |        |                |
5454
         *          |        |                +-- optional path that may reuse
5455
         *          |        |                    STDOUT when set to "-" or drop
5456
         *          |        |                    tiles when left blank
5457
         *          |        +-- charset number (defaults to 1 when omitted)
5458
         *          +-- mapping revision (defaults to 2 when omitted)
5459
         */
5460
        if (drcs_mmv_value < 0 || drcs_mmv_value > 2) {
×
5461
            sixel_helper_set_additional_message(
×
5462
                "unknown DRCS unicode mapping version.");
5463
            status = SIXEL_BAD_ARGUMENT;
×
5464
            goto end;
×
5465
        }
5466
        if (drcs_mmv_value == 0) {
×
5467
            drcs_charset_limit = 126u;
×
5468
        } else if (drcs_mmv_value == 1) {
×
5469
            drcs_charset_limit = 63u;
×
5470
        } else {
5471
            drcs_charset_limit = 158u;
×
5472
        }
5473
        if (drcs_charset_value < 1 ||
×
5474
            (unsigned long)drcs_charset_value > drcs_charset_limit) {
×
5475
            sixel_helper_set_additional_message(
×
5476
                "DRCS charset number is out of range.");
5477
            status = SIXEL_BAD_ARGUMENT;
×
5478
            goto end;
×
5479
        }
5480
        encoder->drcs_mmv = drcs_mmv_value;
×
5481
        encoder->drcs_charset_no = (unsigned short)drcs_charset_value;
×
5482
        if (encoder->tile_outfd >= 0
×
5483
            && encoder->tile_outfd != encoder->outfd
×
5484
            && encoder->tile_outfd != STDOUT_FILENO
×
5485
            && encoder->tile_outfd != STDERR_FILENO) {
×
5486
#if HAVE__CLOSE
5487
            (void) _close(encoder->tile_outfd);
5488
#else
5489
            (void) close(encoder->tile_outfd);
×
5490
#endif  /* HAVE__CLOSE */
5491
        }
5492
        encoder->tile_outfd = (-1);
×
5493
        if (drcs_arg_path != NULL) {
×
5494
            if (strcmp(drcs_arg_path, "-") == 0) {
×
5495
                encoder->tile_outfd = STDOUT_FILENO;
×
5496
            } else {
5497
#if HAVE__OPEN
5498
                encoder->tile_outfd = _open(drcs_arg_path,
5499
                                            O_RDWR|O_CREAT|O_TRUNC,
5500
                                            S_IRUSR|S_IWUSR);
5501
#else
5502
                encoder->tile_outfd = open(drcs_arg_path,
×
5503
                                           O_RDWR|O_CREAT|O_TRUNC,
5504
                                           S_IRUSR|S_IWUSR);
5505
#endif  /* HAVE__OPEN */
5506
                if (encoder->tile_outfd < 0) {
×
5507
                    sixel_helper_set_additional_message(
×
5508
                        "sixel_encoder_setopt: failed to open tile"
5509
                        " output path.");
5510
                    status = SIXEL_RUNTIME_ERROR;
×
5511
                    goto end;
×
5512
                }
5513
            }
5514
        }
5515
        break;
×
5516
    case SIXEL_OPTFLAG_PENETRATE:  /* P */
6✔
5517
        encoder->penetrate_multiplexer = 1;
9✔
5518
        break;
9✔
5519
    case SIXEL_OPTFLAG_ENCODE_POLICY:  /* E */
8✔
5520
        match_result = sixel_option_match_choice(
12✔
5521
            value,
4✔
5522
            g_option_choices_encode_policy,
5523
            sizeof(g_option_choices_encode_policy) /
5524
            sizeof(g_option_choices_encode_policy[0]),
5525
            &match_value,
5526
            match_detail,
4✔
5527
            sizeof(match_detail));
5528
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
12✔
5529
            encoder->encode_policy = match_value;
9✔
5530
        } else {
3✔
5531
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
NEW
5532
                sixel_option_report_ambiguous_prefix(value,
×
5533
                                              match_detail,
5534
                                              match_message,
5535
                                              sizeof(match_message));
5536
            } else {
5537
                sixel_helper_set_additional_message(
3✔
5538
                    "cannot parse encode policy option.");
5539
            }
5540
            status = SIXEL_BAD_ARGUMENT;
3✔
5541
            goto end;
3✔
5542
        }
5543
        break;
9✔
5544
    case SIXEL_OPTFLAG_LUT_POLICY:  /* L */
NEW
5545
        match_result = sixel_option_match_choice(
×
5546
            value,
5547
            g_option_choices_lut_policy,
5548
            sizeof(g_option_choices_lut_policy) /
5549
            sizeof(g_option_choices_lut_policy[0]),
5550
            &match_value,
5551
            match_detail,
5552
            sizeof(match_detail));
5553
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5554
            encoder->lut_policy = match_value;
×
5555
        } else {
5556
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
NEW
5557
                sixel_option_report_ambiguous_prefix(value,
×
5558
                                              match_detail,
5559
                                              match_message,
5560
                                              sizeof(match_message));
5561
            } else {
5562
                sixel_helper_set_additional_message(
×
5563
                    "cannot parse lut policy option.");
5564
            }
5565
            status = SIXEL_BAD_ARGUMENT;
×
5566
            goto end;
×
5567
        }
5568
        if (encoder->dither_cache != NULL) {
×
5569
            sixel_dither_set_lut_policy(encoder->dither_cache,
×
5570
                                        encoder->lut_policy);
5571
        }
5572
        break;
×
5573
    case SIXEL_OPTFLAG_WORKING_COLORSPACE:  /* W */
5574
        if (value == NULL) {
×
5575
            sixel_helper_set_additional_message(
×
5576
                "working-colorspace requires an argument.");
5577
            status = SIXEL_BAD_ARGUMENT;
×
5578
            goto end;
×
5579
        } else {
5580
            len = strlen(value);
×
5581

5582
            if (len >= sizeof(lowered)) {
×
5583
                sixel_helper_set_additional_message(
×
5584
                    "specified working colorspace name is too long.");
5585
                status = SIXEL_BAD_ARGUMENT;
×
5586
                goto end;
×
5587
            }
5588
            for (i = 0; i < len; ++i) {
×
5589
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
5590
            }
5591
            lowered[len] = '\0';
×
5592

NEW
5593
            match_result = sixel_option_match_choice(
×
5594
                lowered,
5595
                g_option_choices_working_colorspace,
5596
                sizeof(g_option_choices_working_colorspace) /
5597
                sizeof(g_option_choices_working_colorspace[0]),
5598
                &match_value,
5599
                match_detail,
5600
                sizeof(match_detail));
5601
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5602
                encoder->working_colorspace = match_value;
×
5603
            } else {
5604
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
NEW
5605
                    sixel_option_report_ambiguous_prefix(value,
×
5606
                        match_detail,
5607
                        match_message,
5608
                        sizeof(match_message));
5609
                } else {
5610
                    sixel_helper_set_additional_message(
×
5611
                        "unsupported working colorspace specified.");
5612
                }
5613
                status = SIXEL_BAD_ARGUMENT;
×
5614
                goto end;
×
5615
            }
5616
        }
5617
        break;
×
5618
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
5619
        if (value == NULL) {
×
5620
            sixel_helper_set_additional_message(
×
5621
                "output-colorspace requires an argument.");
5622
            status = SIXEL_BAD_ARGUMENT;
×
5623
            goto end;
×
5624
        } else {
5625
            len = strlen(value);
×
5626

5627
            if (len >= sizeof(lowered)) {
×
5628
                sixel_helper_set_additional_message(
×
5629
                    "specified output colorspace name is too long.");
5630
                status = SIXEL_BAD_ARGUMENT;
×
5631
                goto end;
×
5632
            }
5633
            for (i = 0; i < len; ++i) {
×
5634
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
5635
            }
5636
            lowered[len] = '\0';
×
5637

NEW
5638
            match_result = sixel_option_match_choice(
×
5639
                lowered,
5640
                g_option_choices_output_colorspace,
5641
                sizeof(g_option_choices_output_colorspace) /
5642
                sizeof(g_option_choices_output_colorspace[0]),
5643
                &match_value,
5644
                match_detail,
5645
                sizeof(match_detail));
5646
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5647
                encoder->output_colorspace = match_value;
×
5648
            } else {
5649
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
NEW
5650
                    sixel_option_report_ambiguous_prefix(value,
×
5651
                        match_detail,
5652
                        match_message,
5653
                        sizeof(match_message));
5654
                } else {
5655
                    sixel_helper_set_additional_message(
×
5656
                        "unsupported output colorspace specified.");
5657
                }
5658
                status = SIXEL_BAD_ARGUMENT;
×
5659
                goto end;
×
5660
            }
5661
        }
5662
        break;
×
5663
    case SIXEL_OPTFLAG_ORMODE:  /* O */
5664
        encoder->ormode = 1;
×
5665
        break;
×
5666
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
6✔
5667
        encoder->complexion = atoi(value);
9✔
5668
        if (encoder->complexion < 1) {
9✔
5669
            sixel_helper_set_additional_message(
3✔
5670
                "complexion parameter must be 1 or more.");
5671
            status = SIXEL_BAD_ARGUMENT;
3✔
5672
            goto end;
3✔
5673
        }
5674
        break;
6✔
5675
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
5676
        encoder->pipe_mode = 1;
×
5677
        break;
×
5678
    case '?':  /* unknown option */
×
5679
    default:
5680
        /* exit if unknown options are specified */
5681
        sixel_helper_set_additional_message(
×
5682
            "unknown option is specified.");
5683
        status = SIXEL_BAD_ARGUMENT;
×
5684
        goto end;
×
5685
    }
5686

5687
    /* detects arguments conflictions */
5688
    if (encoder->reqcolors != (-1)) {
640✔
5689
        switch (encoder->color_option) {
99!
5690
        case SIXEL_COLOR_OPTION_MAPFILE:
5691
            sixel_helper_set_additional_message(
×
5692
                "option -p, --colors conflicts with -m, --mapfile.");
5693
            status = SIXEL_BAD_ARGUMENT;
×
5694
            goto end;
×
5695
        case SIXEL_COLOR_OPTION_MONOCHROME:
2✔
5696
            sixel_helper_set_additional_message(
3✔
5697
                "option -e, --monochrome conflicts with -p, --colors.");
5698
            status = SIXEL_BAD_ARGUMENT;
3✔
5699
            goto end;
3✔
5700
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
2✔
5701
            sixel_helper_set_additional_message(
3✔
5702
                "option -p, --colors conflicts with -I, --high-color.");
5703
            status = SIXEL_BAD_ARGUMENT;
3✔
5704
            goto end;
3✔
5705
        case SIXEL_COLOR_OPTION_BUILTIN:
2✔
5706
            sixel_helper_set_additional_message(
3✔
5707
                "option -p, --colors conflicts with -b, --builtin-palette.");
5708
            status = SIXEL_BAD_ARGUMENT;
3✔
5709
            goto end;
3✔
5710
        default:
60✔
5711
            break;
90✔
5712
        }
5713
    }
30✔
5714

5715
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
5716
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
631✔
5717
        sixel_helper_set_additional_message(
3✔
5718
            "option -8 --8bit-mode conflicts"
5719
            " with -P, --penetrate.");
5720
        status = SIXEL_BAD_ARGUMENT;
3✔
5721
        goto end;
3✔
5722
    }
5723

5724
    status = SIXEL_OK;
628✔
5725

5726
end:
478✔
5727
    if (opt_copy != NULL) {
718!
5728
        sixel_allocator_free(encoder->allocator, opt_copy);
×
5729
    }
5730
    sixel_encoder_unref(encoder);
718✔
5731

5732
    return status;
718✔
5733
}
5734

5735

5736
/* called when image loader component load a image frame */
5737
static SIXELSTATUS
5738
load_image_callback(sixel_frame_t *frame, void *data)
526✔
5739
{
5740
    sixel_encoder_t *encoder;
5741

5742
    encoder = (sixel_encoder_t *)data;
526✔
5743
    if (encoder->capture_source && encoder->capture_source_frame == NULL) {
526!
5744
        sixel_frame_ref(frame);
3✔
5745
        encoder->capture_source_frame = frame;
3✔
5746
    }
1✔
5747

5748
    return sixel_encoder_encode_frame(encoder, frame, NULL);
526✔
5749
}
5750

5751
/*
5752
 * Build a tee for encoded-assessment output:
5753
 *
5754
 *     +-------------+     +-------------------+     +------------+
5755
 *     | encoder FD  | --> | temporary SIXEL   | --> | tee sink   |
5756
 *     +-------------+     +-------------------+     +------------+
5757
 *
5758
 * The tee sink can be stdout or a user-provided file such as /dev/null.
5759
 */
5760
static SIXELSTATUS
5761
copy_file_to_stream(char const *path,
×
5762
                    FILE *stream,
5763
                    sixel_assessment_t *assessment)
5764
{
5765
    FILE *source;
5766
    unsigned char buffer[4096];
5767
    size_t nread;
5768
    size_t nwritten;
5769
    double started_at;
5770
    double finished_at;
5771
    double duration;
5772

5773
    source = NULL;
×
5774
    nread = 0;
×
5775
    nwritten = 0;
×
5776
    started_at = 0.0;
×
5777
    finished_at = 0.0;
×
5778
    duration = 0.0;
×
5779

5780
    source = sixel_compat_fopen(path, "rb");
×
5781
    if (source == NULL) {
×
5782
        sixel_helper_set_additional_message(
×
5783
            "copy_file_to_stream: failed to open assessment staging file.");
5784
        return SIXEL_LIBC_ERROR;
×
5785
    }
5786

5787
    for (;;) {
5788
        nread = fread(buffer, 1, sizeof(buffer), source);
×
5789
        if (nread == 0) {
×
5790
            if (ferror(source)) {
×
5791
                sixel_helper_set_additional_message(
×
5792
                    "copy_file_to_stream: failed while reading assessment staging file.");
5793
                (void) fclose(source);
×
5794
                return SIXEL_LIBC_ERROR;
×
5795
            }
5796
            break;
×
5797
        }
5798
        if (assessment != NULL) {
×
5799
            started_at = sixel_assessment_timer_now();
×
5800
        }
5801
        nwritten = fwrite(buffer, 1, nread, stream);
×
5802
        if (nwritten != nread) {
×
5803
            sixel_helper_set_additional_message(
×
5804
                "img2sixel: failed while copying assessment staging file.");
5805
            (void) fclose(source);
×
5806
            return SIXEL_LIBC_ERROR;
×
5807
        }
5808
        if (assessment != NULL) {
×
5809
            finished_at = sixel_assessment_timer_now();
×
5810
            duration = finished_at - started_at;
×
5811
            if (duration < 0.0) {
×
5812
                duration = 0.0;
×
5813
            }
5814
            sixel_assessment_record_output_write(assessment,
×
5815
                                                 nwritten,
5816
                                                 duration);
5817
        }
5818
    }
5819

5820
    if (fclose(source) != 0) {
×
5821
        sixel_helper_set_additional_message(
×
5822
            "img2sixel: failed to close assessment staging file.");
5823
        return SIXEL_LIBC_ERROR;
×
5824
    }
5825

5826
    return SIXEL_OK;
×
5827
}
5828

5829
typedef struct assessment_json_sink {
5830
    FILE *stream;
5831
    int failed;
5832
} assessment_json_sink_t;
5833

5834
static void
5835
assessment_json_callback(char const *chunk,
42✔
5836
                         size_t length,
5837
                         void *user_data)
5838
{
5839
    assessment_json_sink_t *sink;
5840

5841
    sink = (assessment_json_sink_t *)user_data;
42✔
5842
    if (sink == NULL || sink->stream == NULL) {
42!
5843
        return;
×
5844
    }
5845
    if (sink->failed) {
42!
5846
        return;
×
5847
    }
5848
    if (fwrite(chunk, 1, length, sink->stream) != length) {
42!
5849
        sink->failed = 1;
×
5850
    }
5851
}
14✔
5852

5853
static char *
5854
create_temp_template_with_prefix(sixel_allocator_t *allocator,
11✔
5855
                                 char const *prefix,
5856
                                 size_t *capacity_out)
5857
{
5858
    char const *tmpdir;
5859
    size_t tmpdir_len;
5860
    size_t prefix_len;
5861
    size_t suffix_len;
5862
    size_t template_len;
5863
    char *template_path;
5864
    int needs_separator;
5865
    size_t maximum_tmpdir_len;
5866

5867
    tmpdir = sixel_compat_getenv("TMPDIR");
11✔
5868
#if defined(_WIN32)
5869
    if (tmpdir == NULL || tmpdir[0] == '\0') {
5870
        tmpdir = sixel_compat_getenv("TEMP");
5871
    }
5872
    if (tmpdir == NULL || tmpdir[0] == '\0') {
5873
        tmpdir = sixel_compat_getenv("TMP");
5874
    }
5875
#endif
5876
    if (tmpdir == NULL || tmpdir[0] == '\0') {
11!
5877
#if defined(_WIN32)
5878
        tmpdir = ".";
5879
#else
5880
        tmpdir = "/tmp";
6✔
5881
#endif
5882
    }
5883

5884
    tmpdir_len = strlen(tmpdir);
11✔
5885
    prefix_len = 0u;
11✔
5886
    suffix_len = 0u;
11✔
5887
    if (prefix == NULL) {
11!
5888
        return NULL;
×
5889
    }
5890

5891
    prefix_len = strlen(prefix);
11✔
5892
    suffix_len = prefix_len + strlen("-XXXXXX");
11✔
5893
    maximum_tmpdir_len = (size_t)INT_MAX;
11✔
5894

5895
    if (maximum_tmpdir_len <= suffix_len + 2) {
11!
5896
        return NULL;
×
5897
    }
5898
    if (tmpdir_len > maximum_tmpdir_len - (suffix_len + 2)) {
11!
5899
        return NULL;
×
5900
    }
5901
    needs_separator = 1;
11✔
5902
    if (tmpdir_len > 0) {
11!
5903
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
11!
5904
            needs_separator = 0;
5✔
5905
        }
5✔
5906
    }
5✔
5907

5908
    template_len = tmpdir_len + suffix_len + 2;
11✔
5909
    template_path = (char *)sixel_allocator_malloc(allocator, template_len);
11✔
5910
    if (template_path == NULL) {
11!
5911
        return NULL;
×
5912
    }
5913

5914
    if (needs_separator) {
11!
5915
#if defined(_WIN32)
5916
        (void) snprintf(template_path, template_len,
5917
                        "%s\\%s-XXXXXX", tmpdir, prefix);
5918
#else
5919
        (void) snprintf(template_path, template_len,
6✔
5920
                        "%s/%s-XXXXXX", tmpdir, prefix);
5921
#endif
5922
    } else {
5923
        (void) snprintf(template_path, template_len,
5✔
5924
                        "%s%s-XXXXXX", tmpdir, prefix);
5925
    }
5926

5927
    if (capacity_out != NULL) {
11!
5928
        *capacity_out = template_len;
11✔
5929
    }
5✔
5930

5931
    return template_path;
11✔
5932
}
5✔
5933

5934

5935
static char *
5936
create_temp_template(sixel_allocator_t *allocator,
9✔
5937
                     size_t *capacity_out)
5938
{
5939
    return create_temp_template_with_prefix(allocator,
12✔
5940
                                            "img2sixel",
5941
                                            capacity_out);
3✔
5942
}
5943

5944

5945
static void
5946
clipboard_select_format(char *dest,
2✔
5947
                        size_t dest_size,
5948
                        char const *format,
5949
                        char const *fallback)
5950
{
5951
    char const *source;
5952
    size_t limit;
5953

5954
    if (dest == NULL || dest_size == 0u) {
2!
5955
        return;
×
5956
    }
5957

5958
    source = fallback;
2✔
5959
    if (format != NULL && format[0] != '\0') {
2!
5960
        source = format;
×
5961
    }
5962

5963
    limit = dest_size - 1u;
2✔
5964
    if (limit == 0u) {
2!
5965
        dest[0] = '\0';
×
5966
        return;
×
5967
    }
5968

5969
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
2✔
5970
}
2✔
5971

5972

5973
static SIXELSTATUS
5974
clipboard_create_spool(sixel_allocator_t *allocator,
2✔
5975
                       char const *prefix,
5976
                       char **path_out,
5977
                       int *fd_out)
5978
{
5979
    SIXELSTATUS status;
5980
    char *template_path;
5981
    size_t template_capacity;
5982
    int open_flags;
5983
    int fd;
5984
    char *tmpname_result;
5985

5986
    status = SIXEL_FALSE;
2✔
5987
    template_path = NULL;
2✔
5988
    template_capacity = 0u;
2✔
5989
    open_flags = 0;
2✔
5990
    fd = (-1);
2✔
5991
    tmpname_result = NULL;
2✔
5992

5993
    template_path = create_temp_template_with_prefix(allocator,
4✔
5994
                                                     prefix,
2✔
5995
                                                     &template_capacity);
5996
    if (template_path == NULL) {
2!
5997
        sixel_helper_set_additional_message(
×
5998
            "clipboard: failed to allocate spool template.");
5999
        status = SIXEL_BAD_ALLOCATION;
×
6000
        goto end;
×
6001
    }
6002

6003
    if (sixel_compat_mktemp(template_path, template_capacity) != 0) {
2!
6004
        /* Fall back to tmpnam() when mktemp variants are unavailable. */
6005
        tmpname_result = tmpnam(template_path);
×
6006
        if (tmpname_result == NULL) {
×
6007
            sixel_helper_set_additional_message(
×
6008
                "clipboard: failed to reserve spool template.");
6009
            status = SIXEL_LIBC_ERROR;
×
6010
            goto end;
×
6011
        }
6012
        template_capacity = strlen(template_path) + 1u;
×
6013
    }
6014

6015
    open_flags = O_RDWR | O_CREAT | O_TRUNC;
2✔
6016
#if defined(O_EXCL)
6017
    open_flags |= O_EXCL;
2✔
6018
#endif
6019
    fd = sixel_compat_open(template_path, open_flags, S_IRUSR | S_IWUSR);
2✔
6020
    if (fd < 0) {
2!
6021
        sixel_helper_set_additional_message(
×
6022
            "clipboard: failed to open spool file.");
6023
        status = SIXEL_LIBC_ERROR;
×
6024
        goto end;
×
6025
    }
6026

6027
    *path_out = template_path;
2✔
6028
    if (fd_out != NULL) {
2!
6029
        *fd_out = fd;
1✔
6030
        fd = (-1);
1✔
6031
    }
1✔
6032

6033
    template_path = NULL;
2✔
6034
    status = SIXEL_OK;
2✔
6035

6036
end:
6037
    if (fd >= 0) {
2!
6038
        (void)sixel_compat_close(fd);
1✔
6039
    }
1✔
6040
    if (template_path != NULL) {
2!
6041
        sixel_allocator_free(allocator, template_path);
×
6042
    }
6043

6044
    return status;
2✔
6045
}
6046

6047

6048
static SIXELSTATUS
6049
clipboard_write_file(char const *path,
1✔
6050
                     unsigned char const *data,
6051
                     size_t size)
6052
{
6053
    FILE *stream;
6054
    size_t written;
6055

6056
    if (path == NULL) {
1!
6057
        sixel_helper_set_additional_message(
×
6058
            "clipboard: spool path is null.");
6059
        return SIXEL_BAD_ARGUMENT;
×
6060
    }
6061

6062
    stream = sixel_compat_fopen(path, "wb");
1✔
6063
    if (stream == NULL) {
1!
6064
        sixel_helper_set_additional_message(
×
6065
            "clipboard: failed to open spool file for write.");
6066
        return SIXEL_LIBC_ERROR;
×
6067
    }
6068

6069
    written = 0u;
1✔
6070
    if (size > 0u && data != NULL) {
1!
6071
        written = fwrite(data, 1u, size, stream);
1✔
6072
        if (written != size) {
1!
6073
            (void)fclose(stream);
×
6074
            sixel_helper_set_additional_message(
×
6075
                "clipboard: failed to write spool payload.");
6076
            return SIXEL_LIBC_ERROR;
×
6077
        }
6078
    }
1✔
6079

6080
    if (fclose(stream) != 0) {
1!
6081
        sixel_helper_set_additional_message(
×
6082
            "clipboard: failed to close spool file after write.");
6083
        return SIXEL_LIBC_ERROR;
×
6084
    }
6085

6086
    return SIXEL_OK;
1✔
6087
}
1✔
6088

6089

6090
static SIXELSTATUS
6091
clipboard_read_file(char const *path,
1✔
6092
                    unsigned char **data,
6093
                    size_t *size)
6094
{
6095
    FILE *stream;
6096
    long seek_result;
6097
    long file_size;
6098
    unsigned char *buffer;
6099
    size_t read_size;
6100

6101
    if (data == NULL || size == NULL) {
1!
6102
        sixel_helper_set_additional_message(
×
6103
            "clipboard: read buffer pointers are null.");
6104
        return SIXEL_BAD_ARGUMENT;
×
6105
    }
6106

6107
    *data = NULL;
1✔
6108
    *size = 0u;
1✔
6109

6110
    if (path == NULL) {
1!
6111
        sixel_helper_set_additional_message(
×
6112
            "clipboard: spool path is null.");
6113
        return SIXEL_BAD_ARGUMENT;
×
6114
    }
6115

6116
    stream = sixel_compat_fopen(path, "rb");
1✔
6117
    if (stream == NULL) {
1!
6118
        sixel_helper_set_additional_message(
×
6119
            "clipboard: failed to open spool file for read.");
6120
        return SIXEL_LIBC_ERROR;
×
6121
    }
6122

6123
    seek_result = fseek(stream, 0L, SEEK_END);
1✔
6124
    if (seek_result != 0) {
1!
6125
        (void)fclose(stream);
×
6126
        sixel_helper_set_additional_message(
×
6127
            "clipboard: failed to seek spool file.");
6128
        return SIXEL_LIBC_ERROR;
×
6129
    }
6130

6131
    file_size = ftell(stream);
1✔
6132
    if (file_size < 0) {
1!
6133
        (void)fclose(stream);
×
6134
        sixel_helper_set_additional_message(
×
6135
            "clipboard: failed to determine spool size.");
6136
        return SIXEL_LIBC_ERROR;
×
6137
    }
6138

6139
    seek_result = fseek(stream, 0L, SEEK_SET);
1✔
6140
    if (seek_result != 0) {
1!
6141
        (void)fclose(stream);
×
6142
        sixel_helper_set_additional_message(
×
6143
            "clipboard: failed to rewind spool file.");
6144
        return SIXEL_LIBC_ERROR;
×
6145
    }
6146

6147
    if (file_size == 0) {
1!
6148
        buffer = NULL;
×
6149
        read_size = 0u;
×
6150
    } else {
6151
        buffer = (unsigned char *)malloc((size_t)file_size);
1✔
6152
        if (buffer == NULL) {
1!
6153
            (void)fclose(stream);
×
6154
            sixel_helper_set_additional_message(
×
6155
                "clipboard: malloc() failed for spool payload.");
6156
            return SIXEL_BAD_ALLOCATION;
×
6157
        }
6158
        read_size = fread(buffer, 1u, (size_t)file_size, stream);
1✔
6159
        if (read_size != (size_t)file_size) {
1!
6160
            free(buffer);
×
6161
            (void)fclose(stream);
×
6162
            sixel_helper_set_additional_message(
×
6163
                "clipboard: failed to read spool payload.");
6164
            return SIXEL_LIBC_ERROR;
×
6165
        }
6166
    }
6167

6168
    if (fclose(stream) != 0) {
1!
6169
        if (buffer != NULL) {
×
6170
            free(buffer);
×
6171
        }
6172
        sixel_helper_set_additional_message(
×
6173
            "clipboard: failed to close spool file after read.");
6174
        return SIXEL_LIBC_ERROR;
×
6175
    }
6176

6177
    *data = buffer;
1✔
6178
    *size = read_size;
1✔
6179

6180
    return SIXEL_OK;
1✔
6181
}
1✔
6182

6183

6184
static SIXELSTATUS
6185
write_png_from_sixel(char const *sixel_path, char const *output_path)
9✔
6186
{
6187
    SIXELSTATUS status;
6188
    sixel_decoder_t *decoder;
6189

6190
    status = SIXEL_FALSE;
9✔
6191
    decoder = NULL;
9✔
6192

6193
    status = sixel_decoder_new(&decoder, NULL);
9✔
6194
    if (SIXEL_FAILED(status)) {
9!
6195
        goto end;
×
6196
    }
6197

6198
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_INPUT, sixel_path);
9✔
6199
    if (SIXEL_FAILED(status)) {
9!
6200
        goto end;
×
6201
    }
6202

6203
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_OUTPUT, output_path);
9✔
6204
    if (SIXEL_FAILED(status)) {
9!
6205
        goto end;
×
6206
    }
6207

6208
    status = sixel_decoder_decode(decoder);
9✔
6209

6210
end:
6✔
6211
    sixel_decoder_unref(decoder);
9✔
6212

6213
    return status;
9✔
6214
}
6215

6216

6217
/* load source data from specified file and encode it to SIXEL format
6218
 * output to encoder->outfd */
6219
SIXELAPI SIXELSTATUS
6220
sixel_encoder_encode(
437✔
6221
    sixel_encoder_t *encoder,   /* encoder object */
6222
    char const      *filename)  /* input filename */
6223
{
6224
    SIXELSTATUS status = SIXEL_FALSE;
437✔
6225
    SIXELSTATUS palette_status = SIXEL_OK;
437✔
6226
    int fuse_palette = 1;
437✔
6227
    sixel_loader_t *loader = NULL;
437✔
6228
    sixel_allocator_t *assessment_allocator = NULL;
437✔
6229
    sixel_allocator_t *encode_allocator = NULL;
437✔
6230
    sixel_frame_t *assessment_source_frame = NULL;
437✔
6231
    sixel_frame_t *assessment_target_frame = NULL;
437✔
6232
    sixel_frame_t *assessment_expanded_frame = NULL;
437✔
6233
    unsigned int assessment_section_mask =
437✔
6234
        encoder->assessment_sections & SIXEL_ASSESSMENT_SECTION_MASK;
437✔
6235
    int assessment_need_source_capture = 0;
437✔
6236
    int assessment_need_quantized_capture = 0;
437✔
6237
    int assessment_need_quality = 0;
437✔
6238
    int assessment_quality_quantized = 0;
437✔
6239
    assessment_json_sink_t assessment_sink;
6240
    FILE *assessment_json_file = NULL;
437✔
6241
    FILE *assessment_forward_stream = NULL;
437✔
6242
    int assessment_json_owned = 0;
437✔
6243
    char *assessment_temp_path = NULL;
437✔
6244
    size_t assessment_temp_capacity = 0u;
437✔
6245
    char *assessment_tmpnam_result = NULL;
437✔
6246
    sixel_assessment_spool_mode_t assessment_spool_mode
437✔
6247
        = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
6248
    char *assessment_forward_path = NULL;
437✔
6249
    size_t assessment_output_bytes;
6250
#if HAVE_SYS_STAT_H
6251
    struct stat assessment_stat;
6252
    int assessment_stat_result;
6253
    char const *assessment_size_path = NULL;
437✔
6254
#endif
6255
    char const *png_final_path = NULL;
437✔
6256
    char *png_temp_path = NULL;
437✔
6257
    size_t png_temp_capacity = 0u;
437✔
6258
    char *png_tmpnam_result = NULL;
437✔
6259
    int png_open_flags = 0;
437✔
6260
    int spool_required;
6261
    sixel_clipboard_spec_t clipboard_spec;
6262
    char clipboard_input_format[32];
6263
    char *clipboard_input_path;
6264
    unsigned char *clipboard_blob;
6265
    size_t clipboard_blob_size;
6266
    SIXELSTATUS clipboard_status;
6267
    char const *effective_filename;
6268
    unsigned int path_flags;
6269
    int path_check;
6270

6271
    clipboard_input_format[0] = '\0';
437✔
6272
    clipboard_input_path = NULL;
437✔
6273
    clipboard_blob = NULL;
437✔
6274
    clipboard_blob_size = 0u;
437✔
6275
    clipboard_status = SIXEL_OK;
437✔
6276
    effective_filename = filename;
437✔
6277
    path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
437✔
6278
        SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
6279
        SIXEL_OPTION_PATH_ALLOW_REMOTE;
6280
    path_check = 0;
437✔
6281

6282
    if (filename != NULL) {
437✔
6283
        path_check = sixel_option_validate_filesystem_path(
296✔
6284
            filename,
100✔
6285
            filename,
100✔
6286
            path_flags);
100✔
6287
        if (path_check != 0) {
296!
6288
            status = SIXEL_BAD_ARGUMENT;
1✔
6289
            goto end;
1✔
6290
        }
6291
    }
99✔
6292

6293
    if (encoder != NULL) {
436!
6294
        encode_allocator = encoder->allocator;
436✔
6295
        if (encode_allocator != NULL) {
436!
6296
            /*
6297
             * Hold a reference until cleanup so worker side-effects or loader
6298
             * destruction cannot release the allocator before sequential
6299
             * teardown finishes using it.
6300
             */
6301
            sixel_allocator_ref(encode_allocator);
436✔
6302
        }
146✔
6303
    }
146✔
6304

6305
    clipboard_spec.is_clipboard = 0;
436✔
6306
    clipboard_spec.format[0] = '\0';
436✔
6307
    if (effective_filename != NULL
437!
6308
            && sixel_clipboard_parse_spec(effective_filename,
342!
6309
                                          &clipboard_spec)
6310
            && clipboard_spec.is_clipboard) {
99!
6311
        clipboard_select_format(clipboard_input_format,
2✔
6312
                                sizeof(clipboard_input_format),
6313
                                clipboard_spec.format,
1✔
6314
                                "sixel");
6315
        clipboard_status = sixel_clipboard_read(
1✔
6316
            clipboard_input_format,
1✔
6317
            &clipboard_blob,
6318
            &clipboard_blob_size,
6319
            encoder->allocator);
1✔
6320
        if (SIXEL_FAILED(clipboard_status)) {
1!
6321
            status = clipboard_status;
×
6322
            goto end;
×
6323
        }
6324
        clipboard_status = clipboard_create_spool(
1✔
6325
            encoder->allocator,
1✔
6326
            "clipboard-in",
6327
            &clipboard_input_path,
6328
            NULL);
6329
        if (SIXEL_FAILED(clipboard_status)) {
1!
6330
            status = clipboard_status;
×
6331
            goto end;
×
6332
        }
6333
        clipboard_status = clipboard_write_file(
1✔
6334
            clipboard_input_path,
1✔
6335
            clipboard_blob,
1✔
6336
            clipboard_blob_size);
1✔
6337
        if (SIXEL_FAILED(clipboard_status)) {
1!
6338
            status = clipboard_status;
×
6339
            goto end;
×
6340
        }
6341
        if (clipboard_blob != NULL) {
1!
6342
            free(clipboard_blob);
1✔
6343
            clipboard_blob = NULL;
1✔
6344
        }
1✔
6345
        effective_filename = clipboard_input_path;
1✔
6346
    }
1✔
6347

6348
    if (assessment_section_mask != SIXEL_ASSESSMENT_SECTION_NONE) {
436✔
6349
        status = sixel_allocator_new(&assessment_allocator,
3✔
6350
                                     malloc,
6351
                                     calloc,
6352
                                     realloc,
6353
                                     free);
6354
        if (SIXEL_FAILED(status) || assessment_allocator == NULL) {
3!
6355
            goto end;
×
6356
        }
6357
        status = sixel_assessment_new(&encoder->assessment_observer,
4✔
6358
                                       assessment_allocator);
1✔
6359
        if (SIXEL_FAILED(status) || encoder->assessment_observer == NULL) {
3!
6360
            goto end;
×
6361
        }
6362
        sixel_assessment_select_sections(encoder->assessment_observer,
4✔
6363
                                         encoder->assessment_sections);
1✔
6364
        sixel_assessment_attach_encoder(encoder->assessment_observer,
4✔
6365
                                        encoder);
1✔
6366
        assessment_need_quality =
3✔
6367
            (assessment_section_mask & SIXEL_ASSESSMENT_SECTION_QUALITY) != 0u;
3✔
6368
        assessment_quality_quantized =
3✔
6369
            (encoder->assessment_sections & SIXEL_ASSESSMENT_VIEW_QUANTIZED) != 0u;
3✔
6370
        assessment_need_quantized_capture =
3✔
6371
            ((assessment_section_mask &
3✔
6372
              (SIXEL_ASSESSMENT_SECTION_BASIC |
6373
               SIXEL_ASSESSMENT_SECTION_SIZE)) != 0u) ||
3!
6374
            assessment_quality_quantized;
6375
        assessment_need_source_capture =
3✔
6376
            (assessment_section_mask &
3✔
6377
             (SIXEL_ASSESSMENT_SECTION_BASIC |
6378
              SIXEL_ASSESSMENT_SECTION_PERFORMANCE |
6379
              SIXEL_ASSESSMENT_SECTION_SIZE |
6380
              SIXEL_ASSESSMENT_SECTION_QUALITY)) != 0u;
3✔
6381
        if (assessment_need_quality && !assessment_quality_quantized &&
3!
6382
                encoder->output_is_png) {
×
6383
            sixel_helper_set_additional_message(
×
6384
                "sixel_encoder_setopt: encoded quality assessment requires SIXEL output.");
6385
            status = SIXEL_BAD_ARGUMENT;
×
6386
            goto end;
×
6387
        }
6388
        status = sixel_encoder_enable_source_capture(encoder,
4✔
6389
                                                     assessment_need_source_capture);
1✔
6390
        if (SIXEL_FAILED(status)) {
3!
6391
            goto end;
×
6392
        }
6393
        status = sixel_encoder_setopt(encoder, SIXEL_OPTFLAG_STATIC, NULL);
3✔
6394
        if (SIXEL_FAILED(status)) {
3!
6395
            goto end;
×
6396
        }
6397
        if (assessment_need_quantized_capture) {
3!
6398
            status = sixel_encoder_enable_quantized_capture(encoder, 1);
3✔
6399
            if (SIXEL_FAILED(status)) {
3!
6400
                goto end;
×
6401
            }
6402
        }
1✔
6403
        assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
3✔
6404
        spool_required = 0;
3✔
6405
        if (assessment_need_quality && !assessment_quality_quantized) {
3!
6406
            if (encoder->sixel_output_path == NULL) {
×
6407
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT;
×
6408
                spool_required = 1;
×
6409
            } else if (strcmp(encoder->sixel_output_path, "-") == 0) {
×
6410
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT;
×
6411
                spool_required = 1;
×
6412
                free(encoder->sixel_output_path);
×
6413
                encoder->sixel_output_path = NULL;
×
6414
            } else if (is_dev_null_path(encoder->sixel_output_path)) {
×
6415
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_PATH;
×
6416
                spool_required = 1;
×
6417
                assessment_forward_path = encoder->sixel_output_path;
×
6418
                encoder->sixel_output_path = NULL;
×
6419
            }
6420
        }
6421
        if (spool_required) {
3!
6422
            assessment_temp_capacity = 0u;
×
6423
            assessment_tmpnam_result = NULL;
×
6424
            assessment_temp_path = create_temp_template(encoder->allocator,
×
6425
                                                        &assessment_temp_capacity);
6426
            if (assessment_temp_path == NULL) {
×
6427
                sixel_helper_set_additional_message(
×
6428
                    "sixel_encoder_encode: sixel_allocator_malloc() "
6429
                    "failed for assessment staging path.");
6430
                status = SIXEL_BAD_ALLOCATION;
×
6431
                goto end;
×
6432
            }
6433
            if (sixel_compat_mktemp(assessment_temp_path,
×
6434
                                    assessment_temp_capacity) != 0) {
6435
                /* Fall back to tmpnam() when mktemp variants are unavailable. */
6436
                assessment_tmpnam_result = tmpnam(assessment_temp_path);
×
6437
                if (assessment_tmpnam_result == NULL) {
×
6438
                    sixel_helper_set_additional_message(
×
6439
                        "sixel_encoder_encode: mktemp() failed for assessment staging file.");
6440
                    status = SIXEL_RUNTIME_ERROR;
×
6441
                    goto end;
×
6442
                }
6443
                assessment_temp_capacity = strlen(assessment_temp_path) + 1u;
×
6444
            }
6445
            status = sixel_encoder_setopt(encoder, SIXEL_OPTFLAG_OUTFILE,
×
6446
                                          assessment_temp_path);
6447
            if (SIXEL_FAILED(status)) {
×
6448
                goto end;
×
6449
            }
6450
            encoder->sixel_output_path = (char *)sixel_allocator_malloc(
×
6451
                encoder->allocator, strlen(assessment_temp_path) + 1);
×
6452
            if (encoder->sixel_output_path == NULL) {
×
6453
                sixel_helper_set_additional_message(
×
6454
                    "sixel_encoder_encode: malloc() failed for assessment staging name.");
6455
                status = SIXEL_BAD_ALLOCATION;
×
6456
                goto end;
×
6457
            }
6458
            (void)sixel_compat_strcpy(encoder->sixel_output_path,
×
6459
                                      strlen(assessment_temp_path) + 1,
×
6460
                                      assessment_temp_path);
6461
        }
6462

6463
    }
1✔
6464

6465
    if (encoder->output_is_png) {
436✔
6466
        png_temp_capacity = 0u;
9✔
6467
        png_tmpnam_result = NULL;
9✔
6468
        png_temp_path = create_temp_template(encoder->allocator,
9✔
6469
                                             &png_temp_capacity);
6470
        if (png_temp_path == NULL) {
9!
6471
            sixel_helper_set_additional_message(
×
6472
                "sixel_encoder_encode: malloc() failed for PNG staging path.");
6473
            status = SIXEL_BAD_ALLOCATION;
×
6474
            goto end;
×
6475
        }
6476
        if (sixel_compat_mktemp(png_temp_path, png_temp_capacity) != 0) {
9!
6477
            /* Fall back to tmpnam() when mktemp variants are unavailable. */
6478
            png_tmpnam_result = tmpnam(png_temp_path);
×
6479
            if (png_tmpnam_result == NULL) {
×
6480
                sixel_helper_set_additional_message(
×
6481
                    "sixel_encoder_encode: mktemp() failed for PNG staging file.");
6482
                status = SIXEL_RUNTIME_ERROR;
×
6483
                goto end;
×
6484
            }
6485
            png_temp_capacity = strlen(png_temp_path) + 1u;
×
6486
        }
6487
        if (encoder->outfd >= 0 && encoder->outfd != STDOUT_FILENO) {
9!
6488
            (void)sixel_compat_close(encoder->outfd);
9✔
6489
        }
3✔
6490
        png_open_flags = O_RDWR | O_CREAT | O_TRUNC;
9✔
6491
#if defined(O_EXCL)
6492
        png_open_flags |= O_EXCL;
9✔
6493
#endif
6494
        encoder->outfd = sixel_compat_open(png_temp_path,
12✔
6495
                                           png_open_flags,
3✔
6496
                                           S_IRUSR | S_IWUSR);
6497
        if (encoder->outfd < 0) {
9!
6498
            sixel_helper_set_additional_message(
×
6499
                "sixel_encoder_encode: failed to create the PNG target file.");
6500
            status = SIXEL_LIBC_ERROR;
×
6501
            goto end;
×
6502
        }
6503
    }
3✔
6504

6505
    if (encoder == NULL) {
436!
6506
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6507
#  pragma GCC diagnostic push
6508
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
6509
#endif
6510
        encoder = sixel_encoder_create();
×
6511
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6512
#  pragma GCC diagnostic pop
6513
#endif
6514
        if (encoder == NULL) {
×
6515
            sixel_helper_set_additional_message(
×
6516
                "sixel_encoder_encode: sixel_encoder_create() failed.");
6517
            status = SIXEL_BAD_ALLOCATION;
×
6518
            goto end;
×
6519
        }
6520
    } else {
6521
        sixel_encoder_ref(encoder);
436✔
6522
    }
6523

6524
    if (encode_allocator == NULL && encoder != NULL) {
436!
6525
        encode_allocator = encoder->allocator;
×
6526
        if (encode_allocator != NULL) {
×
6527
            /* Ensure the allocator stays valid after lazy encoder creation. */
6528
            sixel_allocator_ref(encode_allocator);
×
6529
        }
6530
    }
6531

6532
    if (encoder->assessment_observer != NULL) {
436✔
6533
        sixel_assessment_stage_transition(
3✔
6534
            encoder->assessment_observer,
3✔
6535
            SIXEL_ASSESSMENT_STAGE_IMAGE_CHUNK);
6536
    }
1✔
6537
    encoder->last_loader_name[0] = '\0';
436✔
6538
    encoder->last_source_path[0] = '\0';
436✔
6539
    encoder->last_input_bytes = 0u;
436✔
6540

6541
    /* if required color is not set, set the max value */
6542
    if (encoder->reqcolors == (-1)) {
436✔
6543
        encoder->reqcolors = SIXEL_PALETTE_MAX;
418✔
6544
    }
140✔
6545

6546
    if (encoder->capture_source && encoder->capture_source_frame != NULL) {
436!
6547
        sixel_frame_unref(encoder->capture_source_frame);
×
6548
        encoder->capture_source_frame = NULL;
×
6549
    }
6550

6551
    /* if required color is less then 2, set the min value */
6552
    if (encoder->reqcolors < 2) {
436✔
6553
        encoder->reqcolors = SIXEL_PALETTE_MIN;
3✔
6554
    }
1✔
6555

6556
    /* if color space option is not set, choose RGB color space */
6557
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
436✔
6558
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
418✔
6559
    }
140✔
6560

6561
    /* if color option is not default value, prohibit to read
6562
       the file as a paletted image */
6563
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
436✔
6564
        fuse_palette = 0;
96✔
6565
    }
32✔
6566

6567
    /* if scaling options are set, prohibit to read the file as
6568
       a paletted image */
6569
    if (encoder->percentwidth > 0 ||
559✔
6570
        encoder->percentheight > 0 ||
424✔
6571
        encoder->pixelwidth > 0 ||
418✔
6572
        encoder->pixelheight > 0) {
400✔
6573
        fuse_palette = 0;
99✔
6574
    }
33✔
6575

6576
reload:
290✔
6577

6578
    sixel_helper_set_loader_trace(encoder->verbose);
436✔
6579
    sixel_helper_set_thumbnail_size_hint(
436✔
6580
        sixel_encoder_thumbnail_hint(encoder));
146✔
6581

6582
    status = sixel_loader_new(&loader, encoder->allocator);
436✔
6583
    if (SIXEL_FAILED(status)) {
436!
6584
        goto load_end;
×
6585
    }
6586

6587
    status = sixel_loader_setopt(loader,
582✔
6588
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
6589
                                 &encoder->fstatic);
436✔
6590
    if (SIXEL_FAILED(status)) {
436!
6591
        goto load_end;
×
6592
    }
6593

6594
    status = sixel_loader_setopt(loader,
436✔
6595
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
6596
                                 &fuse_palette);
6597
    if (SIXEL_FAILED(status)) {
436!
6598
        goto load_end;
×
6599
    }
6600

6601
    status = sixel_loader_setopt(loader,
582✔
6602
                                 SIXEL_LOADER_OPTION_REQCOLORS,
6603
                                 &encoder->reqcolors);
436✔
6604
    if (SIXEL_FAILED(status)) {
436!
6605
        goto load_end;
×
6606
    }
6607

6608
    status = sixel_loader_setopt(loader,
582✔
6609
                                 SIXEL_LOADER_OPTION_BGCOLOR,
6610
                                 encoder->bgcolor);
436✔
6611
    if (SIXEL_FAILED(status)) {
436!
6612
        goto load_end;
×
6613
    }
6614

6615
    status = sixel_loader_setopt(loader,
582✔
6616
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
6617
                                 &encoder->loop_mode);
436✔
6618
    if (SIXEL_FAILED(status)) {
436!
6619
        goto load_end;
×
6620
    }
6621

6622
    status = sixel_loader_setopt(loader,
582✔
6623
                                 SIXEL_LOADER_OPTION_INSECURE,
6624
                                 &encoder->finsecure);
436✔
6625
    if (SIXEL_FAILED(status)) {
436!
6626
        goto load_end;
×
6627
    }
6628

6629
    status = sixel_loader_setopt(loader,
582✔
6630
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
6631
                                 encoder->cancel_flag);
436✔
6632
    if (SIXEL_FAILED(status)) {
436!
6633
        goto load_end;
×
6634
    }
6635

6636
    status = sixel_loader_setopt(loader,
582✔
6637
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
6638
                                 encoder->loader_order);
436✔
6639
    if (SIXEL_FAILED(status)) {
436!
6640
        goto load_end;
×
6641
    }
6642

6643
    status = sixel_loader_setopt(loader,
582✔
6644
                                 SIXEL_LOADER_OPTION_CONTEXT,
6645
                                 encoder);
146✔
6646
    if (SIXEL_FAILED(status)) {
436!
6647
        goto load_end;
×
6648
    }
6649

6650
    /*
6651
     * Wire the optional assessment observer into the loader.
6652
     *
6653
     * The observer travels separately from the callback context so mapfile
6654
     * palette probes and other callbacks can keep using arbitrary structs.
6655
     */
6656
    status = sixel_loader_setopt(loader,
582✔
6657
                                 SIXEL_LOADER_OPTION_ASSESSMENT,
6658
                                 encoder->assessment_observer);
436✔
6659
    if (SIXEL_FAILED(status)) {
436!
6660
        goto load_end;
×
6661
    }
6662

6663
    status = sixel_loader_load_file(loader,
582✔
6664
                                    effective_filename,
146✔
6665
                                    load_image_callback);
6666
    if (status != SIXEL_OK) {
436✔
6667
        goto load_end;
15✔
6668
    }
6669
    encoder->last_input_bytes = sixel_loader_get_last_input_bytes(loader);
421✔
6670
    if (sixel_loader_get_last_success_name(loader) != NULL) {
421!
6671
        (void)snprintf(encoder->last_loader_name,
421✔
6672
                       sizeof(encoder->last_loader_name),
6673
                       "%s",
6674
                       sixel_loader_get_last_success_name(loader));
6675
    } else {
141✔
6676
        encoder->last_loader_name[0] = '\0';
×
6677
    }
6678
    if (sixel_loader_get_last_source_path(loader) != NULL) {
421✔
6679
        (void)snprintf(encoder->last_source_path,
283✔
6680
                       sizeof(encoder->last_source_path),
6681
                       "%s",
6682
                       sixel_loader_get_last_source_path(loader));
6683
    } else {
95✔
6684
        encoder->last_source_path[0] = '\0';
138✔
6685
    }
6686
    if (encoder->assessment_observer != NULL) {
422✔
6687
        sixel_assessment_record_loader(encoder->assessment_observer,
4✔
6688
                                       encoder->last_source_path,
3✔
6689
                                       encoder->last_loader_name,
3✔
6690
                                       encoder->last_input_bytes);
1✔
6691
    }
1✔
6692

6693
load_end:
278✔
6694
    sixel_loader_unref(loader);
436✔
6695
    loader = NULL;
436✔
6696

6697
    if (status != SIXEL_OK) {
436✔
6698
        goto end;
15✔
6699
    }
6700

6701
    palette_status = sixel_encoder_emit_palette_output(encoder);
421✔
6702
    if (SIXEL_FAILED(palette_status)) {
421!
6703
        status = palette_status;
×
6704
        goto end;
×
6705
    }
6706

6707
    if (encoder->pipe_mode) {
421!
6708
#if HAVE_CLEARERR
6709
        clearerr(stdin);
×
6710
#endif  /* HAVE_FSEEK */
6711
        while (encoder->cancel_flag && !*encoder->cancel_flag) {
×
6712
            status = sixel_tty_wait_stdin(1000000);
×
6713
            if (SIXEL_FAILED(status)) {
×
6714
                goto end;
×
6715
            }
6716
            if (status != SIXEL_OK) {
×
6717
                break;
×
6718
            }
6719
        }
6720
        if (!encoder->cancel_flag || !*encoder->cancel_flag) {
×
6721
            goto reload;
×
6722
        }
6723
    }
6724

6725
    if (encoder->assessment_observer) {
421✔
6726
        if (assessment_allocator == NULL || encoder->assessment_observer == NULL) {
3!
6727
            status = SIXEL_RUNTIME_ERROR;
×
6728
            goto end;
×
6729
        }
6730
        if (assessment_need_source_capture) {
3!
6731
            status = sixel_encoder_copy_source_frame(encoder,
3✔
6732
                                                     &assessment_source_frame);
6733
            if (SIXEL_FAILED(status)) {
3!
6734
                goto end;
×
6735
            }
6736
            sixel_assessment_record_source_frame(encoder->assessment_observer,
4✔
6737
                                                 assessment_source_frame);
1✔
6738
        }
1✔
6739
        if (assessment_need_quality) {
3!
6740
            if (assessment_quality_quantized) {
×
6741
                status = sixel_encoder_copy_quantized_frame(
×
6742
                    encoder, assessment_allocator, &assessment_target_frame);
6743
                if (SIXEL_FAILED(status)) {
×
6744
                    goto end;
×
6745
                }
6746
                status = sixel_assessment_expand_quantized_frame(
×
6747
                    assessment_target_frame,
6748
                    assessment_allocator,
6749
                    &assessment_expanded_frame);
6750
                if (SIXEL_FAILED(status)) {
×
6751
                    goto end;
×
6752
                }
6753
                sixel_frame_unref(assessment_target_frame);
×
6754
                assessment_target_frame = assessment_expanded_frame;
×
6755
                assessment_expanded_frame = NULL;
×
6756
                sixel_assessment_record_quantized_capture(
×
6757
                    encoder->assessment_observer, encoder);
×
6758
            } else {
6759
                status = sixel_assessment_load_single_frame(
×
6760
                    encoder->sixel_output_path,
×
6761
                    assessment_allocator,
6762
                    &assessment_target_frame);
6763
                if (SIXEL_FAILED(status)) {
×
6764
                    goto end;
×
6765
                }
6766
            }
6767
            if (!assessment_quality_quantized &&
×
6768
                    assessment_need_quantized_capture) {
6769
                sixel_assessment_record_quantized_capture(
×
6770
                    encoder->assessment_observer, encoder);
×
6771
            }
6772
        } else if (assessment_need_quantized_capture) {
3!
6773
            sixel_assessment_record_quantized_capture(
3✔
6774
                encoder->assessment_observer, encoder);
3✔
6775
        }
1✔
6776
        if (encoder->assessment_observer != NULL &&
3!
6777
                assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
1✔
6778
            sixel_assessment_stage_transition(
×
6779
                encoder->assessment_observer,
×
6780
                SIXEL_ASSESSMENT_STAGE_OUTPUT);
6781
        }
6782
        if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT) {
3!
6783
            status = copy_file_to_stream(assessment_temp_path,
×
6784
                                         stdout,
6785
                                         encoder->assessment_observer);
×
6786
            if (SIXEL_FAILED(status)) {
×
6787
                goto end;
×
6788
            }
6789
        } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
3!
6790
            if (assessment_forward_path == NULL) {
×
6791
                sixel_helper_set_additional_message(
×
6792
                    "sixel_encoder_encode: missing assessment spool target.");
6793
                status = SIXEL_RUNTIME_ERROR;
×
6794
                goto end;
×
6795
            }
6796
            assessment_forward_stream = sixel_compat_fopen(
×
6797
                assessment_forward_path,
6798
                "wb");
6799
            if (assessment_forward_stream == NULL) {
×
6800
                sixel_helper_set_additional_message(
×
6801
                    "sixel_encoder_encode: failed to open assessment spool sink.");
6802
                status = SIXEL_LIBC_ERROR;
×
6803
                goto end;
×
6804
            }
6805
            status = copy_file_to_stream(assessment_temp_path,
×
6806
                                         assessment_forward_stream,
6807
                                         encoder->assessment_observer);
×
6808
            if (fclose(assessment_forward_stream) != 0) {
×
6809
                if (SIXEL_SUCCEEDED(status)) {
×
6810
                    sixel_helper_set_additional_message(
×
6811
                        "img2sixel: failed to close assessment spool sink.");
6812
                    status = SIXEL_LIBC_ERROR;
×
6813
                }
6814
            }
6815
            assessment_forward_stream = NULL;
×
6816
            if (SIXEL_FAILED(status)) {
×
6817
                goto end;
×
6818
            }
6819
        }
6820
        if (encoder->assessment_observer != NULL &&
3!
6821
                assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
1✔
6822
            sixel_assessment_stage_finish(encoder->assessment_observer);
×
6823
        }
6824
#if HAVE_SYS_STAT_H
6825
        assessment_output_bytes = 0u;
3✔
6826
        assessment_size_path = NULL;
3✔
6827
        if (assessment_need_quality && !assessment_quality_quantized) {
3!
6828
            if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT ||
×
6829
                    assessment_spool_mode ==
6830
                        SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
6831
                assessment_size_path = assessment_temp_path;
×
6832
            } else if (encoder->sixel_output_path != NULL &&
×
6833
                    strcmp(encoder->sixel_output_path, "-") != 0) {
×
6834
                assessment_size_path = encoder->sixel_output_path;
×
6835
            }
6836
        } else {
6837
            if (encoder->sixel_output_path != NULL &&
3!
6838
                    strcmp(encoder->sixel_output_path, "-") != 0) {
×
6839
                assessment_size_path = encoder->sixel_output_path;
×
6840
            } else if (assessment_spool_mode ==
3!
6841
                    SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT ||
3!
6842
                    assessment_spool_mode ==
1✔
6843
                        SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
6844
                assessment_size_path = assessment_temp_path;
×
6845
            }
6846
        }
6847
        if (assessment_size_path != NULL) {
3!
6848
            assessment_stat_result = stat(assessment_size_path,
×
6849
                                          &assessment_stat);
6850
            if (assessment_stat_result == 0 &&
×
6851
                    assessment_stat.st_size >= 0) {
×
6852
                assessment_output_bytes =
×
6853
                    (size_t)assessment_stat.st_size;
×
6854
            }
6855
        }
6856
#else
6857
        assessment_output_bytes = 0u;
6858
#endif
6859
        sixel_assessment_record_output_size(encoder->assessment_observer,
4✔
6860
                                            assessment_output_bytes);
1✔
6861
        if (assessment_need_quality) {
3!
6862
            status = sixel_assessment_analyze(encoder->assessment_observer,
×
6863
                                              assessment_source_frame,
6864
                                              assessment_target_frame);
6865
            if (SIXEL_FAILED(status)) {
×
6866
                goto end;
×
6867
            }
6868
        }
6869
        sixel_assessment_stage_finish(encoder->assessment_observer);
3✔
6870
        if (encoder->assessment_json_path != NULL &&
3!
6871
                strcmp(encoder->assessment_json_path, "-") != 0) {
3!
6872
            assessment_json_file = sixel_compat_fopen(
3✔
6873
                encoder->assessment_json_path,
1✔
6874
                "wb");
6875
            if (assessment_json_file == NULL) {
3!
6876
                sixel_helper_set_additional_message(
×
6877
                    "sixel_encoder_encode: failed to open assessment JSON file.");
6878
                status = SIXEL_LIBC_ERROR;
×
6879
                goto end;
×
6880
            }
6881
            assessment_json_owned = 1;
3✔
6882
            assessment_sink.stream = assessment_json_file;
3✔
6883
        } else {
1✔
6884
            assessment_sink.stream = stdout;
×
6885
        }
6886
        assessment_sink.failed = 0;
3✔
6887
        status = sixel_assessment_get_json(encoder->assessment_observer,
4✔
6888
                                           encoder->assessment_sections,
1✔
6889
                                           assessment_json_callback,
6890
                                           &assessment_sink);
6891
        if (SIXEL_FAILED(status) || assessment_sink.failed) {
3!
6892
            sixel_helper_set_additional_message(
×
6893
                "sixel_encoder_encode: failed to emit assessment JSON.");
6894
            goto end;
×
6895
        }
6896
    } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT) {
419!
6897
        status = copy_file_to_stream(assessment_temp_path,
×
6898
                                     stdout,
6899
                                     encoder->assessment_observer);
×
6900
        if (SIXEL_FAILED(status)) {
×
6901
            goto end;
×
6902
        }
6903
    } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
418!
6904
        if (assessment_forward_path == NULL) {
×
6905
            sixel_helper_set_additional_message(
×
6906
                "img2sixel: missing assessment spool target.");
6907
            status = SIXEL_RUNTIME_ERROR;
×
6908
            goto end;
×
6909
        }
6910
        assessment_forward_stream = sixel_compat_fopen(
×
6911
            assessment_forward_path,
6912
            "wb");
6913
        if (assessment_forward_stream == NULL) {
×
6914
            sixel_helper_set_additional_message(
×
6915
                "img2sixel: failed to open assessment spool sink.");
6916
            status = SIXEL_LIBC_ERROR;
×
6917
            goto end;
×
6918
        }
6919
        status = copy_file_to_stream(assessment_temp_path,
×
6920
                                     assessment_forward_stream,
6921
                                     encoder->assessment_observer);
×
6922
        if (fclose(assessment_forward_stream) != 0) {
×
6923
            if (SIXEL_SUCCEEDED(status)) {
×
6924
                sixel_helper_set_additional_message(
×
6925
                    "img2sixel: failed to close assessment spool sink.");
6926
                status = SIXEL_LIBC_ERROR;
×
6927
            }
6928
        }
6929
        assessment_forward_stream = NULL;
×
6930
        if (SIXEL_FAILED(status)) {
×
6931
            goto end;
×
6932
        }
6933
    }
6934

6935
    if (encoder->output_is_png) {
421✔
6936
        png_final_path = encoder->output_png_to_stdout ? "-" : encoder->png_output_path;
9!
6937
        if (! encoder->output_png_to_stdout && png_final_path == NULL) {
9!
6938
            sixel_helper_set_additional_message(
×
6939
                "sixel_encoder_encode: missing PNG output path.");
6940
            status = SIXEL_RUNTIME_ERROR;
×
6941
            goto end;
×
6942
        }
6943
        status = write_png_from_sixel(png_temp_path, png_final_path);
9✔
6944
        if (SIXEL_FAILED(status)) {
9!
6945
            goto end;
×
6946
        }
6947
    }
3✔
6948

6949
    if (encoder->clipboard_output_active
421!
6950
            && encoder->clipboard_output_path != NULL) {
142!
6951
        unsigned char *clipboard_output_data;
6952
        size_t clipboard_output_size;
6953

6954
        clipboard_output_data = NULL;
1✔
6955
        clipboard_output_size = 0u;
1✔
6956

6957
        if (encoder->outfd
2!
6958
                && encoder->outfd != STDOUT_FILENO
1!
6959
                && encoder->outfd != STDERR_FILENO) {
1!
6960
            (void)sixel_compat_close(encoder->outfd);
1✔
6961
            encoder->outfd = STDOUT_FILENO;
1✔
6962
        }
1✔
6963

6964
        clipboard_status = clipboard_read_file(
1✔
6965
            encoder->clipboard_output_path,
1✔
6966
            &clipboard_output_data,
6967
            &clipboard_output_size);
6968
        if (SIXEL_SUCCEEDED(clipboard_status)) {
1!
6969
            clipboard_status = sixel_clipboard_write(
1✔
6970
                encoder->clipboard_output_format,
1✔
6971
                clipboard_output_data,
1✔
6972
                clipboard_output_size);
1✔
6973
        }
1✔
6974
        if (clipboard_output_data != NULL) {
1!
6975
            free(clipboard_output_data);
1✔
6976
        }
1✔
6977
        if (SIXEL_FAILED(clipboard_status)) {
1!
6978
            status = clipboard_status;
×
6979
            goto end;
×
6980
        }
6981
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
1✔
6982
        sixel_allocator_free(encoder->allocator,
2✔
6983
                             encoder->clipboard_output_path);
1✔
6984
        encoder->clipboard_output_path = NULL;
1✔
6985
        encoder->sixel_output_path = NULL;
1✔
6986
        encoder->clipboard_output_active = 0;
1✔
6987
        encoder->clipboard_output_format[0] = '\0';
1✔
6988
    }
1✔
6989

6990
    /* the status may not be SIXEL_OK */
6991

6992
end:
280✔
6993
    if (png_temp_path != NULL) {
437✔
6994
        (void)sixel_compat_unlink(png_temp_path);
9✔
6995
    }
3✔
6996
    sixel_allocator_free(encoder->allocator, png_temp_path);
437✔
6997
    if (clipboard_input_path != NULL) {
437!
6998
        (void)sixel_compat_unlink(clipboard_input_path);
1✔
6999
        sixel_allocator_free(encoder->allocator, clipboard_input_path);
1✔
7000
    }
1✔
7001
    if (clipboard_blob != NULL) {
437!
7002
        free(clipboard_blob);
×
7003
    }
7004
    if (encoder->clipboard_output_path != NULL) {
437!
7005
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
7006
        sixel_allocator_free(encoder->allocator,
×
7007
                             encoder->clipboard_output_path);
×
7008
        encoder->clipboard_output_path = NULL;
×
7009
        encoder->sixel_output_path = NULL;
×
7010
        encoder->clipboard_output_active = 0;
×
7011
        encoder->clipboard_output_format[0] = '\0';
×
7012
    }
7013
    sixel_allocator_free(encoder->allocator, encoder->png_output_path);
437✔
7014
    encoder->png_output_path = NULL;
437✔
7015
    if (assessment_forward_stream != NULL) {
437!
7016
        (void) fclose(assessment_forward_stream);
×
7017
    }
7018
    if (assessment_temp_path != NULL &&
437!
7019
            assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
7020
        (void)sixel_compat_unlink(assessment_temp_path);
×
7021
    }
7022
    sixel_allocator_free(encoder->allocator, assessment_temp_path);
437✔
7023
    sixel_allocator_free(encoder->allocator, assessment_forward_path);
437✔
7024
    if (assessment_json_owned && assessment_json_file != NULL) {
437!
7025
        (void) fclose(assessment_json_file);
3✔
7026
    }
1✔
7027
    if (assessment_target_frame != NULL) {
437!
7028
        sixel_frame_unref(assessment_target_frame);
×
7029
    }
7030
    if (assessment_expanded_frame != NULL) {
437!
7031
        sixel_frame_unref(assessment_expanded_frame);
×
7032
    }
7033
    if (assessment_source_frame != NULL) {
437✔
7034
        sixel_frame_unref(assessment_source_frame);
3✔
7035
    }
1✔
7036
    if (encoder->assessment_observer != NULL) {
437✔
7037
        sixel_assessment_unref(encoder->assessment_observer);
3✔
7038
        encoder->assessment_observer = NULL;
3✔
7039
    }
1✔
7040
    if (assessment_allocator != NULL) {
437✔
7041
        sixel_allocator_unref(assessment_allocator);
3✔
7042
    }
1✔
7043

7044
    sixel_encoder_unref(encoder);
437✔
7045

7046
    if (encode_allocator != NULL) {
437!
7047
        /*
7048
         * Release the retained allocator reference *after* dropping the
7049
         * encoder reference so that a lazily created encoder can run its
7050
         * destructor while the allocator is still alive.  This ensures that
7051
         * cleanup routines never dereference a freed allocator instance.
7052
         */
7053
        sixel_allocator_unref(encode_allocator);
436✔
7054
        encode_allocator = NULL;
436✔
7055
    }
146✔
7056

7057
    return status;
437✔
7058
}
7059

7060

7061
/* encode specified pixel data to SIXEL format
7062
 * output to encoder->outfd */
7063
SIXELAPI SIXELSTATUS
7064
sixel_encoder_encode_bytes(
×
7065
    sixel_encoder_t     /* in */    *encoder,
7066
    unsigned char       /* in */    *bytes,
7067
    int                 /* in */    width,
7068
    int                 /* in */    height,
7069
    int                 /* in */    pixelformat,
7070
    unsigned char       /* in */    *palette,
7071
    int                 /* in */    ncolors)
7072
{
7073
    SIXELSTATUS status = SIXEL_FALSE;
×
7074
    sixel_frame_t *frame = NULL;
×
7075

7076
    if (encoder == NULL || bytes == NULL) {
×
7077
        status = SIXEL_BAD_ARGUMENT;
×
7078
        goto end;
×
7079
    }
7080

7081
    status = sixel_frame_new(&frame, encoder->allocator);
×
7082
    if (SIXEL_FAILED(status)) {
×
7083
        goto end;
×
7084
    }
7085

7086
    status = sixel_frame_init(frame, bytes, width, height,
×
7087
                              pixelformat, palette, ncolors);
7088
    if (SIXEL_FAILED(status)) {
×
7089
        goto end;
×
7090
    }
7091

7092
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
×
7093
    if (SIXEL_FAILED(status)) {
×
7094
        goto end;
×
7095
    }
7096

7097
    status = SIXEL_OK;
×
7098

7099
end:
7100
    /* we need to free the frame before exiting, but we can't use the
7101
       sixel_frame_destroy function, because that will also attempt to
7102
       free the pixels and palette, which we don't own */
7103
    if (frame != NULL && encoder->allocator != NULL) {
×
7104
        sixel_allocator_free(encoder->allocator, frame);
×
7105
        sixel_allocator_unref(encoder->allocator);
×
7106
    }
7107
    return status;
×
7108
}
7109

7110

7111
/*
7112
 * Toggle source-frame capture for assessment consumers.
7113
 */
7114
SIXELAPI SIXELSTATUS
7115
sixel_encoder_enable_source_capture(
3✔
7116
    sixel_encoder_t *encoder,
7117
    int enable)
7118
{
7119
    if (encoder == NULL) {
3!
7120
        sixel_helper_set_additional_message(
×
7121
            "sixel_encoder_enable_source_capture: encoder is null.");
7122
        return SIXEL_BAD_ARGUMENT;
×
7123
    }
7124

7125
    encoder->capture_source = enable ? 1 : 0;
3✔
7126
    if (!encoder->capture_source && encoder->capture_source_frame != NULL) {
3!
7127
        sixel_frame_unref(encoder->capture_source_frame);
×
7128
        encoder->capture_source_frame = NULL;
×
7129
    }
7130

7131
    return SIXEL_OK;
3✔
7132
}
1✔
7133

7134

7135
/*
7136
 * Enable or disable the quantized-frame capture facility.
7137
 *
7138
 *     capture on --> encoder keeps the latest palette-quantized frame.
7139
 *     capture off --> encoder forgets previously stored frames.
7140
 */
7141
SIXELAPI SIXELSTATUS
7142
sixel_encoder_enable_quantized_capture(
3✔
7143
    sixel_encoder_t *encoder,
7144
    int enable)
7145
{
7146
    if (encoder == NULL) {
3!
7147
        sixel_helper_set_additional_message(
×
7148
            "sixel_encoder_enable_quantized_capture: encoder is null.");
7149
        return SIXEL_BAD_ARGUMENT;
×
7150
    }
7151

7152
    encoder->capture_quantized = enable ? 1 : 0;
3✔
7153
    if (!encoder->capture_quantized) {
3!
7154
        encoder->capture_valid = 0;
×
7155
    }
7156

7157
    return SIXEL_OK;
3✔
7158
}
1✔
7159

7160

7161
/*
7162
 * Materialize the captured quantized frame as a heap-allocated
7163
 * sixel_frame_t instance for assessment consumers.
7164
 */
7165
SIXELAPI SIXELSTATUS
7166
sixel_encoder_copy_quantized_frame(
×
7167
    sixel_encoder_t   *encoder,
7168
    sixel_allocator_t *allocator,
7169
    sixel_frame_t     **ppframe)
7170
{
7171
    SIXELSTATUS status = SIXEL_FALSE;
×
7172
    sixel_frame_t *frame;
7173
    unsigned char *pixels;
7174
    unsigned char *palette;
7175
    size_t palette_bytes;
7176

7177
    if (encoder == NULL || allocator == NULL || ppframe == NULL) {
×
7178
        sixel_helper_set_additional_message(
×
7179
            "sixel_encoder_copy_quantized_frame: invalid argument.");
7180
        return SIXEL_BAD_ARGUMENT;
×
7181
    }
7182

7183
    if (!encoder->capture_quantized || !encoder->capture_valid) {
×
7184
        sixel_helper_set_additional_message(
×
7185
            "sixel_encoder_copy_quantized_frame: no frame captured.");
7186
        return SIXEL_RUNTIME_ERROR;
×
7187
    }
7188

7189
    *ppframe = NULL;
×
7190
    frame = NULL;
×
7191
    pixels = NULL;
×
7192
    palette = NULL;
×
7193

7194
    status = sixel_frame_new(&frame, allocator);
×
7195
    if (SIXEL_FAILED(status)) {
×
7196
        return status;
×
7197
    }
7198

7199
    if (encoder->capture_pixel_bytes > 0) {
×
7200
        pixels = (unsigned char *)sixel_allocator_malloc(
×
7201
            allocator, encoder->capture_pixel_bytes);
7202
        if (pixels == NULL) {
×
7203
            sixel_helper_set_additional_message(
×
7204
                "sixel_encoder_copy_quantized_frame: "
7205
                "sixel_allocator_malloc() failed.");
7206
            status = SIXEL_BAD_ALLOCATION;
×
7207
            goto cleanup;
×
7208
        }
7209
        memcpy(pixels,
×
7210
               encoder->capture_pixels,
7211
               encoder->capture_pixel_bytes);
7212
    }
7213

7214
    palette_bytes = encoder->capture_palette_size;
×
7215
    if (palette_bytes > 0) {
×
7216
        palette = (unsigned char *)sixel_allocator_malloc(allocator,
×
7217
                                                          palette_bytes);
7218
        if (palette == NULL) {
×
7219
            sixel_helper_set_additional_message(
×
7220
                "sixel_encoder_copy_quantized_frame: "
7221
                "sixel_allocator_malloc() failed.");
7222
            status = SIXEL_BAD_ALLOCATION;
×
7223
            goto cleanup;
×
7224
        }
7225
        memcpy(palette,
×
7226
               encoder->capture_palette,
7227
               palette_bytes);
7228
    }
7229

7230
    status = sixel_frame_init(frame,
×
7231
                              pixels,
7232
                              encoder->capture_width,
7233
                              encoder->capture_height,
7234
                              encoder->capture_pixelformat,
7235
                              palette,
7236
                              encoder->capture_ncolors);
7237
    if (SIXEL_FAILED(status)) {
×
7238
        goto cleanup;
×
7239
    }
7240

7241
    pixels = NULL;
×
7242
    palette = NULL;
×
7243
    frame->colorspace = encoder->capture_colorspace;
×
7244
    *ppframe = frame;
×
7245
    return SIXEL_OK;
×
7246

7247
cleanup:
7248
    if (palette != NULL) {
×
7249
        sixel_allocator_free(allocator, palette);
×
7250
    }
7251
    if (pixels != NULL) {
×
7252
        sixel_allocator_free(allocator, pixels);
×
7253
    }
7254
    if (frame != NULL) {
×
7255
        sixel_frame_unref(frame);
×
7256
    }
7257
    return status;
×
7258
}
7259

7260

7261
/*
7262
 * Emit the captured palette in the requested format.
7263
 *
7264
 *   palette_output == NULL  -> skip
7265
 *   palette_output != NULL  -> materialize captured palette
7266
 */
7267
static SIXELSTATUS
7268
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder)
421✔
7269
{
7270
    SIXELSTATUS status;
7271
    sixel_frame_t *frame;
7272
    unsigned char const *palette;
7273
    int exported_colors;
7274
    FILE *stream;
7275
    int close_stream;
7276
    char const *path;
7277
    sixel_palette_format_t format_hint;
7278
    sixel_palette_format_t format_ext;
7279
    sixel_palette_format_t format_final;
7280
    char const *mode;
7281

7282
    status = SIXEL_OK;
421✔
7283
    frame = NULL;
421✔
7284
    palette = NULL;
421✔
7285
    exported_colors = 0;
421✔
7286
    stream = NULL;
421✔
7287
    close_stream = 0;
421✔
7288
    path = NULL;
421✔
7289
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
421✔
7290
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
421✔
7291
    format_final = SIXEL_PALETTE_FORMAT_NONE;
421✔
7292
    mode = "wb";
421✔
7293

7294
    if (encoder == NULL || encoder->palette_output == NULL) {
421!
7295
        return SIXEL_OK;
421✔
7296
    }
7297

7298
    status = sixel_encoder_copy_quantized_frame(encoder,
×
7299
                                                encoder->allocator,
7300
                                                &frame);
7301
    if (SIXEL_FAILED(status)) {
×
7302
        return status;
×
7303
    }
7304

7305
    palette = (unsigned char const *)sixel_frame_get_palette(frame);
×
7306
    exported_colors = sixel_frame_get_ncolors(frame);
×
7307
    if (palette == NULL || exported_colors <= 0) {
×
7308
        sixel_helper_set_additional_message(
×
7309
            "sixel_encoder_emit_palette_output: palette unavailable.");
7310
        status = SIXEL_BAD_INPUT;
×
7311
        goto cleanup;
×
7312
    }
7313
    if (exported_colors > 256) {
×
7314
        exported_colors = 256;
×
7315
    }
7316

7317
    path = sixel_palette_strip_prefix(encoder->palette_output, &format_hint);
×
7318
    if (path == NULL || *path == '\0') {
×
7319
        sixel_helper_set_additional_message(
×
7320
            "sixel_encoder_emit_palette_output: invalid path.");
7321
        status = SIXEL_BAD_ARGUMENT;
×
7322
        goto cleanup;
×
7323
    }
7324

7325
    format_ext = sixel_palette_format_from_extension(path);
×
7326
    format_final = format_hint;
×
7327
    if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
7328
        if (format_ext == SIXEL_PALETTE_FORMAT_NONE) {
×
7329
            if (strcmp(path, "-") == 0) {
×
7330
                sixel_helper_set_additional_message(
×
7331
                    "sixel_encoder_emit_palette_output: "
7332
                    "format required for '-'.");
7333
                status = SIXEL_BAD_ARGUMENT;
×
7334
                goto cleanup;
×
7335
            }
7336
            sixel_helper_set_additional_message(
×
7337
                "sixel_encoder_emit_palette_output: "
7338
                "unknown palette file extension.");
7339
            status = SIXEL_BAD_ARGUMENT;
×
7340
            goto cleanup;
×
7341
        }
7342
        format_final = format_ext;
×
7343
    }
7344
    if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
7345
        format_final = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
7346
    }
7347

7348
    if (strcmp(path, "-") == 0) {
×
7349
        stream = stdout;
×
7350
    } else {
7351
        if (format_final == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
7352
                format_final == SIXEL_PALETTE_FORMAT_GPL) {
7353
            mode = "w";
×
7354
        } else {
7355
            mode = "wb";
×
7356
        }
7357
        stream = fopen(path, mode);
×
7358
        if (stream == NULL) {
×
7359
            sixel_helper_set_additional_message(
×
7360
                "sixel_encoder_emit_palette_output: failed to open file.");
7361
            status = SIXEL_LIBC_ERROR;
×
7362
            goto cleanup;
×
7363
        }
7364
        close_stream = 1;
×
7365
    }
7366

7367
    switch (format_final) {
×
7368
    case SIXEL_PALETTE_FORMAT_ACT:
7369
        status = sixel_palette_write_act(stream, palette, exported_colors);
×
7370
        if (SIXEL_FAILED(status)) {
×
7371
            sixel_helper_set_additional_message(
×
7372
                "sixel_encoder_emit_palette_output: failed to write ACT.");
7373
        }
7374
        break;
×
7375
    case SIXEL_PALETTE_FORMAT_PAL_JASC:
7376
        status = sixel_palette_write_pal_jasc(stream,
×
7377
                                              palette,
7378
                                              exported_colors);
7379
        if (SIXEL_FAILED(status)) {
×
7380
            sixel_helper_set_additional_message(
×
7381
                "sixel_encoder_emit_palette_output: failed to write JASC.");
7382
        }
7383
        break;
×
7384
    case SIXEL_PALETTE_FORMAT_PAL_RIFF:
7385
        status = sixel_palette_write_pal_riff(stream,
×
7386
                                              palette,
7387
                                              exported_colors);
7388
        if (SIXEL_FAILED(status)) {
×
7389
            sixel_helper_set_additional_message(
×
7390
                "sixel_encoder_emit_palette_output: failed to write RIFF.");
7391
        }
7392
        break;
×
7393
    case SIXEL_PALETTE_FORMAT_GPL:
7394
        status = sixel_palette_write_gpl(stream,
×
7395
                                         palette,
7396
                                         exported_colors);
7397
        if (SIXEL_FAILED(status)) {
×
7398
            sixel_helper_set_additional_message(
×
7399
                "sixel_encoder_emit_palette_output: failed to write GPL.");
7400
        }
7401
        break;
×
7402
    default:
7403
        sixel_helper_set_additional_message(
×
7404
            "sixel_encoder_emit_palette_output: unsupported format.");
7405
        status = SIXEL_BAD_ARGUMENT;
×
7406
        break;
×
7407
    }
7408
    if (SIXEL_FAILED(status)) {
×
7409
        goto cleanup;
×
7410
    }
7411

7412
    if (close_stream) {
×
7413
        if (fclose(stream) != 0) {
×
7414
            sixel_helper_set_additional_message(
×
7415
                "sixel_encoder_emit_palette_output: fclose() failed.");
7416
            status = SIXEL_LIBC_ERROR;
×
7417
            stream = NULL;
×
7418
            goto cleanup;
×
7419
        }
7420
        stream = NULL;
×
7421
    } else {
7422
        if (fflush(stream) != 0) {
×
7423
            sixel_helper_set_additional_message(
×
7424
                "sixel_encoder_emit_palette_output: fflush() failed.");
7425
            status = SIXEL_LIBC_ERROR;
×
7426
            goto cleanup;
×
7427
        }
7428
    }
7429

7430
cleanup:
7431
    if (close_stream && stream != NULL) {
×
7432
        (void) fclose(stream);
×
7433
    }
7434
    if (frame != NULL) {
×
7435
        sixel_frame_unref(frame);
×
7436
    }
7437

7438
    return status;
×
7439
}
141✔
7440

7441

7442
/*
7443
 * Share the captured source frame with assessment consumers.
7444
 */
7445
SIXELAPI SIXELSTATUS
7446
sixel_encoder_copy_source_frame(
3✔
7447
    sixel_encoder_t *encoder,
7448
    sixel_frame_t  **ppframe)
7449
{
7450
    if (encoder == NULL || ppframe == NULL) {
3!
7451
        sixel_helper_set_additional_message(
×
7452
            "sixel_encoder_copy_source_frame: invalid argument.");
7453
        return SIXEL_BAD_ARGUMENT;
×
7454
    }
7455

7456
    if (!encoder->capture_source || encoder->capture_source_frame == NULL) {
3!
7457
        sixel_helper_set_additional_message(
×
7458
            "sixel_encoder_copy_source_frame: no frame captured.");
7459
        return SIXEL_RUNTIME_ERROR;
×
7460
    }
7461

7462
    sixel_frame_ref(encoder->capture_source_frame);
3✔
7463
    *ppframe = encoder->capture_source_frame;
3✔
7464

7465
    return SIXEL_OK;
3✔
7466
}
1✔
7467

7468

7469
#if HAVE_TESTS
7470
static int
7471
test1(void)
×
7472
{
7473
    int nret = EXIT_FAILURE;
×
7474
    sixel_encoder_t *encoder = NULL;
×
7475

7476
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7477
#  pragma GCC diagnostic push
7478
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7479
#endif
7480
    encoder = sixel_encoder_create();
×
7481
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7482
#  pragma GCC diagnostic pop
7483
#endif
7484
    if (encoder == NULL) {
×
7485
        goto error;
×
7486
    }
7487
    sixel_encoder_ref(encoder);
×
7488
    sixel_encoder_unref(encoder);
×
7489
    nret = EXIT_SUCCESS;
×
7490

7491
error:
7492
    sixel_encoder_unref(encoder);
×
7493
    return nret;
×
7494
}
7495

7496

7497
static int
7498
test2(void)
×
7499
{
7500
    int nret = EXIT_FAILURE;
×
7501
    SIXELSTATUS status;
7502
    sixel_encoder_t *encoder = NULL;
×
7503
    sixel_frame_t *frame = NULL;
×
7504
    unsigned char *buffer;
7505
    int height = 0;
×
7506
    int is_animation = 0;
×
7507

7508
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7509
#  pragma GCC diagnostic push
7510
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7511
#endif
7512
    encoder = sixel_encoder_create();
×
7513
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7514
#  pragma GCC diagnostic pop
7515
#endif
7516
    if (encoder == NULL) {
×
7517
        goto error;
×
7518
    }
7519

7520
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7521
#  pragma GCC diagnostic push
7522
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7523
#endif
7524
    frame = sixel_frame_create();
×
7525
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7526
#  pragma GCC diagnostic pop
7527
#endif
7528
    if (encoder == NULL) {
×
7529
        goto error;
×
7530
    }
7531

7532
    buffer = (unsigned char *)sixel_allocator_malloc(encoder->allocator, 3);
×
7533
    if (buffer == NULL) {
×
7534
        goto error;
×
7535
    }
7536
    status = sixel_frame_init(frame, buffer, 1, 1,
×
7537
                              SIXEL_PIXELFORMAT_RGB888,
7538
                              NULL, 0);
7539
    if (SIXEL_FAILED(status)) {
×
7540
        goto error;
×
7541
    }
7542

7543
    if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
×
7544
        is_animation = 1;
×
7545
    }
7546

7547
    height = sixel_frame_get_height(frame);
×
7548

7549
    status = sixel_tty_scroll(sixel_write_callback,
×
7550
                              &encoder->outfd,
×
7551
                              encoder->outfd,
7552
                              height,
7553
                              is_animation);
7554
    if (SIXEL_FAILED(status)) {
×
7555
        goto error;
×
7556
    }
7557

7558
    nret = EXIT_SUCCESS;
×
7559

7560
error:
7561
    sixel_encoder_unref(encoder);
×
7562
    sixel_frame_unref(frame);
×
7563
    return nret;
×
7564
}
7565

7566

7567
static int
7568
test3(void)
×
7569
{
7570
    int nret = EXIT_FAILURE;
×
7571
    int result;
7572

7573
    result = sixel_tty_wait_stdin(1000);
×
7574
    if (result != 0) {
×
7575
        goto error;
×
7576
    }
7577

7578
    nret = EXIT_SUCCESS;
×
7579

7580
error:
7581
    return nret;
×
7582
}
7583

7584

7585
static int
7586
test4(void)
×
7587
{
7588
    int nret = EXIT_FAILURE;
×
7589
    sixel_encoder_t *encoder = NULL;
×
7590
    SIXELSTATUS status;
7591

7592
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7593
# pragma GCC diagnostic push
7594
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7595
#endif
7596
    encoder = sixel_encoder_create();
×
7597
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7598
# pragma GCC diagnostic pop
7599
#endif
7600
    if (encoder == NULL) {
×
7601
        goto error;
×
7602
    }
7603

7604
    status = sixel_encoder_setopt(encoder,
×
7605
                                  SIXEL_OPTFLAG_LOOPMODE,
7606
                                  "force");
7607
    if (SIXEL_FAILED(status)) {
×
7608
        goto error;
×
7609
    }
7610

7611
    status = sixel_encoder_setopt(encoder,
×
7612
                                  SIXEL_OPTFLAG_PIPE_MODE,
7613
                                  "force");
7614
    if (SIXEL_FAILED(status)) {
×
7615
        goto error;
×
7616
    }
7617

7618
    nret = EXIT_SUCCESS;
×
7619

7620
error:
7621
    sixel_encoder_unref(encoder);
×
7622
    return nret;
×
7623
}
7624

7625

7626
static int
7627
test5(void)
×
7628
{
7629
    int nret = EXIT_FAILURE;
×
7630
    sixel_encoder_t *encoder = NULL;
×
7631
    sixel_allocator_t *allocator = NULL;
×
7632
    SIXELSTATUS status;
7633

7634
    status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
7635
    if (SIXEL_FAILED(status)) {
×
7636
        goto error;
×
7637
    }
7638

7639
    status = sixel_encoder_new(&encoder, allocator);
×
7640
    if (SIXEL_FAILED(status)) {
×
7641
        goto error;
×
7642
    }
7643

7644
    sixel_encoder_ref(encoder);
×
7645
    sixel_encoder_unref(encoder);
×
7646
    nret = EXIT_SUCCESS;
×
7647

7648
error:
7649
    sixel_encoder_unref(encoder);
×
7650
    return nret;
×
7651
}
7652

7653

7654
SIXELAPI int
7655
sixel_encoder_tests_main(void)
×
7656
{
7657
    int nret = EXIT_FAILURE;
×
7658
    size_t i;
7659
    typedef int (* testcase)(void);
7660

7661
    static testcase const testcases[] = {
7662
        test1,
7663
        test2,
7664
        test3,
7665
        test4,
7666
        test5
7667
    };
7668

7669
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
7670
        nret = testcases[i]();
×
7671
        if (nret != EXIT_SUCCESS) {
×
7672
            goto error;
×
7673
        }
7674
    }
7675

7676
    nret = EXIT_SUCCESS;
×
7677

7678
error:
7679
    return nret;
×
7680
}
7681
#endif  /* HAVE_TESTS */
7682

7683

7684
/* emacs Local Variables:      */
7685
/* emacs mode: c               */
7686
/* emacs tab-width: 4          */
7687
/* emacs indent-tabs-mode: nil */
7688
/* emacs c-basic-offset: 4     */
7689
/* emacs End:                  */
7690
/* vim: set expandtab ts=4 : */
7691
/* 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