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

saitoha / libsixel / 19083438434

04 Nov 2025 09:31PM UTC coverage: 45.798% (-0.6%) from 46.412%
19083438434

push

github

saitoha
feat: introduce new option -Q,--quantize-model (auto,heckbert,kmeans)

7763 of 25233 branches covered (30.77%)

5 of 331 new or added lines in 3 files covered. (1.51%)

11046 of 24119 relevant lines covered (45.8%)

1674054.5 hits per line

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

50.45
/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 "dither.h"
113
#include "frame.h"
114
#include "rgblookup.h"
115
#include "clipboard.h"
116
#include "compat_stub.h"
117

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

133
#if defined(_WIN32)
134

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

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

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

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

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

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

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

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

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

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

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

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

212
#endif /* _WIN32 */
213

214

215
/*
216
 * Prefix matcher roadmap:
217
 *
218
 *   +---------+-------------------+
219
 *   | input   | decision           |
220
 *   +---------+-------------------+
221
 *   | "ave"   | average            |
222
 *   | "a"     | ambiguous (auto?)  |
223
 *   +---------+-------------------+
224
 *
225
 * The helper walks the choice table once, collecting prefixes and
226
 * checking whether a unique destination emerges.  Ambiguous prefixes
227
 * bubble up so the caller can craft a friendly diagnostic.
228
 */
229
typedef struct sixel_option_choice {
230
    char const *name;
231
    int value;
232
} sixel_option_choice_t;
233

234
typedef enum sixel_option_choice_result {
235
    SIXEL_OPTION_CHOICE_MATCH = 0,
236
    SIXEL_OPTION_CHOICE_AMBIGUOUS = 1,
237
    SIXEL_OPTION_CHOICE_NONE = 2
238
} sixel_option_choice_result_t;
239

240
static sixel_option_choice_result_t
241
sixel_match_option_choice(
279✔
242
    char const *value,
243
    sixel_option_choice_t const *choices,
244
    size_t choice_count,
245
    int *matched_value,
246
    char *diagnostic,
247
    size_t diagnostic_size)
248
{
249
    size_t index;
250
    size_t value_length;
251
    int candidate_index;
252
    size_t match_count;
253
    int base_value;
254
    int base_value_set;
255
    int ambiguous_values;
256
    size_t diag_length;
257
    size_t copy_length;
258

259
    if (diagnostic != NULL && diagnostic_size > 0u) {
279!
260
        diagnostic[0] = '\0';
279✔
261
    }
93✔
262
    if (value == NULL) {
279!
263
        return SIXEL_OPTION_CHOICE_NONE;
×
264
    }
265

266
    value_length = strlen(value);
279✔
267
    if (value_length == 0u) {
279!
268
        return SIXEL_OPTION_CHOICE_NONE;
×
269
    }
270

271
    index = 0u;
279✔
272
    candidate_index = (-1);
279✔
273
    match_count = 0u;
279✔
274
    base_value = 0;
279✔
275
    base_value_set = 0;
279✔
276
    ambiguous_values = 0;
279✔
277
    diag_length = 0u;
279✔
278

279
    while (index < choice_count) {
1,533✔
280
        if (strncmp(choices[index].name, value, value_length) == 0) {
1,449✔
281
            if (choices[index].name[value_length] == '\0') {
255✔
282
                *matched_value = choices[index].value;
195✔
283
                return SIXEL_OPTION_CHOICE_MATCH;
195✔
284
            }
285
            if (!base_value_set) {
60✔
286
                base_value = choices[index].value;
45✔
287
                base_value_set = 1;
45✔
288
            } else if (choices[index].value != base_value) {
30✔
289
                ambiguous_values = 1;
9✔
290
            }
3✔
291
            if (candidate_index == (-1)) {
60✔
292
                candidate_index = (int)index;
45✔
293
            }
15✔
294
            ++match_count;
60✔
295
            if (diagnostic != NULL && diagnostic_size > 0u) {
60!
296
                if (diag_length > 0u && diag_length + 2u < diagnostic_size) {
60!
297
                    diagnostic[diag_length] = ',';
15✔
298
                    diagnostic[diag_length + 1u] = ' ';
15✔
299
                    diag_length += 2u;
15✔
300
                    diagnostic[diag_length] = '\0';
15✔
301
                }
5✔
302
                copy_length = strlen(choices[index].name);
60✔
303
                if (copy_length > diagnostic_size - diag_length - 1u) {
60!
304
                    copy_length = diagnostic_size - diag_length - 1u;
×
305
                }
306
                memcpy(diagnostic + diag_length,
60✔
307
                       choices[index].name,
40✔
308
                       copy_length);
309
                diag_length += copy_length;
60✔
310
                diagnostic[diag_length] = '\0';
60✔
311
            }
20✔
312
        }
20✔
313
        ++index;
1,254✔
314
    }
315

316
    if (match_count == 0u) {
84✔
317
        return SIXEL_OPTION_CHOICE_NONE;
39✔
318
    }
319
    if (!ambiguous_values) {
45✔
320
        *matched_value = choices[candidate_index].value;
39✔
321
        return SIXEL_OPTION_CHOICE_MATCH;
39✔
322
    }
323

324
    return SIXEL_OPTION_CHOICE_AMBIGUOUS;
6✔
325
}
93✔
326

327
static void
328
sixel_report_ambiguous_prefix(
6✔
329
    char const *option,
330
    char const *value,
331
    char const *candidates,
332
    char *buffer,
333
    size_t buffer_size)
334
{
335
    int written;
336

337
    if (buffer == NULL || buffer_size == 0u) {
6!
338
        return;
×
339
    }
340
    if (candidates != NULL && candidates[0] != '\0') {
6!
341
        written = snprintf(buffer,
6✔
342
                           buffer_size,
343
                           "ambiguous prefix \"%s\" for %s (matches: %s).",
344
                           value,
345
                           option,
346
                           candidates);
347
    } else {
2✔
348
        written = snprintf(buffer,
×
349
                           buffer_size,
350
                           "ambiguous prefix \"%s\" for %s.",
351
                           value,
352
                           option);
353
    }
354
    (void) written;
2✔
355
    sixel_helper_set_additional_message(buffer);
6✔
356
}
2✔
357

358
static sixel_option_choice_t const g_option_choices_builtin_palette[] = {
359
    { "xterm16", SIXEL_BUILTIN_XTERM16 },
360
    { "xterm256", SIXEL_BUILTIN_XTERM256 },
361
    { "vt340mono", SIXEL_BUILTIN_VT340_MONO },
362
    { "vt340color", SIXEL_BUILTIN_VT340_COLOR },
363
    { "gray1", SIXEL_BUILTIN_G1 },
364
    { "gray2", SIXEL_BUILTIN_G2 },
365
    { "gray4", SIXEL_BUILTIN_G4 },
366
    { "gray8", SIXEL_BUILTIN_G8 }
367
};
368

369
static sixel_option_choice_t const g_option_choices_diffusion[] = {
370
    { "auto", SIXEL_DIFFUSE_AUTO },
371
    { "none", SIXEL_DIFFUSE_NONE },
372
    { "fs", SIXEL_DIFFUSE_FS },
373
    { "atkinson", SIXEL_DIFFUSE_ATKINSON },
374
    { "jajuni", SIXEL_DIFFUSE_JAJUNI },
375
    { "stucki", SIXEL_DIFFUSE_STUCKI },
376
    { "burkes", SIXEL_DIFFUSE_BURKES },
377
    { "sierra1", SIXEL_DIFFUSE_SIERRA1 },
378
    { "sierra2", SIXEL_DIFFUSE_SIERRA2 },
379
    { "sierra3", SIXEL_DIFFUSE_SIERRA3 },
380
    { "a_dither", SIXEL_DIFFUSE_A_DITHER },
381
    { "x_dither", SIXEL_DIFFUSE_X_DITHER },
382
    { "lso1", SIXEL_DIFFUSE_LSO1 },
383
    { "lso2", SIXEL_DIFFUSE_LSO2 },
384
    { "lso3", SIXEL_DIFFUSE_LSO3 }
385
};
386

387
static sixel_option_choice_t const g_option_choices_diffusion_scan[] = {
388
    { "auto", SIXEL_SCAN_AUTO },
389
    { "serpentine", SIXEL_SCAN_SERPENTINE },
390
    { "raster", SIXEL_SCAN_RASTER }
391
};
392

393
static sixel_option_choice_t const g_option_choices_diffusion_carry[] = {
394
    { "auto", SIXEL_CARRY_AUTO },
395
    { "direct", SIXEL_CARRY_DISABLE },
396
    { "carry", SIXEL_CARRY_ENABLE }
397
};
398

399
static sixel_option_choice_t const g_option_choices_find_largest[] = {
400
    { "auto", SIXEL_LARGE_AUTO },
401
    { "norm", SIXEL_LARGE_NORM },
402
    { "lum", SIXEL_LARGE_LUM }
403
};
404

405
static sixel_option_choice_t const g_option_choices_select_color[] = {
406
    { "auto", SIXEL_REP_AUTO },
407
    { "center", SIXEL_REP_CENTER_BOX },
408
    { "average", SIXEL_REP_AVERAGE_COLORS },
409
    { "histogram", SIXEL_REP_AVERAGE_PIXELS },
410
    { "histgram", SIXEL_REP_AVERAGE_PIXELS }
411
};
412

413
static sixel_option_choice_t const g_option_choices_quantize_model[] = {
414
    { "auto", SIXEL_QUANTIZE_MODEL_AUTO },
415
    { "heckbert", SIXEL_QUANTIZE_MODEL_MEDIANCUT },
416
    { "kmeans", SIXEL_QUANTIZE_MODEL_KMEANS }
417
};
418

419
static sixel_option_choice_t const g_option_choices_resampling[] = {
420
    { "nearest", SIXEL_RES_NEAREST },
421
    { "gaussian", SIXEL_RES_GAUSSIAN },
422
    { "hanning", SIXEL_RES_HANNING },
423
    { "hamming", SIXEL_RES_HAMMING },
424
    { "bilinear", SIXEL_RES_BILINEAR },
425
    { "welsh", SIXEL_RES_WELSH },
426
    { "bicubic", SIXEL_RES_BICUBIC },
427
    { "lanczos2", SIXEL_RES_LANCZOS2 },
428
    { "lanczos3", SIXEL_RES_LANCZOS3 },
429
    { "lanczos4", SIXEL_RES_LANCZOS4 }
430
};
431

432
static sixel_option_choice_t const g_option_choices_quality[] = {
433
    { "auto", SIXEL_QUALITY_AUTO },
434
    { "high", SIXEL_QUALITY_HIGH },
435
    { "low", SIXEL_QUALITY_LOW },
436
    { "full", SIXEL_QUALITY_FULL }
437
};
438

439
static sixel_option_choice_t const g_option_choices_loopmode[] = {
440
    { "auto", SIXEL_LOOP_AUTO },
441
    { "force", SIXEL_LOOP_FORCE },
442
    { "disable", SIXEL_LOOP_DISABLE }
443
};
444

445
static sixel_option_choice_t const g_option_choices_palette_type[] = {
446
    { "auto", SIXEL_PALETTETYPE_AUTO },
447
    { "hls", SIXEL_PALETTETYPE_HLS },
448
    { "rgb", SIXEL_PALETTETYPE_RGB }
449
};
450

451
static sixel_option_choice_t const g_option_choices_encode_policy[] = {
452
    { "auto", SIXEL_ENCODEPOLICY_AUTO },
453
    { "fast", SIXEL_ENCODEPOLICY_FAST },
454
    { "size", SIXEL_ENCODEPOLICY_SIZE }
455
};
456

457
static sixel_option_choice_t const g_option_choices_lut_policy[] = {
458
    { "auto", SIXEL_LUT_POLICY_AUTO },
459
    { "5bit", SIXEL_LUT_POLICY_5BIT },
460
    { "6bit", SIXEL_LUT_POLICY_6BIT },
461
    { "robinhood", SIXEL_LUT_POLICY_ROBINHOOD },
462
    { "hopscotch", SIXEL_LUT_POLICY_HOPSCOTCH }
463
};
464

465
static sixel_option_choice_t const g_option_choices_working_colorspace[] = {
466
    { "gamma", SIXEL_COLORSPACE_GAMMA },
467
    { "linear", SIXEL_COLORSPACE_LINEAR },
468
    { "oklab", SIXEL_COLORSPACE_OKLAB }
469
};
470

471
static sixel_option_choice_t const g_option_choices_output_colorspace[] = {
472
    { "gamma", SIXEL_COLORSPACE_GAMMA },
473
    { "linear", SIXEL_COLORSPACE_LINEAR },
474
    { "smpte-c", SIXEL_COLORSPACE_SMPTEC },
475
    { "smptec", SIXEL_COLORSPACE_SMPTEC }
476
};
477

478

479
static char *
480
arg_strdup(
57✔
481
    char const          /* in */ *s,          /* source buffer */
482
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
483
                                                 destination buffer */
484
{
485
    char *p;
486
    size_t len;
487

488
    len = strlen(s);
57✔
489

490
    p = (char *)sixel_allocator_malloc(allocator, len + 1);
57✔
491
    if (p) {
57!
492
        (void)sixel_compat_strcpy(p, len + 1, s);
57✔
493
    }
19✔
494
    return p;
57✔
495
}
496

497

498
/* An clone function of XColorSpec() of xlib */
499
static SIXELSTATUS
500
sixel_parse_x_colorspec(
45✔
501
    unsigned char       /* out */ **bgcolor,     /* destination buffer */
502
    char const          /* in */  *s,            /* source buffer */
503
    sixel_allocator_t   /* in */  *allocator)    /* allocator object for
504
                                                    destination buffer */
505
{
506
    SIXELSTATUS status = SIXEL_FALSE;
45✔
507
    char *p;
508
    unsigned char components[3];
509
    int component_index = 0;
45✔
510
    unsigned long v;
511
    char *endptr;
512
    char *buf = NULL;
45✔
513
    struct color const *pcolor;
514

515
    /* from rgb_lookup.h generated by gpref */
516
    pcolor = lookup_rgb(s, strlen(s));
45✔
517
    if (pcolor) {
45✔
518
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
3✔
519
        if (*bgcolor == NULL) {
3!
520
            sixel_helper_set_additional_message(
×
521
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
522
            status = SIXEL_BAD_ALLOCATION;
×
523
            goto end;
×
524
        }
525
        (*bgcolor)[0] = pcolor->r;
3✔
526
        (*bgcolor)[1] = pcolor->g;
3✔
527
        (*bgcolor)[2] = pcolor->b;
3✔
528
    } else if (s[0] == 'r' && s[1] == 'g' && s[2] == 'b' && s[3] == ':') {
43!
529
        p = buf = arg_strdup(s + 4, allocator);
6✔
530
        if (buf == NULL) {
6!
531
            sixel_helper_set_additional_message(
×
532
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
533
            status = SIXEL_BAD_ALLOCATION;
×
534
            goto end;
×
535
        }
536
        while (*p) {
15!
537
            v = 0;
15✔
538
            for (endptr = p; endptr - p <= 12; ++endptr) {
36!
539
                if (*endptr >= '0' && *endptr <= '9') {
36✔
540
                    v = (v << 4) | (unsigned long)(*endptr - '0');
15✔
541
                } else if (*endptr >= 'a' && *endptr <= 'f') {
26!
542
                    v = (v << 4) | (unsigned long)(*endptr - 'a' + 10);
3✔
543
                } else if (*endptr >= 'A' && *endptr <= 'F') {
19!
544
                    v = (v << 4) | (unsigned long)(*endptr - 'A' + 10);
3✔
545
                } else {
1✔
546
                    break;
5✔
547
                }
548
            }
7✔
549
            if (endptr - p == 0) {
15!
550
                break;
×
551
            }
552
            if (endptr - p > 4) {
15!
553
                break;
×
554
            }
555
            v = v << ((4 - (endptr - p)) * 4) >> 8;
15✔
556
            components[component_index++] = (unsigned char)v;
15✔
557
            p = endptr;
15✔
558
            if (component_index == 3) {
15✔
559
                break;
3✔
560
            }
561
            if (*p == '\0') {
12✔
562
                break;
3✔
563
            }
564
            if (*p != '/') {
9!
565
                break;
×
566
            }
567
            ++p;
9✔
568
        }
569
        if (component_index != 3 || *p != '\0' || *p == '/') {
6!
570
            status = SIXEL_BAD_ARGUMENT;
3✔
571
            goto end;
3✔
572
        }
573
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
3✔
574
        if (*bgcolor == NULL) {
3!
575
            sixel_helper_set_additional_message(
×
576
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
577
            status = SIXEL_BAD_ALLOCATION;
×
578
            goto end;
×
579
        }
580
        (*bgcolor)[0] = components[0];
3✔
581
        (*bgcolor)[1] = components[1];
3✔
582
        (*bgcolor)[2] = components[2];
3✔
583
    } else if (*s == '#') {
37✔
584
        buf = arg_strdup(s + 1, allocator);
27✔
585
        if (buf == NULL) {
27!
586
            sixel_helper_set_additional_message(
×
587
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
588
            status = SIXEL_BAD_ALLOCATION;
×
589
            goto end;
×
590
        }
591
        for (p = endptr = buf; endptr - p <= 12; ++endptr) {
192✔
592
            if (*endptr >= '0' && *endptr <= '9') {
189✔
593
                *endptr -= '0';
99✔
594
            } else if (*endptr >= 'a' && *endptr <= 'f') {
123!
595
                *endptr -= 'a' - 10;
57✔
596
            } else if (*endptr >= 'A' && *endptr <= 'F') {
52✔
597
                *endptr -= 'A' - 10;
9✔
598
            } else if (*endptr == '\0') {
27✔
599
                break;
21✔
600
            } else {
601
                status = SIXEL_BAD_ARGUMENT;
3✔
602
                goto end;
3✔
603
            }
604
        }
55✔
605
        if (endptr - p > 12) {
24✔
606
            status = SIXEL_BAD_ARGUMENT;
3✔
607
            goto end;
3✔
608
        }
609
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
21✔
610
        if (*bgcolor == NULL) {
21!
611
            sixel_helper_set_additional_message(
×
612
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
613
            status = SIXEL_BAD_ALLOCATION;
×
614
            goto end;
×
615
        }
616
        switch (endptr - p) {
21✔
617
        case 3:
6✔
618
            (*bgcolor)[0] = (unsigned char)(p[0] << 4);
9✔
619
            (*bgcolor)[1] = (unsigned char)(p[1] << 4);
9✔
620
            (*bgcolor)[2] = (unsigned char)(p[2] << 4);
9✔
621
            break;
9✔
622
        case 6:
2✔
623
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
624
            (*bgcolor)[1] = (unsigned char)(p[2] << 4 | p[3]);
3✔
625
            (*bgcolor)[2] = (unsigned char)(p[4] << 4 | p[4]);
3✔
626
            break;
3✔
627
        case 9:
2✔
628
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
629
            (*bgcolor)[1] = (unsigned char)(p[3] << 4 | p[4]);
3✔
630
            (*bgcolor)[2] = (unsigned char)(p[6] << 4 | p[7]);
3✔
631
            break;
3✔
632
        case 12:
2✔
633
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
634
            (*bgcolor)[1] = (unsigned char)(p[4] << 4 | p[5]);
3✔
635
            (*bgcolor)[2] = (unsigned char)(p[8] << 4 | p[9]);
3✔
636
            break;
3✔
637
        default:
2✔
638
            status = SIXEL_BAD_ARGUMENT;
3✔
639
            goto end;
3✔
640
        }
641
    } else {
6✔
642
        status = SIXEL_BAD_ARGUMENT;
9✔
643
        goto end;
9✔
644
    }
645

646
    status = SIXEL_OK;
24✔
647

648
end:
30✔
649
    sixel_allocator_free(allocator, buf);
45✔
650

651
    return status;
45✔
652
}
653

654

655
/* generic writer function for passing to sixel_output_new() */
656
static int
657
sixel_write_callback(char *data, int size, void *priv)
5,895✔
658
{
659
    int result;
660

661
    result = (int)sixel_compat_write(*(int *)priv,
8,062✔
662
                                     data,
2,167✔
663
                                     (size_t)size);
2,167✔
664

665
    return result;
5,895✔
666
}
667

668

669
/* the writer function with hex-encoding for passing to sixel_output_new() */
670
static int
671
sixel_hex_write_callback(
73✔
672
    char    /* in */ *data,
673
    int     /* in */ size,
674
    void    /* in */ *priv)
675
{
676
    char hex[SIXEL_OUTPUT_PACKET_SIZE * 2];
677
    int i;
678
    int j;
679
    int result;
680

681
    for (i = j = 0; i < size; ++i, ++j) {
635,609✔
682
        hex[j] = (data[i] >> 4) & 0xf;
635,536✔
683
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
635,536!
684
        hex[++j] = data[i] & 0xf;
635,536✔
685
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
635,536✔
686
    }
168,020✔
687

688
    result = (int)sixel_compat_write(*(int *)priv,
146✔
689
                                     hex,
25✔
690
                                     (size_t)(size * 2));
73✔
691

692
    return result;
73✔
693
}
694

695
typedef struct sixel_encoder_output_probe {
696
    sixel_encoder_t *encoder;
697
    sixel_write_function base_write;
698
    void *base_priv;
699
} sixel_encoder_output_probe_t;
700

701
static int
702
sixel_write_with_probe(char *data, int size, void *priv)
70✔
703
{
704
    sixel_encoder_output_probe_t *probe;
705
    int written;
706
    double started_at;
707
    double finished_at;
708
    double duration;
709

710
    probe = (sixel_encoder_output_probe_t *)priv;
70✔
711
    if (probe == NULL || probe->base_write == NULL) {
70!
712
        return 0;
×
713
    }
714
    started_at = 0.0;
70✔
715
    finished_at = 0.0;
70✔
716
    duration = 0.0;
70✔
717
    if (probe->encoder != NULL &&
70!
718
            probe->encoder->assessment_observer != NULL) {
70!
719
        started_at = sixel_assessment_timer_now();
70✔
720
    }
22✔
721
    written = probe->base_write(data, size, probe->base_priv);
70✔
722
    if (probe->encoder != NULL &&
70!
723
            probe->encoder->assessment_observer != NULL) {
70!
724
        finished_at = sixel_assessment_timer_now();
70✔
725
        duration = finished_at - started_at;
70✔
726
        if (duration < 0.0) {
70!
727
            duration = 0.0;
×
728
        }
729
    }
22✔
730
    if (written > 0 && probe->encoder != NULL &&
70!
731
            probe->encoder->assessment_observer != NULL) {
70!
732
        sixel_assessment_record_output_write(
70✔
733
            probe->encoder->assessment_observer,
70✔
734
            (size_t)written,
22✔
735
            duration);
22✔
736
    }
22✔
737
    return written;
70✔
738
}
22✔
739

740
/*
741
 * Reuse the fn_write probe for raw escape writes so that every
742
 * assessment bucket receives the same accounting.
743
 *
744
 *     encoder        probe wrapper       write(2)
745
 *     +------+    +----------------+    +---------+
746
 *     | data | -> | sixel_write_*  | -> | target  |
747
 *     +------+    +----------------+    +---------+
748
 */
749
static int
750
sixel_encoder_probe_fd_write(sixel_encoder_t *encoder,
×
751
                             char *data,
752
                             int size,
753
                             int fd)
754
{
755
    sixel_encoder_output_probe_t probe;
756
    int written;
757

758
    probe.encoder = encoder;
×
759
    probe.base_write = sixel_write_callback;
×
760
    probe.base_priv = &fd;
×
761
    written = sixel_write_with_probe(data, size, &probe);
×
762

763
    return written;
×
764
}
765

766
static SIXELSTATUS
767
sixel_encoder_ensure_cell_size(sixel_encoder_t *encoder)
×
768
{
769
#if defined(TIOCGWINSZ)
770
    struct winsize ws;
771
    int result;
772
    int fd = 0;
×
773

774
    if (encoder->cell_width > 0 && encoder->cell_height > 0) {
×
775
        return SIXEL_OK;
×
776
    }
777

778
    fd = sixel_compat_open("/dev/tty", O_RDONLY);
×
779
    if (fd >= 0) {
×
780
        result = ioctl(fd, TIOCGWINSZ, &ws);
×
781
        (void)sixel_compat_close(fd);
×
782
    } else {
783
        sixel_helper_set_additional_message(
×
784
            "failed to open /dev/tty");
785
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
786
    }
787
    if (result != 0) {
×
788
        sixel_helper_set_additional_message(
×
789
            "failed to query terminal geometry with ioctl().");
790
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
791
    }
792

793
    if (ws.ws_col <= 0 || ws.ws_row <= 0 ||
×
794
        ws.ws_xpixel <= ws.ws_col || ws.ws_ypixel <= ws.ws_row) {
×
795
        sixel_helper_set_additional_message(
×
796
            "terminal does not report pixel cell size for drcs option.");
797
        return SIXEL_BAD_ARGUMENT;
×
798
    }
799

800
    encoder->cell_width = ws.ws_xpixel / ws.ws_col;
×
801
    encoder->cell_height = ws.ws_ypixel / ws.ws_row;
×
802
    if (encoder->cell_width <= 0 || encoder->cell_height <= 0) {
×
803
        sixel_helper_set_additional_message(
×
804
            "terminal cell size reported zero via ioctl().");
805
        return SIXEL_BAD_ARGUMENT;
×
806
    }
807

808
    return SIXEL_OK;
×
809
#else
810
    (void) encoder;
811
    sixel_helper_set_additional_message(
812
        "drcs option is not supported on this platform.");
813
    return SIXEL_NOT_IMPLEMENTED;
814
#endif
815
}
816

817

818
/* returns monochrome dithering context object */
819
static SIXELSTATUS
820
sixel_prepare_monochrome_palette(
12✔
821
    sixel_dither_t  /* out */ **dither,
822
     int            /* in */  finvert)
823
{
824
    SIXELSTATUS status = SIXEL_FALSE;
12✔
825

826
    if (finvert) {
12✔
827
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_LIGHT);
3✔
828
    } else {
1✔
829
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_DARK);
9✔
830
    }
831
    if (*dither == NULL) {
12!
832
        sixel_helper_set_additional_message(
×
833
            "sixel_prepare_monochrome_palette: sixel_dither_get() failed.");
834
        status = SIXEL_RUNTIME_ERROR;
×
835
        goto end;
×
836
    }
837

838
    status = SIXEL_OK;
12✔
839

840
end:
8✔
841
    return status;
12✔
842
}
843

844

845
/* returns dithering context object with specified builtin palette */
846
typedef struct palette_conversion {
847
    unsigned char *original;
848
    unsigned char *copy;
849
    size_t size;
850
    int convert_inplace;
851
    int converted;
852
    int frame_colorspace;
853
} palette_conversion_t;
854

855
static SIXELSTATUS
856
sixel_encoder_convert_palette(sixel_encoder_t *encoder,
529✔
857
                              sixel_output_t *output,
858
                              sixel_dither_t *dither,
859
                              int frame_colorspace,
860
                              int pixelformat,
861
                              palette_conversion_t *ctx)
862
{
863
    SIXELSTATUS status = SIXEL_OK;
529✔
864
    unsigned char *palette;
865
    int palette_colors;
866

867
    ctx->original = NULL;
529✔
868
    ctx->copy = NULL;
529✔
869
    ctx->size = 0;
529✔
870
    ctx->convert_inplace = 0;
529✔
871
    ctx->converted = 0;
529✔
872
    ctx->frame_colorspace = frame_colorspace;
529✔
873

874
    palette = sixel_dither_get_palette(dither);
529✔
875
    palette_colors = sixel_dither_get_num_of_palette_colors(dither);
529✔
876
    ctx->original = palette;
529✔
877

878
    if (palette == NULL || palette_colors <= 0 ||
529!
879
            frame_colorspace == output->colorspace) {
529!
880
        return SIXEL_OK;
529✔
881
    }
882

883
    ctx->size = (size_t)palette_colors * 3;
×
884

885
    output->pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
886
    output->source_colorspace = frame_colorspace;
×
887

888
    if (palette != (unsigned char *)(dither + 1)) {
×
889
        ctx->copy = (unsigned char *)sixel_allocator_malloc(
×
890
            encoder->allocator, ctx->size);
891
        if (ctx->copy == NULL) {
×
892
            sixel_helper_set_additional_message(
×
893
                "sixel_encoder_convert_palette: "
894
                "sixel_allocator_malloc() failed.");
895
            status = SIXEL_BAD_ALLOCATION;
×
896
            goto end;
×
897
        }
898
        memcpy(ctx->copy, palette, ctx->size);
×
899
        dither->palette = ctx->copy;
×
900
    } else {
901
        ctx->convert_inplace = 1;
×
902
    }
903

904
    status = sixel_output_convert_colorspace(output,
×
905
                                             dither->palette,
906
                                             ctx->size);
907
    if (SIXEL_FAILED(status)) {
×
908
        goto end;
×
909
    }
910
    ctx->converted = 1;
×
911

912
end:
913
    output->pixelformat = pixelformat;
×
914
    output->source_colorspace = frame_colorspace;
×
915

916
    return status;
×
917
}
183✔
918

919
static void
920
sixel_encoder_restore_palette(sixel_encoder_t *encoder,
529✔
921
                              sixel_dither_t *dither,
922
                              palette_conversion_t *ctx)
923
{
924
    if (ctx->copy) {
529!
925
        dither->palette = ctx->original;
×
926
        sixel_allocator_free(encoder->allocator, ctx->copy);
×
927
        ctx->copy = NULL;
×
928
    } else if (ctx->convert_inplace && ctx->converted &&
529!
929
               ctx->original && ctx->size > 0) {
×
930
        (void)sixel_helper_convert_colorspace(ctx->original,
×
931
                                              ctx->size,
932
                                              SIXEL_PIXELFORMAT_RGB888,
933
                                              SIXEL_COLORSPACE_GAMMA,
934
                                              ctx->frame_colorspace);
935
    }
936
}
529✔
937

938
static SIXELSTATUS
939
sixel_encoder_capture_quantized(sixel_encoder_t *encoder,
3✔
940
                                sixel_dither_t *dither,
941
                                unsigned char const *pixels,
942
                                size_t size,
943
                                int width,
944
                                int height,
945
                                int pixelformat,
946
                                int colorspace)
947
{
948
    SIXELSTATUS status;
949
    unsigned char *palette;
950
    int ncolors;
951
    size_t palette_bytes;
952
    unsigned char *new_pixels;
953
    unsigned char *new_palette;
954
    size_t capture_bytes;
955
    unsigned char const *capture_source;
956
    sixel_index_t *paletted_pixels;
957
    size_t quantized_pixels;
958
    sixel_allocator_t *dither_allocator;
959
    int saved_pixelformat;
960
    int restore_pixelformat;
961

962
    /*
963
     * Preserve the quantized frame for assessment observers.
964
     *
965
     *     +-----------------+     +---------------------+
966
     *     | quantized bytes | --> | encoder->capture_*  |
967
     *     +-----------------+     +---------------------+
968
     */
969

970
    status = SIXEL_OK;
3✔
971
    palette = NULL;
3✔
972
    ncolors = 0;
3✔
973
    palette_bytes = 0;
3✔
974
    new_pixels = NULL;
3✔
975
    new_palette = NULL;
3✔
976
    capture_bytes = size;
3✔
977
    capture_source = pixels;
3✔
978
    paletted_pixels = NULL;
3✔
979
    quantized_pixels = 0;
3✔
980
    dither_allocator = NULL;
3✔
981

982
    if (encoder == NULL || pixels == NULL ||
3!
983
            (dither == NULL && size == 0)) {
1!
984
        sixel_helper_set_additional_message(
×
985
            "sixel_encoder_capture_quantized: invalid capture request.");
986
        return SIXEL_BAD_ARGUMENT;
×
987
    }
988

989
    if (!encoder->capture_quantized) {
3!
990
        return SIXEL_OK;
×
991
    }
992

993
    saved_pixelformat = SIXEL_PIXELFORMAT_RGB888;
3✔
994
    restore_pixelformat = 0;
3✔
995
    if (dither != NULL) {
3!
996
        dither_allocator = dither->allocator;
3✔
997
        saved_pixelformat = dither->pixelformat;
3✔
998
        restore_pixelformat = 1;
3✔
999
        if (width <= 0 || height <= 0) {
3!
1000
            sixel_helper_set_additional_message(
×
1001
                "sixel_encoder_capture_quantized: invalid dimensions.");
1002
            status = SIXEL_BAD_ARGUMENT;
×
1003
            goto cleanup;
×
1004
        }
1005
        quantized_pixels = (size_t)width * (size_t)height;
3✔
1006
        if (height != 0 &&
3!
1007
                quantized_pixels / (size_t)height != (size_t)width) {
3!
1008
            sixel_helper_set_additional_message(
×
1009
                "sixel_encoder_capture_quantized: image too large.");
1010
            status = SIXEL_RUNTIME_ERROR;
×
1011
            goto cleanup;
×
1012
        }
1013
        paletted_pixels = sixel_dither_apply_palette(
3✔
1014
            dither, (unsigned char *)pixels, width, height);
1✔
1015
        if (paletted_pixels == NULL) {
3!
1016
            sixel_helper_set_additional_message(
×
1017
                "sixel_encoder_capture_quantized: palette conversion failed.");
1018
            status = SIXEL_RUNTIME_ERROR;
×
1019
            goto cleanup;
×
1020
        }
1021
        capture_source = (unsigned char const *)paletted_pixels;
3✔
1022
        capture_bytes = quantized_pixels;
3✔
1023
    }
1✔
1024

1025
    if (capture_bytes > 0) {
3!
1026
        if (encoder->capture_pixels == NULL ||
3!
1027
                encoder->capture_pixels_size < capture_bytes) {
×
1028
            new_pixels = (unsigned char *)sixel_allocator_malloc(
3✔
1029
                encoder->allocator, capture_bytes);
1✔
1030
            if (new_pixels == NULL) {
3!
1031
                sixel_helper_set_additional_message(
×
1032
                    "sixel_encoder_capture_quantized: "
1033
                    "sixel_allocator_malloc() failed.");
1034
                status = SIXEL_BAD_ALLOCATION;
×
1035
                goto cleanup;
×
1036
            }
1037
            sixel_allocator_free(encoder->allocator, encoder->capture_pixels);
3✔
1038
            encoder->capture_pixels = new_pixels;
3✔
1039
            encoder->capture_pixels_size = capture_bytes;
3✔
1040
        }
1✔
1041
        memcpy(encoder->capture_pixels, capture_source, capture_bytes);
3✔
1042
    }
1✔
1043
    encoder->capture_pixel_bytes = capture_bytes;
3✔
1044

1045
    palette = NULL;
3✔
1046
    ncolors = 0;
3✔
1047
    palette_bytes = 0;
3✔
1048
    if (dither != NULL) {
3!
1049
        palette = sixel_dither_get_palette(dither);
3✔
1050
        ncolors = sixel_dither_get_num_of_palette_colors(dither);
3✔
1051
    }
1✔
1052
    if (palette != NULL && ncolors > 0) {
3!
1053
        palette_bytes = (size_t)ncolors * 3;
3✔
1054
        if (encoder->capture_palette == NULL ||
3!
1055
                encoder->capture_palette_size < palette_bytes) {
×
1056
            new_palette = (unsigned char *)sixel_allocator_malloc(
3✔
1057
                encoder->allocator, palette_bytes);
1✔
1058
            if (new_palette == NULL) {
3!
1059
                sixel_helper_set_additional_message(
×
1060
                    "sixel_encoder_capture_quantized: "
1061
                    "sixel_allocator_malloc() failed.");
1062
                status = SIXEL_BAD_ALLOCATION;
×
1063
                goto cleanup;
×
1064
            }
1065
            sixel_allocator_free(encoder->allocator,
4✔
1066
                                 encoder->capture_palette);
3✔
1067
            encoder->capture_palette = new_palette;
3✔
1068
            encoder->capture_palette_size = palette_bytes;
3✔
1069
        }
1✔
1070
        memcpy(encoder->capture_palette, palette, palette_bytes);
3✔
1071
    }
1✔
1072

1073
    encoder->capture_width = width;
3✔
1074
    encoder->capture_height = height;
3✔
1075
    if (dither != NULL) {
3!
1076
        encoder->capture_pixelformat = SIXEL_PIXELFORMAT_PAL8;
3✔
1077
    } else {
1✔
1078
        encoder->capture_pixelformat = pixelformat;
×
1079
    }
1080
    encoder->capture_colorspace = colorspace;
3✔
1081
    encoder->capture_palette_size = palette_bytes;
3✔
1082
    encoder->capture_ncolors = ncolors;
3✔
1083
    encoder->capture_valid = 1;
3✔
1084

1085
cleanup:
2✔
1086
    if (restore_pixelformat && dither != NULL) {
3!
1087
        /*
1088
         * Undo the normalization performed by sixel_dither_apply_palette().
1089
         *
1090
         *     RGBA8888 --capture--> RGB888 (temporary)
1091
         *          \______________________________/
1092
         *                          |
1093
         *                 restore original state for
1094
         *                 the real encoder execution.
1095
         */
1096
        sixel_dither_set_pixelformat(dither, saved_pixelformat);
3✔
1097
    }
1✔
1098
    if (paletted_pixels != NULL && dither_allocator != NULL) {
3!
1099
        sixel_allocator_free(dither_allocator, paletted_pixels);
3✔
1100
    }
1✔
1101

1102
    return status;
3✔
1103
}
1✔
1104

1105
static SIXELSTATUS
1106
sixel_prepare_builtin_palette(
27✔
1107
    sixel_dither_t /* out */ **dither,
1108
    int            /* in */  builtin_palette)
1109
{
1110
    SIXELSTATUS status = SIXEL_FALSE;
27✔
1111

1112
    *dither = sixel_dither_get(builtin_palette);
27✔
1113
    if (*dither == NULL) {
27!
1114
        sixel_helper_set_additional_message(
×
1115
            "sixel_prepare_builtin_palette: sixel_dither_get() failed.");
1116
        status = SIXEL_RUNTIME_ERROR;
×
1117
        goto end;
×
1118
    }
1119

1120
    status = SIXEL_OK;
27✔
1121

1122
end:
18✔
1123
    return status;
27✔
1124
}
1125

1126
static int
1127
sixel_encoder_thumbnail_hint(sixel_encoder_t *encoder)
457✔
1128
{
1129
    int width_hint;
1130
    int height_hint;
1131
    long base;
1132
    long size;
1133

1134
    width_hint = 0;
457✔
1135
    height_hint = 0;
457✔
1136
    base = 0;
457✔
1137
    size = 0;
457✔
1138

1139
    if (encoder == NULL) {
457!
1140
        return 0;
×
1141
    }
1142

1143
    width_hint = encoder->pixelwidth;
457✔
1144
    height_hint = encoder->pixelheight;
457✔
1145

1146
    /* Request extra resolution for downscaling to preserve detail. */
1147
    if (width_hint > 0 && height_hint > 0) {
457✔
1148
        /* Follow the CLI rule: double the larger axis before doubling
1149
         * again for the final request size. */
1150
        if (width_hint >= height_hint) {
9!
1151
            base = (long)width_hint;
9✔
1152
        } else {
3✔
1153
            base = (long)height_hint;
×
1154
        }
1155
        base *= 2L;
9✔
1156
    } else if (width_hint > 0) {
451✔
1157
        base = (long)width_hint;
48✔
1158
    } else if (height_hint > 0) {
416✔
1159
        base = (long)height_hint;
36✔
1160
    } else {
12✔
1161
        return 0;
364✔
1162
    }
1163

1164
    size = base * 2L;
93✔
1165
    if (size > (long)INT_MAX) {
93!
1166
        size = (long)INT_MAX;
×
1167
    }
1168
    if (size < 1L) {
93!
1169
        size = 1L;
×
1170
    }
1171

1172
    return (int)size;
93✔
1173
}
153✔
1174

1175

1176
typedef struct sixel_callback_context_for_mapfile {
1177
    int reqcolors;
1178
    sixel_dither_t *dither;
1179
    sixel_allocator_t *allocator;
1180
    int working_colorspace;
1181
    int lut_policy;
1182
} sixel_callback_context_for_mapfile_t;
1183

1184

1185
/* callback function for sixel_helper_load_image_file() */
1186
static SIXELSTATUS
1187
load_image_callback_for_palette(
21✔
1188
    sixel_frame_t   /* in */    *frame, /* frame object from image loader */
1189
    void            /* in */    *data)  /* private data */
1190
{
1191
    SIXELSTATUS status = SIXEL_FALSE;
21✔
1192
    sixel_callback_context_for_mapfile_t *callback_context;
1193

1194
    /* get callback context object from the private data */
1195
    callback_context = (sixel_callback_context_for_mapfile_t *)data;
21✔
1196

1197
    status = sixel_frame_ensure_colorspace(frame,
28✔
1198
                                           callback_context->working_colorspace);
7✔
1199
    if (SIXEL_FAILED(status)) {
21!
1200
        goto end;
×
1201
    }
1202

1203
    switch (sixel_frame_get_pixelformat(frame)) {
21!
1204
    case SIXEL_PIXELFORMAT_PAL1:
2✔
1205
    case SIXEL_PIXELFORMAT_PAL2:
1206
    case SIXEL_PIXELFORMAT_PAL4:
1207
    case SIXEL_PIXELFORMAT_PAL8:
1208
        if (sixel_frame_get_palette(frame) == NULL) {
3!
1209
            status = SIXEL_LOGIC_ERROR;
×
1210
            goto end;
×
1211
        }
1212
        /* create new dither object */
1213
        status = sixel_dither_new(
3✔
1214
            &callback_context->dither,
1✔
1215
            sixel_frame_get_ncolors(frame),
1✔
1216
            callback_context->allocator);
1✔
1217
        if (SIXEL_FAILED(status)) {
3!
1218
            goto end;
×
1219
        }
1220

1221
        sixel_dither_set_lut_policy(callback_context->dither,
4✔
1222
                                    callback_context->lut_policy);
1✔
1223

1224
        /* use palette which is extracted from the image */
1225
        sixel_dither_set_palette(callback_context->dither,
4✔
1226
                                 sixel_frame_get_palette(frame));
1✔
1227
        /* success */
1228
        status = SIXEL_OK;
3✔
1229
        break;
3✔
1230
    case SIXEL_PIXELFORMAT_G1:
1231
        /* use 1bpp grayscale builtin palette */
1232
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1233
        /* success */
1234
        status = SIXEL_OK;
×
1235
        break;
×
1236
    case SIXEL_PIXELFORMAT_G2:
1237
        /* use 2bpp grayscale builtin palette */
1238
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1239
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
1240
        /* success */
1241
        status = SIXEL_OK;
×
1242
        break;
×
1243
    case SIXEL_PIXELFORMAT_G4:
1244
        /* use 4bpp grayscale builtin palette */
1245
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
1246
        /* success */
1247
        status = SIXEL_OK;
×
1248
        break;
×
1249
    case SIXEL_PIXELFORMAT_G8:
1250
        /* use 8bpp grayscale builtin palette */
1251
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
1252
        /* success */
1253
        status = SIXEL_OK;
×
1254
        break;
×
1255
    default:
12✔
1256
        /* create new dither object */
1257
        status = sixel_dither_new(
18✔
1258
            &callback_context->dither,
6✔
1259
            callback_context->reqcolors,
6✔
1260
            callback_context->allocator);
6✔
1261
        if (SIXEL_FAILED(status)) {
18!
1262
            goto end;
×
1263
        }
1264

1265
        sixel_dither_set_lut_policy(callback_context->dither,
24✔
1266
                                    callback_context->lut_policy);
6✔
1267

1268
        /* create adaptive palette from given frame object */
1269
        status = sixel_dither_initialize(callback_context->dither,
24✔
1270
                                         sixel_frame_get_pixels(frame),
6✔
1271
                                         sixel_frame_get_width(frame),
6✔
1272
                                         sixel_frame_get_height(frame),
6✔
1273
                                         sixel_frame_get_pixelformat(frame),
6✔
1274
                                         SIXEL_LARGE_NORM,
1275
                                         SIXEL_REP_CENTER_BOX,
1276
                                         SIXEL_QUALITY_HIGH);
1277
        if (SIXEL_FAILED(status)) {
18!
1278
            sixel_dither_unref(callback_context->dither);
×
1279
            goto end;
×
1280
        }
1281

1282
        /* success */
1283
        status = SIXEL_OK;
18✔
1284

1285
        break;
18✔
1286
    }
7✔
1287

1288
end:
14✔
1289
    return status;
21✔
1290
}
1291

1292

1293
static SIXELSTATUS
1294
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder);
1295

1296

1297
static int
1298
sixel_path_has_extension(char const *path, char const *extension)
63✔
1299
{
1300
    size_t path_len;
1301
    size_t ext_len;
1302
    size_t index;
1303

1304
    path_len = 0u;
63✔
1305
    ext_len = 0u;
63✔
1306
    index = 0u;
63✔
1307

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

1312
    path_len = strlen(path);
63✔
1313
    ext_len = strlen(extension);
63✔
1314
    if (ext_len == 0u || path_len < ext_len) {
63!
1315
        return 0;
×
1316
    }
1317

1318
    for (index = 0u; index < ext_len; ++index) {
144!
1319
        unsigned char path_ch;
1320
        unsigned char ext_ch;
1321

1322
        path_ch = (unsigned char)path[path_len - ext_len + index];
144✔
1323
        ext_ch = (unsigned char)extension[index];
144✔
1324
        if (tolower(path_ch) != tolower(ext_ch)) {
144✔
1325
            return 0;
63✔
1326
        }
1327
    }
27✔
1328

1329
    return 1;
×
1330
}
21✔
1331

1332
typedef enum sixel_palette_format {
1333
    SIXEL_PALETTE_FORMAT_NONE = 0,
1334
    SIXEL_PALETTE_FORMAT_ACT,
1335
    SIXEL_PALETTE_FORMAT_PAL_JASC,
1336
    SIXEL_PALETTE_FORMAT_PAL_RIFF,
1337
    SIXEL_PALETTE_FORMAT_PAL_AUTO,
1338
    SIXEL_PALETTE_FORMAT_GPL
1339
} sixel_palette_format_t;
1340

1341
/*
1342
 * Palette specification parser
1343
 *
1344
 *   TYPE:PATH  -> explicit format prefix
1345
 *   PATH       -> rely on extension or heuristics
1346
 *
1347
 * The ASCII diagram below shows how the prefix is peeled:
1348
 *
1349
 *   [type] : [path]
1350
 *    ^-- left part selects decoder/encoder when present.
1351
 */
1352
static char const *
1353
sixel_palette_strip_prefix(char const *spec,
21✔
1354
                           sixel_palette_format_t *format_hint)
1355
{
1356
    char const *colon;
1357
    size_t type_len;
1358
    size_t index;
1359
    char lowered[16];
1360

1361
    colon = NULL;
21✔
1362
    type_len = 0u;
21✔
1363
    index = 0u;
21✔
1364

1365
    if (format_hint != NULL) {
21!
1366
        *format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
1367
    }
7✔
1368
    if (spec == NULL) {
21!
1369
        return NULL;
×
1370
    }
1371

1372
    colon = strchr(spec, ':');
21✔
1373
    if (colon == NULL) {
21!
1374
        return spec;
21✔
1375
    }
1376

1377
    type_len = (size_t)(colon - spec);
×
1378
    if (type_len == 0u || type_len >= sizeof(lowered)) {
×
1379
        return spec;
×
1380
    }
1381

1382
    for (index = 0u; index < type_len; ++index) {
×
1383
        lowered[index] = (char)tolower((unsigned char)spec[index]);
×
1384
    }
1385
    lowered[type_len] = '\0';
×
1386

1387
    if (strcmp(lowered, "act") == 0) {
×
1388
        if (format_hint != NULL) {
×
1389
            *format_hint = SIXEL_PALETTE_FORMAT_ACT;
×
1390
        }
1391
        return colon + 1;
×
1392
    }
1393
    if (strcmp(lowered, "pal") == 0) {
×
1394
        if (format_hint != NULL) {
×
1395
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1396
        }
1397
        return colon + 1;
×
1398
    }
1399
    if (strcmp(lowered, "pal-jasc") == 0) {
×
1400
        if (format_hint != NULL) {
×
1401
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1402
        }
1403
        return colon + 1;
×
1404
    }
1405
    if (strcmp(lowered, "pal-riff") == 0) {
×
1406
        if (format_hint != NULL) {
×
1407
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1408
        }
1409
        return colon + 1;
×
1410
    }
1411
    if (strcmp(lowered, "gpl") == 0) {
×
1412
        if (format_hint != NULL) {
×
1413
            *format_hint = SIXEL_PALETTE_FORMAT_GPL;
×
1414
        }
1415
        return colon + 1;
×
1416
    }
1417

1418
    return spec;
×
1419
}
7✔
1420

1421
static sixel_palette_format_t
1422
sixel_palette_format_from_extension(char const *path)
21✔
1423
{
1424
    if (path == NULL) {
21!
1425
        return SIXEL_PALETTE_FORMAT_NONE;
×
1426
    }
1427

1428
    if (sixel_path_has_extension(path, ".act")) {
21!
1429
        return SIXEL_PALETTE_FORMAT_ACT;
×
1430
    }
1431
    if (sixel_path_has_extension(path, ".pal")) {
21!
1432
        return SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1433
    }
1434
    if (sixel_path_has_extension(path, ".gpl")) {
21!
1435
        return SIXEL_PALETTE_FORMAT_GPL;
×
1436
    }
1437

1438
    return SIXEL_PALETTE_FORMAT_NONE;
21✔
1439
}
7✔
1440

1441
static int
1442
sixel_path_has_any_extension(char const *path)
21✔
1443
{
1444
    char const *slash_forward;
1445
#if defined(_WIN32)
1446
    char const *slash_backward;
1447
#endif
1448
    char const *start;
1449
    char const *dot;
1450

1451
    slash_forward = NULL;
21✔
1452
#if defined(_WIN32)
1453
    slash_backward = NULL;
1454
#endif
1455
    start = path;
21✔
1456
    dot = NULL;
21✔
1457

1458
    if (path == NULL) {
21!
1459
        return 0;
×
1460
    }
1461

1462
    slash_forward = strrchr(path, '/');
21✔
1463
#if defined(_WIN32)
1464
    slash_backward = strrchr(path, '\\');
1465
    if (slash_backward != NULL &&
1466
            (slash_forward == NULL || slash_backward > slash_forward)) {
1467
        slash_forward = slash_backward;
1468
    }
1469
#endif
1470
    if (slash_forward == NULL) {
21!
1471
        start = path;
×
1472
    } else {
1473
        start = slash_forward + 1;
21✔
1474
    }
1475

1476
    dot = strrchr(start, '.');
21✔
1477
    if (dot == NULL) {
21!
1478
        return 0;
×
1479
    }
1480

1481
    if (dot[1] == '\0') {
21!
1482
        return 0;
×
1483
    }
1484

1485
    return 1;
21✔
1486
}
7✔
1487

1488
static int
1489
sixel_palette_has_utf8_bom(unsigned char const *data, size_t size)
×
1490
{
1491
    if (data == NULL || size < 3u) {
×
1492
        return 0;
×
1493
    }
1494
    if (data[0] == 0xefu && data[1] == 0xbbu && data[2] == 0xbfu) {
×
1495
        return 1;
×
1496
    }
1497
    return 0;
×
1498
}
1499

1500

1501
/*
1502
 * Materialize palette bytes from a stream.
1503
 *
1504
 * The flow looks like:
1505
 *
1506
 *   stream --> [scratch buffer] --> [resizable heap buffer]
1507
 *                  ^ looped read        ^ returned payload
1508
 */
1509
static SIXELSTATUS
1510
sixel_palette_read_stream(FILE *stream,
×
1511
                          sixel_allocator_t *allocator,
1512
                          unsigned char **pdata,
1513
                          size_t *psize)
1514
{
1515
    SIXELSTATUS status;
1516
    unsigned char *buffer;
1517
    unsigned char *grown;
1518
    size_t capacity;
1519
    size_t used;
1520
    size_t read_bytes;
1521
    size_t needed;
1522
    size_t new_capacity;
1523
    unsigned char scratch[4096];
1524

1525
    status = SIXEL_FALSE;
×
1526
    buffer = NULL;
×
1527
    grown = NULL;
×
1528
    capacity = 0u;
×
1529
    used = 0u;
×
1530
    read_bytes = 0u;
×
1531
    needed = 0u;
×
1532
    new_capacity = 0u;
×
1533

1534
    if (pdata == NULL || psize == NULL || stream == NULL || allocator == NULL) {
×
1535
        sixel_helper_set_additional_message(
×
1536
            "sixel_palette_read_stream: invalid argument.");
1537
        return SIXEL_BAD_ARGUMENT;
×
1538
    }
1539

1540
    *pdata = NULL;
×
1541
    *psize = 0u;
×
1542

1543
    while (1) {
1544
        read_bytes = fread(scratch, 1, sizeof(scratch), stream);
×
1545
        if (read_bytes == 0u) {
×
1546
            if (ferror(stream)) {
×
1547
                sixel_helper_set_additional_message(
×
1548
                    "sixel_palette_read_stream: fread() failed.");
1549
                status = SIXEL_LIBC_ERROR;
×
1550
                goto cleanup;
×
1551
            }
1552
            break;
×
1553
        }
1554

1555
        if (used > SIZE_MAX - read_bytes) {
×
1556
            sixel_helper_set_additional_message(
×
1557
                "sixel_palette_read_stream: size overflow.");
1558
            status = SIXEL_BAD_ALLOCATION;
×
1559
            goto cleanup;
×
1560
        }
1561
        needed = used + read_bytes;
×
1562

1563
        if (needed > capacity) {
×
1564
            new_capacity = capacity;
×
1565
            if (new_capacity == 0u) {
×
1566
                new_capacity = 4096u;
×
1567
            }
1568
            while (needed > new_capacity) {
×
1569
                if (new_capacity > SIZE_MAX / 2u) {
×
1570
                    sixel_helper_set_additional_message(
×
1571
                        "sixel_palette_read_stream: size overflow.");
1572
                    status = SIXEL_BAD_ALLOCATION;
×
1573
                    goto cleanup;
×
1574
                }
1575
                new_capacity *= 2u;
×
1576
            }
1577

1578
            grown = (unsigned char *)sixel_allocator_malloc(allocator,
×
1579
                                                             new_capacity);
1580
            if (grown == NULL) {
×
1581
                sixel_helper_set_additional_message(
×
1582
                    "sixel_palette_read_stream: allocation failed.");
1583
                status = SIXEL_BAD_ALLOCATION;
×
1584
                goto cleanup;
×
1585
            }
1586

1587
            if (buffer != NULL) {
×
1588
                memcpy(grown, buffer, used);
×
1589
                sixel_allocator_free(allocator, buffer);
×
1590
            }
1591

1592
            buffer = grown;
×
1593
            grown = NULL;
×
1594
            capacity = new_capacity;
×
1595
        }
1596

1597
        memcpy(buffer + used, scratch, read_bytes);
×
1598
        used += read_bytes;
×
1599
    }
1600

1601
    *pdata = buffer;
×
1602
    *psize = used;
×
1603
    status = SIXEL_OK;
×
1604
    return status;
×
1605

1606
cleanup:
1607
    if (grown != NULL) {
×
1608
        sixel_allocator_free(allocator, grown);
×
1609
    }
1610
    if (buffer != NULL) {
×
1611
        sixel_allocator_free(allocator, buffer);
×
1612
    }
1613
    return status;
×
1614
}
1615

1616

1617
static SIXELSTATUS
1618
sixel_palette_open_read(char const *path, FILE **pstream, int *pclose)
×
1619
{
1620
    if (pstream == NULL || pclose == NULL || path == NULL) {
×
1621
        sixel_helper_set_additional_message(
×
1622
            "sixel_palette_open_read: invalid argument.");
1623
        return SIXEL_BAD_ARGUMENT;
×
1624
    }
1625

1626
    if (strcmp(path, "-") == 0) {
×
1627
        *pstream = stdin;
×
1628
        *pclose = 0;
×
1629
        return SIXEL_OK;
×
1630
    }
1631

1632
    *pstream = fopen(path, "rb");
×
1633
    if (*pstream == NULL) {
×
1634
        sixel_helper_set_additional_message(
×
1635
            "sixel_palette_open_read: failed to open file.");
1636
        return SIXEL_LIBC_ERROR;
×
1637
    }
1638

1639
    *pclose = 1;
×
1640
    return SIXEL_OK;
×
1641
}
1642

1643

1644
static void
1645
sixel_palette_close_stream(FILE *stream, int close_stream)
×
1646
{
1647
    if (close_stream && stream != NULL) {
×
1648
        (void) fclose(stream);
×
1649
    }
1650
}
×
1651

1652

1653
static sixel_palette_format_t
1654
sixel_palette_guess_format(unsigned char const *data, size_t size)
×
1655
{
1656
    size_t offset;
1657
    size_t data_size;
1658

1659
    offset = 0u;
×
1660
    data_size = size;
×
1661

1662
    if (data == NULL || size == 0u) {
×
1663
        return SIXEL_PALETTE_FORMAT_NONE;
×
1664
    }
1665

1666
    if (size == 256u * 3u || size == 256u * 3u + 4u) {
×
1667
        return SIXEL_PALETTE_FORMAT_ACT;
×
1668
    }
1669

1670
    if (size >= 12u && memcmp(data, "RIFF", 4) == 0
×
1671
            && memcmp(data + 8, "PAL ", 4) == 0) {
×
1672
        return SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1673
    }
1674

1675
    if (sixel_palette_has_utf8_bom(data, size)) {
×
1676
        offset = 3u;
×
1677
        data_size = size - 3u;
×
1678
    }
1679

1680
    if (data_size >= 8u && memcmp(data + offset, "JASC-PAL", 8) == 0) {
×
1681
        return SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1682
    }
1683
    if (data_size >= 12u && memcmp(data + offset, "GIMP Palette", 12) == 0) {
×
1684
        return SIXEL_PALETTE_FORMAT_GPL;
×
1685
    }
1686

1687
    return SIXEL_PALETTE_FORMAT_NONE;
×
1688
}
1689

1690

1691
static unsigned int
1692
sixel_palette_read_le16(unsigned char const *ptr)
×
1693
{
1694
    if (ptr == NULL) {
×
1695
        return 0u;
×
1696
    }
1697
    return (unsigned int)ptr[0] | ((unsigned int)ptr[1] << 8);
×
1698
}
1699

1700

1701
static unsigned int
1702
sixel_palette_read_le32(unsigned char const *ptr)
×
1703
{
1704
    if (ptr == NULL) {
×
1705
        return 0u;
×
1706
    }
1707
    return ((unsigned int)ptr[0])
×
1708
        | ((unsigned int)ptr[1] << 8)
×
1709
        | ((unsigned int)ptr[2] << 16)
×
1710
        | ((unsigned int)ptr[3] << 24);
×
1711
}
1712

1713

1714
/*
1715
 * Adobe Color Table (*.act) reader
1716
 *
1717
 *   +-----------+---------------------------+
1718
 *   | section   | bytes                     |
1719
 *   +-----------+---------------------------+
1720
 *   | palette   | 256 entries * 3 RGB bytes |
1721
 *   | trailer   | optional count/start pair |
1722
 *   +-----------+---------------------------+
1723
 */
1724
static SIXELSTATUS
1725
sixel_palette_parse_act(unsigned char const *data,
×
1726
                        size_t size,
1727
                        sixel_encoder_t *encoder,
1728
                        sixel_dither_t **dither)
1729
{
1730
    SIXELSTATUS status;
1731
    sixel_dither_t *local;
1732
    unsigned char const *palette_start;
1733
    unsigned char const *trailer;
1734
    unsigned char *target;
1735
    size_t copy_bytes;
1736
    int exported_colors;
1737
    int start_index;
1738

1739
    status = SIXEL_FALSE;
×
1740
    local = NULL;
×
1741
    palette_start = data;
×
1742
    trailer = NULL;
×
1743
    target = NULL;
×
1744
    copy_bytes = 0u;
×
1745
    exported_colors = 0;
×
1746
    start_index = 0;
×
1747

1748
    if (encoder == NULL || dither == NULL) {
×
1749
        sixel_helper_set_additional_message(
×
1750
            "sixel_palette_parse_act: invalid argument.");
1751
        return SIXEL_BAD_ARGUMENT;
×
1752
    }
1753
    if (data == NULL || size < 256u * 3u) {
×
1754
        sixel_helper_set_additional_message(
×
1755
            "sixel_palette_parse_act: truncated ACT palette.");
1756
        return SIXEL_BAD_INPUT;
×
1757
    }
1758

1759
    if (size == 256u * 3u) {
×
1760
        exported_colors = 256;
×
1761
        start_index = 0;
×
1762
    } else if (size == 256u * 3u + 4u) {
×
1763
        trailer = data + 256u * 3u;
×
1764
        exported_colors = (int)(((unsigned int)trailer[0] << 8)
×
1765
                                | (unsigned int)trailer[1]);
×
1766
        start_index = (int)(((unsigned int)trailer[2] << 8)
×
1767
                            | (unsigned int)trailer[3]);
×
1768
    } else {
1769
        sixel_helper_set_additional_message(
×
1770
            "sixel_palette_parse_act: invalid ACT length.");
1771
        return SIXEL_BAD_INPUT;
×
1772
    }
1773

1774
    if (start_index < 0 || start_index >= 256) {
×
1775
        sixel_helper_set_additional_message(
×
1776
            "sixel_palette_parse_act: ACT start index out of range.");
1777
        return SIXEL_BAD_INPUT;
×
1778
    }
1779
    if (exported_colors <= 0 || exported_colors > 256) {
×
1780
        exported_colors = 256;
×
1781
    }
1782
    if (start_index + exported_colors > 256) {
×
1783
        sixel_helper_set_additional_message(
×
1784
            "sixel_palette_parse_act: ACT palette exceeds 256 slots.");
1785
        return SIXEL_BAD_INPUT;
×
1786
    }
1787

1788
    status = sixel_dither_new(&local, exported_colors, encoder->allocator);
×
1789
    if (SIXEL_FAILED(status)) {
×
1790
        return status;
×
1791
    }
1792

1793
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1794

1795
    target = sixel_dither_get_palette(local);
×
1796
    copy_bytes = (size_t)exported_colors * 3u;
×
1797
    memcpy(target, palette_start + (size_t)start_index * 3u, copy_bytes);
×
1798

1799
    *dither = local;
×
1800
    return SIXEL_OK;
×
1801
}
1802

1803

1804
static SIXELSTATUS
1805
sixel_palette_parse_pal_jasc(unsigned char const *data,
×
1806
                             size_t size,
1807
                             sixel_encoder_t *encoder,
1808
                             sixel_dither_t **dither)
1809
{
1810
    SIXELSTATUS status;
1811
    char *text;
1812
    size_t index;
1813
    size_t offset;
1814
    char *cursor;
1815
    char *line;
1816
    char *line_end;
1817
    int stage;
1818
    int exported_colors;
1819
    int parsed_colors;
1820
    sixel_dither_t *local;
1821
    unsigned char *target;
1822
    long component;
1823
    char *parse_end;
1824
    int value_index;
1825
    int values[3];
1826
    char tail;
1827

1828
    status = SIXEL_FALSE;
×
1829
    text = NULL;
×
1830
    index = 0u;
×
1831
    offset = 0u;
×
1832
    cursor = NULL;
×
1833
    line = NULL;
×
1834
    line_end = NULL;
×
1835
    stage = 0;
×
1836
    exported_colors = 0;
×
1837
    parsed_colors = 0;
×
1838
    local = NULL;
×
1839
    target = NULL;
×
1840
    component = 0;
×
1841
    parse_end = NULL;
×
1842
    value_index = 0;
×
1843
    values[0] = 0;
×
1844
    values[1] = 0;
×
1845
    values[2] = 0;
×
1846

1847
    if (encoder == NULL || dither == NULL) {
×
1848
        sixel_helper_set_additional_message(
×
1849
            "sixel_palette_parse_pal_jasc: invalid argument.");
1850
        return SIXEL_BAD_ARGUMENT;
×
1851
    }
1852
    if (data == NULL || size == 0u) {
×
1853
        sixel_helper_set_additional_message(
×
1854
            "sixel_palette_parse_pal_jasc: empty palette.");
1855
        return SIXEL_BAD_INPUT;
×
1856
    }
1857

1858
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
1859
    if (text == NULL) {
×
1860
        sixel_helper_set_additional_message(
×
1861
            "sixel_palette_parse_pal_jasc: allocation failed.");
1862
        return SIXEL_BAD_ALLOCATION;
×
1863
    }
1864
    memcpy(text, data, size);
×
1865
    text[size] = '\0';
×
1866

1867
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
1868
        offset = 3u;
×
1869
    }
1870
    cursor = text + offset;
×
1871

1872
    while (*cursor != '\0') {
×
1873
        line = cursor;
×
1874
        line_end = cursor;
×
1875
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
1876
            ++line_end;
×
1877
        }
1878
        if (*line_end != '\0') {
×
1879
            *line_end = '\0';
×
1880
            cursor = line_end + 1;
×
1881
        } else {
1882
            cursor = line_end;
×
1883
        }
1884
        while (*cursor == '\n' || *cursor == '\r') {
×
1885
            ++cursor;
×
1886
        }
1887

1888
        while (*line == ' ' || *line == '\t') {
×
1889
            ++line;
×
1890
        }
1891
        index = strlen(line);
×
1892
        while (index > 0u) {
×
1893
            tail = line[index - 1];
×
1894
            if (tail != ' ' && tail != '\t') {
×
1895
                break;
×
1896
            }
1897
            line[index - 1] = '\0';
×
1898
            --index;
×
1899
        }
1900
        if (*line == '\0') {
×
1901
            continue;
×
1902
        }
1903
        if (*line == '#') {
×
1904
            continue;
×
1905
        }
1906

1907
        if (stage == 0) {
×
1908
            if (strcmp(line, "JASC-PAL") != 0) {
×
1909
                sixel_helper_set_additional_message(
×
1910
                    "sixel_palette_parse_pal_jasc: missing header.");
1911
                status = SIXEL_BAD_INPUT;
×
1912
                goto cleanup;
×
1913
            }
1914
            stage = 1;
×
1915
            continue;
×
1916
        }
1917
        if (stage == 1) {
×
1918
            stage = 2;
×
1919
            continue;
×
1920
        }
1921
        if (stage == 2) {
×
1922
            component = strtol(line, &parse_end, 10);
×
1923
            if (parse_end == line || component <= 0L || component > 256L) {
×
1924
                sixel_helper_set_additional_message(
×
1925
                    "sixel_palette_parse_pal_jasc: invalid color count.");
1926
                status = SIXEL_BAD_INPUT;
×
1927
                goto cleanup;
×
1928
            }
1929
            exported_colors = (int)component;
×
1930
            status = sixel_dither_new(&local, exported_colors,
×
1931
                                      encoder->allocator);
1932
            if (SIXEL_FAILED(status)) {
×
1933
                goto cleanup;
×
1934
            }
1935
            sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1936
            target = sixel_dither_get_palette(local);
×
1937
            stage = 3;
×
1938
            continue;
×
1939
        }
1940

1941
        value_index = 0;
×
1942
        while (value_index < 3) {
×
1943
            component = strtol(line, &parse_end, 10);
×
1944
            if (parse_end == line || component < 0L || component > 255L) {
×
1945
                sixel_helper_set_additional_message(
×
1946
                    "sixel_palette_parse_pal_jasc: invalid component.");
1947
                status = SIXEL_BAD_INPUT;
×
1948
                goto cleanup;
×
1949
            }
1950
            values[value_index] = (int)component;
×
1951
            ++value_index;
×
1952
            line = parse_end;
×
1953
            while (*line == ' ' || *line == '\t') {
×
1954
                ++line;
×
1955
            }
1956
        }
1957

1958
        if (parsed_colors >= exported_colors) {
×
1959
            sixel_helper_set_additional_message(
×
1960
                "sixel_palette_parse_pal_jasc: excess entries.");
1961
            status = SIXEL_BAD_INPUT;
×
1962
            goto cleanup;
×
1963
        }
1964

1965
        target[parsed_colors * 3 + 0] =
×
1966
            (unsigned char)values[0];
×
1967
        target[parsed_colors * 3 + 1] =
×
1968
            (unsigned char)values[1];
×
1969
        target[parsed_colors * 3 + 2] =
×
1970
            (unsigned char)values[2];
×
1971
        ++parsed_colors;
×
1972
    }
1973

1974
    if (stage < 3) {
×
1975
        sixel_helper_set_additional_message(
×
1976
            "sixel_palette_parse_pal_jasc: incomplete header.");
1977
        status = SIXEL_BAD_INPUT;
×
1978
        goto cleanup;
×
1979
    }
1980
    if (parsed_colors != exported_colors) {
×
1981
        sixel_helper_set_additional_message(
×
1982
            "sixel_palette_parse_pal_jasc: color count mismatch.");
1983
        status = SIXEL_BAD_INPUT;
×
1984
        goto cleanup;
×
1985
    }
1986

1987
    *dither = local;
×
1988
    status = SIXEL_OK;
×
1989

1990
cleanup:
1991
    if (SIXEL_FAILED(status) && local != NULL) {
×
1992
        sixel_dither_unref(local);
×
1993
    }
1994
    if (text != NULL) {
×
1995
        sixel_allocator_free(encoder->allocator, text);
×
1996
    }
1997
    return status;
×
1998
}
1999

2000

2001
static SIXELSTATUS
2002
sixel_palette_parse_pal_riff(unsigned char const *data,
×
2003
                             size_t size,
2004
                             sixel_encoder_t *encoder,
2005
                             sixel_dither_t **dither)
2006
{
2007
    SIXELSTATUS status;
2008
    size_t offset;
2009
    size_t chunk_size;
2010
    sixel_dither_t *local;
2011
    unsigned char const *chunk;
2012
    unsigned char *target;
2013
    unsigned int entry_count;
2014
    unsigned int version;
2015
    unsigned int index;
2016
    size_t palette_offset;
2017

2018
    status = SIXEL_FALSE;
×
2019
    offset = 0u;
×
2020
    chunk_size = 0u;
×
2021
    local = NULL;
×
2022
    chunk = NULL;
×
2023
    target = NULL;
×
2024
    entry_count = 0u;
×
2025
    version = 0u;
×
2026
    index = 0u;
×
2027
    palette_offset = 0u;
×
2028

2029
    if (encoder == NULL || dither == NULL) {
×
2030
        sixel_helper_set_additional_message(
×
2031
            "sixel_palette_parse_pal_riff: invalid argument.");
2032
        return SIXEL_BAD_ARGUMENT;
×
2033
    }
2034
    if (data == NULL || size < 12u) {
×
2035
        sixel_helper_set_additional_message(
×
2036
            "sixel_palette_parse_pal_riff: truncated palette.");
2037
        return SIXEL_BAD_INPUT;
×
2038
    }
2039
    if (memcmp(data, "RIFF", 4) != 0 || memcmp(data + 8, "PAL ", 4) != 0) {
×
2040
        sixel_helper_set_additional_message(
×
2041
            "sixel_palette_parse_pal_riff: missing RIFF header.");
2042
        return SIXEL_BAD_INPUT;
×
2043
    }
2044

2045
    offset = 12u;
×
2046
    while (offset + 8u <= size) {
×
2047
        chunk = data + offset;
×
2048
        chunk_size = (size_t)sixel_palette_read_le32(chunk + 4);
×
2049
        if (offset + 8u + chunk_size > size) {
×
2050
            sixel_helper_set_additional_message(
×
2051
                "sixel_palette_parse_pal_riff: chunk extends past end.");
2052
            return SIXEL_BAD_INPUT;
×
2053
        }
2054
        if (memcmp(chunk, "data", 4) == 0) {
×
2055
            break;
×
2056
        }
2057
        offset += 8u + ((chunk_size + 1u) & ~1u);
×
2058
    }
2059

2060
    if (offset + 8u > size || memcmp(chunk, "data", 4) != 0) {
×
2061
        sixel_helper_set_additional_message(
×
2062
            "sixel_palette_parse_pal_riff: missing data chunk.");
2063
        return SIXEL_BAD_INPUT;
×
2064
    }
2065

2066
    if (chunk_size < 4u) {
×
2067
        sixel_helper_set_additional_message(
×
2068
            "sixel_palette_parse_pal_riff: data chunk too small.");
2069
        return SIXEL_BAD_INPUT;
×
2070
    }
2071
    version = sixel_palette_read_le16(chunk + 8);
×
2072
    (void)version;
2073
    entry_count = sixel_palette_read_le16(chunk + 10);
×
2074
    if (entry_count == 0u || entry_count > 256u) {
×
2075
        sixel_helper_set_additional_message(
×
2076
            "sixel_palette_parse_pal_riff: invalid entry count.");
2077
        return SIXEL_BAD_INPUT;
×
2078
    }
2079
    if (chunk_size != 4u + (size_t)entry_count * 4u) {
×
2080
        sixel_helper_set_additional_message(
×
2081
            "sixel_palette_parse_pal_riff: unexpected chunk size.");
2082
        return SIXEL_BAD_INPUT;
×
2083
    }
2084

2085
    status = sixel_dither_new(&local, (int)entry_count, encoder->allocator);
×
2086
    if (SIXEL_FAILED(status)) {
×
2087
        return status;
×
2088
    }
2089
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2090
    target = sixel_dither_get_palette(local);
×
2091
    palette_offset = 12u;
×
2092
    for (index = 0u; index < entry_count; ++index) {
×
2093
        target[index * 3u + 0u] =
×
2094
            chunk[palette_offset + index * 4u + 0u];
×
2095
        target[index * 3u + 1u] =
×
2096
            chunk[palette_offset + index * 4u + 1u];
×
2097
        target[index * 3u + 2u] =
×
2098
            chunk[palette_offset + index * 4u + 2u];
×
2099
    }
2100

2101
    *dither = local;
×
2102
    return SIXEL_OK;
×
2103
}
2104

2105

2106
static SIXELSTATUS
2107
sixel_palette_parse_gpl(unsigned char const *data,
×
2108
                        size_t size,
2109
                        sixel_encoder_t *encoder,
2110
                        sixel_dither_t **dither)
2111
{
2112
    SIXELSTATUS status;
2113
    char *text;
2114
    size_t offset;
2115
    char *cursor;
2116
    char *line;
2117
    char *line_end;
2118
    size_t index;
2119
    int header_seen;
2120
    int parsed_colors;
2121
    unsigned char palette_bytes[256 * 3];
2122
    long component;
2123
    char *parse_end;
2124
    int value_index;
2125
    int values[3];
2126
    sixel_dither_t *local;
2127
    unsigned char *target;
2128
    char tail;
2129

2130
    status = SIXEL_FALSE;
×
2131
    text = NULL;
×
2132
    offset = 0u;
×
2133
    cursor = NULL;
×
2134
    line = NULL;
×
2135
    line_end = NULL;
×
2136
    index = 0u;
×
2137
    header_seen = 0;
×
2138
    parsed_colors = 0;
×
2139
    component = 0;
×
2140
    parse_end = NULL;
×
2141
    value_index = 0;
×
2142
    values[0] = 0;
×
2143
    values[1] = 0;
×
2144
    values[2] = 0;
×
2145
    local = NULL;
×
2146
    target = NULL;
×
2147

2148
    if (encoder == NULL || dither == NULL) {
×
2149
        sixel_helper_set_additional_message(
×
2150
            "sixel_palette_parse_gpl: invalid argument.");
2151
        return SIXEL_BAD_ARGUMENT;
×
2152
    }
2153
    if (data == NULL || size == 0u) {
×
2154
        sixel_helper_set_additional_message(
×
2155
            "sixel_palette_parse_gpl: empty palette.");
2156
        return SIXEL_BAD_INPUT;
×
2157
    }
2158

2159
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
2160
    if (text == NULL) {
×
2161
        sixel_helper_set_additional_message(
×
2162
            "sixel_palette_parse_gpl: allocation failed.");
2163
        return SIXEL_BAD_ALLOCATION;
×
2164
    }
2165
    memcpy(text, data, size);
×
2166
    text[size] = '\0';
×
2167

2168
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
2169
        offset = 3u;
×
2170
    }
2171
    cursor = text + offset;
×
2172

2173
    while (*cursor != '\0') {
×
2174
        line = cursor;
×
2175
        line_end = cursor;
×
2176
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
2177
            ++line_end;
×
2178
        }
2179
        if (*line_end != '\0') {
×
2180
            *line_end = '\0';
×
2181
            cursor = line_end + 1;
×
2182
        } else {
2183
            cursor = line_end;
×
2184
        }
2185
        while (*cursor == '\n' || *cursor == '\r') {
×
2186
            ++cursor;
×
2187
        }
2188

2189
        while (*line == ' ' || *line == '\t') {
×
2190
            ++line;
×
2191
        }
2192
        index = strlen(line);
×
2193
        while (index > 0u) {
×
2194
            tail = line[index - 1];
×
2195
            if (tail != ' ' && tail != '\t') {
×
2196
                break;
×
2197
            }
2198
            line[index - 1] = '\0';
×
2199
            --index;
×
2200
        }
2201
        if (*line == '\0') {
×
2202
            continue;
×
2203
        }
2204
        if (*line == '#') {
×
2205
            continue;
×
2206
        }
2207
        if (strncmp(line, "Name:", 5) == 0) {
×
2208
            continue;
×
2209
        }
2210
        if (strncmp(line, "Columns:", 8) == 0) {
×
2211
            continue;
×
2212
        }
2213

2214
        if (!header_seen) {
×
2215
            if (strcmp(line, "GIMP Palette") != 0) {
×
2216
                sixel_helper_set_additional_message(
×
2217
                    "sixel_palette_parse_gpl: missing header.");
2218
                status = SIXEL_BAD_INPUT;
×
2219
                goto cleanup;
×
2220
            }
2221
            header_seen = 1;
×
2222
            continue;
×
2223
        }
2224

2225
        if (parsed_colors >= 256) {
×
2226
            sixel_helper_set_additional_message(
×
2227
                "sixel_palette_parse_gpl: too many colors.");
2228
            status = SIXEL_BAD_INPUT;
×
2229
            goto cleanup;
×
2230
        }
2231

2232
        value_index = 0;
×
2233
        while (value_index < 3) {
×
2234
            component = strtol(line, &parse_end, 10);
×
2235
            if (parse_end == line || component < 0L || component > 255L) {
×
2236
                sixel_helper_set_additional_message(
×
2237
                    "sixel_palette_parse_gpl: invalid component.");
2238
                status = SIXEL_BAD_INPUT;
×
2239
                goto cleanup;
×
2240
            }
2241
            values[value_index] = (int)component;
×
2242
            ++value_index;
×
2243
            line = parse_end;
×
2244
            while (*line == ' ' || *line == '\t') {
×
2245
                ++line;
×
2246
            }
2247
        }
2248

2249
        palette_bytes[parsed_colors * 3 + 0] =
×
2250
            (unsigned char)values[0];
×
2251
        palette_bytes[parsed_colors * 3 + 1] =
×
2252
            (unsigned char)values[1];
×
2253
        palette_bytes[parsed_colors * 3 + 2] =
×
2254
            (unsigned char)values[2];
×
2255
        ++parsed_colors;
×
2256
    }
2257

2258
    if (!header_seen) {
×
2259
        sixel_helper_set_additional_message(
×
2260
            "sixel_palette_parse_gpl: header missing.");
2261
        status = SIXEL_BAD_INPUT;
×
2262
        goto cleanup;
×
2263
    }
2264
    if (parsed_colors <= 0) {
×
2265
        sixel_helper_set_additional_message(
×
2266
            "sixel_palette_parse_gpl: no colors parsed.");
2267
        status = SIXEL_BAD_INPUT;
×
2268
        goto cleanup;
×
2269
    }
2270

2271
    status = sixel_dither_new(&local, parsed_colors, encoder->allocator);
×
2272
    if (SIXEL_FAILED(status)) {
×
2273
        goto cleanup;
×
2274
    }
2275
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2276
    target = sixel_dither_get_palette(local);
×
2277
    memcpy(target, palette_bytes, (size_t)parsed_colors * 3u);
×
2278

2279
    *dither = local;
×
2280
    status = SIXEL_OK;
×
2281

2282
cleanup:
2283
    if (SIXEL_FAILED(status) && local != NULL) {
×
2284
        sixel_dither_unref(local);
×
2285
    }
2286
    if (text != NULL) {
×
2287
        sixel_allocator_free(encoder->allocator, text);
×
2288
    }
2289
    return status;
×
2290
}
2291

2292

2293
/*
2294
 * Palette exporters
2295
 *
2296
 *   +----------+-------------------------+
2297
 *   | format   | emission strategy       |
2298
 *   +----------+-------------------------+
2299
 *   | ACT      | fixed 256 entries + EOF |
2300
 *   | PAL JASC | textual lines           |
2301
 *   | PAL RIFF | RIFF container          |
2302
 *   | GPL      | textual lines           |
2303
 *   +----------+-------------------------+
2304
 */
2305
static SIXELSTATUS
2306
sixel_palette_write_act(FILE *stream,
×
2307
                        unsigned char const *palette,
2308
                        int exported_colors)
2309
{
2310
    SIXELSTATUS status;
2311
    unsigned char act_table[256 * 3];
2312
    unsigned char trailer[4];
2313
    size_t exported_bytes;
2314

2315
    status = SIXEL_FALSE;
×
2316
    exported_bytes = 0u;
×
2317

2318
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2319
        return SIXEL_BAD_ARGUMENT;
×
2320
    }
2321
    if (exported_colors > 256) {
×
2322
        exported_colors = 256;
×
2323
    }
2324

2325
    memset(act_table, 0, sizeof(act_table));
×
2326
    exported_bytes = (size_t)exported_colors * 3u;
×
2327
    memcpy(act_table, palette, exported_bytes);
×
2328

2329
    trailer[0] = (unsigned char)(((unsigned int)exported_colors >> 8)
×
2330
                                 & 0xffu);
2331
    trailer[1] = (unsigned char)((unsigned int)exported_colors & 0xffu);
×
2332
    trailer[2] = 0u;
×
2333
    trailer[3] = 0u;
×
2334

2335
    if (fwrite(act_table, 1, sizeof(act_table), stream)
×
2336
            != sizeof(act_table)) {
2337
        status = SIXEL_LIBC_ERROR;
×
2338
        return status;
×
2339
    }
2340
    if (fwrite(trailer, 1, sizeof(trailer), stream)
×
2341
            != sizeof(trailer)) {
2342
        status = SIXEL_LIBC_ERROR;
×
2343
        return status;
×
2344
    }
2345

2346
    return SIXEL_OK;
×
2347
}
2348

2349

2350
static SIXELSTATUS
2351
sixel_palette_write_pal_jasc(FILE *stream,
×
2352
                             unsigned char const *palette,
2353
                             int exported_colors)
2354
{
2355
    int index;
2356

2357
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2358
        return SIXEL_BAD_ARGUMENT;
×
2359
    }
2360
    if (fprintf(stream, "JASC-PAL\n0100\n%d\n", exported_colors) < 0) {
×
2361
        return SIXEL_LIBC_ERROR;
×
2362
    }
2363
    for (index = 0; index < exported_colors; ++index) {
×
2364
        if (fprintf(stream, "%d %d %d\n",
×
2365
                    (int)palette[index * 3 + 0],
×
2366
                    (int)palette[index * 3 + 1],
×
2367
                    (int)palette[index * 3 + 2]) < 0) {
×
2368
            return SIXEL_LIBC_ERROR;
×
2369
        }
2370
    }
2371
    return SIXEL_OK;
×
2372
}
2373

2374

2375
static SIXELSTATUS
2376
sixel_palette_write_pal_riff(FILE *stream,
×
2377
                             unsigned char const *palette,
2378
                             int exported_colors)
2379
{
2380
    unsigned char header[12];
2381
    unsigned char chunk[8];
2382
    unsigned char log_palette[4 + 256 * 4];
2383
    unsigned int data_size;
2384
    unsigned int riff_size;
2385
    int index;
2386

2387
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2388
        return SIXEL_BAD_ARGUMENT;
×
2389
    }
2390
    if (exported_colors > 256) {
×
2391
        exported_colors = 256;
×
2392
    }
2393

2394
    data_size = 4u + (unsigned int)exported_colors * 4u;
×
2395
    riff_size = 4u + 8u + data_size;
×
2396

2397
    memcpy(header, "RIFF", 4);
×
2398
    header[4] = (unsigned char)(riff_size & 0xffu);
×
2399
    header[5] = (unsigned char)((riff_size >> 8) & 0xffu);
×
2400
    header[6] = (unsigned char)((riff_size >> 16) & 0xffu);
×
2401
    header[7] = (unsigned char)((riff_size >> 24) & 0xffu);
×
2402
    memcpy(header + 8, "PAL ", 4);
×
2403

2404
    memcpy(chunk, "data", 4);
×
2405
    chunk[4] = (unsigned char)(data_size & 0xffu);
×
2406
    chunk[5] = (unsigned char)((data_size >> 8) & 0xffu);
×
2407
    chunk[6] = (unsigned char)((data_size >> 16) & 0xffu);
×
2408
    chunk[7] = (unsigned char)((data_size >> 24) & 0xffu);
×
2409

2410
    memset(log_palette, 0, sizeof(log_palette));
×
2411
    log_palette[0] = 0x00;
×
2412
    log_palette[1] = 0x03;
×
2413
    log_palette[2] = (unsigned char)(exported_colors & 0xff);
×
2414
    log_palette[3] = (unsigned char)((exported_colors >> 8) & 0xff);
×
2415
    for (index = 0; index < exported_colors; ++index) {
×
2416
        log_palette[4 + index * 4 + 0] = palette[index * 3 + 0];
×
2417
        log_palette[4 + index * 4 + 1] = palette[index * 3 + 1];
×
2418
        log_palette[4 + index * 4 + 2] = palette[index * 3 + 2];
×
2419
        log_palette[4 + index * 4 + 3] = 0u;
×
2420
    }
2421

2422
    if (fwrite(header, 1, sizeof(header), stream)
×
2423
            != sizeof(header)) {
2424
        return SIXEL_LIBC_ERROR;
×
2425
    }
2426
    if (fwrite(chunk, 1, sizeof(chunk), stream) != sizeof(chunk)) {
×
2427
        return SIXEL_LIBC_ERROR;
×
2428
    }
2429
    if (fwrite(log_palette, 1, (size_t)data_size, stream)
×
2430
            != (size_t)data_size) {
×
2431
        return SIXEL_LIBC_ERROR;
×
2432
    }
2433
    return SIXEL_OK;
×
2434
}
2435

2436

2437
static SIXELSTATUS
2438
sixel_palette_write_gpl(FILE *stream,
×
2439
                        unsigned char const *palette,
2440
                        int exported_colors)
2441
{
2442
    int index;
2443

2444
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2445
        return SIXEL_BAD_ARGUMENT;
×
2446
    }
2447
    if (fprintf(stream, "GIMP Palette\n") < 0) {
×
2448
        return SIXEL_LIBC_ERROR;
×
2449
    }
2450
    if (fprintf(stream, "Name: libsixel export\n") < 0) {
×
2451
        return SIXEL_LIBC_ERROR;
×
2452
    }
2453
    if (fprintf(stream, "Columns: 16\n") < 0) {
×
2454
        return SIXEL_LIBC_ERROR;
×
2455
    }
2456
    if (fprintf(stream, "# Exported by libsixel\n") < 0) {
×
2457
        return SIXEL_LIBC_ERROR;
×
2458
    }
2459
    for (index = 0; index < exported_colors; ++index) {
×
2460
        if (fprintf(stream, "%3d %3d %3d\tIndex %d\n",
×
2461
                    (int)palette[index * 3 + 0],
×
2462
                    (int)palette[index * 3 + 1],
×
2463
                    (int)palette[index * 3 + 2],
×
2464
                    index) < 0) {
2465
            return SIXEL_LIBC_ERROR;
×
2466
        }
2467
    }
2468
    return SIXEL_OK;
×
2469
}
2470

2471

2472
/* create palette from specified map file */
2473
static SIXELSTATUS
2474
sixel_prepare_specified_palette(
21✔
2475
    sixel_dither_t  /* out */   **dither,
2476
    sixel_encoder_t /* in */    *encoder)
2477
{
2478
    SIXELSTATUS status;
2479
    sixel_callback_context_for_mapfile_t callback_context;
2480
    sixel_loader_t *loader;
2481
    int fstatic;
2482
    int fuse_palette;
2483
    int reqcolors;
2484
    int loop_override;
2485
    char const *path;
2486
    sixel_palette_format_t format_hint;
2487
    sixel_palette_format_t format_ext;
2488
    sixel_palette_format_t format_final;
2489
    sixel_palette_format_t format_detected;
2490
    FILE *stream;
2491
    int close_stream;
2492
    unsigned char *buffer;
2493
    size_t buffer_size;
2494
    int palette_request;
2495
    int need_detection;
2496
    int treat_as_image;
2497
    int path_has_extension;
2498

2499
    status = SIXEL_FALSE;
21✔
2500
    loader = NULL;
21✔
2501
    fstatic = 1;
21✔
2502
    fuse_palette = 1;
21✔
2503
    reqcolors = SIXEL_PALETTE_MAX;
21✔
2504
    loop_override = SIXEL_LOOP_DISABLE;
21✔
2505
    path = NULL;
21✔
2506
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
2507
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
21✔
2508
    format_final = SIXEL_PALETTE_FORMAT_NONE;
21✔
2509
    format_detected = SIXEL_PALETTE_FORMAT_NONE;
21✔
2510
    stream = NULL;
21✔
2511
    close_stream = 0;
21✔
2512
    buffer = NULL;
21✔
2513
    buffer_size = 0u;
21✔
2514
    palette_request = 0;
21✔
2515
    need_detection = 0;
21✔
2516
    treat_as_image = 0;
21✔
2517
    path_has_extension = 0;
21✔
2518

2519
    if (dither == NULL || encoder == NULL || encoder->mapfile == NULL) {
21!
2520
        sixel_helper_set_additional_message(
×
2521
            "sixel_prepare_specified_palette: invalid mapfile path.");
2522
        return SIXEL_BAD_ARGUMENT;
×
2523
    }
2524

2525
    path = sixel_palette_strip_prefix(encoder->mapfile, &format_hint);
21✔
2526
    if (path == NULL || *path == '\0') {
21!
2527
        sixel_helper_set_additional_message(
×
2528
            "sixel_prepare_specified_palette: empty mapfile path.");
2529
        return SIXEL_BAD_ARGUMENT;
×
2530
    }
2531

2532
    format_ext = sixel_palette_format_from_extension(path);
21✔
2533
    path_has_extension = sixel_path_has_any_extension(path);
21✔
2534

2535
    if (format_hint != SIXEL_PALETTE_FORMAT_NONE) {
21!
2536
        palette_request = 1;
×
2537
        format_final = format_hint;
×
2538
    } else if (format_ext != SIXEL_PALETTE_FORMAT_NONE) {
21!
2539
        palette_request = 1;
×
2540
        format_final = format_ext;
×
2541
    } else if (!path_has_extension) {
21!
2542
        palette_request = 1;
×
2543
        need_detection = 1;
×
2544
    } else {
2545
        treat_as_image = 1;
21✔
2546
    }
2547

2548
    if (palette_request) {
21!
2549
        status = sixel_palette_open_read(path, &stream, &close_stream);
×
2550
        if (SIXEL_FAILED(status)) {
×
2551
            goto palette_cleanup;
×
2552
        }
2553
        status = sixel_palette_read_stream(stream,
×
2554
                                           encoder->allocator,
2555
                                           &buffer,
2556
                                           &buffer_size);
2557
        if (close_stream) {
×
2558
            sixel_palette_close_stream(stream, close_stream);
×
2559
            stream = NULL;
×
2560
            close_stream = 0;
×
2561
        }
2562
        if (SIXEL_FAILED(status)) {
×
2563
            goto palette_cleanup;
×
2564
        }
2565
        if (buffer_size == 0u) {
×
2566
            sixel_helper_set_additional_message(
×
2567
                "sixel_prepare_specified_palette: mapfile is empty.");
2568
            status = SIXEL_BAD_INPUT;
×
2569
            goto palette_cleanup;
×
2570
        }
2571

2572
        if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
2573
            format_detected = sixel_palette_guess_format(buffer,
×
2574
                                                         buffer_size);
2575
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
2576
                sixel_helper_set_additional_message(
×
2577
                    "sixel_prepare_specified_palette: "
2578
                    "unable to detect palette format.");
2579
                status = SIXEL_BAD_INPUT;
×
2580
                goto palette_cleanup;
×
2581
            }
2582
            format_final = format_detected;
×
2583
        } else if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
2584
            format_detected = sixel_palette_guess_format(buffer,
×
2585
                                                         buffer_size);
2586
            if (format_detected == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
2587
                    format_detected == SIXEL_PALETTE_FORMAT_PAL_RIFF) {
2588
                format_final = format_detected;
×
2589
            } else {
2590
                sixel_helper_set_additional_message(
×
2591
                    "sixel_prepare_specified_palette: "
2592
                    "ambiguous .pal content.");
2593
                status = SIXEL_BAD_INPUT;
×
2594
                goto palette_cleanup;
×
2595
            }
2596
        } else if (need_detection) {
×
2597
            format_detected = sixel_palette_guess_format(buffer,
×
2598
                                                         buffer_size);
2599
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
2600
                sixel_helper_set_additional_message(
×
2601
                    "sixel_prepare_specified_palette: "
2602
                    "unable to detect palette format.");
2603
                status = SIXEL_BAD_INPUT;
×
2604
                goto palette_cleanup;
×
2605
            }
2606
            format_final = format_detected;
×
2607
        }
2608

2609
        switch (format_final) {
×
2610
        case SIXEL_PALETTE_FORMAT_ACT:
2611
            status = sixel_palette_parse_act(buffer,
×
2612
                                             buffer_size,
2613
                                             encoder,
2614
                                             dither);
2615
            break;
×
2616
        case SIXEL_PALETTE_FORMAT_PAL_JASC:
2617
            status = sixel_palette_parse_pal_jasc(buffer,
×
2618
                                                  buffer_size,
2619
                                                  encoder,
2620
                                                  dither);
2621
            break;
×
2622
        case SIXEL_PALETTE_FORMAT_PAL_RIFF:
2623
            status = sixel_palette_parse_pal_riff(buffer,
×
2624
                                                  buffer_size,
2625
                                                  encoder,
2626
                                                  dither);
2627
            break;
×
2628
        case SIXEL_PALETTE_FORMAT_GPL:
2629
            status = sixel_palette_parse_gpl(buffer,
×
2630
                                             buffer_size,
2631
                                             encoder,
2632
                                             dither);
2633
            break;
×
2634
        default:
2635
            sixel_helper_set_additional_message(
×
2636
                "sixel_prepare_specified_palette: "
2637
                "unsupported palette format.");
2638
            status = SIXEL_BAD_INPUT;
×
2639
            break;
×
2640
        }
2641

2642
palette_cleanup:
2643
        if (buffer != NULL) {
×
2644
            sixel_allocator_free(encoder->allocator, buffer);
×
2645
            buffer = NULL;
×
2646
        }
2647
        if (stream != NULL) {
×
2648
            sixel_palette_close_stream(stream, close_stream);
×
2649
            stream = NULL;
×
2650
        }
2651
        if (SIXEL_SUCCEEDED(status)) {
×
2652
            return status;
×
2653
        }
2654
        if (!treat_as_image) {
×
2655
            return status;
×
2656
        }
2657
    }
2658

2659
    callback_context.reqcolors = encoder->reqcolors;
21✔
2660
    callback_context.dither = NULL;
21✔
2661
    callback_context.allocator = encoder->allocator;
21✔
2662
    callback_context.working_colorspace = encoder->working_colorspace;
21✔
2663
    callback_context.lut_policy = encoder->lut_policy;
21✔
2664

2665
    sixel_helper_set_loader_trace(encoder->verbose);
21✔
2666
    sixel_helper_set_thumbnail_size_hint(
21✔
2667
        sixel_encoder_thumbnail_hint(encoder));
7✔
2668
    status = sixel_loader_new(&loader, encoder->allocator);
21✔
2669
    if (SIXEL_FAILED(status)) {
21!
2670
        goto end_loader;
×
2671
    }
2672

2673
    status = sixel_loader_setopt(loader,
21✔
2674
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
2675
                                 &fstatic);
2676
    if (SIXEL_FAILED(status)) {
21!
2677
        goto end_loader;
×
2678
    }
2679

2680
    status = sixel_loader_setopt(loader,
21✔
2681
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
2682
                                 &fuse_palette);
2683
    if (SIXEL_FAILED(status)) {
21!
2684
        goto end_loader;
×
2685
    }
2686

2687
    status = sixel_loader_setopt(loader,
21✔
2688
                                 SIXEL_LOADER_OPTION_REQCOLORS,
2689
                                 &reqcolors);
2690
    if (SIXEL_FAILED(status)) {
21!
2691
        goto end_loader;
×
2692
    }
2693

2694
    status = sixel_loader_setopt(loader,
28✔
2695
                                 SIXEL_LOADER_OPTION_BGCOLOR,
2696
                                 encoder->bgcolor);
21✔
2697
    if (SIXEL_FAILED(status)) {
21!
2698
        goto end_loader;
×
2699
    }
2700

2701
    status = sixel_loader_setopt(loader,
21✔
2702
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
2703
                                 &loop_override);
2704
    if (SIXEL_FAILED(status)) {
21!
2705
        goto end_loader;
×
2706
    }
2707

2708
    status = sixel_loader_setopt(loader,
28✔
2709
                                 SIXEL_LOADER_OPTION_INSECURE,
2710
                                 &encoder->finsecure);
21✔
2711
    if (SIXEL_FAILED(status)) {
21!
2712
        goto end_loader;
×
2713
    }
2714

2715
    status = sixel_loader_setopt(loader,
28✔
2716
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
2717
                                 encoder->cancel_flag);
21✔
2718
    if (SIXEL_FAILED(status)) {
21!
2719
        goto end_loader;
×
2720
    }
2721

2722
    status = sixel_loader_setopt(loader,
28✔
2723
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
2724
                                 encoder->loader_order);
21✔
2725
    if (SIXEL_FAILED(status)) {
21!
2726
        goto end_loader;
×
2727
    }
2728

2729
    status = sixel_loader_setopt(loader,
21✔
2730
                                 SIXEL_LOADER_OPTION_CONTEXT,
2731
                                 &callback_context);
2732
    if (SIXEL_FAILED(status)) {
21!
2733
        goto end_loader;
×
2734
    }
2735

2736
    status = sixel_loader_load_file(loader,
28✔
2737
                                    encoder->mapfile,
21✔
2738
                                    load_image_callback_for_palette);
2739
    if (status != SIXEL_OK) {
21!
2740
        goto end_loader;
×
2741
    }
2742

2743
end_loader:
14✔
2744
    sixel_loader_unref(loader);
21✔
2745

2746
    if (status != SIXEL_OK) {
21!
2747
        return status;
×
2748
    }
2749

2750
    if (! callback_context.dither) {
21!
2751
        sixel_helper_set_additional_message(
×
2752
            "sixel_prepare_specified_palette() failed.\n"
2753
            "reason: mapfile is empty.");
2754
        return SIXEL_BAD_INPUT;
×
2755
    }
2756

2757
    *dither = callback_context.dither;
21✔
2758

2759
    return status;
21✔
2760
}
7✔
2761

2762

2763
/* create dither object from a frame */
2764
static SIXELSTATUS
2765
sixel_encoder_prepare_palette(
529✔
2766
    sixel_encoder_t *encoder,  /* encoder object */
2767
    sixel_frame_t   *frame,    /* input frame object */
2768
    sixel_dither_t  **dither)  /* dither object to be created from the frame */
2769
{
2770
    SIXELSTATUS status = SIXEL_FALSE;
529✔
2771
    int histogram_colors;
2772
    sixel_assessment_t *assessment;
2773
    int promoted_stage;
2774

2775
    assessment = NULL;
529✔
2776
    promoted_stage = 0;
529✔
2777
    if (encoder != NULL) {
529!
2778
        assessment = encoder->assessment_observer;
529✔
2779
    }
183✔
2780

2781
    switch (encoder->color_option) {
529!
2782
    case SIXEL_COLOR_OPTION_HIGHCOLOR:
24✔
2783
        if (encoder->dither_cache) {
36!
2784
            *dither = encoder->dither_cache;
×
2785
            status = SIXEL_OK;
×
2786
        } else {
2787
            status = sixel_dither_new(dither, (-1), encoder->allocator);
36✔
2788
            sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
36✔
2789
        }
2790
        goto end;
36✔
2791
    case SIXEL_COLOR_OPTION_MONOCHROME:
8✔
2792
        if (encoder->dither_cache) {
12!
2793
            *dither = encoder->dither_cache;
×
2794
            status = SIXEL_OK;
×
2795
        } else {
2796
            status = sixel_prepare_monochrome_palette(dither, encoder->finvert);
12✔
2797
        }
2798
        goto end;
12✔
2799
    case SIXEL_COLOR_OPTION_MAPFILE:
14✔
2800
        if (encoder->dither_cache) {
21!
2801
            *dither = encoder->dither_cache;
×
2802
            status = SIXEL_OK;
×
2803
        } else {
2804
            status = sixel_prepare_specified_palette(dither, encoder);
21✔
2805
        }
2806
        goto end;
21✔
2807
    case SIXEL_COLOR_OPTION_BUILTIN:
18✔
2808
        if (encoder->dither_cache) {
27!
2809
            *dither = encoder->dither_cache;
×
2810
            status = SIXEL_OK;
×
2811
        } else {
2812
            status = sixel_prepare_builtin_palette(dither, encoder->builtin_palette);
27✔
2813
        }
2814
        goto end;
27✔
2815
    case SIXEL_COLOR_OPTION_DEFAULT:
433✔
2816
    default:
2817
        break;
433✔
2818
    }
2819

2820
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE) {
433✔
2821
        if (!sixel_frame_get_palette(frame)) {
191!
2822
            status = SIXEL_LOGIC_ERROR;
×
2823
            goto end;
×
2824
        }
2825
        status = sixel_dither_new(dither, sixel_frame_get_ncolors(frame),
230✔
2826
                                  encoder->allocator);
39✔
2827
        if (SIXEL_FAILED(status)) {
191!
2828
            goto end;
×
2829
        }
2830
        sixel_dither_set_palette(*dither, sixel_frame_get_palette(frame));
191✔
2831
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
191✔
2832
        if (sixel_frame_get_transparent(frame) != (-1)) {
191!
2833
            sixel_dither_set_transparent(*dither, sixel_frame_get_transparent(frame));
×
2834
        }
2835
        if (*dither && encoder->dither_cache) {
191!
2836
            sixel_dither_unref(encoder->dither_cache);
×
2837
        }
2838
        goto end;
191✔
2839
    }
2840

2841
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_GRAYSCALE) {
242!
2842
        switch (sixel_frame_get_pixelformat(frame)) {
×
2843
        case SIXEL_PIXELFORMAT_G1:
2844
            *dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
2845
            break;
×
2846
        case SIXEL_PIXELFORMAT_G2:
2847
            *dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
2848
            break;
×
2849
        case SIXEL_PIXELFORMAT_G4:
2850
            *dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
2851
            break;
×
2852
        case SIXEL_PIXELFORMAT_G8:
2853
            *dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
2854
            break;
×
2855
        default:
2856
            *dither = NULL;
×
2857
            status = SIXEL_LOGIC_ERROR;
×
2858
            goto end;
×
2859
        }
2860
        if (*dither && encoder->dither_cache) {
×
2861
            sixel_dither_unref(encoder->dither_cache);
×
2862
        }
2863
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
×
2864
        status = SIXEL_OK;
×
2865
        goto end;
×
2866
    }
2867

2868
    if (encoder->dither_cache) {
242!
2869
        sixel_dither_unref(encoder->dither_cache);
×
2870
    }
2871
    status = sixel_dither_new(dither, encoder->reqcolors, encoder->allocator);
242✔
2872
    if (SIXEL_FAILED(status)) {
242!
2873
        goto end;
×
2874
    }
2875

2876
    sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
242✔
2877
    sixel_dither_set_sixel_reversible(*dither,
354✔
2878
                                      encoder->sixel_reversible);
112✔
2879
    (*dither)->quantize_model = encoder->quantize_model;
242✔
2880

2881
    status = sixel_dither_initialize(*dither,
354✔
2882
                                     sixel_frame_get_pixels(frame),
112✔
2883
                                     sixel_frame_get_width(frame),
112✔
2884
                                     sixel_frame_get_height(frame),
112✔
2885
                                     sixel_frame_get_pixelformat(frame),
112✔
2886
                                     encoder->method_for_largest,
112✔
2887
                                     encoder->method_for_rep,
112✔
2888
                                     encoder->quality_mode);
112✔
2889
    if (SIXEL_FAILED(status)) {
242!
2890
        sixel_dither_unref(*dither);
×
2891
        goto end;
×
2892
    }
2893

2894
    if (assessment != NULL && promoted_stage == 0) {
242!
2895
        sixel_assessment_stage_transition(
3✔
2896
            assessment,
1✔
2897
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2898
        promoted_stage = 1;
3✔
2899
    }
1✔
2900

2901
    histogram_colors = sixel_dither_get_num_of_histogram_colors(*dither);
242✔
2902
    if (histogram_colors <= encoder->reqcolors) {
242✔
2903
        encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
173✔
2904
    }
89✔
2905
    sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
242✔
2906

2907
    status = SIXEL_OK;
242✔
2908

2909
end:
346✔
2910
    if (assessment != NULL && promoted_stage == 0) {
529!
2911
        sixel_assessment_stage_transition(
×
2912
            assessment,
2913
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2914
        promoted_stage = 1;
×
2915
    }
2916
    if (SIXEL_SUCCEEDED(status) && dither != NULL && *dither != NULL) {
529!
2917
        sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
529✔
2918
        /* pass down the user's demand for an exact palette size */
2919
        (*dither)->force_palette = encoder->force_palette;
529✔
2920
    }
183✔
2921
    return status;
529✔
2922
}
2923

2924

2925
/* resize a frame with settings of specified encoder object */
2926
static SIXELSTATUS
2927
sixel_encoder_do_resize(
536✔
2928
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
2929
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
2930
{
2931
    SIXELSTATUS status = SIXEL_FALSE;
536✔
2932
    int src_width;
2933
    int src_height;
2934
    int dst_width;
2935
    int dst_height;
2936

2937
    /* get frame width and height */
2938
    src_width = sixel_frame_get_width(frame);
536✔
2939
    src_height = sixel_frame_get_height(frame);
536✔
2940

2941
    if (src_width < 1) {
536✔
2942
         sixel_helper_set_additional_message(
6✔
2943
             "sixel_encoder_do_resize: "
2944
             "detected a frame with a non-positive width.");
2945
        return SIXEL_BAD_ARGUMENT;
6✔
2946
    }
2947

2948
    if (src_height < 1) {
530!
2949
         sixel_helper_set_additional_message(
×
2950
             "sixel_encoder_do_resize: "
2951
             "detected a frame with a non-positive height.");
2952
        return SIXEL_BAD_ARGUMENT;
×
2953
    }
2954

2955
    /* settings around scaling */
2956
    dst_width = encoder->pixelwidth;    /* may be -1 (default) */
530✔
2957
    dst_height = encoder->pixelheight;  /* may be -1 (default) */
530✔
2958

2959
    /* if the encoder has percentwidth or percentheight property,
2960
       convert them to pixelwidth / pixelheight */
2961
    if (encoder->percentwidth > 0) {
530✔
2962
        dst_width = src_width * encoder->percentwidth / 100;
12✔
2963
    }
4✔
2964
    if (encoder->percentheight > 0) {
530✔
2965
        dst_height = src_height * encoder->percentheight / 100;
9✔
2966
    }
3✔
2967

2968
    /* if only either width or height is set, set also the other
2969
       to retain frame aspect ratio */
2970
    if (dst_width > 0 && dst_height <= 0) {
530✔
2971
        dst_height = src_height * dst_width / src_width;
42✔
2972
    }
14✔
2973
    if (dst_height > 0 && dst_width <= 0) {
530✔
2974
        dst_width = src_width * dst_height / src_height;
33✔
2975
    }
11✔
2976

2977
    /* do resize */
2978
    if (dst_width > 0 && dst_height > 0) {
530!
2979
        status = sixel_frame_resize(frame, dst_width, dst_height,
124✔
2980
                                    encoder->method_for_resampling);
31✔
2981
        if (SIXEL_FAILED(status)) {
93!
2982
            goto end;
×
2983
        }
2984
    }
31✔
2985

2986
    /* success */
2987
    status = SIXEL_OK;
530✔
2988

2989
end:
346✔
2990
    return status;
530✔
2991
}
186✔
2992

2993

2994
/* clip a frame with settings of specified encoder object */
2995
static SIXELSTATUS
2996
sixel_encoder_do_clip(
532✔
2997
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
2998
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
2999
{
3000
    SIXELSTATUS status = SIXEL_FALSE;
532✔
3001
    int src_width;
3002
    int src_height;
3003
    int clip_x;
3004
    int clip_y;
3005
    int clip_w;
3006
    int clip_h;
3007

3008
    /* get frame width and height */
3009
    src_width = sixel_frame_get_width(frame);
532✔
3010
    src_height = sixel_frame_get_height(frame);
532✔
3011

3012
    /* settings around clipping */
3013
    clip_x = encoder->clipx;
532✔
3014
    clip_y = encoder->clipy;
532✔
3015
    clip_w = encoder->clipwidth;
532✔
3016
    clip_h = encoder->clipheight;
532✔
3017

3018
    /* adjust clipping width with comparing it to frame width */
3019
    if (clip_w + clip_x > src_width) {
532✔
3020
        if (clip_x > src_width) {
7✔
3021
            clip_w = 0;
3✔
3022
        } else {
1✔
3023
            clip_w = src_width - clip_x;
4✔
3024
        }
3025
    }
3✔
3026

3027
    /* adjust clipping height with comparing it to frame height */
3028
    if (clip_h + clip_y > src_height) {
532✔
3029
        if (clip_y > src_height) {
6✔
3030
            clip_h = 0;
3✔
3031
        } else {
1✔
3032
            clip_h = src_height - clip_y;
3✔
3033
        }
3034
    }
2✔
3035

3036
    /* do clipping */
3037
    if (clip_w > 0 && clip_h > 0) {
532!
3038
        status = sixel_frame_clip(frame, clip_x, clip_y, clip_w, clip_h);
15✔
3039
        if (SIXEL_FAILED(status)) {
15!
3040
            goto end;
3✔
3041
        }
3042
    }
4✔
3043

3044
    /* success */
3045
    status = SIXEL_OK;
529✔
3046

3047
end:
346✔
3048
    return status;
532✔
3049
}
3050

3051

3052
static void
3053
sixel_debug_print_palette(
3✔
3054
    sixel_dither_t /* in */ *dither /* dithering object */
3055
)
3056
{
3057
    unsigned char *palette;
3058
    int i;
3059

3060
    palette = sixel_dither_get_palette(dither);
3✔
3061
    fprintf(stderr, "palette:\n");
3✔
3062
    for (i = 0; i < sixel_dither_get_num_of_palette_colors(dither); ++i) {
51✔
3063
        fprintf(stderr, "%d: #%02x%02x%02x\n", i,
64✔
3064
                palette[i * 3 + 0],
48✔
3065
                palette[i * 3 + 1],
48✔
3066
                palette[i * 3 + 2]);
48✔
3067
    }
16✔
3068
}
3✔
3069

3070

3071
static SIXELSTATUS
3072
sixel_encoder_output_without_macro(
436✔
3073
    sixel_frame_t       /* in */ *frame,
3074
    sixel_dither_t      /* in */ *dither,
3075
    sixel_output_t      /* in */ *output,
3076
    sixel_encoder_t     /* in */ *encoder)
3077
{
3078
    SIXELSTATUS status = SIXEL_OK;
436✔
3079
    static unsigned char *p;
3080
    int depth;
3081
    enum { message_buffer_size = 2048 };
3082
    char message[message_buffer_size];
3083
    int nwrite;
3084
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3085
    int dulation;
3086
    int delay;
3087
    struct timespec tv;
3088
#endif
3089
    unsigned char *pixbuf;
3090
    int width;
3091
    int height;
3092
    int pixelformat = 0;
436✔
3093
    size_t size;
3094
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
436✔
3095
    palette_conversion_t palette_ctx;
3096

3097
    memset(&palette_ctx, 0, sizeof(palette_ctx));
436✔
3098
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3099
    sixel_clock_t last_clock;
3100
#endif
3101

3102
    if (encoder == NULL) {
436!
3103
        sixel_helper_set_additional_message(
×
3104
            "sixel_encoder_output_without_macro: encoder object is null.");
3105
        status = SIXEL_BAD_ARGUMENT;
×
3106
        goto end;
×
3107
    }
3108

3109
    if (encoder->assessment_observer != NULL) {
436✔
3110
        sixel_assessment_stage_transition(
3✔
3111
            encoder->assessment_observer,
3✔
3112
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3113
    }
1✔
3114

3115
    if (encoder->color_option == SIXEL_COLOR_OPTION_DEFAULT) {
436✔
3116
        if (encoder->force_palette) {
340!
3117
            /* keep every slot when the user forced the palette size */
3118
            sixel_dither_set_optimize_palette(dither, 0);
×
3119
        } else {
3120
            sixel_dither_set_optimize_palette(dither, 1);
340✔
3121
        }
3122
    }
114✔
3123

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

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

3200
    pixbuf = sixel_frame_get_pixels(frame);
436✔
3201
    memcpy(p, pixbuf, (size_t)(width * height * depth));
436✔
3202

3203
    status = sixel_output_convert_colorspace(output, p, size);
436✔
3204
    if (SIXEL_FAILED(status)) {
436!
3205
        goto end;
×
3206
    }
3207

3208
    if (encoder->cancel_flag && *encoder->cancel_flag) {
436!
3209
        goto end;
×
3210
    }
3211

3212
    status = sixel_encoder_convert_palette(encoder,
582✔
3213
                                           output,
146✔
3214
                                           dither,
146✔
3215
                                           frame_colorspace,
146✔
3216
                                           pixelformat,
146✔
3217
                                           &palette_ctx);
3218
    if (SIXEL_FAILED(status)) {
436!
3219
        goto end;
×
3220
    }
3221

3222
    if (encoder->capture_quantized) {
436✔
3223
        status = sixel_encoder_capture_quantized(encoder,
4✔
3224
                                                 dither,
1✔
3225
                                                 p,
1✔
3226
                                                 size,
1✔
3227
                                                 width,
1✔
3228
                                                 height,
1✔
3229
                                                 pixelformat,
1✔
3230
                                                 output->colorspace);
1✔
3231
        if (SIXEL_FAILED(status)) {
3!
3232
            goto end;
×
3233
        }
3234
    }
1✔
3235

3236
    if (encoder->assessment_observer != NULL) {
436✔
3237
        sixel_assessment_stage_transition(
3✔
3238
            encoder->assessment_observer,
3✔
3239
            SIXEL_ASSESSMENT_STAGE_ENCODE);
3240
    }
1✔
3241
    status = sixel_encode(p, width, height, depth, dither, output);
436✔
3242
    if (encoder->assessment_observer != NULL) {
436✔
3243
        sixel_assessment_stage_finish(encoder->assessment_observer);
3✔
3244
    }
1✔
3245
    if (status != SIXEL_OK) {
436!
3246
        goto end;
×
3247
    }
3248

3249
end:
290✔
3250
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
436✔
3251
    output->pixelformat = pixelformat;
436✔
3252
    output->source_colorspace = frame_colorspace;
436✔
3253
    sixel_allocator_free(encoder->allocator, p);
436✔
3254

3255
    return status;
436✔
3256
}
3257

3258

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

3292
    memset(&palette_ctx, 0, sizeof(palette_ctx));
93✔
3293

3294
    if (encoder != NULL && encoder->assessment_observer != NULL) {
93!
3295
        sixel_assessment_stage_transition(
×
3296
            encoder->assessment_observer,
×
3297
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3298
    }
3299

3300
#if defined(HAVE_CLOCK)
3301
    if (output->last_clock == 0) {
93!
3302
        output->last_clock = clock();
93✔
3303
    }
37✔
3304
#elif defined(HAVE_CLOCK_WIN)
3305
    if (output->last_clock == 0) {
3306
        output->last_clock = clock_win();
3307
    }
3308
#endif
3309

3310
    width = sixel_frame_get_width(frame);
93✔
3311
    height = sixel_frame_get_height(frame);
93✔
3312
    pixelformat = sixel_frame_get_pixelformat(frame);
93✔
3313
    depth = sixel_helper_compute_depth(pixelformat);
93✔
3314
    if (depth < 0) {
93!
3315
        status = SIXEL_LOGIC_ERROR;
×
3316
        sixel_helper_set_additional_message(
×
3317
            "sixel_encoder_output_with_macro: "
3318
            "sixel_helper_compute_depth() failed.");
3319
        goto end;
×
3320
    }
3321

3322
    frame_colorspace = sixel_frame_get_colorspace(frame);
93✔
3323
    size = (size_t)width * (size_t)height * (size_t)depth;
93✔
3324
    converted = (unsigned char *)sixel_allocator_malloc(
93✔
3325
        encoder->allocator, size);
37✔
3326
    if (converted == NULL) {
93!
3327
        sixel_helper_set_additional_message(
×
3328
            "sixel_encoder_output_with_macro: "
3329
            "sixel_allocator_malloc() failed.");
3330
        status = SIXEL_BAD_ALLOCATION;
×
3331
        goto end;
×
3332
    }
3333

3334
    memcpy(converted, sixel_frame_get_pixels(frame), size);
93✔
3335
    output->pixelformat = pixelformat;
93✔
3336
    output->source_colorspace = frame_colorspace;
93✔
3337
    output->colorspace = encoder->output_colorspace;
93✔
3338
    status = sixel_output_convert_colorspace(output, converted, size);
93✔
3339
    if (SIXEL_FAILED(status)) {
93!
3340
        goto end;
×
3341
    }
3342

3343
    status = sixel_encoder_convert_palette(encoder,
130✔
3344
                                           output,
37✔
3345
                                           dither,
37✔
3346
                                           frame_colorspace,
37✔
3347
                                           pixelformat,
37✔
3348
                                           &palette_ctx);
3349
    if (SIXEL_FAILED(status)) {
93!
3350
        goto end;
×
3351
    }
3352

3353
    if (sixel_frame_get_loop_no(frame) == 0) {
93✔
3354
        if (encoder->macro_number >= 0) {
55!
3355
            nwrite = sixel_compat_snprintf(
1✔
3356
                buffer,
1✔
3357
                sizeof(buffer),
3358
                "\033P%d;0;1!z",
3359
                encoder->macro_number);
1✔
3360
        } else {
1✔
3361
            nwrite = sixel_compat_snprintf(
54✔
3362
                buffer,
18✔
3363
                sizeof(buffer),
3364
                "\033P%d;0;1!z",
3365
                sixel_frame_get_frame_no(frame));
18✔
3366
        }
3367
        if (nwrite < 0) {
55!
3368
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3369
            sixel_helper_set_additional_message(
×
3370
                "sixel_encoder_output_with_macro: command format failed.");
3371
            goto end;
×
3372
        }
3373
        write_started_at = 0.0;
55✔
3374
        write_finished_at = 0.0;
55✔
3375
        write_duration = 0.0;
55✔
3376
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3377
            write_started_at = sixel_assessment_timer_now();
×
3378
        }
3379
        nwrite = sixel_write_callback(buffer,
74✔
3380
                                      (int)strlen(buffer),
55✔
3381
                                      &encoder->outfd);
55✔
3382
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3383
            write_finished_at = sixel_assessment_timer_now();
×
3384
            write_duration = write_finished_at - write_started_at;
×
3385
            if (write_duration < 0.0) {
×
3386
                write_duration = 0.0;
×
3387
            }
3388
        }
3389
        if (nwrite < 0) {
55!
3390
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3391
            sixel_helper_set_additional_message(
×
3392
                "sixel_encoder_output_with_macro: "
3393
                "sixel_write_callback() failed.");
3394
            goto end;
×
3395
        }
3396
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3397
            sixel_assessment_record_output_write(
×
3398
                encoder->assessment_observer,
×
3399
                (size_t)nwrite,
3400
                write_duration);
3401
        }
3402

3403
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3404
            sixel_assessment_stage_transition(
×
3405
                encoder->assessment_observer,
×
3406
                SIXEL_ASSESSMENT_STAGE_ENCODE);
3407
        }
3408
        status = sixel_encode(converted,
74✔
3409
                              width,
19✔
3410
                              height,
19✔
3411
                              depth,
19✔
3412
                              dither,
19✔
3413
                              output);
19✔
3414
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3415
            sixel_assessment_stage_finish(
×
3416
                encoder->assessment_observer);
×
3417
        }
3418
        if (SIXEL_FAILED(status)) {
55!
3419
            goto end;
×
3420
        }
3421

3422
        write_started_at = 0.0;
55✔
3423
        write_finished_at = 0.0;
55✔
3424
        write_duration = 0.0;
55✔
3425
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3426
            write_started_at = sixel_assessment_timer_now();
×
3427
        }
3428
        nwrite = sixel_write_callback("\033\\", 2, &encoder->outfd);
55✔
3429
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3430
            write_finished_at = sixel_assessment_timer_now();
×
3431
            write_duration = write_finished_at - write_started_at;
×
3432
            if (write_duration < 0.0) {
×
3433
                write_duration = 0.0;
×
3434
            }
3435
        }
3436
        if (nwrite < 0) {
55!
3437
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3438
            sixel_helper_set_additional_message(
×
3439
                "sixel_encoder_output_with_macro: "
3440
                "sixel_write_callback() failed.");
3441
            goto end;
×
3442
        }
3443
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3444
            sixel_assessment_record_output_write(
×
3445
                encoder->assessment_observer,
×
3446
                (size_t)nwrite,
3447
                write_duration);
3448
        }
3449
    }
19✔
3450
    if (encoder->macro_number < 0) {
129✔
3451
        nwrite = sixel_compat_snprintf(
90✔
3452
            buffer,
36✔
3453
            sizeof(buffer),
3454
            "\033[%d*z",
3455
            sixel_frame_get_frame_no(frame));
36✔
3456
        if (nwrite < 0) {
90!
3457
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3458
            sixel_helper_set_additional_message(
×
3459
                "sixel_encoder_output_with_macro: command format failed.");
3460
        }
3461
        write_started_at = 0.0;
90✔
3462
        write_finished_at = 0.0;
90✔
3463
        write_duration = 0.0;
90✔
3464
        if (encoder != NULL && encoder->assessment_observer != NULL) {
90!
3465
            write_started_at = sixel_assessment_timer_now();
×
3466
        }
3467
        nwrite = sixel_write_callback(buffer,
126✔
3468
                                      (int)strlen(buffer),
90✔
3469
                                      &encoder->outfd);
90✔
3470
        if (encoder != NULL && encoder->assessment_observer != NULL) {
90!
3471
            write_finished_at = sixel_assessment_timer_now();
×
3472
            write_duration = write_finished_at - write_started_at;
×
3473
            if (write_duration < 0.0) {
×
3474
                write_duration = 0.0;
×
3475
            }
3476
        }
3477
        if (nwrite < 0) {
90!
3478
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3479
            sixel_helper_set_additional_message(
×
3480
                "sixel_encoder_output_with_macro: "
3481
                "sixel_write_callback() failed.");
3482
            goto end;
×
3483
        }
3484
        if (encoder != NULL && encoder->assessment_observer != NULL) {
90!
3485
            sixel_assessment_record_output_write(
×
3486
                encoder->assessment_observer,
×
3487
                (size_t)nwrite,
3488
                write_duration);
3489
        }
3490
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3491
        delay = sixel_frame_get_delay(frame);
90✔
3492
        if (delay > 0 && !encoder->fignore_delay) {
90!
3493
# if defined(HAVE_CLOCK)
3494
            last_clock = clock();
63✔
3495
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
3496
#  if defined(__APPLE__)
3497
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
54✔
3498
                             / 100000);
27✔
3499
#  else
3500
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
36✔
3501
                             / CLOCKS_PER_SEC);
3502
#  endif
3503
            output->last_clock = last_clock;
63✔
3504
# elif defined(HAVE_CLOCK_WIN)
3505
            last_clock = clock_win();
3506
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3507
                             / CLOCKS_PER_SEC);
3508
            output->last_clock = last_clock;
3509
# else
3510
            dulation = 0;
3511
# endif
3512
            if (dulation < 1000 * 10 * delay) {
63!
3513
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3514
                tv.tv_sec = 0;
59✔
3515
                tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
59✔
3516
#  if defined(HAVE_NANOSLEEP)
3517
                nanosleep(&tv, NULL);
59✔
3518
#  else
3519
                nanosleep_win(&tv, NULL);
3520
#  endif
3521
# endif
3522
            }
23✔
3523
        }
27✔
3524
#endif
3525
    }
36✔
3526

3527
end:
20✔
3528
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
93✔
3529
    output->pixelformat = pixelformat;
93✔
3530
    output->source_colorspace = frame_colorspace;
93✔
3531
    sixel_allocator_free(encoder->allocator, converted);
93✔
3532

3533
    return status;
93✔
3534
}
3535

3536

3537
static SIXELSTATUS
3538
sixel_encoder_emit_iso2022_chars(
×
3539
    sixel_encoder_t *encoder,
3540
    sixel_frame_t *frame
3541
)
3542
{
3543
    char *buf_p, *buf;
3544
    int col, row;
3545
    int charset;
3546
    int is_96cs;
3547
    unsigned int charset_no;
3548
    unsigned int code;
3549
    int num_cols, num_rows;
3550
    SIXELSTATUS status;
3551
    size_t alloc_size;
3552
    int nwrite;
3553
    int target_fd;
3554
    int chunk_size;
3555

3556
    charset_no = encoder->drcs_charset_no;
×
3557
    if (charset_no == 0u) {
×
3558
        charset_no = 1u;
×
3559
    }
3560
    if (encoder->drcs_mmv == 0) {
×
3561
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3562
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3563
    } else if (encoder->drcs_mmv == 1) {
×
3564
        is_96cs = 0;
×
3565
        charset = (int)(charset_no + 0x3fu);
×
3566
    } else {
3567
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3568
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3569
    }
3570
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3571
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3572
             / encoder->cell_width;
×
3573
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3574
             / encoder->cell_height;
×
3575

3576
    /* cols x rows + designation(4 chars) + SI + SO + LFs */
3577
    alloc_size = num_cols * num_rows + (num_cols * num_rows / 96 + 1) * 4 + 2 + num_rows;
×
3578
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3579
    if (buf == NULL) {
×
3580
        sixel_helper_set_additional_message(
×
3581
            "sixel_encoder_emit_iso2022_chars: sixel_allocator_malloc() failed.");
3582
        status = SIXEL_BAD_ALLOCATION;
×
3583
        goto end;
×
3584
    }
3585

3586
    code = 0x20;
×
3587
    *(buf_p++) = '\016';  /* SI */
×
3588
    *(buf_p++) = '\033';
×
3589
    *(buf_p++) = ')';
×
3590
    *(buf_p++) = ' ';
×
3591
    *(buf_p++) = charset;
×
3592
    for(row = 0; row < num_rows; row++) {
×
3593
        for(col = 0; col < num_cols; col++) {
×
3594
            if ((code & 0x7f) == 0x0) {
×
3595
                if (charset == 0x7e) {
×
3596
                    is_96cs = 1 - is_96cs;
×
3597
                    charset = '0';
×
3598
                } else {
3599
                    charset++;
×
3600
                }
3601
                code = 0x20;
×
3602
                *(buf_p++) = '\033';
×
3603
                *(buf_p++) = is_96cs ? '-': ')';
×
3604
                *(buf_p++) = ' ';
×
3605
                *(buf_p++) = charset;
×
3606
            }
3607
            *(buf_p++) = code++;
×
3608
        }
3609
        *(buf_p++) = '\n';
×
3610
    }
3611
    *(buf_p++) = '\017';  /* SO */
×
3612

3613
    if (encoder->tile_outfd >= 0) {
×
3614
        target_fd = encoder->tile_outfd;
×
3615
    } else {
3616
        target_fd = encoder->outfd;
×
3617
    }
3618

3619
    chunk_size = (int)(buf_p - buf);
×
3620
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3621
                                          buf,
3622
                                          chunk_size,
3623
                                          target_fd);
3624
    if (nwrite != chunk_size) {
×
3625
        sixel_helper_set_additional_message(
×
3626
            "sixel_encoder_emit_iso2022_chars: write() failed.");
3627
        status = SIXEL_RUNTIME_ERROR;
×
3628
        goto end;
×
3629
    }
3630

3631
    sixel_allocator_free(encoder->allocator, buf);
×
3632

3633
    status = SIXEL_OK;
×
3634

3635
end:
3636
    return status;
×
3637
}
3638

3639

3640
/*
3641
 * This routine is derived from mlterm's drcssixel.c
3642
 * (https://raw.githubusercontent.com/arakiken/mlterm/master/drcssixel/drcssixel.c).
3643
 * The original implementation is credited to Araki Ken.
3644
 * Adjusted here to integrate with libsixel's encoder pipeline.
3645
 */
3646
static SIXELSTATUS
3647
sixel_encoder_emit_drcsmmv2_chars(
×
3648
    sixel_encoder_t *encoder,
3649
    sixel_frame_t *frame
3650
)
3651
{
3652
    char *buf_p, *buf;
3653
    int col, row;
3654
    int charset;
3655
    int is_96cs;
3656
    unsigned int charset_no;
3657
    unsigned int code;
3658
    int num_cols, num_rows;
3659
    SIXELSTATUS status;
3660
    size_t alloc_size;
3661
    int nwrite;
3662
    int target_fd;
3663
    int chunk_size;
3664

3665
    charset_no = encoder->drcs_charset_no;
×
3666
    if (charset_no == 0u) {
×
3667
        charset_no = 1u;
×
3668
    }
3669
    if (encoder->drcs_mmv == 0) {
×
3670
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3671
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3672
    } else if (encoder->drcs_mmv == 1) {
×
3673
        is_96cs = 0;
×
3674
        charset = (int)(charset_no + 0x3fu);
×
3675
    } else {
3676
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3677
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3678
    }
3679
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3680
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3681
             / encoder->cell_width;
×
3682
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3683
             / encoder->cell_height;
×
3684

3685
    /* cols x rows x 4(out of BMP) + rows(LFs) */
3686
    alloc_size = num_cols * num_rows * 4 + num_rows;
×
3687
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3688
    if (buf == NULL) {
×
3689
        sixel_helper_set_additional_message(
×
3690
            "sixel_encoder_emit_drcsmmv2_chars: sixel_allocator_malloc() failed.");
3691
        status = SIXEL_BAD_ALLOCATION;
×
3692
        goto end;
×
3693
    }
3694

3695
    for(row = 0; row < num_rows; row++) {
×
3696
        for(col = 0; col < num_cols; col++) {
×
3697
            *(buf_p++) = ((code >> 18) & 0x07) | 0xf0;
×
3698
            *(buf_p++) = ((code >> 12) & 0x3f) | 0x80;
×
3699
            *(buf_p++) = ((code >> 6) & 0x3f) | 0x80;
×
3700
            *(buf_p++) = (code & 0x3f) | 0x80;
×
3701
            code++;
×
3702
            if ((code & 0x7f) == 0x0) {
×
3703
                if (charset == 0x7e) {
×
3704
                    is_96cs = 1 - is_96cs;
×
3705
                    charset = '0';
×
3706
                } else {
3707
                    charset++;
×
3708
                }
3709
                code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3710
            }
3711
        }
3712
        *(buf_p++) = '\n';
×
3713
    }
3714

3715
    if (charset == 0x7e) {
×
3716
        is_96cs = 1 - is_96cs;
×
3717
    } else {
3718
        charset = '0';
×
3719
        charset++;
×
3720
    }
3721
    if (encoder->tile_outfd >= 0) {
×
3722
        target_fd = encoder->tile_outfd;
×
3723
    } else {
3724
        target_fd = encoder->outfd;
×
3725
    }
3726

3727
    chunk_size = (int)(buf_p - buf);
×
3728
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3729
                                          buf,
3730
                                          chunk_size,
3731
                                          target_fd);
3732
    if (nwrite != chunk_size) {
×
3733
        sixel_helper_set_additional_message(
×
3734
            "sixel_encoder_emit_drcsmmv2_chars: write() failed.");
3735
        status = SIXEL_RUNTIME_ERROR;
×
3736
        goto end;
×
3737
    }
3738

3739
    sixel_allocator_free(encoder->allocator, buf);
×
3740

3741
    status = SIXEL_OK;
×
3742

3743
end:
3744
    return status;
×
3745
}
3746

3747
static SIXELSTATUS
3748
sixel_encoder_encode_frame(
538✔
3749
    sixel_encoder_t *encoder,
3750
    sixel_frame_t   *frame,
3751
    sixel_output_t  *output)
3752
{
3753
    SIXELSTATUS status = SIXEL_FALSE;
538✔
3754
    sixel_dither_t *dither = NULL;
538✔
3755
    int height;
3756
    int is_animation = 0;
538✔
3757
    int nwrite;
3758
    int drcs_is_96cs_param;
3759
    int drcs_designate_char;
3760
    char buf[256];
3761
    sixel_write_function fn_write;
3762
    sixel_write_function write_callback;
3763
    sixel_write_function scroll_callback;
3764
    void *write_priv;
3765
    void *scroll_priv;
3766
    sixel_encoder_output_probe_t probe;
3767
    sixel_encoder_output_probe_t scroll_probe;
3768
    sixel_assessment_t *assessment;
3769

3770
    fn_write = sixel_write_callback;
538✔
3771
    write_callback = sixel_write_callback;
538✔
3772
    scroll_callback = sixel_write_callback;
538✔
3773
    write_priv = &encoder->outfd;
538✔
3774
    scroll_priv = &encoder->outfd;
538✔
3775
    probe.encoder = NULL;
538✔
3776
    probe.base_write = NULL;
538✔
3777
    probe.base_priv = NULL;
538✔
3778
    scroll_probe.encoder = NULL;
538✔
3779
    scroll_probe.base_write = NULL;
538✔
3780
    scroll_probe.base_priv = NULL;
538✔
3781
    assessment = NULL;
538✔
3782
    if (encoder != NULL) {
538!
3783
        assessment = encoder->assessment_observer;
538✔
3784
    }
188✔
3785
    if (assessment != NULL) {
538✔
3786
        if (encoder->clipfirst) {
3!
3787
            sixel_assessment_stage_transition(
×
3788
                assessment,
3789
                SIXEL_ASSESSMENT_STAGE_CROP);
3790
        } else {
3791
            sixel_assessment_stage_transition(
3✔
3792
                assessment,
1✔
3793
                SIXEL_ASSESSMENT_STAGE_SCALE);
3794
        }
3795
    }
1✔
3796

3797
    /*
3798
     *  Geometry timeline:
3799
     *
3800
     *      +-------+    +------+    +---------------+
3801
     *      | scale | -> | crop | -> | color convert |
3802
     *      +-------+    +------+    +---------------+
3803
     *
3804
     *  The order of the first two blocks mirrors `-c`, so we hop between
3805
     *  SCALE and CROP depending on `clipfirst`.
3806
     */
3807

3808
    if (encoder->clipfirst) {
538✔
3809
        status = sixel_encoder_do_clip(encoder, frame);
8✔
3810
        if (SIXEL_FAILED(status)) {
8!
3811
            goto end;
2✔
3812
        }
3813
        if (assessment != NULL) {
6!
3814
            sixel_assessment_stage_transition(
×
3815
                assessment,
3816
                SIXEL_ASSESSMENT_STAGE_SCALE);
3817
        }
3818
        status = sixel_encoder_do_resize(encoder, frame);
6✔
3819
        if (SIXEL_FAILED(status)) {
6!
3820
            goto end;
×
3821
        }
3822
    } else {
2✔
3823
        status = sixel_encoder_do_resize(encoder, frame);
530✔
3824
        if (SIXEL_FAILED(status)) {
530✔
3825
            goto end;
6✔
3826
        }
3827
        if (assessment != NULL) {
524✔
3828
            sixel_assessment_stage_transition(
3✔
3829
                assessment,
1✔
3830
                SIXEL_ASSESSMENT_STAGE_CROP);
3831
        }
1✔
3832
        status = sixel_encoder_do_clip(encoder, frame);
524✔
3833
        if (SIXEL_FAILED(status)) {
524!
3834
            goto end;
1✔
3835
        }
3836
    }
3837

3838
    if (assessment != NULL) {
529✔
3839
        sixel_assessment_stage_transition(
3✔
3840
            assessment,
1✔
3841
            SIXEL_ASSESSMENT_STAGE_COLORSPACE);
3842
    }
1✔
3843

3844
    status = sixel_frame_ensure_colorspace(frame,
712✔
3845
                                           encoder->working_colorspace);
183✔
3846
    if (SIXEL_FAILED(status)) {
529!
3847
        goto end;
×
3848
    }
3849

3850
    if (assessment != NULL) {
529✔
3851
        sixel_assessment_stage_transition(
3✔
3852
            assessment,
1✔
3853
            SIXEL_ASSESSMENT_STAGE_PALETTE_HISTOGRAM);
3854
    }
1✔
3855

3856
    /* prepare dither context */
3857
    status = sixel_encoder_prepare_palette(encoder, frame, &dither);
529✔
3858
    if (status != SIXEL_OK) {
529!
3859
        dither = NULL;
×
3860
        goto end;
×
3861
    }
3862

3863
    if (encoder->dither_cache != NULL) {
529!
3864
        encoder->dither_cache = dither;
×
3865
        sixel_dither_ref(dither);
×
3866
    }
3867

3868
    if (encoder->fdrcs) {
529!
3869
        status = sixel_encoder_ensure_cell_size(encoder);
×
3870
        if (SIXEL_FAILED(status)) {
×
3871
            goto end;
×
3872
        }
3873
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
×
3874
            sixel_helper_set_additional_message(
×
3875
                "drcs option cannot be used together with macro output.");
3876
            status = SIXEL_BAD_ARGUMENT;
×
3877
            goto end;
×
3878
        }
3879
    }
3880

3881
    /* evaluate -v option: print palette */
3882
    if (encoder->verbose) {
529✔
3883
        if ((sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE)) {
9✔
3884
            sixel_debug_print_palette(dither);
3✔
3885
        }
1✔
3886
    }
3✔
3887

3888
    /* evaluate -d option: set method for diffusion */
3889
    sixel_dither_set_diffusion_type(dither, encoder->method_for_diffuse);
529✔
3890
    sixel_dither_set_diffusion_scan(dither, encoder->method_for_scan);
529✔
3891
    sixel_dither_set_diffusion_carry(dither, encoder->method_for_carry);
529✔
3892

3893
    /* evaluate -C option: set complexion score */
3894
    if (encoder->complexion > 1) {
529✔
3895
        sixel_dither_set_complexion_score(dither, encoder->complexion);
6✔
3896
    }
2✔
3897

3898
    if (output) {
529!
3899
        sixel_output_ref(output);
×
3900
        if (encoder->assessment_observer != NULL) {
×
3901
            probe.encoder = encoder;
×
3902
            probe.base_write = fn_write;
×
3903
            probe.base_priv = &encoder->outfd;
×
3904
            write_callback = sixel_write_with_probe;
×
3905
            write_priv = &probe;
×
3906
        }
3907
    } else {
3908
        /* create output context */
3909
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
529✔
3910
            /* -u or -n option */
3911
            fn_write = sixel_hex_write_callback;
93✔
3912
        } else {
37✔
3913
            fn_write = sixel_write_callback;
436✔
3914
        }
3915
        write_callback = fn_write;
529✔
3916
        write_priv = &encoder->outfd;
529✔
3917
        if (encoder->assessment_observer != NULL) {
529✔
3918
            probe.encoder = encoder;
3✔
3919
            probe.base_write = fn_write;
3✔
3920
            probe.base_priv = &encoder->outfd;
3✔
3921
            write_callback = sixel_write_with_probe;
3✔
3922
            write_priv = &probe;
3✔
3923
        }
1✔
3924
        status = sixel_output_new(&output,
529✔
3925
                                  write_callback,
183✔
3926
                                  write_priv,
183✔
3927
                                  encoder->allocator);
183✔
3928
        if (SIXEL_FAILED(status)) {
529!
3929
            goto end;
×
3930
        }
3931
    }
3932

3933
    if (encoder->fdrcs) {
529!
3934
        sixel_output_set_skip_dcs_envelope(output, 1);
×
3935
        sixel_output_set_skip_header(output, 1);
×
3936
    }
3937

3938
    sixel_output_set_8bit_availability(output, encoder->f8bit);
529✔
3939
    sixel_output_set_gri_arg_limit(output, encoder->has_gri_arg_limit);
529✔
3940
    sixel_output_set_palette_type(output, encoder->palette_type);
529✔
3941
    sixel_output_set_penetrate_multiplexer(
529✔
3942
        output, encoder->penetrate_multiplexer);
183✔
3943
    sixel_output_set_encode_policy(output, encoder->encode_policy);
529✔
3944
    sixel_output_set_ormode(output, encoder->ormode);
529✔
3945

3946
    if (sixel_frame_get_multiframe(frame) && !encoder->fstatic) {
529!
3947
        if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
117✔
3948
            is_animation = 1;
108✔
3949
        }
42✔
3950
        height = sixel_frame_get_height(frame);
117✔
3951
        if (encoder->assessment_observer != NULL) {
117!
3952
            scroll_probe.encoder = encoder;
×
3953
            scroll_probe.base_write = sixel_write_callback;
×
3954
            scroll_probe.base_priv = &encoder->outfd;
×
3955
            scroll_callback = sixel_write_with_probe;
×
3956
            scroll_priv = &scroll_probe;
×
3957
        } else {
3958
            scroll_callback = sixel_write_callback;
117✔
3959
            scroll_priv = &encoder->outfd;
117✔
3960
        }
3961
        (void) sixel_tty_scroll(scroll_callback,
162✔
3962
                                scroll_priv,
45✔
3963
                                encoder->outfd,
45✔
3964
                                height,
45✔
3965
                                is_animation);
45✔
3966
    }
45✔
3967

3968
    if (encoder->cancel_flag && *encoder->cancel_flag) {
529!
3969
        status = SIXEL_INTERRUPTED;
×
3970
        goto end;
×
3971
    }
3972

3973
    if (encoder->fdrcs) {  /* -@ option */
529!
3974
        if (encoder->drcs_mmv == 0) {
×
3975
            drcs_is_96cs_param =
×
3976
                (encoder->drcs_charset_no > 63u) ? 1 : 0;
×
3977
            drcs_designate_char =
×
3978
                (int)(((encoder->drcs_charset_no - 1u) % 63u) + 0x40u);
×
3979
        } else if (encoder->drcs_mmv == 1) {
×
3980
            drcs_is_96cs_param = 0;
×
3981
            drcs_designate_char =
×
3982
                (int)(encoder->drcs_charset_no + 0x3fu);
×
3983
        } else {
3984
            drcs_is_96cs_param =
×
3985
                (encoder->drcs_charset_no > 79u) ? 1 : 0;
×
3986
            drcs_designate_char =
×
3987
                (int)(((encoder->drcs_charset_no - 1u) % 79u) + 0x30u);
×
3988
        }
3989
        nwrite = sprintf(buf,
×
3990
                         "%s%s1;0;0;%d;1;3;%d;%d{ %c",
3991
                         (encoder->drcs_mmv > 0) ? (
×
3992
                             encoder->f8bit ? "\233?8800h": "\033[?8800h"
×
3993
                         ): "",
3994
                         encoder->f8bit ? "\220": "\033P",
×
3995
                         encoder->cell_width,
3996
                         encoder->cell_height,
3997
                         drcs_is_96cs_param,
3998
                         drcs_designate_char);
3999
        if (nwrite < 0) {
×
4000
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4001
            sixel_helper_set_additional_message(
×
4002
                "sixel_encoder_encode_frame: command format failed.");
4003
            goto end;
×
4004
        }
4005
        nwrite = write_callback(buf, nwrite, write_priv);
×
4006
        if (nwrite < 0) {
×
4007
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4008
            sixel_helper_set_additional_message(
×
4009
                "sixel_encoder_encode_frame: write() failed.");
4010
            goto end;
×
4011
        }
4012
    }
4013

4014
    /* output sixel: junction of multi-frame processing strategy */
4015
    if (encoder->fuse_macro) {  /* -u option */
529✔
4016
        /* use macro */
4017
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
90✔
4018
    } else if (encoder->macro_number >= 0) { /* -n option */
475✔
4019
        /* use macro */
4020
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
3✔
4021
    } else {
1✔
4022
        /* do not use macro */
4023
        status = sixel_encoder_output_without_macro(frame, dither, output, encoder);
436✔
4024
    }
4025
    if (SIXEL_FAILED(status)) {
529!
4026
        goto end;
×
4027
    }
4028

4029
    if (encoder->cancel_flag && *encoder->cancel_flag) {
529!
4030
        nwrite = write_callback("\x18\033\\", 3, write_priv);
×
4031
        if (nwrite < 0) {
×
4032
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4033
            sixel_helper_set_additional_message(
×
4034
                "sixel_encoder_encode_frame: write_callback() failed.");
4035
            goto end;
×
4036
        }
4037
        status = SIXEL_INTERRUPTED;
×
4038
    }
4039
    if (SIXEL_FAILED(status)) {
529!
4040
        goto end;
×
4041
    }
4042

4043
    if (encoder->fdrcs) {  /* -@ option */
529!
4044
        if (encoder->f8bit) {
×
4045
            nwrite = write_callback("\234", 1, write_priv);
×
4046
        } else {
4047
            nwrite = write_callback("\033\\", 2, write_priv);
×
4048
        }
4049
        if (nwrite < 0) {
×
4050
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4051
            sixel_helper_set_additional_message(
×
4052
                "sixel_encoder_encode_frame: write_callback() failed.");
4053
            goto end;
×
4054
        }
4055

4056
        if (encoder->tile_outfd >= 0) {
×
4057
            if (encoder->drcs_mmv == 0) {
×
4058
                status = sixel_encoder_emit_iso2022_chars(encoder, frame);
×
4059
                if (SIXEL_FAILED(status)) {
×
4060
                    goto end;
×
4061
                }
4062
            } else {
4063
                status = sixel_encoder_emit_drcsmmv2_chars(encoder, frame);
×
4064
                if (SIXEL_FAILED(status)) {
×
4065
                    goto end;
×
4066
                }
4067
            }
4068
        }
4069
    }
4070

4071

4072
end:
346✔
4073
    if (output) {
538✔
4074
        sixel_output_unref(output);
529✔
4075
    }
183✔
4076
    if (dither) {
538✔
4077
        sixel_dither_unref(dither);
529✔
4078
    }
183✔
4079

4080
    return status;
538✔
4081
}
4082

4083

4084
/* create encoder object */
4085
SIXELAPI SIXELSTATUS
4086
sixel_encoder_new(
547✔
4087
    sixel_encoder_t     /* out */ **ppencoder, /* encoder object to be created */
4088
    sixel_allocator_t   /* in */  *allocator)  /* allocator, null if you use
4089
                                                  default allocator */
4090
{
4091
    SIXELSTATUS status = SIXEL_FALSE;
547✔
4092
    char const *env_default_bgcolor = NULL;
547✔
4093
    char const *env_default_ncolors = NULL;
547✔
4094
    int ncolors;
4095
#if HAVE__DUPENV_S
4096
    errno_t e;
4097
    size_t len;
4098
#endif  /* HAVE__DUPENV_S */
4099

4100
    if (allocator == NULL) {
547!
4101
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
547✔
4102
        if (SIXEL_FAILED(status)) {
547!
4103
            goto end;
×
4104
        }
4105
    } else {
183✔
4106
        sixel_allocator_ref(allocator);
×
4107
    }
4108

4109
    *ppencoder
183✔
4110
        = (sixel_encoder_t *)sixel_allocator_malloc(allocator,
730✔
4111
                                                    sizeof(sixel_encoder_t));
4112
    if (*ppencoder == NULL) {
547!
4113
        sixel_helper_set_additional_message(
×
4114
            "sixel_encoder_new: sixel_allocator_malloc() failed.");
4115
        status = SIXEL_BAD_ALLOCATION;
×
4116
        sixel_allocator_unref(allocator);
×
4117
        goto end;
×
4118
    }
4119

4120
    (*ppencoder)->ref                   = 1;
547✔
4121
    (*ppencoder)->reqcolors             = (-1);
547✔
4122
    (*ppencoder)->force_palette         = 0;
547✔
4123
    (*ppencoder)->mapfile               = NULL;
547✔
4124
    (*ppencoder)->palette_output        = NULL;
547✔
4125
    (*ppencoder)->loader_order          = NULL;
547✔
4126
    (*ppencoder)->color_option          = SIXEL_COLOR_OPTION_DEFAULT;
547✔
4127
    (*ppencoder)->builtin_palette       = 0;
547✔
4128
    (*ppencoder)->method_for_diffuse    = SIXEL_DIFFUSE_AUTO;
547✔
4129
    (*ppencoder)->method_for_scan       = SIXEL_SCAN_AUTO;
547✔
4130
    (*ppencoder)->method_for_carry      = SIXEL_CARRY_AUTO;
547✔
4131
    (*ppencoder)->method_for_largest    = SIXEL_LARGE_AUTO;
547✔
4132
    (*ppencoder)->method_for_rep        = SIXEL_REP_AUTO;
547✔
4133
    (*ppencoder)->quality_mode          = SIXEL_QUALITY_AUTO;
547✔
4134
    (*ppencoder)->quantize_model        = SIXEL_QUANTIZE_MODEL_AUTO;
547✔
4135
    (*ppencoder)->lut_policy            = SIXEL_LUT_POLICY_AUTO;
547✔
4136
    (*ppencoder)->sixel_reversible      = 0;
547✔
4137
    (*ppencoder)->method_for_resampling = SIXEL_RES_BILINEAR;
547✔
4138
    (*ppencoder)->loop_mode             = SIXEL_LOOP_AUTO;
547✔
4139
    (*ppencoder)->palette_type          = SIXEL_PALETTETYPE_AUTO;
547✔
4140
    (*ppencoder)->f8bit                 = 0;
547✔
4141
    (*ppencoder)->has_gri_arg_limit     = 0;
547✔
4142
    (*ppencoder)->finvert               = 0;
547✔
4143
    (*ppencoder)->fuse_macro            = 0;
547✔
4144
    (*ppencoder)->fdrcs                 = 0;
547✔
4145
    (*ppencoder)->fignore_delay         = 0;
547✔
4146
    (*ppencoder)->complexion            = 1;
547✔
4147
    (*ppencoder)->fstatic               = 0;
547✔
4148
    (*ppencoder)->cell_width            = 0;
547✔
4149
    (*ppencoder)->cell_height           = 0;
547✔
4150
    (*ppencoder)->pixelwidth            = (-1);
547✔
4151
    (*ppencoder)->pixelheight           = (-1);
547✔
4152
    (*ppencoder)->percentwidth          = (-1);
547✔
4153
    (*ppencoder)->percentheight         = (-1);
547✔
4154
    (*ppencoder)->clipx                 = 0;
547✔
4155
    (*ppencoder)->clipy                 = 0;
547✔
4156
    (*ppencoder)->clipwidth             = 0;
547✔
4157
    (*ppencoder)->clipheight            = 0;
547✔
4158
    (*ppencoder)->clipfirst             = 0;
547✔
4159
    (*ppencoder)->macro_number          = (-1);
547✔
4160
    (*ppencoder)->verbose               = 0;
547✔
4161
    (*ppencoder)->penetrate_multiplexer = 0;
547✔
4162
    (*ppencoder)->encode_policy         = SIXEL_ENCODEPOLICY_AUTO;
547✔
4163
    (*ppencoder)->working_colorspace    = SIXEL_COLORSPACE_GAMMA;
547✔
4164
    (*ppencoder)->output_colorspace     = SIXEL_COLORSPACE_GAMMA;
547✔
4165
    (*ppencoder)->ormode                = 0;
547✔
4166
    (*ppencoder)->pipe_mode             = 0;
547✔
4167
    (*ppencoder)->bgcolor               = NULL;
547✔
4168
    (*ppencoder)->outfd                 = STDOUT_FILENO;
547✔
4169
    (*ppencoder)->tile_outfd            = (-1);
547✔
4170
    (*ppencoder)->finsecure             = 0;
547✔
4171
    (*ppencoder)->cancel_flag           = NULL;
547✔
4172
    (*ppencoder)->dither_cache          = NULL;
547✔
4173
    (*ppencoder)->drcs_charset_no       = 1u;
547✔
4174
    (*ppencoder)->drcs_mmv              = 2;
547✔
4175
    (*ppencoder)->capture_quantized     = 0;
547✔
4176
    (*ppencoder)->capture_source        = 0;
547✔
4177
    (*ppencoder)->capture_pixels        = NULL;
547✔
4178
    (*ppencoder)->capture_pixels_size   = 0;
547✔
4179
    (*ppencoder)->capture_palette       = NULL;
547✔
4180
    (*ppencoder)->capture_palette_size  = 0;
547✔
4181
    (*ppencoder)->capture_pixel_bytes   = 0;
547✔
4182
    (*ppencoder)->capture_width         = 0;
547✔
4183
    (*ppencoder)->capture_height        = 0;
547✔
4184
    (*ppencoder)->capture_pixelformat   = SIXEL_PIXELFORMAT_RGB888;
547✔
4185
    (*ppencoder)->capture_colorspace    = SIXEL_COLORSPACE_GAMMA;
547✔
4186
    (*ppencoder)->capture_ncolors       = 0;
547✔
4187
    (*ppencoder)->capture_valid         = 0;
547✔
4188
    (*ppencoder)->capture_source_frame  = NULL;
547✔
4189
    (*ppencoder)->assessment_observer   = NULL;
547✔
4190
    (*ppencoder)->assessment_json_path  = NULL;
547✔
4191
    (*ppencoder)->assessment_sections   = SIXEL_ASSESSMENT_SECTION_NONE;
547✔
4192
    (*ppencoder)->last_loader_name[0]   = '\0';
547✔
4193
    (*ppencoder)->last_source_path[0]   = '\0';
547✔
4194
    (*ppencoder)->last_input_bytes      = 0u;
547✔
4195
    (*ppencoder)->output_is_png         = 0;
547✔
4196
    (*ppencoder)->output_png_to_stdout  = 0;
547✔
4197
    (*ppencoder)->png_output_path       = NULL;
547✔
4198
    (*ppencoder)->sixel_output_path     = NULL;
547✔
4199
    (*ppencoder)->clipboard_output_active = 0;
547✔
4200
    (*ppencoder)->clipboard_output_format[0] = '\0';
547✔
4201
    (*ppencoder)->clipboard_output_path = NULL;
547✔
4202
    (*ppencoder)->allocator             = allocator;
547✔
4203

4204
    /* evaluate environment variable ${SIXEL_BGCOLOR} */
4205
#if HAVE__DUPENV_S
4206
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_BGCOLOR");
4207
    if (e != (0)) {
4208
        sixel_helper_set_additional_message(
4209
            "failed to get environment variable $SIXEL_BGCOLOR.");
4210
        return (SIXEL_LIBC_ERROR | (e & 0xff));
4211
    }
4212
#else
4213
    env_default_bgcolor = sixel_compat_getenv("SIXEL_BGCOLOR");
547✔
4214
#endif  /* HAVE__DUPENV_S */
4215
    if (env_default_bgcolor != NULL) {
547!
4216
        status = sixel_parse_x_colorspec(&(*ppencoder)->bgcolor,
×
4217
                                         env_default_bgcolor,
4218
                                         allocator);
4219
        if (SIXEL_FAILED(status)) {
×
4220
            goto error;
×
4221
        }
4222
    }
4223

4224
    /* evaluate environment variable ${SIXEL_COLORS} */
4225
#if HAVE__DUPENV_S
4226
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_COLORS");
4227
    if (e != (0)) {
4228
        sixel_helper_set_additional_message(
4229
            "failed to get environment variable $SIXEL_COLORS.");
4230
        return (SIXEL_LIBC_ERROR | (e & 0xff));
4231
    }
4232
#else
4233
    env_default_ncolors = sixel_compat_getenv("SIXEL_COLORS");
547✔
4234
#endif  /* HAVE__DUPENV_S */
4235
    if (env_default_ncolors) {
547!
4236
        ncolors = atoi(env_default_ncolors); /* may overflow */
×
4237
        if (ncolors > 1 && ncolors <= SIXEL_PALETTE_MAX) {
×
4238
            (*ppencoder)->reqcolors = ncolors;
×
4239
        }
4240
    }
4241

4242
    /* success */
4243
    status = SIXEL_OK;
547✔
4244

4245
    goto end;
547✔
4246

4247
error:
4248
    sixel_allocator_free(allocator, *ppencoder);
×
4249
    sixel_allocator_unref(allocator);
×
4250
    *ppencoder = NULL;
×
4251

4252
end:
364✔
4253
#if HAVE__DUPENV_S
4254
    free(env_default_bgcolor);
4255
    free(env_default_ncolors);
4256
#endif  /* HAVE__DUPENV_S */
4257
    return status;
547✔
4258
}
4259

4260

4261
/* create encoder object (deprecated version) */
4262
SIXELAPI /* deprecated */ sixel_encoder_t *
4263
sixel_encoder_create(void)
×
4264
{
4265
    SIXELSTATUS status = SIXEL_FALSE;
×
4266
    sixel_encoder_t *encoder = NULL;
×
4267

4268
    status = sixel_encoder_new(&encoder, NULL);
×
4269
    if (SIXEL_FAILED(status)) {
×
4270
        return NULL;
×
4271
    }
4272

4273
    return encoder;
×
4274
}
4275

4276

4277
/* destroy encoder object */
4278
static void
4279
sixel_encoder_destroy(sixel_encoder_t *encoder)
547✔
4280
{
4281
    sixel_allocator_t *allocator;
4282

4283
    if (encoder) {
547!
4284
        allocator = encoder->allocator;
547✔
4285
        sixel_allocator_free(allocator, encoder->mapfile);
547✔
4286
        sixel_allocator_free(allocator, encoder->palette_output);
547✔
4287
        sixel_allocator_free(allocator, encoder->loader_order);
547✔
4288
        sixel_allocator_free(allocator, encoder->bgcolor);
547✔
4289
        sixel_dither_unref(encoder->dither_cache);
547✔
4290
        if (encoder->outfd
557!
4291
            && encoder->outfd != STDOUT_FILENO
547!
4292
            && encoder->outfd != STDERR_FILENO) {
203!
4293
            (void)sixel_compat_close(encoder->outfd);
30✔
4294
        }
10✔
4295
        if (encoder->tile_outfd >= 0
547!
4296
            && encoder->tile_outfd != encoder->outfd
183!
4297
            && encoder->tile_outfd != STDOUT_FILENO
×
4298
            && encoder->tile_outfd != STDERR_FILENO) {
×
4299
            (void)sixel_compat_close(encoder->tile_outfd);
×
4300
        }
4301
        if (encoder->capture_source_frame != NULL) {
547✔
4302
            sixel_frame_unref(encoder->capture_source_frame);
3✔
4303
        }
1✔
4304
        if (encoder->clipboard_output_path != NULL) {
547!
4305
            (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
4306
            encoder->clipboard_output_path = NULL;
×
4307
        }
4308
        encoder->clipboard_output_active = 0;
547✔
4309
        encoder->clipboard_output_format[0] = '\0';
547✔
4310
        sixel_allocator_free(allocator, encoder->capture_pixels);
547✔
4311
        sixel_allocator_free(allocator, encoder->capture_palette);
547✔
4312
        sixel_allocator_free(allocator, encoder->png_output_path);
547✔
4313
        sixel_allocator_free(allocator, encoder->sixel_output_path);
547✔
4314
        sixel_allocator_free(allocator, encoder);
547✔
4315
        sixel_allocator_unref(allocator);
547✔
4316
    }
183✔
4317
}
547✔
4318

4319

4320
/* increase reference count of encoder object (thread-unsafe) */
4321
SIXELAPI void
4322
sixel_encoder_ref(sixel_encoder_t *encoder)
1,151✔
4323
{
4324
    /* TODO: be thread safe */
4325
    ++encoder->ref;
1,151✔
4326
}
1,151✔
4327

4328

4329
/* decrease reference count of encoder object (thread-unsafe) */
4330
SIXELAPI void
4331
sixel_encoder_unref(sixel_encoder_t *encoder)
1,698✔
4332
{
4333
    /* TODO: be thread safe */
4334
    if (encoder != NULL && --encoder->ref == 0) {
1,698!
4335
        sixel_encoder_destroy(encoder);
547✔
4336
    }
183✔
4337
}
1,698✔
4338

4339

4340
/* set cancel state flag to encoder object */
4341
SIXELAPI SIXELSTATUS
4342
sixel_encoder_set_cancel_flag(
442✔
4343
    sixel_encoder_t /* in */ *encoder,
4344
    int             /* in */ *cancel_flag
4345
)
4346
{
4347
    SIXELSTATUS status = SIXEL_OK;
442✔
4348

4349
    encoder->cancel_flag = cancel_flag;
442✔
4350

4351
    return status;
442✔
4352
}
4353

4354

4355
/*
4356
 * parse_assessment_sections() translates a comma-separated section list into
4357
 * the bitmask understood by the assessment back-end.  The accepted grammar is
4358
 * intentionally small so that the CLI contract stays predictable:
4359
 *
4360
 *     list := section {"," section}
4361
 *     section := name | name "@" view
4362
 *
4363
 * Each name maps to a bit flag while the optional view toggles the encoded vs
4364
 * quantized quality comparison.  The helper folds case, trims ASCII
4365
 * whitespace, and rejects mixed encoded/quantized requests so that the caller
4366
 * can rely on a single coherent capture strategy.
4367
 */
4368
static int
4369
parse_assessment_sections(char const *spec,
6✔
4370
                          unsigned int *out_sections)
4371
{
4372
    char *copy;
4373
    char *cursor;
4374
    char *token;
4375
    char *context;
4376
    unsigned int sections;
4377
    unsigned int view;
4378
    int result;
4379
    size_t length;
4380
    size_t spec_len;
4381
    char *at;
4382
    char *base;
4383
    char *variant;
4384
    char *p;
4385

4386
    if (spec == NULL || out_sections == NULL) {
6!
4387
        return -1;
×
4388
    }
4389
    spec_len = strlen(spec);
6✔
4390
    copy = (char *)malloc(spec_len + 1u);
6✔
4391
    if (copy == NULL) {
6!
4392
        return -1;
×
4393
    }
4394
    memcpy(copy, spec, spec_len + 1u);
6✔
4395
    cursor = copy;
6✔
4396
    sections = 0u;
6✔
4397
    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
6✔
4398
    result = 0;
6✔
4399
    context = NULL;
6✔
4400
    while (result == 0) {
12!
4401
        token = sixel_compat_strtok(cursor, ",", &context);
12✔
4402
        if (token == NULL) {
12✔
4403
            break;
6✔
4404
        }
4405
        cursor = NULL;
6✔
4406
        while (*token == ' ' || *token == '\t') {
6!
4407
            token += 1;
×
4408
        }
4409
        length = strlen(token);
6✔
4410
        while (length > 0u &&
8!
4411
               (token[length - 1u] == ' ' || token[length - 1u] == '\t')) {
6!
4412
            token[length - 1u] = '\0';
×
4413
            length -= 1u;
×
4414
        }
4415
        if (*token == '\0') {
6!
4416
            result = -1;
×
4417
            break;
×
4418
        }
4419
        for (p = token; *p != '\0'; ++p) {
42✔
4420
            *p = (char)tolower((unsigned char)*p);
36✔
4421
        }
12✔
4422
        at = strchr(token, '@');
6✔
4423
        if (at != NULL) {
6!
4424
            *at = '\0';
×
4425
            variant = at + 1;
×
4426
            if (*variant == '\0') {
×
4427
                result = -1;
×
4428
                break;
×
4429
            }
4430
        } else {
4431
            variant = NULL;
6✔
4432
        }
4433
        base = token;
6✔
4434
        if (strcmp(base, "all") == 0) {
6!
4435
            sections |= SIXEL_ASSESSMENT_SECTION_ALL;
×
4436
            if (variant != NULL && variant[0] != '\0') {
×
4437
                if (strcmp(variant, "quantized") == 0) {
×
4438
                    if (view != SIXEL_ASSESSMENT_VIEW_ENCODED &&
×
4439
                            view != SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
4440
                        result = -1;
×
4441
                    }
4442
                    view = SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
4443
                } else if (strcmp(variant, "encoded") == 0) {
×
4444
                    if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4445
                        result = -1;
×
4446
                    }
4447
                    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
×
4448
                } else {
4449
                    result = -1;
×
4450
                }
4451
            }
4452
        } else if (strcmp(base, "basic") == 0) {
6✔
4453
            sections |= SIXEL_ASSESSMENT_SECTION_BASIC;
3✔
4454
            if (variant != NULL) {
3!
4455
                result = -1;
×
4456
            }
4457
        } else if (strcmp(base, "performance") == 0) {
4!
4458
            sections |= SIXEL_ASSESSMENT_SECTION_PERFORMANCE;
×
4459
            if (variant != NULL) {
×
4460
                result = -1;
×
4461
            }
4462
        } else if (strcmp(base, "size") == 0) {
3!
4463
            sections |= SIXEL_ASSESSMENT_SECTION_SIZE;
×
4464
            if (variant != NULL) {
×
4465
                result = -1;
×
4466
            }
4467
        } else if (strcmp(base, "quality") == 0) {
3!
4468
            sections |= SIXEL_ASSESSMENT_SECTION_QUALITY;
3✔
4469
            if (variant != NULL && variant[0] != '\0') {
3!
4470
                if (strcmp(variant, "quantized") == 0) {
×
4471
                    if (view != SIXEL_ASSESSMENT_VIEW_ENCODED &&
×
4472
                            view != SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
4473
                        result = -1;
×
4474
                    }
4475
                    view = SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
4476
                } else if (strcmp(variant, "encoded") == 0) {
×
4477
                    if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4478
                        result = -1;
×
4479
                    }
4480
                    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
×
4481
                } else {
4482
                    result = -1;
×
4483
                }
4484
            } else if (variant != NULL) {
3!
4485
                if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4486
                    result = -1;
×
4487
                }
4488
                view = SIXEL_ASSESSMENT_VIEW_ENCODED;
×
4489
            }
4490
        } else {
1✔
4491
            result = -1;
×
4492
        }
4493
    }
4494
    if (result == 0) {
6!
4495
        if (sections == 0u) {
6!
4496
            result = -1;
×
4497
        } else {
4498
            if ((sections & SIXEL_ASSESSMENT_SECTION_QUALITY) != 0u &&
6!
4499
                    view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
1✔
4500
                sections |= SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
4501
            }
4502
            *out_sections = sections;
6✔
4503
        }
4504
    }
2✔
4505
    free(copy);
6✔
4506
    return result;
6✔
4507
}
2✔
4508

4509

4510
static int
4511
is_png_target(char const *path)
31✔
4512
{
4513
    size_t len;
4514
    int matched;
4515

4516
    /*
4517
     * Detect PNG requests from explicit prefixes or a ".png" suffix:
4518
     *
4519
     *   argument
4520
     *   |
4521
     *   v
4522
     *   .............. . p n g
4523
     *   ^             ^^^^^^^^^
4524
     *   |             +-- case-insensitive suffix comparison
4525
     *   +-- accepts the "png:" inline prefix used for stdout capture
4526
     */
4527

4528
    len = 0;
31✔
4529
    matched = 0;
31✔
4530

4531
    if (path == NULL) {
31!
4532
        return 0;
×
4533
    }
4534

4535
    if (strncmp(path, "png:", 4) == 0) {
31✔
4536
        return path[4] != '\0';
6✔
4537
    }
4538

4539
    len = strlen(path);
25✔
4540
    if (len >= 4) {
25✔
4541
        matched = (tolower((unsigned char)path[len - 4]) == '.')
23✔
4542
            && (tolower((unsigned char)path[len - 3]) == 'p')
10!
4543
            && (tolower((unsigned char)path[len - 2]) == 'n')
3!
4544
            && (tolower((unsigned char)path[len - 1]) == 'g');
24!
4545
    }
8✔
4546

4547
    return matched;
25✔
4548
}
11✔
4549

4550

4551
static char const *
4552
png_target_payload_view(char const *argument)
9✔
4553
{
4554
    /*
4555
     * Inline PNG targets split into either a prefix/payload pair or rely on
4556
     * a simple file-name suffix:
4557
     *
4558
     *   +--------------+------------+-------------+
4559
     *   | form         | payload    | destination |
4560
     *   +--------------+------------+-------------+
4561
     *   | png:         | -          | stdout      |
4562
     *   | png:         | filename   | filesystem  |
4563
     *   | *.png        | filename   | filesystem  |
4564
     *   +--------------+------------+-------------+
4565
     *
4566
     * The caller only needs the payload column, so we expose it here.  When
4567
     * the user omits the prefix we simply echo the original pointer so the
4568
     * caller can copy the value verbatim.
4569
     */
4570
    if (argument == NULL) {
9!
4571
        return NULL;
×
4572
    }
4573
    if (strncmp(argument, "png:", 4) == 0) {
9✔
4574
        return argument + 4;
6✔
4575
    }
4576

4577
    return argument;
3✔
4578
}
3✔
4579

4580
static void
4581
normalise_windows_drive_path(char *path)
9✔
4582
{
4583
#if defined(_WIN32)
4584
    size_t length;
4585

4586
    /*
4587
     * MSYS-like environments forward POSIX-looking absolute paths to native
4588
     * binaries.  When a user writes "/d/..." MSYS converts the command line to
4589
     * UTF-16 and preserves the literal bytes.  The Windows CRT, however,
4590
     * expects "d:/..." or "d:\...".  The tiny state machine below rewrites the
4591
     * leading token so the runtime resolves the drive correctly:
4592
     *
4593
     *   input     normalised
4594
     *   |         |
4595
     *   v         v
4596
     *   / d / ... d : / ...
4597
     *
4598
     * The body keeps the rest of the string intact so UNC paths ("//server")
4599
     * and relative references pass through untouched.
4600
     */
4601

4602
    length = 0u;
4603

4604
    if (path == NULL) {
4605
        return;
4606
    }
4607

4608
    length = strlen(path);
4609
    if (length >= 3u
4610
            && path[0] == '/'
4611
            && ((path[1] >= 'A' && path[1] <= 'Z')
4612
                || (path[1] >= 'a' && path[1] <= 'z'))
4613
            && path[2] == '/') {
4614
        path[0] = path[1];
4615
        path[1] = ':';
4616
    }
4617
#else
4618
    (void)path;
3✔
4619
#endif
4620
}
9✔
4621

4622

4623
static int
4624
is_dev_null_path(char const *path)
×
4625
{
4626
    if (path == NULL || path[0] == '\0') {
×
4627
        return 0;
×
4628
    }
4629
#if defined(_WIN32)
4630
    if (_stricmp(path, "nul") == 0) {
4631
        return 1;
4632
    }
4633
#endif
4634
    return strcmp(path, "/dev/null") == 0;
×
4635
}
4636

4637

4638
/* set an option flag to encoder object */
4639
SIXELAPI SIXELSTATUS
4640
sixel_encoder_setopt(
715✔
4641
    sixel_encoder_t /* in */ *encoder,
4642
    int             /* in */ arg,
4643
    char const      /* in */ *value)
4644
{
4645
    SIXELSTATUS status = SIXEL_FALSE;
715✔
4646
    int number;
4647
    int parsed;
4648
    char unit[32];
4649
    char lowered[16];
4650
    size_t len;
4651
    size_t i;
4652
    long parsed_reqcolors;
4653
    char *endptr;
4654
    int forced_palette;
4655
    char *opt_copy;
4656
    char const *drcs_arg_delim;
4657
    char const *drcs_arg_charset;
4658
    char const *drcs_arg_second_delim;
4659
    char const *drcs_arg_path;
4660
    size_t drcs_arg_path_length;
4661
    size_t drcs_segment_length;
4662
    char drcs_segment[32];
4663
    int drcs_mmv_value;
4664
    long drcs_charset_value;
4665
    unsigned int drcs_charset_limit;
4666
    sixel_option_choice_result_t match_result;
4667
    int match_value;
4668
    char match_detail[128];
4669
    char match_message[256];
4670
    int png_argument_has_prefix = 0;
715✔
4671
    char const *png_path_view = NULL;
715✔
4672
    size_t png_path_length;
4673

4674
    sixel_encoder_ref(encoder);
715✔
4675
    opt_copy = NULL;
715✔
4676

4677
    switch(arg) {
715!
4678
    case SIXEL_OPTFLAG_OUTFILE:  /* o */
20✔
4679
        if (*value == '\0') {
31!
4680
            sixel_helper_set_additional_message(
×
4681
                "no file name specified.");
4682
            status = SIXEL_BAD_ARGUMENT;
×
4683
            goto end;
×
4684
        }
4685
        if (is_png_target(value)) {
31✔
4686
            encoder->output_is_png = 1;
9✔
4687
            png_argument_has_prefix =
9✔
4688
                (value != NULL)
3✔
4689
                && (strncmp(value, "png:", 4) == 0);
9!
4690
            png_path_view = png_target_payload_view(value);
9✔
4691
            if (png_argument_has_prefix
11!
4692
                    && (png_path_view == NULL
7!
4693
                        || png_path_view[0] == '\0')) {
6!
4694
                sixel_helper_set_additional_message(
×
4695
                    "sixel_encoder_setopt: missing target after the \"png:\" "
4696
                    "prefix. use png:- or png:<path> with a non-empty payload."
4697
                );
4698
                status = SIXEL_BAD_ARGUMENT;
×
4699
                goto end;
×
4700
            }
4701
            encoder->output_png_to_stdout =
9✔
4702
                (png_path_view != NULL)
3✔
4703
                && (strcmp(png_path_view, "-") == 0);
9!
4704
            sixel_allocator_free(encoder->allocator, encoder->png_output_path);
9✔
4705
            encoder->png_output_path = NULL;
9✔
4706
            sixel_allocator_free(encoder->allocator, encoder->sixel_output_path);
9✔
4707
            encoder->sixel_output_path = NULL;
9✔
4708
            if (! encoder->output_png_to_stdout) {
9!
4709
                /*
4710
                 * +-----------------------------------------+
4711
                 * |  PNG target normalization               |
4712
                 * +-----------------------------------------+
4713
                 * |  Raw input  |  Stored file path         |
4714
                 * |-------------+---------------------------|
4715
                 * |  png:-      |  "-" (stdout sentinel)    |
4716
                 * |  png:/foo   |  "/foo"                   |
4717
                 * +-----------------------------------------+
4718
                 * Strip the "png:" prefix so the decoder can
4719
                 * pass the true filesystem path to libpng
4720
                 * while the CLI retains its shorthand.
4721
                 */
4722
                png_path_view = value;
9✔
4723
                if (strncmp(value, "png:", 4) == 0) {
9✔
4724
                    png_path_view = value + 4;
6✔
4725
                }
2✔
4726
                if (png_path_view[0] == '\0') {
9!
4727
                    sixel_helper_set_additional_message(
×
4728
                        "sixel_encoder_setopt: PNG output path is empty.");
4729
                    status = SIXEL_BAD_ARGUMENT;
×
4730
                    goto end;
×
4731
                }
4732
                png_path_length = strlen(png_path_view);
9✔
4733
                encoder->png_output_path =
9✔
4734
                    (char *)sixel_allocator_malloc(
9✔
4735
                        encoder->allocator, png_path_length + 1u);
3✔
4736
                if (encoder->png_output_path == NULL) {
9!
4737
                    sixel_helper_set_additional_message(
×
4738
                        "sixel_encoder_setopt: sixel_allocator_malloc() "
4739
                        "failed for PNG output path.");
4740
                    status = SIXEL_BAD_ALLOCATION;
×
4741
                    goto end;
×
4742
                }
4743
                if (png_path_view != NULL) {
9!
4744
                    (void)sixel_compat_strcpy(encoder->png_output_path,
12✔
4745
                                              png_path_length + 1u,
3✔
4746
                                              png_path_view);
3✔
4747
                } else {
3✔
4748
                    encoder->png_output_path[0] = '\0';
×
4749
                }
4750
                normalise_windows_drive_path(encoder->png_output_path);
9✔
4751
            }
3✔
4752
        } else {
3✔
4753
            encoder->output_is_png = 0;
22✔
4754
            encoder->output_png_to_stdout = 0;
22✔
4755
            png_argument_has_prefix = 0;
22✔
4756
            png_path_view = NULL;
22✔
4757
            sixel_allocator_free(encoder->allocator, encoder->png_output_path);
22✔
4758
            encoder->png_output_path = NULL;
22✔
4759
            sixel_allocator_free(encoder->allocator, encoder->sixel_output_path);
22✔
4760
            encoder->sixel_output_path = NULL;
22✔
4761
            if (encoder->clipboard_output_path != NULL) {
22!
4762
                (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
4763
                sixel_allocator_free(encoder->allocator,
×
4764
                                     encoder->clipboard_output_path);
×
4765
                encoder->clipboard_output_path = NULL;
×
4766
            }
4767
            encoder->clipboard_output_active = 0;
22✔
4768
            encoder->clipboard_output_format[0] = '\0';
22✔
4769
            {
4770
                sixel_clipboard_spec_t clipboard_spec;
4771
                SIXELSTATUS clip_status;
4772
                char *spool_path;
4773
                int spool_fd;
4774

4775
                clipboard_spec.is_clipboard = 0;
22✔
4776
                clipboard_spec.format[0] = '\0';
22✔
4777
                clip_status = SIXEL_OK;
22✔
4778
                spool_path = NULL;
22✔
4779
                spool_fd = (-1);
22✔
4780

4781
                if (sixel_clipboard_parse_spec(value, &clipboard_spec)
22!
4782
                        && clipboard_spec.is_clipboard) {
8!
4783
                    clip_status = clipboard_create_spool(
1✔
4784
                        encoder->allocator,
1✔
4785
                        "clipboard-out",
4786
                        &spool_path,
4787
                        &spool_fd);
4788
                    if (SIXEL_FAILED(clip_status)) {
1!
4789
                        status = clip_status;
×
4790
                        goto end;
×
4791
                    }
4792
                    clipboard_select_format(
1✔
4793
                        encoder->clipboard_output_format,
1✔
4794
                        sizeof(encoder->clipboard_output_format),
4795
                        clipboard_spec.format,
1✔
4796
                        "sixel");
4797
                    if (encoder->outfd
1!
4798
                            && encoder->outfd != STDOUT_FILENO
1!
4799
                            && encoder->outfd != STDERR_FILENO) {
1!
4800
                        (void)sixel_compat_close(encoder->outfd);
×
4801
                    }
4802
                    encoder->outfd = spool_fd;
1✔
4803
                    spool_fd = (-1);
1✔
4804
                    encoder->sixel_output_path = spool_path;
1✔
4805
                    encoder->clipboard_output_path = spool_path;
1✔
4806
                    spool_path = NULL;
1✔
4807
                    encoder->clipboard_output_active = 1;
1✔
4808
                    break;
1✔
4809
                }
4810

4811
                if (spool_fd >= 0) {
21!
4812
                    (void)sixel_compat_close(spool_fd);
×
4813
                }
4814
                if (spool_path != NULL) {
21!
4815
                    sixel_allocator_free(encoder->allocator, spool_path);
×
4816
                }
4817
            }
4818
            if (strcmp(value, "-") != 0) {
21!
4819
                encoder->sixel_output_path = (char *)sixel_allocator_malloc(
35✔
4820
                    encoder->allocator, strlen(value) + 1);
21✔
4821
                if (encoder->sixel_output_path == NULL) {
21!
4822
                    sixel_helper_set_additional_message(
×
4823
                        "sixel_encoder_setopt: malloc() failed for output path.");
4824
                    status = SIXEL_BAD_ALLOCATION;
×
4825
                    goto end;
×
4826
                }
4827
                (void)sixel_compat_strcpy(encoder->sixel_output_path,
28✔
4828
                                          strlen(value) + 1,
21✔
4829
                                          value);
7✔
4830
            }
7✔
4831
        }
4832

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

5632
            if (len >= sizeof(lowered)) {
×
5633
                sixel_helper_set_additional_message(
×
5634
                    "specified working colorspace name is too long.");
5635
                status = SIXEL_BAD_ARGUMENT;
×
5636
                goto end;
×
5637
            }
5638
            for (i = 0; i < len; ++i) {
×
5639
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
5640
            }
5641
            lowered[len] = '\0';
×
5642

5643
            match_result = sixel_match_option_choice(
×
5644
                lowered,
5645
                g_option_choices_working_colorspace,
5646
                sizeof(g_option_choices_working_colorspace) /
5647
                sizeof(g_option_choices_working_colorspace[0]),
5648
                &match_value,
5649
                match_detail,
5650
                sizeof(match_detail));
5651
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5652
                encoder->working_colorspace = match_value;
×
5653
            } else {
5654
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5655
                    sixel_report_ambiguous_prefix(
×
5656
                        "--working-colorspace",
5657
                        value,
5658
                        match_detail,
5659
                        match_message,
5660
                        sizeof(match_message));
5661
                } else {
5662
                    sixel_helper_set_additional_message(
×
5663
                        "unsupported working colorspace specified.");
5664
                }
5665
                status = SIXEL_BAD_ARGUMENT;
×
5666
                goto end;
×
5667
            }
5668
        }
5669
        break;
×
5670
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
5671
        if (value == NULL) {
×
5672
            sixel_helper_set_additional_message(
×
5673
                "output-colorspace requires an argument.");
5674
            status = SIXEL_BAD_ARGUMENT;
×
5675
            goto end;
×
5676
        } else {
5677
            len = strlen(value);
×
5678

5679
            if (len >= sizeof(lowered)) {
×
5680
                sixel_helper_set_additional_message(
×
5681
                    "specified output colorspace name is too long.");
5682
                status = SIXEL_BAD_ARGUMENT;
×
5683
                goto end;
×
5684
            }
5685
            for (i = 0; i < len; ++i) {
×
5686
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
5687
            }
5688
            lowered[len] = '\0';
×
5689

5690
            match_result = sixel_match_option_choice(
×
5691
                lowered,
5692
                g_option_choices_output_colorspace,
5693
                sizeof(g_option_choices_output_colorspace) /
5694
                sizeof(g_option_choices_output_colorspace[0]),
5695
                &match_value,
5696
                match_detail,
5697
                sizeof(match_detail));
5698
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5699
                encoder->output_colorspace = match_value;
×
5700
            } else {
5701
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5702
                    sixel_report_ambiguous_prefix(
×
5703
                        "--output-colorspace",
5704
                        value,
5705
                        match_detail,
5706
                        match_message,
5707
                        sizeof(match_message));
5708
                } else {
5709
                    sixel_helper_set_additional_message(
×
5710
                        "unsupported output colorspace specified.");
5711
                }
5712
                status = SIXEL_BAD_ARGUMENT;
×
5713
                goto end;
×
5714
            }
5715
        }
5716
        break;
×
5717
    case SIXEL_OPTFLAG_ORMODE:  /* O */
5718
        encoder->ormode = 1;
×
5719
        break;
×
5720
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
6✔
5721
        encoder->complexion = atoi(value);
9✔
5722
        if (encoder->complexion < 1) {
9✔
5723
            sixel_helper_set_additional_message(
3✔
5724
                "complexion parameter must be 1 or more.");
5725
            status = SIXEL_BAD_ARGUMENT;
3✔
5726
            goto end;
3✔
5727
        }
5728
        break;
6✔
5729
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
5730
        encoder->pipe_mode = 1;
×
5731
        break;
×
5732
    case '?':  /* unknown option */
×
5733
    default:
5734
        /* exit if unknown options are specified */
5735
        sixel_helper_set_additional_message(
×
5736
            "unknown option is specified.");
5737
        status = SIXEL_BAD_ARGUMENT;
×
5738
        goto end;
×
5739
    }
5740

5741
    /* detects arguments conflictions */
5742
    if (encoder->reqcolors != (-1)) {
640✔
5743
        switch (encoder->color_option) {
99!
5744
        case SIXEL_COLOR_OPTION_MAPFILE:
5745
            sixel_helper_set_additional_message(
×
5746
                "option -p, --colors conflicts with -m, --mapfile.");
5747
            status = SIXEL_BAD_ARGUMENT;
×
5748
            goto end;
×
5749
        case SIXEL_COLOR_OPTION_MONOCHROME:
2✔
5750
            sixel_helper_set_additional_message(
3✔
5751
                "option -e, --monochrome conflicts with -p, --colors.");
5752
            status = SIXEL_BAD_ARGUMENT;
3✔
5753
            goto end;
3✔
5754
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
2✔
5755
            sixel_helper_set_additional_message(
3✔
5756
                "option -p, --colors conflicts with -I, --high-color.");
5757
            status = SIXEL_BAD_ARGUMENT;
3✔
5758
            goto end;
3✔
5759
        case SIXEL_COLOR_OPTION_BUILTIN:
2✔
5760
            sixel_helper_set_additional_message(
3✔
5761
                "option -p, --colors conflicts with -b, --builtin-palette.");
5762
            status = SIXEL_BAD_ARGUMENT;
3✔
5763
            goto end;
3✔
5764
        default:
60✔
5765
            break;
90✔
5766
        }
5767
    }
30✔
5768

5769
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
5770
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
631✔
5771
        sixel_helper_set_additional_message(
3✔
5772
            "option -8 --8bit-mode conflicts"
5773
            " with -P, --penetrate.");
5774
        status = SIXEL_BAD_ARGUMENT;
3✔
5775
        goto end;
3✔
5776
    }
5777

5778
    status = SIXEL_OK;
628✔
5779

5780
end:
476✔
5781
    if (opt_copy != NULL) {
715!
5782
        sixel_allocator_free(encoder->allocator, opt_copy);
×
5783
    }
5784
    sixel_encoder_unref(encoder);
715✔
5785

5786
    return status;
715✔
5787
}
5788

5789

5790
/* called when image loader component load a image frame */
5791
static SIXELSTATUS
5792
load_image_callback(sixel_frame_t *frame, void *data)
538✔
5793
{
5794
    sixel_encoder_t *encoder;
5795

5796
    encoder = (sixel_encoder_t *)data;
538✔
5797
    if (encoder->capture_source && encoder->capture_source_frame == NULL) {
538!
5798
        sixel_frame_ref(frame);
3✔
5799
        encoder->capture_source_frame = frame;
3✔
5800
    }
1✔
5801

5802
    return sixel_encoder_encode_frame(encoder, frame, NULL);
538✔
5803
}
5804

5805
/*
5806
 * Build a tee for encoded-assessment output:
5807
 *
5808
 *     +-------------+     +-------------------+     +------------+
5809
 *     | encoder FD  | --> | temporary SIXEL   | --> | tee sink   |
5810
 *     +-------------+     +-------------------+     +------------+
5811
 *
5812
 * The tee sink can be stdout or a user-provided file such as /dev/null.
5813
 */
5814
static SIXELSTATUS
5815
copy_file_to_stream(char const *path,
×
5816
                    FILE *stream,
5817
                    sixel_assessment_t *assessment)
5818
{
5819
    FILE *source;
5820
    unsigned char buffer[4096];
5821
    size_t nread;
5822
    size_t nwritten;
5823
    double started_at;
5824
    double finished_at;
5825
    double duration;
5826

5827
    source = NULL;
×
5828
    nread = 0;
×
5829
    nwritten = 0;
×
5830
    started_at = 0.0;
×
5831
    finished_at = 0.0;
×
5832
    duration = 0.0;
×
5833

5834
    source = sixel_compat_fopen(path, "rb");
×
5835
    if (source == NULL) {
×
5836
        sixel_helper_set_additional_message(
×
5837
            "copy_file_to_stream: failed to open assessment staging file.");
5838
        return SIXEL_LIBC_ERROR;
×
5839
    }
5840

5841
    for (;;) {
5842
        nread = fread(buffer, 1, sizeof(buffer), source);
×
5843
        if (nread == 0) {
×
5844
            if (ferror(source)) {
×
5845
                sixel_helper_set_additional_message(
×
5846
                    "copy_file_to_stream: failed while reading assessment staging file.");
5847
                (void) fclose(source);
×
5848
                return SIXEL_LIBC_ERROR;
×
5849
            }
5850
            break;
×
5851
        }
5852
        if (assessment != NULL) {
×
5853
            started_at = sixel_assessment_timer_now();
×
5854
        }
5855
        nwritten = fwrite(buffer, 1, nread, stream);
×
5856
        if (nwritten != nread) {
×
5857
            sixel_helper_set_additional_message(
×
5858
                "img2sixel: failed while copying assessment staging file.");
5859
            (void) fclose(source);
×
5860
            return SIXEL_LIBC_ERROR;
×
5861
        }
5862
        if (assessment != NULL) {
×
5863
            finished_at = sixel_assessment_timer_now();
×
5864
            duration = finished_at - started_at;
×
5865
            if (duration < 0.0) {
×
5866
                duration = 0.0;
×
5867
            }
5868
            sixel_assessment_record_output_write(assessment,
×
5869
                                                 nwritten,
5870
                                                 duration);
5871
        }
5872
    }
5873

5874
    if (fclose(source) != 0) {
×
5875
        sixel_helper_set_additional_message(
×
5876
            "img2sixel: failed to close assessment staging file.");
5877
        return SIXEL_LIBC_ERROR;
×
5878
    }
5879

5880
    return SIXEL_OK;
×
5881
}
5882

5883
typedef struct assessment_json_sink {
5884
    FILE *stream;
5885
    int failed;
5886
} assessment_json_sink_t;
5887

5888
static void
5889
assessment_json_callback(char const *chunk,
42✔
5890
                         size_t length,
5891
                         void *user_data)
5892
{
5893
    assessment_json_sink_t *sink;
5894

5895
    sink = (assessment_json_sink_t *)user_data;
42✔
5896
    if (sink == NULL || sink->stream == NULL) {
42!
5897
        return;
×
5898
    }
5899
    if (sink->failed) {
42!
5900
        return;
×
5901
    }
5902
    if (fwrite(chunk, 1, length, sink->stream) != length) {
42!
5903
        sink->failed = 1;
×
5904
    }
5905
}
14✔
5906

5907
static char *
5908
create_temp_template_with_prefix(sixel_allocator_t *allocator,
11✔
5909
                                 char const *prefix,
5910
                                 size_t *capacity_out)
5911
{
5912
    char const *tmpdir;
5913
    size_t tmpdir_len;
5914
    size_t prefix_len;
5915
    size_t suffix_len;
5916
    size_t template_len;
5917
    char *template_path;
5918
    int needs_separator;
5919
    size_t maximum_tmpdir_len;
5920

5921
    tmpdir = sixel_compat_getenv("TMPDIR");
11✔
5922
#if defined(_WIN32)
5923
    if (tmpdir == NULL || tmpdir[0] == '\0') {
5924
        tmpdir = sixel_compat_getenv("TEMP");
5925
    }
5926
    if (tmpdir == NULL || tmpdir[0] == '\0') {
5927
        tmpdir = sixel_compat_getenv("TMP");
5928
    }
5929
#endif
5930
    if (tmpdir == NULL || tmpdir[0] == '\0') {
11!
5931
#if defined(_WIN32)
5932
        tmpdir = ".";
5933
#else
5934
        tmpdir = "/tmp";
6✔
5935
#endif
5936
    }
5937

5938
    tmpdir_len = strlen(tmpdir);
11✔
5939
    prefix_len = 0u;
11✔
5940
    suffix_len = 0u;
11✔
5941
    if (prefix == NULL) {
11!
5942
        return NULL;
×
5943
    }
5944

5945
    prefix_len = strlen(prefix);
11✔
5946
    suffix_len = prefix_len + strlen("-XXXXXX");
11✔
5947
    maximum_tmpdir_len = (size_t)INT_MAX;
11✔
5948

5949
    if (maximum_tmpdir_len <= suffix_len + 2) {
11!
5950
        return NULL;
×
5951
    }
5952
    if (tmpdir_len > maximum_tmpdir_len - (suffix_len + 2)) {
11!
5953
        return NULL;
×
5954
    }
5955
    needs_separator = 1;
11✔
5956
    if (tmpdir_len > 0) {
11!
5957
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
11!
5958
            needs_separator = 0;
5✔
5959
        }
5✔
5960
    }
5✔
5961

5962
    template_len = tmpdir_len + suffix_len + 2;
11✔
5963
    template_path = (char *)sixel_allocator_malloc(allocator, template_len);
11✔
5964
    if (template_path == NULL) {
11!
5965
        return NULL;
×
5966
    }
5967

5968
    if (needs_separator) {
11!
5969
#if defined(_WIN32)
5970
        (void) snprintf(template_path, template_len,
5971
                        "%s\\%s-XXXXXX", tmpdir, prefix);
5972
#else
5973
        (void) snprintf(template_path, template_len,
6✔
5974
                        "%s/%s-XXXXXX", tmpdir, prefix);
5975
#endif
5976
    } else {
5977
        (void) snprintf(template_path, template_len,
5✔
5978
                        "%s%s-XXXXXX", tmpdir, prefix);
5979
    }
5980

5981
    if (capacity_out != NULL) {
11!
5982
        *capacity_out = template_len;
11✔
5983
    }
5✔
5984

5985
    return template_path;
11✔
5986
}
5✔
5987

5988

5989
static char *
5990
create_temp_template(sixel_allocator_t *allocator,
9✔
5991
                     size_t *capacity_out)
5992
{
5993
    return create_temp_template_with_prefix(allocator,
12✔
5994
                                            "img2sixel",
5995
                                            capacity_out);
3✔
5996
}
5997

5998

5999
static void
6000
clipboard_select_format(char *dest,
2✔
6001
                        size_t dest_size,
6002
                        char const *format,
6003
                        char const *fallback)
6004
{
6005
    char const *source;
6006
    size_t limit;
6007

6008
    if (dest == NULL || dest_size == 0u) {
2!
6009
        return;
×
6010
    }
6011

6012
    source = fallback;
2✔
6013
    if (format != NULL && format[0] != '\0') {
2!
6014
        source = format;
×
6015
    }
6016

6017
    limit = dest_size - 1u;
2✔
6018
    if (limit == 0u) {
2!
6019
        dest[0] = '\0';
×
6020
        return;
×
6021
    }
6022

6023
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
2✔
6024
}
2✔
6025

6026

6027
static SIXELSTATUS
6028
clipboard_create_spool(sixel_allocator_t *allocator,
2✔
6029
                       char const *prefix,
6030
                       char **path_out,
6031
                       int *fd_out)
6032
{
6033
    SIXELSTATUS status;
6034
    char *template_path;
6035
    size_t template_capacity;
6036
    int open_flags;
6037
    int fd;
6038
    char *tmpname_result;
6039

6040
    status = SIXEL_FALSE;
2✔
6041
    template_path = NULL;
2✔
6042
    template_capacity = 0u;
2✔
6043
    open_flags = 0;
2✔
6044
    fd = (-1);
2✔
6045
    tmpname_result = NULL;
2✔
6046

6047
    template_path = create_temp_template_with_prefix(allocator,
4✔
6048
                                                     prefix,
2✔
6049
                                                     &template_capacity);
6050
    if (template_path == NULL) {
2!
6051
        sixel_helper_set_additional_message(
×
6052
            "clipboard: failed to allocate spool template.");
6053
        status = SIXEL_BAD_ALLOCATION;
×
6054
        goto end;
×
6055
    }
6056

6057
    if (sixel_compat_mktemp(template_path, template_capacity) != 0) {
2!
6058
        /* Fall back to tmpnam() when mktemp variants are unavailable. */
6059
        tmpname_result = tmpnam(template_path);
×
6060
        if (tmpname_result == NULL) {
×
6061
            sixel_helper_set_additional_message(
×
6062
                "clipboard: failed to reserve spool template.");
6063
            status = SIXEL_LIBC_ERROR;
×
6064
            goto end;
×
6065
        }
6066
        template_capacity = strlen(template_path) + 1u;
×
6067
    }
6068

6069
    open_flags = O_RDWR | O_CREAT | O_TRUNC;
2✔
6070
#if defined(O_EXCL)
6071
    open_flags |= O_EXCL;
2✔
6072
#endif
6073
    fd = sixel_compat_open(template_path, open_flags, S_IRUSR | S_IWUSR);
2✔
6074
    if (fd < 0) {
2!
6075
        sixel_helper_set_additional_message(
×
6076
            "clipboard: failed to open spool file.");
6077
        status = SIXEL_LIBC_ERROR;
×
6078
        goto end;
×
6079
    }
6080

6081
    *path_out = template_path;
2✔
6082
    if (fd_out != NULL) {
2!
6083
        *fd_out = fd;
1✔
6084
        fd = (-1);
1✔
6085
    }
1✔
6086

6087
    template_path = NULL;
2✔
6088
    status = SIXEL_OK;
2✔
6089

6090
end:
6091
    if (fd >= 0) {
2!
6092
        (void)sixel_compat_close(fd);
1✔
6093
    }
1✔
6094
    if (template_path != NULL) {
2!
6095
        sixel_allocator_free(allocator, template_path);
×
6096
    }
6097

6098
    return status;
2✔
6099
}
6100

6101

6102
static SIXELSTATUS
6103
clipboard_write_file(char const *path,
1✔
6104
                     unsigned char const *data,
6105
                     size_t size)
6106
{
6107
    FILE *stream;
6108
    size_t written;
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, "wb");
1✔
6117
    if (stream == NULL) {
1!
6118
        sixel_helper_set_additional_message(
×
6119
            "clipboard: failed to open spool file for write.");
6120
        return SIXEL_LIBC_ERROR;
×
6121
    }
6122

6123
    written = 0u;
1✔
6124
    if (size > 0u && data != NULL) {
1!
6125
        written = fwrite(data, 1u, size, stream);
1✔
6126
        if (written != size) {
1!
6127
            (void)fclose(stream);
×
6128
            sixel_helper_set_additional_message(
×
6129
                "clipboard: failed to write spool payload.");
6130
            return SIXEL_LIBC_ERROR;
×
6131
        }
6132
    }
1✔
6133

6134
    if (fclose(stream) != 0) {
1!
6135
        sixel_helper_set_additional_message(
×
6136
            "clipboard: failed to close spool file after write.");
6137
        return SIXEL_LIBC_ERROR;
×
6138
    }
6139

6140
    return SIXEL_OK;
1✔
6141
}
1✔
6142

6143

6144
static SIXELSTATUS
6145
clipboard_read_file(char const *path,
1✔
6146
                    unsigned char **data,
6147
                    size_t *size)
6148
{
6149
    FILE *stream;
6150
    long seek_result;
6151
    long file_size;
6152
    unsigned char *buffer;
6153
    size_t read_size;
6154

6155
    if (data == NULL || size == NULL) {
1!
6156
        sixel_helper_set_additional_message(
×
6157
            "clipboard: read buffer pointers are null.");
6158
        return SIXEL_BAD_ARGUMENT;
×
6159
    }
6160

6161
    *data = NULL;
1✔
6162
    *size = 0u;
1✔
6163

6164
    if (path == NULL) {
1!
6165
        sixel_helper_set_additional_message(
×
6166
            "clipboard: spool path is null.");
6167
        return SIXEL_BAD_ARGUMENT;
×
6168
    }
6169

6170
    stream = sixel_compat_fopen(path, "rb");
1✔
6171
    if (stream == NULL) {
1!
6172
        sixel_helper_set_additional_message(
×
6173
            "clipboard: failed to open spool file for read.");
6174
        return SIXEL_LIBC_ERROR;
×
6175
    }
6176

6177
    seek_result = fseek(stream, 0L, SEEK_END);
1✔
6178
    if (seek_result != 0) {
1!
6179
        (void)fclose(stream);
×
6180
        sixel_helper_set_additional_message(
×
6181
            "clipboard: failed to seek spool file.");
6182
        return SIXEL_LIBC_ERROR;
×
6183
    }
6184

6185
    file_size = ftell(stream);
1✔
6186
    if (file_size < 0) {
1!
6187
        (void)fclose(stream);
×
6188
        sixel_helper_set_additional_message(
×
6189
            "clipboard: failed to determine spool size.");
6190
        return SIXEL_LIBC_ERROR;
×
6191
    }
6192

6193
    seek_result = fseek(stream, 0L, SEEK_SET);
1✔
6194
    if (seek_result != 0) {
1!
6195
        (void)fclose(stream);
×
6196
        sixel_helper_set_additional_message(
×
6197
            "clipboard: failed to rewind spool file.");
6198
        return SIXEL_LIBC_ERROR;
×
6199
    }
6200

6201
    if (file_size == 0) {
1!
6202
        buffer = NULL;
×
6203
        read_size = 0u;
×
6204
    } else {
6205
        buffer = (unsigned char *)malloc((size_t)file_size);
1✔
6206
        if (buffer == NULL) {
1!
6207
            (void)fclose(stream);
×
6208
            sixel_helper_set_additional_message(
×
6209
                "clipboard: malloc() failed for spool payload.");
6210
            return SIXEL_BAD_ALLOCATION;
×
6211
        }
6212
        read_size = fread(buffer, 1u, (size_t)file_size, stream);
1✔
6213
        if (read_size != (size_t)file_size) {
1!
6214
            free(buffer);
×
6215
            (void)fclose(stream);
×
6216
            sixel_helper_set_additional_message(
×
6217
                "clipboard: failed to read spool payload.");
6218
            return SIXEL_LIBC_ERROR;
×
6219
        }
6220
    }
6221

6222
    if (fclose(stream) != 0) {
1!
6223
        if (buffer != NULL) {
×
6224
            free(buffer);
×
6225
        }
6226
        sixel_helper_set_additional_message(
×
6227
            "clipboard: failed to close spool file after read.");
6228
        return SIXEL_LIBC_ERROR;
×
6229
    }
6230

6231
    *data = buffer;
1✔
6232
    *size = read_size;
1✔
6233

6234
    return SIXEL_OK;
1✔
6235
}
1✔
6236

6237

6238
static SIXELSTATUS
6239
write_png_from_sixel(char const *sixel_path, char const *output_path)
9✔
6240
{
6241
    SIXELSTATUS status;
6242
    sixel_decoder_t *decoder;
6243

6244
    status = SIXEL_FALSE;
9✔
6245
    decoder = NULL;
9✔
6246

6247
    status = sixel_decoder_new(&decoder, NULL);
9✔
6248
    if (SIXEL_FAILED(status)) {
9!
6249
        goto end;
×
6250
    }
6251

6252
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_INPUT, sixel_path);
9✔
6253
    if (SIXEL_FAILED(status)) {
9!
6254
        goto end;
×
6255
    }
6256

6257
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_OUTPUT, output_path);
9✔
6258
    if (SIXEL_FAILED(status)) {
9!
6259
        goto end;
×
6260
    }
6261

6262
    status = sixel_decoder_decode(decoder);
9✔
6263

6264
end:
6✔
6265
    sixel_decoder_unref(decoder);
9✔
6266

6267
    return status;
9✔
6268
}
6269

6270

6271
/* load source data from specified file and encode it to SIXEL format
6272
 * output to encoder->outfd */
6273
SIXELAPI SIXELSTATUS
6274
sixel_encoder_encode(
436✔
6275
    sixel_encoder_t *encoder,   /* encoder object */
6276
    char const      *filename)  /* input filename */
6277
{
6278
    SIXELSTATUS status = SIXEL_FALSE;
436✔
6279
    SIXELSTATUS palette_status = SIXEL_OK;
436✔
6280
    int fuse_palette = 1;
436✔
6281
    sixel_loader_t *loader = NULL;
436✔
6282
    sixel_allocator_t *assessment_allocator = NULL;
436✔
6283
    sixel_frame_t *assessment_source_frame = NULL;
436✔
6284
    sixel_frame_t *assessment_target_frame = NULL;
436✔
6285
    sixel_frame_t *assessment_expanded_frame = NULL;
436✔
6286
    unsigned int assessment_section_mask =
436✔
6287
        encoder->assessment_sections & SIXEL_ASSESSMENT_SECTION_MASK;
436✔
6288
    int assessment_need_source_capture = 0;
436✔
6289
    int assessment_need_quantized_capture = 0;
436✔
6290
    int assessment_need_quality = 0;
436✔
6291
    int assessment_quality_quantized = 0;
436✔
6292
    assessment_json_sink_t assessment_sink;
6293
    FILE *assessment_json_file = NULL;
436✔
6294
    FILE *assessment_forward_stream = NULL;
436✔
6295
    int assessment_json_owned = 0;
436✔
6296
    char *assessment_temp_path = NULL;
436✔
6297
    size_t assessment_temp_capacity = 0u;
436✔
6298
    char *assessment_tmpnam_result = NULL;
436✔
6299
    sixel_assessment_spool_mode_t assessment_spool_mode
436✔
6300
        = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
6301
    char *assessment_forward_path = NULL;
436✔
6302
    size_t assessment_output_bytes;
6303
#if HAVE_SYS_STAT_H
6304
    struct stat assessment_stat;
6305
    int assessment_stat_result;
6306
    char const *assessment_size_path = NULL;
436✔
6307
#endif
6308
    char const *png_final_path = NULL;
436✔
6309
    char *png_temp_path = NULL;
436✔
6310
    size_t png_temp_capacity = 0u;
436✔
6311
    char *png_tmpnam_result = NULL;
436✔
6312
    int png_open_flags = 0;
436✔
6313
    int spool_required;
6314
    sixel_clipboard_spec_t clipboard_spec;
6315
    char clipboard_input_format[32];
6316
    char *clipboard_input_path;
6317
    unsigned char *clipboard_blob;
6318
    size_t clipboard_blob_size;
6319
    SIXELSTATUS clipboard_status;
6320
    char const *effective_filename;
6321

6322
    clipboard_input_format[0] = '\0';
436✔
6323
    clipboard_input_path = NULL;
436✔
6324
    clipboard_blob = NULL;
436✔
6325
    clipboard_blob_size = 0u;
436✔
6326
    clipboard_status = SIXEL_OK;
436✔
6327
    effective_filename = filename;
436✔
6328

6329
    clipboard_spec.is_clipboard = 0;
436✔
6330
    clipboard_spec.format[0] = '\0';
436✔
6331
    if (effective_filename != NULL
437!
6332
            && sixel_clipboard_parse_spec(effective_filename,
342!
6333
                                          &clipboard_spec)
6334
            && clipboard_spec.is_clipboard) {
99!
6335
        clipboard_select_format(clipboard_input_format,
2✔
6336
                                sizeof(clipboard_input_format),
6337
                                clipboard_spec.format,
1✔
6338
                                "sixel");
6339
        clipboard_status = sixel_clipboard_read(
1✔
6340
            clipboard_input_format,
1✔
6341
            &clipboard_blob,
6342
            &clipboard_blob_size,
6343
            encoder->allocator);
1✔
6344
        if (SIXEL_FAILED(clipboard_status)) {
1!
6345
            status = clipboard_status;
×
6346
            goto end;
×
6347
        }
6348
        clipboard_status = clipboard_create_spool(
1✔
6349
            encoder->allocator,
1✔
6350
            "clipboard-in",
6351
            &clipboard_input_path,
6352
            NULL);
6353
        if (SIXEL_FAILED(clipboard_status)) {
1!
6354
            status = clipboard_status;
×
6355
            goto end;
×
6356
        }
6357
        clipboard_status = clipboard_write_file(
1✔
6358
            clipboard_input_path,
1✔
6359
            clipboard_blob,
1✔
6360
            clipboard_blob_size);
1✔
6361
        if (SIXEL_FAILED(clipboard_status)) {
1!
6362
            status = clipboard_status;
×
6363
            goto end;
×
6364
        }
6365
        if (clipboard_blob != NULL) {
1!
6366
            free(clipboard_blob);
1✔
6367
            clipboard_blob = NULL;
1✔
6368
        }
1✔
6369
        effective_filename = clipboard_input_path;
1✔
6370
    }
1✔
6371

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

6487
    }
1✔
6488

6489
    if (encoder->output_is_png) {
436✔
6490
        png_temp_capacity = 0u;
9✔
6491
        png_tmpnam_result = NULL;
9✔
6492
        png_temp_path = create_temp_template(encoder->allocator,
9✔
6493
                                             &png_temp_capacity);
6494
        if (png_temp_path == NULL) {
9!
6495
            sixel_helper_set_additional_message(
×
6496
                "sixel_encoder_encode: malloc() failed for PNG staging path.");
6497
            status = SIXEL_BAD_ALLOCATION;
×
6498
            goto end;
×
6499
        }
6500
        if (sixel_compat_mktemp(png_temp_path, png_temp_capacity) != 0) {
9!
6501
            /* Fall back to tmpnam() when mktemp variants are unavailable. */
6502
            png_tmpnam_result = tmpnam(png_temp_path);
×
6503
            if (png_tmpnam_result == NULL) {
×
6504
                sixel_helper_set_additional_message(
×
6505
                    "sixel_encoder_encode: mktemp() failed for PNG staging file.");
6506
                status = SIXEL_RUNTIME_ERROR;
×
6507
                goto end;
×
6508
            }
6509
            png_temp_capacity = strlen(png_temp_path) + 1u;
×
6510
        }
6511
        if (encoder->outfd >= 0 && encoder->outfd != STDOUT_FILENO) {
9!
6512
            (void)sixel_compat_close(encoder->outfd);
9✔
6513
        }
3✔
6514
        png_open_flags = O_RDWR | O_CREAT | O_TRUNC;
9✔
6515
#if defined(O_EXCL)
6516
        png_open_flags |= O_EXCL;
9✔
6517
#endif
6518
        encoder->outfd = sixel_compat_open(png_temp_path,
12✔
6519
                                           png_open_flags,
3✔
6520
                                           S_IRUSR | S_IWUSR);
6521
        if (encoder->outfd < 0) {
9!
6522
            sixel_helper_set_additional_message(
×
6523
                "sixel_encoder_encode: failed to create the PNG target file.");
6524
            status = SIXEL_LIBC_ERROR;
×
6525
            goto end;
×
6526
        }
6527
    }
3✔
6528

6529
    if (encoder == NULL) {
436!
6530
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6531
#  pragma GCC diagnostic push
6532
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
6533
#endif
6534
        encoder = sixel_encoder_create();
×
6535
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6536
#  pragma GCC diagnostic pop
6537
#endif
6538
        if (encoder == NULL) {
×
6539
            sixel_helper_set_additional_message(
×
6540
                "sixel_encoder_encode: sixel_encoder_create() failed.");
6541
            status = SIXEL_BAD_ALLOCATION;
×
6542
            goto end;
×
6543
        }
6544
    } else {
6545
        sixel_encoder_ref(encoder);
436✔
6546
    }
6547

6548
    if (encoder->assessment_observer != NULL) {
436✔
6549
        sixel_assessment_stage_transition(
3✔
6550
            encoder->assessment_observer,
3✔
6551
            SIXEL_ASSESSMENT_STAGE_IMAGE_CHUNK);
6552
    }
1✔
6553
    encoder->last_loader_name[0] = '\0';
436✔
6554
    encoder->last_source_path[0] = '\0';
436✔
6555
    encoder->last_input_bytes = 0u;
436✔
6556

6557
    /* if required color is not set, set the max value */
6558
    if (encoder->reqcolors == (-1)) {
436✔
6559
        encoder->reqcolors = SIXEL_PALETTE_MAX;
418✔
6560
    }
140✔
6561

6562
    if (encoder->capture_source && encoder->capture_source_frame != NULL) {
436!
6563
        sixel_frame_unref(encoder->capture_source_frame);
×
6564
        encoder->capture_source_frame = NULL;
×
6565
    }
6566

6567
    /* if required color is less then 2, set the min value */
6568
    if (encoder->reqcolors < 2) {
436✔
6569
        encoder->reqcolors = SIXEL_PALETTE_MIN;
3✔
6570
    }
1✔
6571

6572
    /* if color space option is not set, choose RGB color space */
6573
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
436✔
6574
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
418✔
6575
    }
140✔
6576

6577
    /* if color option is not default value, prohibit to read
6578
       the file as a paletted image */
6579
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
436✔
6580
        fuse_palette = 0;
96✔
6581
    }
32✔
6582

6583
    /* if scaling options are set, prohibit to read the file as
6584
       a paletted image */
6585
    if (encoder->percentwidth > 0 ||
559✔
6586
        encoder->percentheight > 0 ||
424✔
6587
        encoder->pixelwidth > 0 ||
418✔
6588
        encoder->pixelheight > 0) {
400✔
6589
        fuse_palette = 0;
99✔
6590
    }
33✔
6591

6592
reload:
290✔
6593

6594
    sixel_helper_set_loader_trace(encoder->verbose);
436✔
6595
    sixel_helper_set_thumbnail_size_hint(
436✔
6596
        sixel_encoder_thumbnail_hint(encoder));
146✔
6597

6598
    status = sixel_loader_new(&loader, encoder->allocator);
436✔
6599
    if (SIXEL_FAILED(status)) {
436!
6600
        goto load_end;
×
6601
    }
6602

6603
    status = sixel_loader_setopt(loader,
582✔
6604
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
6605
                                 &encoder->fstatic);
436✔
6606
    if (SIXEL_FAILED(status)) {
436!
6607
        goto load_end;
×
6608
    }
6609

6610
    status = sixel_loader_setopt(loader,
436✔
6611
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
6612
                                 &fuse_palette);
6613
    if (SIXEL_FAILED(status)) {
436!
6614
        goto load_end;
×
6615
    }
6616

6617
    status = sixel_loader_setopt(loader,
582✔
6618
                                 SIXEL_LOADER_OPTION_REQCOLORS,
6619
                                 &encoder->reqcolors);
436✔
6620
    if (SIXEL_FAILED(status)) {
436!
6621
        goto load_end;
×
6622
    }
6623

6624
    status = sixel_loader_setopt(loader,
582✔
6625
                                 SIXEL_LOADER_OPTION_BGCOLOR,
6626
                                 encoder->bgcolor);
436✔
6627
    if (SIXEL_FAILED(status)) {
436!
6628
        goto load_end;
×
6629
    }
6630

6631
    status = sixel_loader_setopt(loader,
582✔
6632
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
6633
                                 &encoder->loop_mode);
436✔
6634
    if (SIXEL_FAILED(status)) {
436!
6635
        goto load_end;
×
6636
    }
6637

6638
    status = sixel_loader_setopt(loader,
582✔
6639
                                 SIXEL_LOADER_OPTION_INSECURE,
6640
                                 &encoder->finsecure);
436✔
6641
    if (SIXEL_FAILED(status)) {
436!
6642
        goto load_end;
×
6643
    }
6644

6645
    status = sixel_loader_setopt(loader,
582✔
6646
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
6647
                                 encoder->cancel_flag);
436✔
6648
    if (SIXEL_FAILED(status)) {
436!
6649
        goto load_end;
×
6650
    }
6651

6652
    status = sixel_loader_setopt(loader,
582✔
6653
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
6654
                                 encoder->loader_order);
436✔
6655
    if (SIXEL_FAILED(status)) {
436!
6656
        goto load_end;
×
6657
    }
6658

6659
    status = sixel_loader_setopt(loader,
582✔
6660
                                 SIXEL_LOADER_OPTION_CONTEXT,
6661
                                 encoder);
146✔
6662
    if (SIXEL_FAILED(status)) {
436!
6663
        goto load_end;
×
6664
    }
6665

6666
    /*
6667
     * Wire the optional assessment observer into the loader.
6668
     *
6669
     * The observer travels separately from the callback context so mapfile
6670
     * palette probes and other callbacks can keep using arbitrary structs.
6671
     */
6672
    status = sixel_loader_setopt(loader,
582✔
6673
                                 SIXEL_LOADER_OPTION_ASSESSMENT,
6674
                                 encoder->assessment_observer);
436✔
6675
    if (SIXEL_FAILED(status)) {
436!
6676
        goto load_end;
×
6677
    }
6678

6679
    status = sixel_loader_load_file(loader,
582✔
6680
                                    effective_filename,
146✔
6681
                                    load_image_callback);
6682
    if (status != SIXEL_OK) {
436✔
6683
        goto load_end;
15✔
6684
    }
6685
    encoder->last_input_bytes = sixel_loader_get_last_input_bytes(loader);
421✔
6686
    if (sixel_loader_get_last_success_name(loader) != NULL) {
421!
6687
        (void)snprintf(encoder->last_loader_name,
421✔
6688
                       sizeof(encoder->last_loader_name),
6689
                       "%s",
6690
                       sixel_loader_get_last_success_name(loader));
6691
    } else {
141✔
6692
        encoder->last_loader_name[0] = '\0';
×
6693
    }
6694
    if (sixel_loader_get_last_source_path(loader) != NULL) {
421✔
6695
        (void)snprintf(encoder->last_source_path,
283✔
6696
                       sizeof(encoder->last_source_path),
6697
                       "%s",
6698
                       sixel_loader_get_last_source_path(loader));
6699
    } else {
95✔
6700
        encoder->last_source_path[0] = '\0';
138✔
6701
    }
6702
    if (encoder->assessment_observer != NULL) {
422✔
6703
        sixel_assessment_record_loader(encoder->assessment_observer,
4✔
6704
                                       encoder->last_source_path,
3✔
6705
                                       encoder->last_loader_name,
3✔
6706
                                       encoder->last_input_bytes);
1✔
6707
    }
1✔
6708

6709
load_end:
278✔
6710
    sixel_loader_unref(loader);
436✔
6711
    loader = NULL;
436✔
6712

6713
    if (status != SIXEL_OK) {
436✔
6714
        goto end;
15✔
6715
    }
6716

6717
    palette_status = sixel_encoder_emit_palette_output(encoder);
421✔
6718
    if (SIXEL_FAILED(palette_status)) {
421!
6719
        status = palette_status;
×
6720
        goto end;
×
6721
    }
6722

6723
    if (encoder->pipe_mode) {
421!
6724
#if HAVE_CLEARERR
6725
        clearerr(stdin);
×
6726
#endif  /* HAVE_FSEEK */
6727
        while (encoder->cancel_flag && !*encoder->cancel_flag) {
×
6728
            status = sixel_tty_wait_stdin(1000000);
×
6729
            if (SIXEL_FAILED(status)) {
×
6730
                goto end;
×
6731
            }
6732
            if (status != SIXEL_OK) {
×
6733
                break;
×
6734
            }
6735
        }
6736
        if (!encoder->cancel_flag || !*encoder->cancel_flag) {
×
6737
            goto reload;
×
6738
        }
6739
    }
6740

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

6951
    if (encoder->output_is_png) {
421✔
6952
        png_final_path = encoder->output_png_to_stdout ? "-" : encoder->png_output_path;
9!
6953
        if (! encoder->output_png_to_stdout && png_final_path == NULL) {
9!
6954
            sixel_helper_set_additional_message(
×
6955
                "sixel_encoder_encode: missing PNG output path.");
6956
            status = SIXEL_RUNTIME_ERROR;
×
6957
            goto end;
×
6958
        }
6959
        status = write_png_from_sixel(png_temp_path, png_final_path);
9✔
6960
        if (SIXEL_FAILED(status)) {
9!
6961
            goto end;
×
6962
        }
6963
    }
3✔
6964

6965
    if (encoder->clipboard_output_active
421!
6966
            && encoder->clipboard_output_path != NULL) {
142!
6967
        unsigned char *clipboard_output_data;
6968
        size_t clipboard_output_size;
6969

6970
        clipboard_output_data = NULL;
1✔
6971
        clipboard_output_size = 0u;
1✔
6972

6973
        if (encoder->outfd
2!
6974
                && encoder->outfd != STDOUT_FILENO
1!
6975
                && encoder->outfd != STDERR_FILENO) {
1!
6976
            (void)sixel_compat_close(encoder->outfd);
1✔
6977
            encoder->outfd = STDOUT_FILENO;
1✔
6978
        }
1✔
6979

6980
        clipboard_status = clipboard_read_file(
1✔
6981
            encoder->clipboard_output_path,
1✔
6982
            &clipboard_output_data,
6983
            &clipboard_output_size);
6984
        if (SIXEL_SUCCEEDED(clipboard_status)) {
1!
6985
            clipboard_status = sixel_clipboard_write(
1✔
6986
                encoder->clipboard_output_format,
1✔
6987
                clipboard_output_data,
1✔
6988
                clipboard_output_size);
1✔
6989
        }
1✔
6990
        if (clipboard_output_data != NULL) {
1!
6991
            free(clipboard_output_data);
1✔
6992
        }
1✔
6993
        if (SIXEL_FAILED(clipboard_status)) {
1!
6994
            status = clipboard_status;
×
6995
            goto end;
×
6996
        }
6997
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
1✔
6998
        sixel_allocator_free(encoder->allocator,
2✔
6999
                             encoder->clipboard_output_path);
1✔
7000
        encoder->clipboard_output_path = NULL;
1✔
7001
        encoder->sixel_output_path = NULL;
1✔
7002
        encoder->clipboard_output_active = 0;
1✔
7003
        encoder->clipboard_output_format[0] = '\0';
1✔
7004
    }
1✔
7005

7006
    /* the status may not be SIXEL_OK */
7007

7008
end:
280✔
7009
    if (png_temp_path != NULL) {
436✔
7010
        (void)sixel_compat_unlink(png_temp_path);
9✔
7011
    }
3✔
7012
    sixel_allocator_free(encoder->allocator, png_temp_path);
436✔
7013
    if (clipboard_input_path != NULL) {
436!
7014
        (void)sixel_compat_unlink(clipboard_input_path);
1✔
7015
        sixel_allocator_free(encoder->allocator, clipboard_input_path);
1✔
7016
    }
1✔
7017
    if (clipboard_blob != NULL) {
436!
7018
        free(clipboard_blob);
×
7019
    }
7020
    if (encoder->clipboard_output_path != NULL) {
436!
7021
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
7022
        sixel_allocator_free(encoder->allocator,
×
7023
                             encoder->clipboard_output_path);
×
7024
        encoder->clipboard_output_path = NULL;
×
7025
        encoder->sixel_output_path = NULL;
×
7026
        encoder->clipboard_output_active = 0;
×
7027
        encoder->clipboard_output_format[0] = '\0';
×
7028
    }
7029
    sixel_allocator_free(encoder->allocator, encoder->png_output_path);
436✔
7030
    encoder->png_output_path = NULL;
436✔
7031
    if (assessment_forward_stream != NULL) {
436!
7032
        (void) fclose(assessment_forward_stream);
×
7033
    }
7034
    if (assessment_temp_path != NULL &&
436!
7035
            assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
7036
        (void)sixel_compat_unlink(assessment_temp_path);
×
7037
    }
7038
    sixel_allocator_free(encoder->allocator, assessment_temp_path);
436✔
7039
    sixel_allocator_free(encoder->allocator, assessment_forward_path);
436✔
7040
    if (assessment_json_owned && assessment_json_file != NULL) {
436!
7041
        (void) fclose(assessment_json_file);
3✔
7042
    }
1✔
7043
    if (assessment_target_frame != NULL) {
436!
7044
        sixel_frame_unref(assessment_target_frame);
×
7045
    }
7046
    if (assessment_expanded_frame != NULL) {
436!
7047
        sixel_frame_unref(assessment_expanded_frame);
×
7048
    }
7049
    if (assessment_source_frame != NULL) {
436✔
7050
        sixel_frame_unref(assessment_source_frame);
3✔
7051
    }
1✔
7052
    if (encoder->assessment_observer != NULL) {
436✔
7053
        sixel_assessment_unref(encoder->assessment_observer);
3✔
7054
        encoder->assessment_observer = NULL;
3✔
7055
    }
1✔
7056
    if (assessment_allocator != NULL) {
436✔
7057
        sixel_allocator_unref(assessment_allocator);
3✔
7058
    }
1✔
7059

7060
    sixel_encoder_unref(encoder);
436✔
7061

7062
    return status;
436✔
7063
}
7064

7065

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

7081
    if (encoder == NULL || bytes == NULL) {
×
7082
        status = SIXEL_BAD_ARGUMENT;
×
7083
        goto end;
×
7084
    }
7085

7086
    status = sixel_frame_new(&frame, encoder->allocator);
×
7087
    if (SIXEL_FAILED(status)) {
×
7088
        goto end;
×
7089
    }
7090

7091
    status = sixel_frame_init(frame, bytes, width, height,
×
7092
                              pixelformat, palette, ncolors);
7093
    if (SIXEL_FAILED(status)) {
×
7094
        goto end;
×
7095
    }
7096

7097
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
×
7098
    if (SIXEL_FAILED(status)) {
×
7099
        goto end;
×
7100
    }
7101

7102
    status = SIXEL_OK;
×
7103

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

7115

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

7130
    encoder->capture_source = enable ? 1 : 0;
3✔
7131
    if (!encoder->capture_source && encoder->capture_source_frame != NULL) {
3!
7132
        sixel_frame_unref(encoder->capture_source_frame);
×
7133
        encoder->capture_source_frame = NULL;
×
7134
    }
7135

7136
    return SIXEL_OK;
3✔
7137
}
1✔
7138

7139

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

7157
    encoder->capture_quantized = enable ? 1 : 0;
3✔
7158
    if (!encoder->capture_quantized) {
3!
7159
        encoder->capture_valid = 0;
×
7160
    }
7161

7162
    return SIXEL_OK;
3✔
7163
}
1✔
7164

7165

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

7182
    if (encoder == NULL || allocator == NULL || ppframe == NULL) {
×
7183
        sixel_helper_set_additional_message(
×
7184
            "sixel_encoder_copy_quantized_frame: invalid argument.");
7185
        return SIXEL_BAD_ARGUMENT;
×
7186
    }
7187

7188
    if (!encoder->capture_quantized || !encoder->capture_valid) {
×
7189
        sixel_helper_set_additional_message(
×
7190
            "sixel_encoder_copy_quantized_frame: no frame captured.");
7191
        return SIXEL_RUNTIME_ERROR;
×
7192
    }
7193

7194
    *ppframe = NULL;
×
7195
    frame = NULL;
×
7196
    pixels = NULL;
×
7197
    palette = NULL;
×
7198

7199
    status = sixel_frame_new(&frame, allocator);
×
7200
    if (SIXEL_FAILED(status)) {
×
7201
        return status;
×
7202
    }
7203

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

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

7235
    status = sixel_frame_init(frame,
×
7236
                              pixels,
7237
                              encoder->capture_width,
7238
                              encoder->capture_height,
7239
                              encoder->capture_pixelformat,
7240
                              palette,
7241
                              encoder->capture_ncolors);
7242
    if (SIXEL_FAILED(status)) {
×
7243
        goto cleanup;
×
7244
    }
7245

7246
    pixels = NULL;
×
7247
    palette = NULL;
×
7248
    frame->colorspace = encoder->capture_colorspace;
×
7249
    *ppframe = frame;
×
7250
    return SIXEL_OK;
×
7251

7252
cleanup:
7253
    if (palette != NULL) {
×
7254
        sixel_allocator_free(allocator, palette);
×
7255
    }
7256
    if (pixels != NULL) {
×
7257
        sixel_allocator_free(allocator, pixels);
×
7258
    }
7259
    if (frame != NULL) {
×
7260
        sixel_frame_unref(frame);
×
7261
    }
7262
    return status;
×
7263
}
7264

7265

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

7287
    status = SIXEL_OK;
421✔
7288
    frame = NULL;
421✔
7289
    palette = NULL;
421✔
7290
    exported_colors = 0;
421✔
7291
    stream = NULL;
421✔
7292
    close_stream = 0;
421✔
7293
    path = NULL;
421✔
7294
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
421✔
7295
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
421✔
7296
    format_final = SIXEL_PALETTE_FORMAT_NONE;
421✔
7297
    mode = "wb";
421✔
7298

7299
    if (encoder == NULL || encoder->palette_output == NULL) {
421!
7300
        return SIXEL_OK;
421✔
7301
    }
7302

7303
    status = sixel_encoder_copy_quantized_frame(encoder,
×
7304
                                                encoder->allocator,
7305
                                                &frame);
7306
    if (SIXEL_FAILED(status)) {
×
7307
        return status;
×
7308
    }
7309

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

7322
    path = sixel_palette_strip_prefix(encoder->palette_output, &format_hint);
×
7323
    if (path == NULL || *path == '\0') {
×
7324
        sixel_helper_set_additional_message(
×
7325
            "sixel_encoder_emit_palette_output: invalid path.");
7326
        status = SIXEL_BAD_ARGUMENT;
×
7327
        goto cleanup;
×
7328
    }
7329

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

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

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

7417
    if (close_stream) {
×
7418
        if (fclose(stream) != 0) {
×
7419
            sixel_helper_set_additional_message(
×
7420
                "sixel_encoder_emit_palette_output: fclose() failed.");
7421
            status = SIXEL_LIBC_ERROR;
×
7422
            stream = NULL;
×
7423
            goto cleanup;
×
7424
        }
7425
        stream = NULL;
×
7426
    } else {
7427
        if (fflush(stream) != 0) {
×
7428
            sixel_helper_set_additional_message(
×
7429
                "sixel_encoder_emit_palette_output: fflush() failed.");
7430
            status = SIXEL_LIBC_ERROR;
×
7431
            goto cleanup;
×
7432
        }
7433
    }
7434

7435
cleanup:
7436
    if (close_stream && stream != NULL) {
×
7437
        (void) fclose(stream);
×
7438
    }
7439
    if (frame != NULL) {
×
7440
        sixel_frame_unref(frame);
×
7441
    }
7442

7443
    return status;
×
7444
}
141✔
7445

7446

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

7461
    if (!encoder->capture_source || encoder->capture_source_frame == NULL) {
3!
7462
        sixel_helper_set_additional_message(
×
7463
            "sixel_encoder_copy_source_frame: no frame captured.");
7464
        return SIXEL_RUNTIME_ERROR;
×
7465
    }
7466

7467
    sixel_frame_ref(encoder->capture_source_frame);
3✔
7468
    *ppframe = encoder->capture_source_frame;
3✔
7469

7470
    return SIXEL_OK;
3✔
7471
}
1✔
7472

7473

7474
#if HAVE_TESTS
7475
static int
7476
test1(void)
×
7477
{
7478
    int nret = EXIT_FAILURE;
×
7479
    sixel_encoder_t *encoder = NULL;
×
7480

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

7496
error:
7497
    sixel_encoder_unref(encoder);
×
7498
    return nret;
×
7499
}
7500

7501

7502
static int
7503
test2(void)
×
7504
{
7505
    int nret = EXIT_FAILURE;
×
7506
    SIXELSTATUS status;
7507
    sixel_encoder_t *encoder = NULL;
×
7508
    sixel_frame_t *frame = NULL;
×
7509
    unsigned char *buffer;
7510
    int height = 0;
×
7511
    int is_animation = 0;
×
7512

7513
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7514
#  pragma GCC diagnostic push
7515
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7516
#endif
7517
    encoder = sixel_encoder_create();
×
7518
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7519
#  pragma GCC diagnostic pop
7520
#endif
7521
    if (encoder == NULL) {
×
7522
        goto error;
×
7523
    }
7524

7525
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7526
#  pragma GCC diagnostic push
7527
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7528
#endif
7529
    frame = sixel_frame_create();
×
7530
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7531
#  pragma GCC diagnostic pop
7532
#endif
7533
    if (encoder == NULL) {
×
7534
        goto error;
×
7535
    }
7536

7537
    buffer = (unsigned char *)sixel_allocator_malloc(encoder->allocator, 3);
×
7538
    if (buffer == NULL) {
×
7539
        goto error;
×
7540
    }
7541
    status = sixel_frame_init(frame, buffer, 1, 1,
×
7542
                              SIXEL_PIXELFORMAT_RGB888,
7543
                              NULL, 0);
7544
    if (SIXEL_FAILED(status)) {
×
7545
        goto error;
×
7546
    }
7547

7548
    if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
×
7549
        is_animation = 1;
×
7550
    }
7551

7552
    height = sixel_frame_get_height(frame);
×
7553

7554
    status = sixel_tty_scroll(sixel_write_callback,
×
7555
                              &encoder->outfd,
×
7556
                              encoder->outfd,
7557
                              height,
7558
                              is_animation);
7559
    if (SIXEL_FAILED(status)) {
×
7560
        goto error;
×
7561
    }
7562

7563
    nret = EXIT_SUCCESS;
×
7564

7565
error:
7566
    sixel_encoder_unref(encoder);
×
7567
    sixel_frame_unref(frame);
×
7568
    return nret;
×
7569
}
7570

7571

7572
static int
7573
test3(void)
×
7574
{
7575
    int nret = EXIT_FAILURE;
×
7576
    int result;
7577

7578
    result = sixel_tty_wait_stdin(1000);
×
7579
    if (result != 0) {
×
7580
        goto error;
×
7581
    }
7582

7583
    nret = EXIT_SUCCESS;
×
7584

7585
error:
7586
    return nret;
×
7587
}
7588

7589

7590
static int
7591
test4(void)
×
7592
{
7593
    int nret = EXIT_FAILURE;
×
7594
    sixel_encoder_t *encoder = NULL;
×
7595
    SIXELSTATUS status;
7596

7597
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7598
# pragma GCC diagnostic push
7599
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7600
#endif
7601
    encoder = sixel_encoder_create();
×
7602
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7603
# pragma GCC diagnostic pop
7604
#endif
7605
    if (encoder == NULL) {
×
7606
        goto error;
×
7607
    }
7608

7609
    status = sixel_encoder_setopt(encoder,
×
7610
                                  SIXEL_OPTFLAG_LOOPMODE,
7611
                                  "force");
7612
    if (SIXEL_FAILED(status)) {
×
7613
        goto error;
×
7614
    }
7615

7616
    status = sixel_encoder_setopt(encoder,
×
7617
                                  SIXEL_OPTFLAG_PIPE_MODE,
7618
                                  "force");
7619
    if (SIXEL_FAILED(status)) {
×
7620
        goto error;
×
7621
    }
7622

7623
    nret = EXIT_SUCCESS;
×
7624

7625
error:
7626
    sixel_encoder_unref(encoder);
×
7627
    return nret;
×
7628
}
7629

7630

7631
static int
7632
test5(void)
×
7633
{
7634
    int nret = EXIT_FAILURE;
×
7635
    sixel_encoder_t *encoder = NULL;
×
7636
    sixel_allocator_t *allocator = NULL;
×
7637
    SIXELSTATUS status;
7638

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

7644
    status = sixel_encoder_new(&encoder, allocator);
×
7645
    if (SIXEL_FAILED(status)) {
×
7646
        goto error;
×
7647
    }
7648

7649
    sixel_encoder_ref(encoder);
×
7650
    sixel_encoder_unref(encoder);
×
7651
    nret = EXIT_SUCCESS;
×
7652

7653
error:
7654
    sixel_encoder_unref(encoder);
×
7655
    return nret;
×
7656
}
7657

7658

7659
SIXELAPI int
7660
sixel_encoder_tests_main(void)
×
7661
{
7662
    int nret = EXIT_FAILURE;
×
7663
    size_t i;
7664
    typedef int (* testcase)(void);
7665

7666
    static testcase const testcases[] = {
7667
        test1,
7668
        test2,
7669
        test3,
7670
        test4,
7671
        test5
7672
    };
7673

7674
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
7675
        nret = testcases[i]();
×
7676
        if (nret != EXIT_SUCCESS) {
×
7677
            goto error;
×
7678
        }
7679
    }
7680

7681
    nret = EXIT_SUCCESS;
×
7682

7683
error:
7684
    return nret;
×
7685
}
7686
#endif  /* HAVE_TESTS */
7687

7688

7689
/* emacs Local Variables:      */
7690
/* emacs mode: c               */
7691
/* emacs tab-width: 4          */
7692
/* emacs indent-tabs-mode: nil */
7693
/* emacs c-basic-offset: 4     */
7694
/* emacs End:                  */
7695
/* vim: set expandtab ts=4 : */
7696
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc