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

saitoha / libsixel / 19211260254

09 Nov 2025 04:26PM UTC coverage: 46.579% (+0.3%) from 46.268%
19211260254

push

github

saitoha
palette: move palette generation into palette module

8397 of 26675 branches covered (31.48%)

294 of 414 new or added lines in 5 files covered. (71.01%)

3 existing lines in 1 file now uncovered.

11960 of 25677 relevant lines covered (46.58%)

1899906.87 hits per line

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

50.37
/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,515✔
280
        if (strncmp(choices[index].name, value, value_length) == 0) {
1,431✔
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,236✔
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
    { "lso2", SIXEL_DIFFUSE_LSO2 },
383
};
384

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

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

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

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

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

417
static sixel_option_choice_t const g_option_choices_final_merge[] = {
418
    { "auto", SIXEL_FINAL_MERGE_AUTO },
419
    { "none", SIXEL_FINAL_MERGE_NONE },
420
    { "ward", SIXEL_FINAL_MERGE_WARD }
421
};
422

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

436
static sixel_option_choice_t const g_option_choices_quality[] = {
437
    { "auto", SIXEL_QUALITY_AUTO },
438
    { "high", SIXEL_QUALITY_HIGH },
439
    { "low", SIXEL_QUALITY_LOW },
440
    { "full", SIXEL_QUALITY_FULL }
441
};
442

443
static sixel_option_choice_t const g_option_choices_loopmode[] = {
444
    { "auto", SIXEL_LOOP_AUTO },
445
    { "force", SIXEL_LOOP_FORCE },
446
    { "disable", SIXEL_LOOP_DISABLE }
447
};
448

449
static sixel_option_choice_t const g_option_choices_palette_type[] = {
450
    { "auto", SIXEL_PALETTETYPE_AUTO },
451
    { "hls", SIXEL_PALETTETYPE_HLS },
452
    { "rgb", SIXEL_PALETTETYPE_RGB }
453
};
454

455
static sixel_option_choice_t const g_option_choices_encode_policy[] = {
456
    { "auto", SIXEL_ENCODEPOLICY_AUTO },
457
    { "fast", SIXEL_ENCODEPOLICY_FAST },
458
    { "size", SIXEL_ENCODEPOLICY_SIZE }
459
};
460

461
static sixel_option_choice_t const g_option_choices_lut_policy[] = {
462
    { "auto", SIXEL_LUT_POLICY_AUTO },
463
    { "5bit", SIXEL_LUT_POLICY_5BIT },
464
    { "6bit", SIXEL_LUT_POLICY_6BIT },
465
    { "robinhood", SIXEL_LUT_POLICY_ROBINHOOD },
466
    { "hopscotch", SIXEL_LUT_POLICY_HOPSCOTCH },
467
    { "certlut", SIXEL_LUT_POLICY_CERTLUT }
468
};
469

470
static sixel_option_choice_t const g_option_choices_working_colorspace[] = {
471
    { "gamma", SIXEL_COLORSPACE_GAMMA },
472
    { "linear", SIXEL_COLORSPACE_LINEAR },
473
    { "oklab", SIXEL_COLORSPACE_OKLAB }
474
};
475

476
static sixel_option_choice_t const g_option_choices_output_colorspace[] = {
477
    { "gamma", SIXEL_COLORSPACE_GAMMA },
478
    { "linear", SIXEL_COLORSPACE_LINEAR },
479
    { "smpte-c", SIXEL_COLORSPACE_SMPTEC },
480
    { "smptec", SIXEL_COLORSPACE_SMPTEC }
481
};
482

483

484
static char *
485
arg_strdup(
57✔
486
    char const          /* in */ *s,          /* source buffer */
487
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
488
                                                 destination buffer */
489
{
490
    char *p;
491
    size_t len;
492

493
    len = strlen(s);
57✔
494

495
    p = (char *)sixel_allocator_malloc(allocator, len + 1);
57✔
496
    if (p) {
57!
497
        (void)sixel_compat_strcpy(p, len + 1, s);
57✔
498
    }
19✔
499
    return p;
57✔
500
}
501

502

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

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

651
    status = SIXEL_OK;
24✔
652

653
end:
30✔
654
    sixel_allocator_free(allocator, buf);
45✔
655

656
    return status;
45✔
657
}
658

659

660
/* generic writer function for passing to sixel_output_new() */
661
static int
662
sixel_write_callback(char *data, int size, void *priv)
5,829✔
663
{
664
    int result;
665

666
    result = (int)sixel_compat_write(*(int *)priv,
7,966✔
667
                                     data,
2,137✔
668
                                     (size_t)size);
2,137✔
669

670
    return result;
5,829✔
671
}
672

673

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

686
    for (i = j = 0; i < size; ++i, ++j) {
634,503✔
687
        hex[j] = (data[i] >> 4) & 0xf;
634,430✔
688
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
634,430!
689
        hex[++j] = data[i] & 0xf;
634,430✔
690
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
634,430✔
691
    }
166,962✔
692

693
    result = (int)sixel_compat_write(*(int *)priv,
146✔
694
                                     hex,
25✔
695
                                     (size_t)(size * 2));
73✔
696

697
    return result;
73✔
698
}
699

700
typedef struct sixel_encoder_output_probe {
701
    sixel_encoder_t *encoder;
702
    sixel_write_function base_write;
703
    void *base_priv;
704
} sixel_encoder_output_probe_t;
705

706
static int
707
sixel_write_with_probe(char *data, int size, void *priv)
63✔
708
{
709
    sixel_encoder_output_probe_t *probe;
710
    int written;
711
    double started_at;
712
    double finished_at;
713
    double duration;
714

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

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

763
    probe.encoder = encoder;
×
764
    probe.base_write = sixel_write_callback;
×
765
    probe.base_priv = &fd;
×
766
    written = sixel_write_with_probe(data, size, &probe);
×
767

768
    return written;
×
769
}
770

771
static SIXELSTATUS
772
sixel_encoder_ensure_cell_size(sixel_encoder_t *encoder)
×
773
{
774
#if defined(TIOCGWINSZ)
775
    struct winsize ws;
776
    int result;
777
    int fd = 0;
×
778

779
    if (encoder->cell_width > 0 && encoder->cell_height > 0) {
×
780
        return SIXEL_OK;
×
781
    }
782

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

798
    if (ws.ws_col <= 0 || ws.ws_row <= 0 ||
×
799
        ws.ws_xpixel <= ws.ws_col || ws.ws_ypixel <= ws.ws_row) {
×
800
        sixel_helper_set_additional_message(
×
801
            "terminal does not report pixel cell size for drcs option.");
802
        return SIXEL_BAD_ARGUMENT;
×
803
    }
804

805
    encoder->cell_width = ws.ws_xpixel / ws.ws_col;
×
806
    encoder->cell_height = ws.ws_ypixel / ws.ws_row;
×
807
    if (encoder->cell_width <= 0 || encoder->cell_height <= 0) {
×
808
        sixel_helper_set_additional_message(
×
809
            "terminal cell size reported zero via ioctl().");
810
        return SIXEL_BAD_ARGUMENT;
×
811
    }
812

813
    return SIXEL_OK;
×
814
#else
815
    (void) encoder;
816
    sixel_helper_set_additional_message(
817
        "drcs option is not supported on this platform.");
818
    return SIXEL_NOT_IMPLEMENTED;
819
#endif
820
}
821

822

823
/* returns monochrome dithering context object */
824
static SIXELSTATUS
825
sixel_prepare_monochrome_palette(
12✔
826
    sixel_dither_t  /* out */ **dither,
827
     int            /* in */  finvert)
828
{
829
    SIXELSTATUS status = SIXEL_FALSE;
12✔
830

831
    if (finvert) {
12✔
832
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_LIGHT);
3✔
833
    } else {
1✔
834
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_DARK);
9✔
835
    }
836
    if (*dither == NULL) {
12!
837
        sixel_helper_set_additional_message(
×
838
            "sixel_prepare_monochrome_palette: sixel_dither_get() failed.");
839
        status = SIXEL_RUNTIME_ERROR;
×
840
        goto end;
×
841
    }
842

843
    status = SIXEL_OK;
12✔
844

845
end:
8✔
846
    return status;
12✔
847
}
848

849

850
/* returns dithering context object with specified builtin palette */
851
typedef struct palette_conversion {
852
    unsigned char *original;
853
    unsigned char *copy;
854
    size_t size;
855
    int convert_inplace;
856
    int converted;
857
    int frame_colorspace;
858
} palette_conversion_t;
859

860
static SIXELSTATUS
861
sixel_encoder_convert_palette(sixel_encoder_t *encoder,
529✔
862
                              sixel_output_t *output,
863
                              sixel_dither_t *dither,
864
                              int frame_colorspace,
865
                              int pixelformat,
866
                              palette_conversion_t *ctx)
867
{
868
    SIXELSTATUS status = SIXEL_OK;
529✔
869
    unsigned char *palette;
870
    int palette_colors;
871

872
    ctx->original = NULL;
529✔
873
    ctx->copy = NULL;
529✔
874
    ctx->size = 0;
529✔
875
    ctx->convert_inplace = 0;
529✔
876
    ctx->converted = 0;
529✔
877
    ctx->frame_colorspace = frame_colorspace;
529✔
878

879
    palette = sixel_dither_get_palette(dither);
529✔
880
    palette_colors = sixel_dither_get_num_of_palette_colors(dither);
529✔
881
    ctx->original = palette;
529✔
882

883
    if (palette == NULL || palette_colors <= 0 ||
529!
884
            frame_colorspace == output->colorspace) {
529!
885
        return SIXEL_OK;
529✔
886
    }
887

888
    ctx->size = (size_t)palette_colors * 3;
×
889

890
    output->pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
891
    output->source_colorspace = frame_colorspace;
×
892

NEW
893
    ctx->copy = (unsigned char *)sixel_allocator_malloc(encoder->allocator,
×
894
                                                        ctx->size);
NEW
895
    if (ctx->copy == NULL) {
×
NEW
896
        sixel_helper_set_additional_message(
×
897
            "sixel_encoder_convert_palette: "
898
            "sixel_allocator_malloc() failed.");
NEW
899
        status = SIXEL_BAD_ALLOCATION;
×
NEW
900
        goto end;
×
901
    }
NEW
902
    memcpy(ctx->copy, palette, ctx->size);
×
903

904
    status = sixel_output_convert_colorspace(output,
×
905
                                             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 != NULL && ctx->size > 0) {
529!
925
        unsigned char *palette;
926

NEW
927
        palette = sixel_dither_get_palette(dither);
×
NEW
928
        if (palette != NULL) {
×
NEW
929
            memcpy(palette, ctx->copy, ctx->size);
×
930
        }
931
        sixel_allocator_free(encoder->allocator, ctx->copy);
×
932
        ctx->copy = NULL;
×
933
    } else if (ctx->convert_inplace && ctx->converted &&
529!
934
               ctx->original && ctx->size > 0) {
×
935
        (void)sixel_helper_convert_colorspace(ctx->original,
×
936
                                              ctx->size,
937
                                              SIXEL_PIXELFORMAT_RGB888,
938
                                              SIXEL_COLORSPACE_GAMMA,
939
                                              ctx->frame_colorspace);
940
    }
941
}
529✔
942

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

967
    /*
968
     * Preserve the quantized frame for assessment observers.
969
     *
970
     *     +-----------------+     +---------------------+
971
     *     | quantized bytes | --> | encoder->capture_*  |
972
     *     +-----------------+     +---------------------+
973
     */
974

975
    status = SIXEL_OK;
3✔
976
    palette = NULL;
3✔
977
    ncolors = 0;
3✔
978
    palette_bytes = 0;
3✔
979
    new_pixels = NULL;
3✔
980
    new_palette = NULL;
3✔
981
    capture_bytes = size;
3✔
982
    capture_source = pixels;
3✔
983
    paletted_pixels = NULL;
3✔
984
    quantized_pixels = 0;
3✔
985
    dither_allocator = NULL;
3✔
986

987
    if (encoder == NULL || pixels == NULL ||
3!
988
            (dither == NULL && size == 0)) {
1!
989
        sixel_helper_set_additional_message(
×
990
            "sixel_encoder_capture_quantized: invalid capture request.");
991
        return SIXEL_BAD_ARGUMENT;
×
992
    }
993

994
    if (!encoder->capture_quantized) {
3!
995
        return SIXEL_OK;
×
996
    }
997

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

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

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

1078
    encoder->capture_width = width;
3✔
1079
    encoder->capture_height = height;
3✔
1080
    if (dither != NULL) {
3!
1081
        encoder->capture_pixelformat = SIXEL_PIXELFORMAT_PAL8;
3✔
1082
    } else {
1✔
1083
        encoder->capture_pixelformat = pixelformat;
×
1084
    }
1085
    encoder->capture_colorspace = colorspace;
3✔
1086
    encoder->capture_palette_size = palette_bytes;
3✔
1087
    encoder->capture_ncolors = ncolors;
3✔
1088
    encoder->capture_valid = 1;
3✔
1089

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

1107
    return status;
3✔
1108
}
1✔
1109

1110
static SIXELSTATUS
1111
sixel_prepare_builtin_palette(
27✔
1112
    sixel_dither_t /* out */ **dither,
1113
    int            /* in */  builtin_palette)
1114
{
1115
    SIXELSTATUS status = SIXEL_FALSE;
27✔
1116

1117
    *dither = sixel_dither_get(builtin_palette);
27✔
1118
    if (*dither == NULL) {
27!
1119
        sixel_helper_set_additional_message(
×
1120
            "sixel_prepare_builtin_palette: sixel_dither_get() failed.");
1121
        status = SIXEL_RUNTIME_ERROR;
×
1122
        goto end;
×
1123
    }
1124

1125
    status = SIXEL_OK;
27✔
1126

1127
end:
18✔
1128
    return status;
27✔
1129
}
1130

1131
static int
1132
sixel_encoder_thumbnail_hint(sixel_encoder_t *encoder)
457✔
1133
{
1134
    int width_hint;
1135
    int height_hint;
1136
    long base;
1137
    long size;
1138

1139
    width_hint = 0;
457✔
1140
    height_hint = 0;
457✔
1141
    base = 0;
457✔
1142
    size = 0;
457✔
1143

1144
    if (encoder == NULL) {
457!
1145
        return 0;
×
1146
    }
1147

1148
    width_hint = encoder->pixelwidth;
457✔
1149
    height_hint = encoder->pixelheight;
457✔
1150

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

1169
    size = base * 2L;
93✔
1170
    if (size > (long)INT_MAX) {
93!
1171
        size = (long)INT_MAX;
×
1172
    }
1173
    if (size < 1L) {
93!
1174
        size = 1L;
×
1175
    }
1176

1177
    return (int)size;
93✔
1178
}
153✔
1179

1180

1181
typedef struct sixel_callback_context_for_mapfile {
1182
    int reqcolors;
1183
    sixel_dither_t *dither;
1184
    sixel_allocator_t *allocator;
1185
    int working_colorspace;
1186
    int lut_policy;
1187
} sixel_callback_context_for_mapfile_t;
1188

1189

1190
/* callback function for sixel_helper_load_image_file() */
1191
static SIXELSTATUS
1192
load_image_callback_for_palette(
21✔
1193
    sixel_frame_t   /* in */    *frame, /* frame object from image loader */
1194
    void            /* in */    *data)  /* private data */
1195
{
1196
    SIXELSTATUS status = SIXEL_FALSE;
21✔
1197
    sixel_callback_context_for_mapfile_t *callback_context;
1198

1199
    /* get callback context object from the private data */
1200
    callback_context = (sixel_callback_context_for_mapfile_t *)data;
21✔
1201

1202
    status = sixel_frame_ensure_colorspace(frame,
28✔
1203
                                           callback_context->working_colorspace);
7✔
1204
    if (SIXEL_FAILED(status)) {
21!
1205
        goto end;
×
1206
    }
1207

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

1226
        sixel_dither_set_lut_policy(callback_context->dither,
4✔
1227
                                    callback_context->lut_policy);
1✔
1228

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

1270
        sixel_dither_set_lut_policy(callback_context->dither,
24✔
1271
                                    callback_context->lut_policy);
6✔
1272

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

1287
        /* success */
1288
        status = SIXEL_OK;
18✔
1289

1290
        break;
18✔
1291
    }
7✔
1292

1293
end:
14✔
1294
    return status;
21✔
1295
}
1296

1297

1298
static SIXELSTATUS
1299
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder);
1300

1301

1302
static int
1303
sixel_path_has_extension(char const *path, char const *extension)
63✔
1304
{
1305
    size_t path_len;
1306
    size_t ext_len;
1307
    size_t index;
1308

1309
    path_len = 0u;
63✔
1310
    ext_len = 0u;
63✔
1311
    index = 0u;
63✔
1312

1313
    if (path == NULL || extension == NULL) {
63!
1314
        return 0;
×
1315
    }
1316

1317
    path_len = strlen(path);
63✔
1318
    ext_len = strlen(extension);
63✔
1319
    if (ext_len == 0u || path_len < ext_len) {
63!
1320
        return 0;
×
1321
    }
1322

1323
    for (index = 0u; index < ext_len; ++index) {
144!
1324
        unsigned char path_ch;
1325
        unsigned char ext_ch;
1326

1327
        path_ch = (unsigned char)path[path_len - ext_len + index];
144✔
1328
        ext_ch = (unsigned char)extension[index];
144✔
1329
        if (tolower(path_ch) != tolower(ext_ch)) {
144✔
1330
            return 0;
63✔
1331
        }
1332
    }
27✔
1333

1334
    return 1;
×
1335
}
21✔
1336

1337
typedef enum sixel_palette_format {
1338
    SIXEL_PALETTE_FORMAT_NONE = 0,
1339
    SIXEL_PALETTE_FORMAT_ACT,
1340
    SIXEL_PALETTE_FORMAT_PAL_JASC,
1341
    SIXEL_PALETTE_FORMAT_PAL_RIFF,
1342
    SIXEL_PALETTE_FORMAT_PAL_AUTO,
1343
    SIXEL_PALETTE_FORMAT_GPL
1344
} sixel_palette_format_t;
1345

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

1366
    colon = NULL;
21✔
1367
    type_len = 0u;
21✔
1368
    index = 0u;
21✔
1369

1370
    if (format_hint != NULL) {
21!
1371
        *format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
1372
    }
7✔
1373
    if (spec == NULL) {
21!
1374
        return NULL;
×
1375
    }
1376

1377
    colon = strchr(spec, ':');
21✔
1378
    if (colon == NULL) {
21!
1379
        return spec;
21✔
1380
    }
1381

1382
    type_len = (size_t)(colon - spec);
×
1383
    if (type_len == 0u || type_len >= sizeof(lowered)) {
×
1384
        return spec;
×
1385
    }
1386

1387
    for (index = 0u; index < type_len; ++index) {
×
1388
        lowered[index] = (char)tolower((unsigned char)spec[index]);
×
1389
    }
1390
    lowered[type_len] = '\0';
×
1391

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

1423
    return spec;
×
1424
}
7✔
1425

1426
static sixel_palette_format_t
1427
sixel_palette_format_from_extension(char const *path)
21✔
1428
{
1429
    if (path == NULL) {
21!
1430
        return SIXEL_PALETTE_FORMAT_NONE;
×
1431
    }
1432

1433
    if (sixel_path_has_extension(path, ".act")) {
21!
1434
        return SIXEL_PALETTE_FORMAT_ACT;
×
1435
    }
1436
    if (sixel_path_has_extension(path, ".pal")) {
21!
1437
        return SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1438
    }
1439
    if (sixel_path_has_extension(path, ".gpl")) {
21!
1440
        return SIXEL_PALETTE_FORMAT_GPL;
×
1441
    }
1442

1443
    return SIXEL_PALETTE_FORMAT_NONE;
21✔
1444
}
7✔
1445

1446
static int
1447
sixel_path_has_any_extension(char const *path)
21✔
1448
{
1449
    char const *slash_forward;
1450
#if defined(_WIN32)
1451
    char const *slash_backward;
1452
#endif
1453
    char const *start;
1454
    char const *dot;
1455

1456
    slash_forward = NULL;
21✔
1457
#if defined(_WIN32)
1458
    slash_backward = NULL;
1459
#endif
1460
    start = path;
21✔
1461
    dot = NULL;
21✔
1462

1463
    if (path == NULL) {
21!
1464
        return 0;
×
1465
    }
1466

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

1481
    dot = strrchr(start, '.');
21✔
1482
    if (dot == NULL) {
21!
1483
        return 0;
×
1484
    }
1485

1486
    if (dot[1] == '\0') {
21!
1487
        return 0;
×
1488
    }
1489

1490
    return 1;
21✔
1491
}
7✔
1492

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

1505

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

1530
    status = SIXEL_FALSE;
×
1531
    buffer = NULL;
×
1532
    grown = NULL;
×
1533
    capacity = 0u;
×
1534
    used = 0u;
×
1535
    read_bytes = 0u;
×
1536
    needed = 0u;
×
1537
    new_capacity = 0u;
×
1538

1539
    if (pdata == NULL || psize == NULL || stream == NULL || allocator == NULL) {
×
1540
        sixel_helper_set_additional_message(
×
1541
            "sixel_palette_read_stream: invalid argument.");
1542
        return SIXEL_BAD_ARGUMENT;
×
1543
    }
1544

1545
    *pdata = NULL;
×
1546
    *psize = 0u;
×
1547

1548
    while (1) {
1549
        read_bytes = fread(scratch, 1, sizeof(scratch), stream);
×
1550
        if (read_bytes == 0u) {
×
1551
            if (ferror(stream)) {
×
1552
                sixel_helper_set_additional_message(
×
1553
                    "sixel_palette_read_stream: fread() failed.");
1554
                status = SIXEL_LIBC_ERROR;
×
1555
                goto cleanup;
×
1556
            }
1557
            break;
×
1558
        }
1559

1560
        if (used > SIZE_MAX - read_bytes) {
×
1561
            sixel_helper_set_additional_message(
×
1562
                "sixel_palette_read_stream: size overflow.");
1563
            status = SIXEL_BAD_ALLOCATION;
×
1564
            goto cleanup;
×
1565
        }
1566
        needed = used + read_bytes;
×
1567

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

1583
            grown = (unsigned char *)sixel_allocator_malloc(allocator,
×
1584
                                                             new_capacity);
1585
            if (grown == NULL) {
×
1586
                sixel_helper_set_additional_message(
×
1587
                    "sixel_palette_read_stream: allocation failed.");
1588
                status = SIXEL_BAD_ALLOCATION;
×
1589
                goto cleanup;
×
1590
            }
1591

1592
            if (buffer != NULL) {
×
1593
                memcpy(grown, buffer, used);
×
1594
                sixel_allocator_free(allocator, buffer);
×
1595
            }
1596

1597
            buffer = grown;
×
1598
            grown = NULL;
×
1599
            capacity = new_capacity;
×
1600
        }
1601

1602
        memcpy(buffer + used, scratch, read_bytes);
×
1603
        used += read_bytes;
×
1604
    }
1605

1606
    *pdata = buffer;
×
1607
    *psize = used;
×
1608
    status = SIXEL_OK;
×
1609
    return status;
×
1610

1611
cleanup:
1612
    if (grown != NULL) {
×
1613
        sixel_allocator_free(allocator, grown);
×
1614
    }
1615
    if (buffer != NULL) {
×
1616
        sixel_allocator_free(allocator, buffer);
×
1617
    }
1618
    return status;
×
1619
}
1620

1621

1622
static SIXELSTATUS
1623
sixel_palette_open_read(char const *path, FILE **pstream, int *pclose)
×
1624
{
1625
    if (pstream == NULL || pclose == NULL || path == NULL) {
×
1626
        sixel_helper_set_additional_message(
×
1627
            "sixel_palette_open_read: invalid argument.");
1628
        return SIXEL_BAD_ARGUMENT;
×
1629
    }
1630

1631
    if (strcmp(path, "-") == 0) {
×
1632
        *pstream = stdin;
×
1633
        *pclose = 0;
×
1634
        return SIXEL_OK;
×
1635
    }
1636

1637
    *pstream = fopen(path, "rb");
×
1638
    if (*pstream == NULL) {
×
1639
        sixel_helper_set_additional_message(
×
1640
            "sixel_palette_open_read: failed to open file.");
1641
        return SIXEL_LIBC_ERROR;
×
1642
    }
1643

1644
    *pclose = 1;
×
1645
    return SIXEL_OK;
×
1646
}
1647

1648

1649
static void
1650
sixel_palette_close_stream(FILE *stream, int close_stream)
×
1651
{
1652
    if (close_stream && stream != NULL) {
×
1653
        (void) fclose(stream);
×
1654
    }
1655
}
×
1656

1657

1658
static sixel_palette_format_t
1659
sixel_palette_guess_format(unsigned char const *data, size_t size)
×
1660
{
1661
    size_t offset;
1662
    size_t data_size;
1663

1664
    offset = 0u;
×
1665
    data_size = size;
×
1666

1667
    if (data == NULL || size == 0u) {
×
1668
        return SIXEL_PALETTE_FORMAT_NONE;
×
1669
    }
1670

1671
    if (size == 256u * 3u || size == 256u * 3u + 4u) {
×
1672
        return SIXEL_PALETTE_FORMAT_ACT;
×
1673
    }
1674

1675
    if (size >= 12u && memcmp(data, "RIFF", 4) == 0
×
1676
            && memcmp(data + 8, "PAL ", 4) == 0) {
×
1677
        return SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1678
    }
1679

1680
    if (sixel_palette_has_utf8_bom(data, size)) {
×
1681
        offset = 3u;
×
1682
        data_size = size - 3u;
×
1683
    }
1684

1685
    if (data_size >= 8u && memcmp(data + offset, "JASC-PAL", 8) == 0) {
×
1686
        return SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1687
    }
1688
    if (data_size >= 12u && memcmp(data + offset, "GIMP Palette", 12) == 0) {
×
1689
        return SIXEL_PALETTE_FORMAT_GPL;
×
1690
    }
1691

1692
    return SIXEL_PALETTE_FORMAT_NONE;
×
1693
}
1694

1695

1696
static unsigned int
1697
sixel_palette_read_le16(unsigned char const *ptr)
×
1698
{
1699
    if (ptr == NULL) {
×
1700
        return 0u;
×
1701
    }
1702
    return (unsigned int)ptr[0] | ((unsigned int)ptr[1] << 8);
×
1703
}
1704

1705

1706
static unsigned int
1707
sixel_palette_read_le32(unsigned char const *ptr)
×
1708
{
1709
    if (ptr == NULL) {
×
1710
        return 0u;
×
1711
    }
1712
    return ((unsigned int)ptr[0])
×
1713
        | ((unsigned int)ptr[1] << 8)
×
1714
        | ((unsigned int)ptr[2] << 16)
×
1715
        | ((unsigned int)ptr[3] << 24);
×
1716
}
1717

1718

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

1744
    status = SIXEL_FALSE;
×
1745
    local = NULL;
×
1746
    palette_start = data;
×
1747
    trailer = NULL;
×
1748
    target = NULL;
×
1749
    copy_bytes = 0u;
×
1750
    exported_colors = 0;
×
1751
    start_index = 0;
×
1752

1753
    if (encoder == NULL || dither == NULL) {
×
1754
        sixel_helper_set_additional_message(
×
1755
            "sixel_palette_parse_act: invalid argument.");
1756
        return SIXEL_BAD_ARGUMENT;
×
1757
    }
1758
    if (data == NULL || size < 256u * 3u) {
×
1759
        sixel_helper_set_additional_message(
×
1760
            "sixel_palette_parse_act: truncated ACT palette.");
1761
        return SIXEL_BAD_INPUT;
×
1762
    }
1763

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

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

1793
    status = sixel_dither_new(&local, exported_colors, encoder->allocator);
×
1794
    if (SIXEL_FAILED(status)) {
×
1795
        return status;
×
1796
    }
1797

1798
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1799

1800
    target = sixel_dither_get_palette(local);
×
1801
    copy_bytes = (size_t)exported_colors * 3u;
×
1802
    memcpy(target, palette_start + (size_t)start_index * 3u, copy_bytes);
×
1803

1804
    *dither = local;
×
1805
    return SIXEL_OK;
×
1806
}
1807

1808

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

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

1852
    if (encoder == NULL || dither == NULL) {
×
1853
        sixel_helper_set_additional_message(
×
1854
            "sixel_palette_parse_pal_jasc: invalid argument.");
1855
        return SIXEL_BAD_ARGUMENT;
×
1856
    }
1857
    if (data == NULL || size == 0u) {
×
1858
        sixel_helper_set_additional_message(
×
1859
            "sixel_palette_parse_pal_jasc: empty palette.");
1860
        return SIXEL_BAD_INPUT;
×
1861
    }
1862

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

1872
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
1873
        offset = 3u;
×
1874
    }
1875
    cursor = text + offset;
×
1876

1877
    while (*cursor != '\0') {
×
1878
        line = cursor;
×
1879
        line_end = cursor;
×
1880
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
1881
            ++line_end;
×
1882
        }
1883
        if (*line_end != '\0') {
×
1884
            *line_end = '\0';
×
1885
            cursor = line_end + 1;
×
1886
        } else {
1887
            cursor = line_end;
×
1888
        }
1889
        while (*cursor == '\n' || *cursor == '\r') {
×
1890
            ++cursor;
×
1891
        }
1892

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

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

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

1963
        if (parsed_colors >= exported_colors) {
×
1964
            sixel_helper_set_additional_message(
×
1965
                "sixel_palette_parse_pal_jasc: excess entries.");
1966
            status = SIXEL_BAD_INPUT;
×
1967
            goto cleanup;
×
1968
        }
1969

1970
        target[parsed_colors * 3 + 0] =
×
1971
            (unsigned char)values[0];
×
1972
        target[parsed_colors * 3 + 1] =
×
1973
            (unsigned char)values[1];
×
1974
        target[parsed_colors * 3 + 2] =
×
1975
            (unsigned char)values[2];
×
1976
        ++parsed_colors;
×
1977
    }
1978

1979
    if (stage < 3) {
×
1980
        sixel_helper_set_additional_message(
×
1981
            "sixel_palette_parse_pal_jasc: incomplete header.");
1982
        status = SIXEL_BAD_INPUT;
×
1983
        goto cleanup;
×
1984
    }
1985
    if (parsed_colors != exported_colors) {
×
1986
        sixel_helper_set_additional_message(
×
1987
            "sixel_palette_parse_pal_jasc: color count mismatch.");
1988
        status = SIXEL_BAD_INPUT;
×
1989
        goto cleanup;
×
1990
    }
1991

1992
    *dither = local;
×
1993
    status = SIXEL_OK;
×
1994

1995
cleanup:
1996
    if (SIXEL_FAILED(status) && local != NULL) {
×
1997
        sixel_dither_unref(local);
×
1998
    }
1999
    if (text != NULL) {
×
2000
        sixel_allocator_free(encoder->allocator, text);
×
2001
    }
2002
    return status;
×
2003
}
2004

2005

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

2023
    status = SIXEL_FALSE;
×
2024
    offset = 0u;
×
2025
    chunk_size = 0u;
×
2026
    local = NULL;
×
2027
    chunk = NULL;
×
2028
    target = NULL;
×
2029
    entry_count = 0u;
×
2030
    version = 0u;
×
2031
    index = 0u;
×
2032
    palette_offset = 0u;
×
2033

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

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

2065
    if (offset + 8u > size || memcmp(chunk, "data", 4) != 0) {
×
2066
        sixel_helper_set_additional_message(
×
2067
            "sixel_palette_parse_pal_riff: missing data chunk.");
2068
        return SIXEL_BAD_INPUT;
×
2069
    }
2070

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

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

2106
    *dither = local;
×
2107
    return SIXEL_OK;
×
2108
}
2109

2110

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

2135
    status = SIXEL_FALSE;
×
2136
    text = NULL;
×
2137
    offset = 0u;
×
2138
    cursor = NULL;
×
2139
    line = NULL;
×
2140
    line_end = NULL;
×
2141
    index = 0u;
×
2142
    header_seen = 0;
×
2143
    parsed_colors = 0;
×
2144
    component = 0;
×
2145
    parse_end = NULL;
×
2146
    value_index = 0;
×
2147
    values[0] = 0;
×
2148
    values[1] = 0;
×
2149
    values[2] = 0;
×
2150
    local = NULL;
×
2151
    target = NULL;
×
2152

2153
    if (encoder == NULL || dither == NULL) {
×
2154
        sixel_helper_set_additional_message(
×
2155
            "sixel_palette_parse_gpl: invalid argument.");
2156
        return SIXEL_BAD_ARGUMENT;
×
2157
    }
2158
    if (data == NULL || size == 0u) {
×
2159
        sixel_helper_set_additional_message(
×
2160
            "sixel_palette_parse_gpl: empty palette.");
2161
        return SIXEL_BAD_INPUT;
×
2162
    }
2163

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

2173
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
2174
        offset = 3u;
×
2175
    }
2176
    cursor = text + offset;
×
2177

2178
    while (*cursor != '\0') {
×
2179
        line = cursor;
×
2180
        line_end = cursor;
×
2181
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
2182
            ++line_end;
×
2183
        }
2184
        if (*line_end != '\0') {
×
2185
            *line_end = '\0';
×
2186
            cursor = line_end + 1;
×
2187
        } else {
2188
            cursor = line_end;
×
2189
        }
2190
        while (*cursor == '\n' || *cursor == '\r') {
×
2191
            ++cursor;
×
2192
        }
2193

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

2219
        if (!header_seen) {
×
2220
            if (strcmp(line, "GIMP Palette") != 0) {
×
2221
                sixel_helper_set_additional_message(
×
2222
                    "sixel_palette_parse_gpl: missing header.");
2223
                status = SIXEL_BAD_INPUT;
×
2224
                goto cleanup;
×
2225
            }
2226
            header_seen = 1;
×
2227
            continue;
×
2228
        }
2229

2230
        if (parsed_colors >= 256) {
×
2231
            sixel_helper_set_additional_message(
×
2232
                "sixel_palette_parse_gpl: too many colors.");
2233
            status = SIXEL_BAD_INPUT;
×
2234
            goto cleanup;
×
2235
        }
2236

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

2254
        palette_bytes[parsed_colors * 3 + 0] =
×
2255
            (unsigned char)values[0];
×
2256
        palette_bytes[parsed_colors * 3 + 1] =
×
2257
            (unsigned char)values[1];
×
2258
        palette_bytes[parsed_colors * 3 + 2] =
×
2259
            (unsigned char)values[2];
×
2260
        ++parsed_colors;
×
2261
    }
2262

2263
    if (!header_seen) {
×
2264
        sixel_helper_set_additional_message(
×
2265
            "sixel_palette_parse_gpl: header missing.");
2266
        status = SIXEL_BAD_INPUT;
×
2267
        goto cleanup;
×
2268
    }
2269
    if (parsed_colors <= 0) {
×
2270
        sixel_helper_set_additional_message(
×
2271
            "sixel_palette_parse_gpl: no colors parsed.");
2272
        status = SIXEL_BAD_INPUT;
×
2273
        goto cleanup;
×
2274
    }
2275

2276
    status = sixel_dither_new(&local, parsed_colors, encoder->allocator);
×
2277
    if (SIXEL_FAILED(status)) {
×
2278
        goto cleanup;
×
2279
    }
2280
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2281
    target = sixel_dither_get_palette(local);
×
2282
    memcpy(target, palette_bytes, (size_t)parsed_colors * 3u);
×
2283

2284
    *dither = local;
×
2285
    status = SIXEL_OK;
×
2286

2287
cleanup:
2288
    if (SIXEL_FAILED(status) && local != NULL) {
×
2289
        sixel_dither_unref(local);
×
2290
    }
2291
    if (text != NULL) {
×
2292
        sixel_allocator_free(encoder->allocator, text);
×
2293
    }
2294
    return status;
×
2295
}
2296

2297

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

2320
    status = SIXEL_FALSE;
×
2321
    exported_bytes = 0u;
×
2322

2323
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2324
        return SIXEL_BAD_ARGUMENT;
×
2325
    }
2326
    if (exported_colors > 256) {
×
2327
        exported_colors = 256;
×
2328
    }
2329

2330
    memset(act_table, 0, sizeof(act_table));
×
2331
    exported_bytes = (size_t)exported_colors * 3u;
×
2332
    memcpy(act_table, palette, exported_bytes);
×
2333

2334
    trailer[0] = (unsigned char)(((unsigned int)exported_colors >> 8)
×
2335
                                 & 0xffu);
2336
    trailer[1] = (unsigned char)((unsigned int)exported_colors & 0xffu);
×
2337
    trailer[2] = 0u;
×
2338
    trailer[3] = 0u;
×
2339

2340
    if (fwrite(act_table, 1, sizeof(act_table), stream)
×
2341
            != sizeof(act_table)) {
2342
        status = SIXEL_LIBC_ERROR;
×
2343
        return status;
×
2344
    }
2345
    if (fwrite(trailer, 1, sizeof(trailer), stream)
×
2346
            != sizeof(trailer)) {
2347
        status = SIXEL_LIBC_ERROR;
×
2348
        return status;
×
2349
    }
2350

2351
    return SIXEL_OK;
×
2352
}
2353

2354

2355
static SIXELSTATUS
2356
sixel_palette_write_pal_jasc(FILE *stream,
×
2357
                             unsigned char const *palette,
2358
                             int exported_colors)
2359
{
2360
    int index;
2361

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

2379

2380
static SIXELSTATUS
2381
sixel_palette_write_pal_riff(FILE *stream,
×
2382
                             unsigned char const *palette,
2383
                             int exported_colors)
2384
{
2385
    unsigned char header[12];
2386
    unsigned char chunk[8];
2387
    unsigned char log_palette[4 + 256 * 4];
2388
    unsigned int data_size;
2389
    unsigned int riff_size;
2390
    int index;
2391

2392
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2393
        return SIXEL_BAD_ARGUMENT;
×
2394
    }
2395
    if (exported_colors > 256) {
×
2396
        exported_colors = 256;
×
2397
    }
2398

2399
    data_size = 4u + (unsigned int)exported_colors * 4u;
×
2400
    riff_size = 4u + 8u + data_size;
×
2401

2402
    memcpy(header, "RIFF", 4);
×
2403
    header[4] = (unsigned char)(riff_size & 0xffu);
×
2404
    header[5] = (unsigned char)((riff_size >> 8) & 0xffu);
×
2405
    header[6] = (unsigned char)((riff_size >> 16) & 0xffu);
×
2406
    header[7] = (unsigned char)((riff_size >> 24) & 0xffu);
×
2407
    memcpy(header + 8, "PAL ", 4);
×
2408

2409
    memcpy(chunk, "data", 4);
×
2410
    chunk[4] = (unsigned char)(data_size & 0xffu);
×
2411
    chunk[5] = (unsigned char)((data_size >> 8) & 0xffu);
×
2412
    chunk[6] = (unsigned char)((data_size >> 16) & 0xffu);
×
2413
    chunk[7] = (unsigned char)((data_size >> 24) & 0xffu);
×
2414

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

2427
    if (fwrite(header, 1, sizeof(header), stream)
×
2428
            != sizeof(header)) {
2429
        return SIXEL_LIBC_ERROR;
×
2430
    }
2431
    if (fwrite(chunk, 1, sizeof(chunk), stream) != sizeof(chunk)) {
×
2432
        return SIXEL_LIBC_ERROR;
×
2433
    }
2434
    if (fwrite(log_palette, 1, (size_t)data_size, stream)
×
2435
            != (size_t)data_size) {
×
2436
        return SIXEL_LIBC_ERROR;
×
2437
    }
2438
    return SIXEL_OK;
×
2439
}
2440

2441

2442
static SIXELSTATUS
2443
sixel_palette_write_gpl(FILE *stream,
×
2444
                        unsigned char const *palette,
2445
                        int exported_colors)
2446
{
2447
    int index;
2448

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

2476

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

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

2524
    if (dither == NULL || encoder == NULL || encoder->mapfile == NULL) {
21!
2525
        sixel_helper_set_additional_message(
×
2526
            "sixel_prepare_specified_palette: invalid mapfile path.");
2527
        return SIXEL_BAD_ARGUMENT;
×
2528
    }
2529

2530
    path = sixel_palette_strip_prefix(encoder->mapfile, &format_hint);
21✔
2531
    if (path == NULL || *path == '\0') {
21!
2532
        sixel_helper_set_additional_message(
×
2533
            "sixel_prepare_specified_palette: empty mapfile path.");
2534
        return SIXEL_BAD_ARGUMENT;
×
2535
    }
2536

2537
    format_ext = sixel_palette_format_from_extension(path);
21✔
2538
    path_has_extension = sixel_path_has_any_extension(path);
21✔
2539

2540
    if (format_hint != SIXEL_PALETTE_FORMAT_NONE) {
21!
2541
        palette_request = 1;
×
2542
        format_final = format_hint;
×
2543
    } else if (format_ext != SIXEL_PALETTE_FORMAT_NONE) {
21!
2544
        palette_request = 1;
×
2545
        format_final = format_ext;
×
2546
    } else if (!path_has_extension) {
21!
2547
        palette_request = 1;
×
2548
        need_detection = 1;
×
2549
    } else {
2550
        treat_as_image = 1;
21✔
2551
    }
2552

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

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

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

2647
palette_cleanup:
2648
        if (buffer != NULL) {
×
2649
            sixel_allocator_free(encoder->allocator, buffer);
×
2650
            buffer = NULL;
×
2651
        }
2652
        if (stream != NULL) {
×
2653
            sixel_palette_close_stream(stream, close_stream);
×
2654
            stream = NULL;
×
2655
        }
2656
        if (SIXEL_SUCCEEDED(status)) {
×
2657
            return status;
×
2658
        }
2659
        if (!treat_as_image) {
×
2660
            return status;
×
2661
        }
2662
    }
2663

2664
    callback_context.reqcolors = encoder->reqcolors;
21✔
2665
    callback_context.dither = NULL;
21✔
2666
    callback_context.allocator = encoder->allocator;
21✔
2667
    callback_context.working_colorspace = encoder->working_colorspace;
21✔
2668
    callback_context.lut_policy = encoder->lut_policy;
21✔
2669

2670
    sixel_helper_set_loader_trace(encoder->verbose);
21✔
2671
    sixel_helper_set_thumbnail_size_hint(
21✔
2672
        sixel_encoder_thumbnail_hint(encoder));
7✔
2673
    status = sixel_loader_new(&loader, encoder->allocator);
21✔
2674
    if (SIXEL_FAILED(status)) {
21!
2675
        goto end_loader;
×
2676
    }
2677

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

2685
    status = sixel_loader_setopt(loader,
21✔
2686
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
2687
                                 &fuse_palette);
2688
    if (SIXEL_FAILED(status)) {
21!
2689
        goto end_loader;
×
2690
    }
2691

2692
    status = sixel_loader_setopt(loader,
21✔
2693
                                 SIXEL_LOADER_OPTION_REQCOLORS,
2694
                                 &reqcolors);
2695
    if (SIXEL_FAILED(status)) {
21!
2696
        goto end_loader;
×
2697
    }
2698

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

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

2713
    status = sixel_loader_setopt(loader,
28✔
2714
                                 SIXEL_LOADER_OPTION_INSECURE,
2715
                                 &encoder->finsecure);
21✔
2716
    if (SIXEL_FAILED(status)) {
21!
2717
        goto end_loader;
×
2718
    }
2719

2720
    status = sixel_loader_setopt(loader,
28✔
2721
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
2722
                                 encoder->cancel_flag);
21✔
2723
    if (SIXEL_FAILED(status)) {
21!
2724
        goto end_loader;
×
2725
    }
2726

2727
    status = sixel_loader_setopt(loader,
28✔
2728
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
2729
                                 encoder->loader_order);
21✔
2730
    if (SIXEL_FAILED(status)) {
21!
2731
        goto end_loader;
×
2732
    }
2733

2734
    status = sixel_loader_setopt(loader,
21✔
2735
                                 SIXEL_LOADER_OPTION_CONTEXT,
2736
                                 &callback_context);
2737
    if (SIXEL_FAILED(status)) {
21!
2738
        goto end_loader;
×
2739
    }
2740

2741
    status = sixel_loader_load_file(loader,
28✔
2742
                                    encoder->mapfile,
21✔
2743
                                    load_image_callback_for_palette);
2744
    if (status != SIXEL_OK) {
21!
2745
        goto end_loader;
×
2746
    }
2747

2748
end_loader:
14✔
2749
    sixel_loader_unref(loader);
21✔
2750

2751
    if (status != SIXEL_OK) {
21!
2752
        return status;
×
2753
    }
2754

2755
    if (! callback_context.dither) {
21!
2756
        sixel_helper_set_additional_message(
×
2757
            "sixel_prepare_specified_palette() failed.\n"
2758
            "reason: mapfile is empty.");
2759
        return SIXEL_BAD_INPUT;
×
2760
    }
2761

2762
    *dither = callback_context.dither;
21✔
2763

2764
    return status;
21✔
2765
}
7✔
2766

2767

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

2780
    assessment = NULL;
529✔
2781
    promoted_stage = 0;
529✔
2782
    if (encoder != NULL) {
529!
2783
        assessment = encoder->assessment_observer;
529✔
2784
    }
183✔
2785

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

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

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

2873
    if (encoder->dither_cache) {
242!
2874
        sixel_dither_unref(encoder->dither_cache);
×
2875
    }
2876
    status = sixel_dither_new(dither, encoder->reqcolors, encoder->allocator);
242✔
2877
    if (SIXEL_FAILED(status)) {
242!
2878
        goto end;
×
2879
    }
2880

2881
    sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
242✔
2882
    sixel_dither_set_sixel_reversible(*dither,
354✔
2883
                                      encoder->sixel_reversible);
112✔
2884
    sixel_dither_set_final_merge(*dither, encoder->final_merge_mode);
242✔
2885
    (*dither)->quantize_model = encoder->quantize_model;
242✔
2886

2887
    status = sixel_dither_initialize(*dither,
354✔
2888
                                     sixel_frame_get_pixels(frame),
112✔
2889
                                     sixel_frame_get_width(frame),
112✔
2890
                                     sixel_frame_get_height(frame),
112✔
2891
                                     sixel_frame_get_pixelformat(frame),
112✔
2892
                                     encoder->method_for_largest,
112✔
2893
                                     encoder->method_for_rep,
112✔
2894
                                     encoder->quality_mode);
112✔
2895
    if (SIXEL_FAILED(status)) {
242!
2896
        sixel_dither_unref(*dither);
×
2897
        goto end;
×
2898
    }
2899

2900
    if (assessment != NULL && promoted_stage == 0) {
242!
2901
        sixel_assessment_stage_transition(
3✔
2902
            assessment,
1✔
2903
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2904
        promoted_stage = 1;
3✔
2905
    }
1✔
2906

2907
    histogram_colors = sixel_dither_get_num_of_histogram_colors(*dither);
242✔
2908
    if (histogram_colors <= encoder->reqcolors) {
242✔
2909
        encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
173✔
2910
    }
89✔
2911
    sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
242✔
2912

2913
    status = SIXEL_OK;
242✔
2914

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

2930

2931
/* resize a frame with settings of specified encoder object */
2932
static SIXELSTATUS
2933
sixel_encoder_do_resize(
536✔
2934
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
2935
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
2936
{
2937
    SIXELSTATUS status = SIXEL_FALSE;
536✔
2938
    int src_width;
2939
    int src_height;
2940
    int dst_width;
2941
    int dst_height;
2942

2943
    /* get frame width and height */
2944
    src_width = sixel_frame_get_width(frame);
536✔
2945
    src_height = sixel_frame_get_height(frame);
536✔
2946

2947
    if (src_width < 1) {
536✔
2948
         sixel_helper_set_additional_message(
6✔
2949
             "sixel_encoder_do_resize: "
2950
             "detected a frame with a non-positive width.");
2951
        return SIXEL_BAD_ARGUMENT;
6✔
2952
    }
2953

2954
    if (src_height < 1) {
530!
2955
         sixel_helper_set_additional_message(
×
2956
             "sixel_encoder_do_resize: "
2957
             "detected a frame with a non-positive height.");
2958
        return SIXEL_BAD_ARGUMENT;
×
2959
    }
2960

2961
    /* settings around scaling */
2962
    dst_width = encoder->pixelwidth;    /* may be -1 (default) */
530✔
2963
    dst_height = encoder->pixelheight;  /* may be -1 (default) */
530✔
2964

2965
    /* if the encoder has percentwidth or percentheight property,
2966
       convert them to pixelwidth / pixelheight */
2967
    if (encoder->percentwidth > 0) {
530✔
2968
        dst_width = src_width * encoder->percentwidth / 100;
12✔
2969
    }
4✔
2970
    if (encoder->percentheight > 0) {
530✔
2971
        dst_height = src_height * encoder->percentheight / 100;
9✔
2972
    }
3✔
2973

2974
    /* if only either width or height is set, set also the other
2975
       to retain frame aspect ratio */
2976
    if (dst_width > 0 && dst_height <= 0) {
530✔
2977
        dst_height = src_height * dst_width / src_width;
42✔
2978
    }
14✔
2979
    if (dst_height > 0 && dst_width <= 0) {
530✔
2980
        dst_width = src_width * dst_height / src_height;
33✔
2981
    }
11✔
2982

2983
    /* do resize */
2984
    if (dst_width > 0 && dst_height > 0) {
530!
2985
        status = sixel_frame_resize(frame, dst_width, dst_height,
124✔
2986
                                    encoder->method_for_resampling);
31✔
2987
        if (SIXEL_FAILED(status)) {
93!
2988
            goto end;
×
2989
        }
2990
    }
31✔
2991

2992
    /* success */
2993
    status = SIXEL_OK;
530✔
2994

2995
end:
346✔
2996
    return status;
530✔
2997
}
186✔
2998

2999

3000
/* clip a frame with settings of specified encoder object */
3001
static SIXELSTATUS
3002
sixel_encoder_do_clip(
532✔
3003
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
3004
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
3005
{
3006
    SIXELSTATUS status = SIXEL_FALSE;
532✔
3007
    int src_width;
3008
    int src_height;
3009
    int clip_x;
3010
    int clip_y;
3011
    int clip_w;
3012
    int clip_h;
3013

3014
    /* get frame width and height */
3015
    src_width = sixel_frame_get_width(frame);
532✔
3016
    src_height = sixel_frame_get_height(frame);
532✔
3017

3018
    /* settings around clipping */
3019
    clip_x = encoder->clipx;
532✔
3020
    clip_y = encoder->clipy;
532✔
3021
    clip_w = encoder->clipwidth;
532✔
3022
    clip_h = encoder->clipheight;
532✔
3023

3024
    /* adjust clipping width with comparing it to frame width */
3025
    if (clip_w + clip_x > src_width) {
532✔
3026
        if (clip_x > src_width) {
7✔
3027
            clip_w = 0;
3✔
3028
        } else {
1✔
3029
            clip_w = src_width - clip_x;
4✔
3030
        }
3031
    }
3✔
3032

3033
    /* adjust clipping height with comparing it to frame height */
3034
    if (clip_h + clip_y > src_height) {
532✔
3035
        if (clip_y > src_height) {
6✔
3036
            clip_h = 0;
3✔
3037
        } else {
1✔
3038
            clip_h = src_height - clip_y;
3✔
3039
        }
3040
    }
2✔
3041

3042
    /* do clipping */
3043
    if (clip_w > 0 && clip_h > 0) {
532!
3044
        status = sixel_frame_clip(frame, clip_x, clip_y, clip_w, clip_h);
15✔
3045
        if (SIXEL_FAILED(status)) {
15!
3046
            goto end;
3✔
3047
        }
3048
    }
4✔
3049

3050
    /* success */
3051
    status = SIXEL_OK;
529✔
3052

3053
end:
346✔
3054
    return status;
532✔
3055
}
3056

3057

3058
static void
3059
sixel_debug_print_palette(
3✔
3060
    sixel_dither_t /* in */ *dither /* dithering object */
3061
)
3062
{
3063
    unsigned char *palette;
3064
    int i;
3065

3066
    palette = sixel_dither_get_palette(dither);
3✔
3067
    fprintf(stderr, "palette:\n");
3✔
3068
    for (i = 0; i < sixel_dither_get_num_of_palette_colors(dither); ++i) {
51✔
3069
        fprintf(stderr, "%d: #%02x%02x%02x\n", i,
64✔
3070
                palette[i * 3 + 0],
48✔
3071
                palette[i * 3 + 1],
48✔
3072
                palette[i * 3 + 2]);
48✔
3073
    }
16✔
3074
}
3✔
3075

3076

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

3103
    memset(&palette_ctx, 0, sizeof(palette_ctx));
436✔
3104
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3105
    sixel_clock_t last_clock;
3106
#endif
3107

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

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

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

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

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

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

3209
    status = sixel_output_convert_colorspace(output, p, size);
436✔
3210
    if (SIXEL_FAILED(status)) {
436!
3211
        goto end;
×
3212
    }
3213

3214
    if (encoder->cancel_flag && *encoder->cancel_flag) {
436!
3215
        goto end;
×
3216
    }
3217

3218
    status = sixel_encoder_convert_palette(encoder,
582✔
3219
                                           output,
146✔
3220
                                           dither,
146✔
3221
                                           frame_colorspace,
146✔
3222
                                           pixelformat,
146✔
3223
                                           &palette_ctx);
3224
    if (SIXEL_FAILED(status)) {
436!
3225
        goto end;
×
3226
    }
3227

3228
    if (encoder->capture_quantized) {
436✔
3229
        status = sixel_encoder_capture_quantized(encoder,
4✔
3230
                                                 dither,
1✔
3231
                                                 p,
1✔
3232
                                                 size,
1✔
3233
                                                 width,
1✔
3234
                                                 height,
1✔
3235
                                                 pixelformat,
1✔
3236
                                                 output->colorspace);
1✔
3237
        if (SIXEL_FAILED(status)) {
3!
3238
            goto end;
×
3239
        }
3240
    }
1✔
3241

3242
    if (encoder->assessment_observer != NULL) {
436✔
3243
        sixel_assessment_stage_transition(
3✔
3244
            encoder->assessment_observer,
3✔
3245
            SIXEL_ASSESSMENT_STAGE_ENCODE);
3246
    }
1✔
3247
    status = sixel_encode(p, width, height, depth, dither, output);
436✔
3248
    if (encoder->assessment_observer != NULL) {
436✔
3249
        sixel_assessment_stage_finish(encoder->assessment_observer);
3✔
3250
    }
1✔
3251
    if (status != SIXEL_OK) {
436!
3252
        goto end;
×
3253
    }
3254

3255
end:
290✔
3256
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
436✔
3257
    output->pixelformat = pixelformat;
436✔
3258
    output->source_colorspace = frame_colorspace;
436✔
3259
    sixel_allocator_free(encoder->allocator, p);
436✔
3260

3261
    return status;
436✔
3262
}
3263

3264

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

3298
    memset(&palette_ctx, 0, sizeof(palette_ctx));
93✔
3299

3300
    if (encoder != NULL && encoder->assessment_observer != NULL) {
93!
3301
        sixel_assessment_stage_transition(
×
3302
            encoder->assessment_observer,
×
3303
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3304
    }
3305

3306
#if defined(HAVE_CLOCK)
3307
    if (output->last_clock == 0) {
93!
3308
        output->last_clock = clock();
93✔
3309
    }
37✔
3310
#elif defined(HAVE_CLOCK_WIN)
3311
    if (output->last_clock == 0) {
3312
        output->last_clock = clock_win();
3313
    }
3314
#endif
3315

3316
    width = sixel_frame_get_width(frame);
93✔
3317
    height = sixel_frame_get_height(frame);
93✔
3318
    pixelformat = sixel_frame_get_pixelformat(frame);
93✔
3319
    depth = sixel_helper_compute_depth(pixelformat);
93✔
3320
    if (depth < 0) {
93!
3321
        status = SIXEL_LOGIC_ERROR;
×
3322
        sixel_helper_set_additional_message(
×
3323
            "sixel_encoder_output_with_macro: "
3324
            "sixel_helper_compute_depth() failed.");
3325
        goto end;
×
3326
    }
3327

3328
    frame_colorspace = sixel_frame_get_colorspace(frame);
93✔
3329
    size = (size_t)width * (size_t)height * (size_t)depth;
93✔
3330
    converted = (unsigned char *)sixel_allocator_malloc(
93✔
3331
        encoder->allocator, size);
37✔
3332
    if (converted == NULL) {
93!
3333
        sixel_helper_set_additional_message(
×
3334
            "sixel_encoder_output_with_macro: "
3335
            "sixel_allocator_malloc() failed.");
3336
        status = SIXEL_BAD_ALLOCATION;
×
3337
        goto end;
×
3338
    }
3339

3340
    memcpy(converted, sixel_frame_get_pixels(frame), size);
93✔
3341
    output->pixelformat = pixelformat;
93✔
3342
    output->source_colorspace = frame_colorspace;
93✔
3343
    output->colorspace = encoder->output_colorspace;
93✔
3344
    status = sixel_output_convert_colorspace(output, converted, size);
93✔
3345
    if (SIXEL_FAILED(status)) {
93!
3346
        goto end;
×
3347
    }
3348

3349
    status = sixel_encoder_convert_palette(encoder,
130✔
3350
                                           output,
37✔
3351
                                           dither,
37✔
3352
                                           frame_colorspace,
37✔
3353
                                           pixelformat,
37✔
3354
                                           &palette_ctx);
3355
    if (SIXEL_FAILED(status)) {
93!
3356
        goto end;
×
3357
    }
3358

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

3409
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3410
            sixel_assessment_stage_transition(
×
3411
                encoder->assessment_observer,
×
3412
                SIXEL_ASSESSMENT_STAGE_ENCODE);
3413
        }
3414
        status = sixel_encode(converted,
74✔
3415
                              width,
19✔
3416
                              height,
19✔
3417
                              depth,
19✔
3418
                              dither,
19✔
3419
                              output);
19✔
3420
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3421
            sixel_assessment_stage_finish(
×
3422
                encoder->assessment_observer);
×
3423
        }
3424
        if (SIXEL_FAILED(status)) {
55!
3425
            goto end;
×
3426
        }
3427

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

3533
end:
20✔
3534
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
93✔
3535
    output->pixelformat = pixelformat;
93✔
3536
    output->source_colorspace = frame_colorspace;
93✔
3537
    sixel_allocator_free(encoder->allocator, converted);
93✔
3538

3539
    return status;
93✔
3540
}
3541

3542

3543
static SIXELSTATUS
3544
sixel_encoder_emit_iso2022_chars(
×
3545
    sixel_encoder_t *encoder,
3546
    sixel_frame_t *frame
3547
)
3548
{
3549
    char *buf_p, *buf;
3550
    int col, row;
3551
    int charset;
3552
    int is_96cs;
3553
    unsigned int charset_no;
3554
    unsigned int code;
3555
    int num_cols, num_rows;
3556
    SIXELSTATUS status;
3557
    size_t alloc_size;
3558
    int nwrite;
3559
    int target_fd;
3560
    int chunk_size;
3561

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

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

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

3619
    if (encoder->tile_outfd >= 0) {
×
3620
        target_fd = encoder->tile_outfd;
×
3621
    } else {
3622
        target_fd = encoder->outfd;
×
3623
    }
3624

3625
    chunk_size = (int)(buf_p - buf);
×
3626
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3627
                                          buf,
3628
                                          chunk_size,
3629
                                          target_fd);
3630
    if (nwrite != chunk_size) {
×
3631
        sixel_helper_set_additional_message(
×
3632
            "sixel_encoder_emit_iso2022_chars: write() failed.");
3633
        status = SIXEL_RUNTIME_ERROR;
×
3634
        goto end;
×
3635
    }
3636

3637
    sixel_allocator_free(encoder->allocator, buf);
×
3638

3639
    status = SIXEL_OK;
×
3640

3641
end:
3642
    return status;
×
3643
}
3644

3645

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

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

3691
    /* cols x rows x 4(out of BMP) + rows(LFs) */
3692
    alloc_size = num_cols * num_rows * 4 + num_rows;
×
3693
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3694
    if (buf == NULL) {
×
3695
        sixel_helper_set_additional_message(
×
3696
            "sixel_encoder_emit_drcsmmv2_chars: sixel_allocator_malloc() failed.");
3697
        status = SIXEL_BAD_ALLOCATION;
×
3698
        goto end;
×
3699
    }
3700

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

3721
    if (charset == 0x7e) {
×
3722
        is_96cs = 1 - is_96cs;
×
3723
    } else {
3724
        charset = '0';
×
3725
        charset++;
×
3726
    }
3727
    if (encoder->tile_outfd >= 0) {
×
3728
        target_fd = encoder->tile_outfd;
×
3729
    } else {
3730
        target_fd = encoder->outfd;
×
3731
    }
3732

3733
    chunk_size = (int)(buf_p - buf);
×
3734
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3735
                                          buf,
3736
                                          chunk_size,
3737
                                          target_fd);
3738
    if (nwrite != chunk_size) {
×
3739
        sixel_helper_set_additional_message(
×
3740
            "sixel_encoder_emit_drcsmmv2_chars: write() failed.");
3741
        status = SIXEL_RUNTIME_ERROR;
×
3742
        goto end;
×
3743
    }
3744

3745
    sixel_allocator_free(encoder->allocator, buf);
×
3746

3747
    status = SIXEL_OK;
×
3748

3749
end:
3750
    return status;
×
3751
}
3752

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

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

3803
    /*
3804
     *  Geometry timeline:
3805
     *
3806
     *      +-------+    +------+    +---------------+
3807
     *      | scale | -> | crop | -> | color convert |
3808
     *      +-------+    +------+    +---------------+
3809
     *
3810
     *  The order of the first two blocks mirrors `-c`, so we hop between
3811
     *  SCALE and CROP depending on `clipfirst`.
3812
     */
3813

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

3844
    if (assessment != NULL) {
529✔
3845
        sixel_assessment_stage_transition(
3✔
3846
            assessment,
1✔
3847
            SIXEL_ASSESSMENT_STAGE_COLORSPACE);
3848
    }
1✔
3849

3850
    status = sixel_frame_ensure_colorspace(frame,
712✔
3851
                                           encoder->working_colorspace);
183✔
3852
    if (SIXEL_FAILED(status)) {
529!
3853
        goto end;
×
3854
    }
3855

3856
    if (assessment != NULL) {
529✔
3857
        sixel_assessment_stage_transition(
3✔
3858
            assessment,
1✔
3859
            SIXEL_ASSESSMENT_STAGE_PALETTE_HISTOGRAM);
3860
    }
1✔
3861

3862
    /* prepare dither context */
3863
    status = sixel_encoder_prepare_palette(encoder, frame, &dither);
529✔
3864
    if (status != SIXEL_OK) {
529!
3865
        dither = NULL;
×
3866
        goto end;
×
3867
    }
3868

3869
    if (encoder->dither_cache != NULL) {
529!
3870
        encoder->dither_cache = dither;
×
3871
        sixel_dither_ref(dither);
×
3872
    }
3873

3874
    if (encoder->fdrcs) {
529!
3875
        status = sixel_encoder_ensure_cell_size(encoder);
×
3876
        if (SIXEL_FAILED(status)) {
×
3877
            goto end;
×
3878
        }
3879
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
×
3880
            sixel_helper_set_additional_message(
×
3881
                "drcs option cannot be used together with macro output.");
3882
            status = SIXEL_BAD_ARGUMENT;
×
3883
            goto end;
×
3884
        }
3885
    }
3886

3887
    /* evaluate -v option: print palette */
3888
    if (encoder->verbose) {
529✔
3889
        if ((sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE)) {
9✔
3890
            sixel_debug_print_palette(dither);
3✔
3891
        }
1✔
3892
    }
3✔
3893

3894
    /* evaluate -d option: set method for diffusion */
3895
    sixel_dither_set_diffusion_type(dither, encoder->method_for_diffuse);
529✔
3896
    sixel_dither_set_diffusion_scan(dither, encoder->method_for_scan);
529✔
3897
    sixel_dither_set_diffusion_carry(dither, encoder->method_for_carry);
529✔
3898

3899
    /* evaluate -C option: set complexion score */
3900
    if (encoder->complexion > 1) {
529✔
3901
        sixel_dither_set_complexion_score(dither, encoder->complexion);
6✔
3902
    }
2✔
3903

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

3939
    if (encoder->fdrcs) {
529!
3940
        sixel_output_set_skip_dcs_envelope(output, 1);
×
3941
        sixel_output_set_skip_header(output, 1);
×
3942
    }
3943

3944
    sixel_output_set_8bit_availability(output, encoder->f8bit);
529✔
3945
    sixel_output_set_gri_arg_limit(output, encoder->has_gri_arg_limit);
529✔
3946
    sixel_output_set_palette_type(output, encoder->palette_type);
529✔
3947
    sixel_output_set_penetrate_multiplexer(
529✔
3948
        output, encoder->penetrate_multiplexer);
183✔
3949
    sixel_output_set_encode_policy(output, encoder->encode_policy);
529✔
3950
    sixel_output_set_ormode(output, encoder->ormode);
529✔
3951

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

3974
    if (encoder->cancel_flag && *encoder->cancel_flag) {
529!
3975
        status = SIXEL_INTERRUPTED;
×
3976
        goto end;
×
3977
    }
3978

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

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

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

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

4062
        if (encoder->tile_outfd >= 0) {
×
4063
            if (encoder->drcs_mmv == 0) {
×
4064
                status = sixel_encoder_emit_iso2022_chars(encoder, frame);
×
4065
                if (SIXEL_FAILED(status)) {
×
4066
                    goto end;
×
4067
                }
4068
            } else {
4069
                status = sixel_encoder_emit_drcsmmv2_chars(encoder, frame);
×
4070
                if (SIXEL_FAILED(status)) {
×
4071
                    goto end;
×
4072
                }
4073
            }
4074
        }
4075
    }
4076

4077

4078
end:
346✔
4079
    if (output) {
538✔
4080
        sixel_output_unref(output);
529✔
4081
    }
183✔
4082
    if (dither) {
538✔
4083
        sixel_dither_unref(dither);
529✔
4084
    }
183✔
4085

4086
    return status;
538✔
4087
}
4088

4089

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

4106
    if (allocator == NULL) {
547!
4107
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
547✔
4108
        if (SIXEL_FAILED(status)) {
547!
4109
            goto end;
×
4110
        }
4111
    } else {
183✔
4112
        sixel_allocator_ref(allocator);
×
4113
    }
4114

4115
    *ppencoder
183✔
4116
        = (sixel_encoder_t *)sixel_allocator_malloc(allocator,
730✔
4117
                                                    sizeof(sixel_encoder_t));
4118
    if (*ppencoder == NULL) {
547!
4119
        sixel_helper_set_additional_message(
×
4120
            "sixel_encoder_new: sixel_allocator_malloc() failed.");
4121
        status = SIXEL_BAD_ALLOCATION;
×
4122
        sixel_allocator_unref(allocator);
×
4123
        goto end;
×
4124
    }
4125

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

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

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

4249
    /* success */
4250
    status = SIXEL_OK;
547✔
4251

4252
    goto end;
547✔
4253

4254
error:
4255
    sixel_allocator_free(allocator, *ppencoder);
×
4256
    sixel_allocator_unref(allocator);
×
4257
    *ppencoder = NULL;
×
4258

4259
end:
364✔
4260
#if HAVE__DUPENV_S
4261
    free(env_default_bgcolor);
4262
    free(env_default_ncolors);
4263
#endif  /* HAVE__DUPENV_S */
4264
    return status;
547✔
4265
}
4266

4267

4268
/* create encoder object (deprecated version) */
4269
SIXELAPI /* deprecated */ sixel_encoder_t *
4270
sixel_encoder_create(void)
×
4271
{
4272
    SIXELSTATUS status = SIXEL_FALSE;
×
4273
    sixel_encoder_t *encoder = NULL;
×
4274

4275
    status = sixel_encoder_new(&encoder, NULL);
×
4276
    if (SIXEL_FAILED(status)) {
×
4277
        return NULL;
×
4278
    }
4279

4280
    return encoder;
×
4281
}
4282

4283

4284
/* destroy encoder object */
4285
static void
4286
sixel_encoder_destroy(sixel_encoder_t *encoder)
547✔
4287
{
4288
    sixel_allocator_t *allocator;
4289

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

4326

4327
/* increase reference count of encoder object (thread-unsafe) */
4328
SIXELAPI void
4329
sixel_encoder_ref(sixel_encoder_t *encoder)
1,151✔
4330
{
4331
    /* TODO: be thread safe */
4332
    ++encoder->ref;
1,151✔
4333
}
1,151✔
4334

4335

4336
/* decrease reference count of encoder object (thread-unsafe) */
4337
SIXELAPI void
4338
sixel_encoder_unref(sixel_encoder_t *encoder)
1,698✔
4339
{
4340
    /* TODO: be thread safe */
4341
    if (encoder != NULL && --encoder->ref == 0) {
1,698!
4342
        sixel_encoder_destroy(encoder);
547✔
4343
    }
183✔
4344
}
1,698✔
4345

4346

4347
/* set cancel state flag to encoder object */
4348
SIXELAPI SIXELSTATUS
4349
sixel_encoder_set_cancel_flag(
442✔
4350
    sixel_encoder_t /* in */ *encoder,
4351
    int             /* in */ *cancel_flag
4352
)
4353
{
4354
    SIXELSTATUS status = SIXEL_OK;
442✔
4355

4356
    encoder->cancel_flag = cancel_flag;
442✔
4357

4358
    return status;
442✔
4359
}
4360

4361

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

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

4516

4517
static int
4518
is_png_target(char const *path)
31✔
4519
{
4520
    size_t len;
4521
    int matched;
4522

4523
    /*
4524
     * Detect PNG requests from explicit prefixes or a ".png" suffix:
4525
     *
4526
     *   argument
4527
     *   |
4528
     *   v
4529
     *   .............. . p n g
4530
     *   ^             ^^^^^^^^^
4531
     *   |             +-- case-insensitive suffix comparison
4532
     *   +-- accepts the "png:" inline prefix used for stdout capture
4533
     */
4534

4535
    len = 0;
31✔
4536
    matched = 0;
31✔
4537

4538
    if (path == NULL) {
31!
4539
        return 0;
×
4540
    }
4541

4542
    if (strncmp(path, "png:", 4) == 0) {
31✔
4543
        return path[4] != '\0';
6✔
4544
    }
4545

4546
    len = strlen(path);
25✔
4547
    if (len >= 4) {
25✔
4548
        matched = (tolower((unsigned char)path[len - 4]) == '.')
23✔
4549
            && (tolower((unsigned char)path[len - 3]) == 'p')
10!
4550
            && (tolower((unsigned char)path[len - 2]) == 'n')
3!
4551
            && (tolower((unsigned char)path[len - 1]) == 'g');
24!
4552
    }
8✔
4553

4554
    return matched;
25✔
4555
}
11✔
4556

4557

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

4584
    return argument;
3✔
4585
}
3✔
4586

4587
static void
4588
normalise_windows_drive_path(char *path)
9✔
4589
{
4590
#if defined(_WIN32)
4591
    size_t length;
4592

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

4609
    length = 0u;
4610

4611
    if (path == NULL) {
4612
        return;
4613
    }
4614

4615
    length = strlen(path);
4616
    if (length >= 3u
4617
            && path[0] == '/'
4618
            && ((path[1] >= 'A' && path[1] <= 'Z')
4619
                || (path[1] >= 'a' && path[1] <= 'z'))
4620
            && path[2] == '/') {
4621
        path[0] = path[1];
4622
        path[1] = ':';
4623
    }
4624
#else
4625
    (void)path;
3✔
4626
#endif
4627
}
9✔
4628

4629

4630
static int
4631
is_dev_null_path(char const *path)
×
4632
{
4633
    if (path == NULL || path[0] == '\0') {
×
4634
        return 0;
×
4635
    }
4636
#if defined(_WIN32)
4637
    if (_stricmp(path, "nul") == 0) {
4638
        return 1;
4639
    }
4640
#endif
4641
    return strcmp(path, "/dev/null") == 0;
×
4642
}
4643

4644

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

4681
    sixel_encoder_ref(encoder);
715✔
4682
    opt_copy = NULL;
715✔
4683

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

4782
                clipboard_spec.is_clipboard = 0;
22✔
4783
                clipboard_spec.format[0] = '\0';
22✔
4784
                clip_status = SIXEL_OK;
22✔
4785
                spool_path = NULL;
22✔
4786
                spool_fd = (-1);
22✔
4787

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

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

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

5665
            if (len >= sizeof(lowered)) {
×
5666
                sixel_helper_set_additional_message(
×
5667
                    "specified working colorspace name is too long.");
5668
                status = SIXEL_BAD_ARGUMENT;
×
5669
                goto end;
×
5670
            }
5671
            for (i = 0; i < len; ++i) {
×
5672
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
5673
            }
5674
            lowered[len] = '\0';
×
5675

5676
            match_result = sixel_match_option_choice(
×
5677
                lowered,
5678
                g_option_choices_working_colorspace,
5679
                sizeof(g_option_choices_working_colorspace) /
5680
                sizeof(g_option_choices_working_colorspace[0]),
5681
                &match_value,
5682
                match_detail,
5683
                sizeof(match_detail));
5684
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5685
                encoder->working_colorspace = match_value;
×
5686
            } else {
5687
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5688
                    sixel_report_ambiguous_prefix(
×
5689
                        "--working-colorspace",
5690
                        value,
5691
                        match_detail,
5692
                        match_message,
5693
                        sizeof(match_message));
5694
                } else {
5695
                    sixel_helper_set_additional_message(
×
5696
                        "unsupported working colorspace specified.");
5697
                }
5698
                status = SIXEL_BAD_ARGUMENT;
×
5699
                goto end;
×
5700
            }
5701
        }
5702
        break;
×
5703
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
5704
        if (value == NULL) {
×
5705
            sixel_helper_set_additional_message(
×
5706
                "output-colorspace requires an argument.");
5707
            status = SIXEL_BAD_ARGUMENT;
×
5708
            goto end;
×
5709
        } else {
5710
            len = strlen(value);
×
5711

5712
            if (len >= sizeof(lowered)) {
×
5713
                sixel_helper_set_additional_message(
×
5714
                    "specified output colorspace name is too long.");
5715
                status = SIXEL_BAD_ARGUMENT;
×
5716
                goto end;
×
5717
            }
5718
            for (i = 0; i < len; ++i) {
×
5719
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
5720
            }
5721
            lowered[len] = '\0';
×
5722

5723
            match_result = sixel_match_option_choice(
×
5724
                lowered,
5725
                g_option_choices_output_colorspace,
5726
                sizeof(g_option_choices_output_colorspace) /
5727
                sizeof(g_option_choices_output_colorspace[0]),
5728
                &match_value,
5729
                match_detail,
5730
                sizeof(match_detail));
5731
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5732
                encoder->output_colorspace = match_value;
×
5733
            } else {
5734
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5735
                    sixel_report_ambiguous_prefix(
×
5736
                        "--output-colorspace",
5737
                        value,
5738
                        match_detail,
5739
                        match_message,
5740
                        sizeof(match_message));
5741
                } else {
5742
                    sixel_helper_set_additional_message(
×
5743
                        "unsupported output colorspace specified.");
5744
                }
5745
                status = SIXEL_BAD_ARGUMENT;
×
5746
                goto end;
×
5747
            }
5748
        }
5749
        break;
×
5750
    case SIXEL_OPTFLAG_ORMODE:  /* O */
5751
        encoder->ormode = 1;
×
5752
        break;
×
5753
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
6✔
5754
        encoder->complexion = atoi(value);
9✔
5755
        if (encoder->complexion < 1) {
9✔
5756
            sixel_helper_set_additional_message(
3✔
5757
                "complexion parameter must be 1 or more.");
5758
            status = SIXEL_BAD_ARGUMENT;
3✔
5759
            goto end;
3✔
5760
        }
5761
        break;
6✔
5762
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
5763
        encoder->pipe_mode = 1;
×
5764
        break;
×
5765
    case '?':  /* unknown option */
×
5766
    default:
5767
        /* exit if unknown options are specified */
5768
        sixel_helper_set_additional_message(
×
5769
            "unknown option is specified.");
5770
        status = SIXEL_BAD_ARGUMENT;
×
5771
        goto end;
×
5772
    }
5773

5774
    /* detects arguments conflictions */
5775
    if (encoder->reqcolors != (-1)) {
640✔
5776
        switch (encoder->color_option) {
99!
5777
        case SIXEL_COLOR_OPTION_MAPFILE:
5778
            sixel_helper_set_additional_message(
×
5779
                "option -p, --colors conflicts with -m, --mapfile.");
5780
            status = SIXEL_BAD_ARGUMENT;
×
5781
            goto end;
×
5782
        case SIXEL_COLOR_OPTION_MONOCHROME:
2✔
5783
            sixel_helper_set_additional_message(
3✔
5784
                "option -e, --monochrome conflicts with -p, --colors.");
5785
            status = SIXEL_BAD_ARGUMENT;
3✔
5786
            goto end;
3✔
5787
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
2✔
5788
            sixel_helper_set_additional_message(
3✔
5789
                "option -p, --colors conflicts with -I, --high-color.");
5790
            status = SIXEL_BAD_ARGUMENT;
3✔
5791
            goto end;
3✔
5792
        case SIXEL_COLOR_OPTION_BUILTIN:
2✔
5793
            sixel_helper_set_additional_message(
3✔
5794
                "option -p, --colors conflicts with -b, --builtin-palette.");
5795
            status = SIXEL_BAD_ARGUMENT;
3✔
5796
            goto end;
3✔
5797
        default:
60✔
5798
            break;
90✔
5799
        }
5800
    }
30✔
5801

5802
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
5803
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
631✔
5804
        sixel_helper_set_additional_message(
3✔
5805
            "option -8 --8bit-mode conflicts"
5806
            " with -P, --penetrate.");
5807
        status = SIXEL_BAD_ARGUMENT;
3✔
5808
        goto end;
3✔
5809
    }
5810

5811
    status = SIXEL_OK;
628✔
5812

5813
end:
476✔
5814
    if (opt_copy != NULL) {
715!
5815
        sixel_allocator_free(encoder->allocator, opt_copy);
×
5816
    }
5817
    sixel_encoder_unref(encoder);
715✔
5818

5819
    return status;
715✔
5820
}
5821

5822

5823
/* called when image loader component load a image frame */
5824
static SIXELSTATUS
5825
load_image_callback(sixel_frame_t *frame, void *data)
538✔
5826
{
5827
    sixel_encoder_t *encoder;
5828

5829
    encoder = (sixel_encoder_t *)data;
538✔
5830
    if (encoder->capture_source && encoder->capture_source_frame == NULL) {
538!
5831
        sixel_frame_ref(frame);
3✔
5832
        encoder->capture_source_frame = frame;
3✔
5833
    }
1✔
5834

5835
    return sixel_encoder_encode_frame(encoder, frame, NULL);
538✔
5836
}
5837

5838
/*
5839
 * Build a tee for encoded-assessment output:
5840
 *
5841
 *     +-------------+     +-------------------+     +------------+
5842
 *     | encoder FD  | --> | temporary SIXEL   | --> | tee sink   |
5843
 *     +-------------+     +-------------------+     +------------+
5844
 *
5845
 * The tee sink can be stdout or a user-provided file such as /dev/null.
5846
 */
5847
static SIXELSTATUS
5848
copy_file_to_stream(char const *path,
×
5849
                    FILE *stream,
5850
                    sixel_assessment_t *assessment)
5851
{
5852
    FILE *source;
5853
    unsigned char buffer[4096];
5854
    size_t nread;
5855
    size_t nwritten;
5856
    double started_at;
5857
    double finished_at;
5858
    double duration;
5859

5860
    source = NULL;
×
5861
    nread = 0;
×
5862
    nwritten = 0;
×
5863
    started_at = 0.0;
×
5864
    finished_at = 0.0;
×
5865
    duration = 0.0;
×
5866

5867
    source = sixel_compat_fopen(path, "rb");
×
5868
    if (source == NULL) {
×
5869
        sixel_helper_set_additional_message(
×
5870
            "copy_file_to_stream: failed to open assessment staging file.");
5871
        return SIXEL_LIBC_ERROR;
×
5872
    }
5873

5874
    for (;;) {
5875
        nread = fread(buffer, 1, sizeof(buffer), source);
×
5876
        if (nread == 0) {
×
5877
            if (ferror(source)) {
×
5878
                sixel_helper_set_additional_message(
×
5879
                    "copy_file_to_stream: failed while reading assessment staging file.");
5880
                (void) fclose(source);
×
5881
                return SIXEL_LIBC_ERROR;
×
5882
            }
5883
            break;
×
5884
        }
5885
        if (assessment != NULL) {
×
5886
            started_at = sixel_assessment_timer_now();
×
5887
        }
5888
        nwritten = fwrite(buffer, 1, nread, stream);
×
5889
        if (nwritten != nread) {
×
5890
            sixel_helper_set_additional_message(
×
5891
                "img2sixel: failed while copying assessment staging file.");
5892
            (void) fclose(source);
×
5893
            return SIXEL_LIBC_ERROR;
×
5894
        }
5895
        if (assessment != NULL) {
×
5896
            finished_at = sixel_assessment_timer_now();
×
5897
            duration = finished_at - started_at;
×
5898
            if (duration < 0.0) {
×
5899
                duration = 0.0;
×
5900
            }
5901
            sixel_assessment_record_output_write(assessment,
×
5902
                                                 nwritten,
5903
                                                 duration);
5904
        }
5905
    }
5906

5907
    if (fclose(source) != 0) {
×
5908
        sixel_helper_set_additional_message(
×
5909
            "img2sixel: failed to close assessment staging file.");
5910
        return SIXEL_LIBC_ERROR;
×
5911
    }
5912

5913
    return SIXEL_OK;
×
5914
}
5915

5916
typedef struct assessment_json_sink {
5917
    FILE *stream;
5918
    int failed;
5919
} assessment_json_sink_t;
5920

5921
static void
5922
assessment_json_callback(char const *chunk,
42✔
5923
                         size_t length,
5924
                         void *user_data)
5925
{
5926
    assessment_json_sink_t *sink;
5927

5928
    sink = (assessment_json_sink_t *)user_data;
42✔
5929
    if (sink == NULL || sink->stream == NULL) {
42!
5930
        return;
×
5931
    }
5932
    if (sink->failed) {
42!
5933
        return;
×
5934
    }
5935
    if (fwrite(chunk, 1, length, sink->stream) != length) {
42!
5936
        sink->failed = 1;
×
5937
    }
5938
}
14✔
5939

5940
static char *
5941
create_temp_template_with_prefix(sixel_allocator_t *allocator,
11✔
5942
                                 char const *prefix,
5943
                                 size_t *capacity_out)
5944
{
5945
    char const *tmpdir;
5946
    size_t tmpdir_len;
5947
    size_t prefix_len;
5948
    size_t suffix_len;
5949
    size_t template_len;
5950
    char *template_path;
5951
    int needs_separator;
5952
    size_t maximum_tmpdir_len;
5953

5954
    tmpdir = sixel_compat_getenv("TMPDIR");
11✔
5955
#if defined(_WIN32)
5956
    if (tmpdir == NULL || tmpdir[0] == '\0') {
5957
        tmpdir = sixel_compat_getenv("TEMP");
5958
    }
5959
    if (tmpdir == NULL || tmpdir[0] == '\0') {
5960
        tmpdir = sixel_compat_getenv("TMP");
5961
    }
5962
#endif
5963
    if (tmpdir == NULL || tmpdir[0] == '\0') {
11!
5964
#if defined(_WIN32)
5965
        tmpdir = ".";
5966
#else
5967
        tmpdir = "/tmp";
6✔
5968
#endif
5969
    }
5970

5971
    tmpdir_len = strlen(tmpdir);
11✔
5972
    prefix_len = 0u;
11✔
5973
    suffix_len = 0u;
11✔
5974
    if (prefix == NULL) {
11!
5975
        return NULL;
×
5976
    }
5977

5978
    prefix_len = strlen(prefix);
11✔
5979
    suffix_len = prefix_len + strlen("-XXXXXX");
11✔
5980
    maximum_tmpdir_len = (size_t)INT_MAX;
11✔
5981

5982
    if (maximum_tmpdir_len <= suffix_len + 2) {
11!
5983
        return NULL;
×
5984
    }
5985
    if (tmpdir_len > maximum_tmpdir_len - (suffix_len + 2)) {
11!
5986
        return NULL;
×
5987
    }
5988
    needs_separator = 1;
11✔
5989
    if (tmpdir_len > 0) {
11!
5990
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
11!
5991
            needs_separator = 0;
5✔
5992
        }
5✔
5993
    }
5✔
5994

5995
    template_len = tmpdir_len + suffix_len + 2;
11✔
5996
    template_path = (char *)sixel_allocator_malloc(allocator, template_len);
11✔
5997
    if (template_path == NULL) {
11!
5998
        return NULL;
×
5999
    }
6000

6001
    if (needs_separator) {
11!
6002
#if defined(_WIN32)
6003
        (void) snprintf(template_path, template_len,
6004
                        "%s\\%s-XXXXXX", tmpdir, prefix);
6005
#else
6006
        (void) snprintf(template_path, template_len,
6✔
6007
                        "%s/%s-XXXXXX", tmpdir, prefix);
6008
#endif
6009
    } else {
6010
        (void) snprintf(template_path, template_len,
5✔
6011
                        "%s%s-XXXXXX", tmpdir, prefix);
6012
    }
6013

6014
    if (capacity_out != NULL) {
11!
6015
        *capacity_out = template_len;
11✔
6016
    }
5✔
6017

6018
    return template_path;
11✔
6019
}
5✔
6020

6021

6022
static char *
6023
create_temp_template(sixel_allocator_t *allocator,
9✔
6024
                     size_t *capacity_out)
6025
{
6026
    return create_temp_template_with_prefix(allocator,
12✔
6027
                                            "img2sixel",
6028
                                            capacity_out);
3✔
6029
}
6030

6031

6032
static void
6033
clipboard_select_format(char *dest,
2✔
6034
                        size_t dest_size,
6035
                        char const *format,
6036
                        char const *fallback)
6037
{
6038
    char const *source;
6039
    size_t limit;
6040

6041
    if (dest == NULL || dest_size == 0u) {
2!
6042
        return;
×
6043
    }
6044

6045
    source = fallback;
2✔
6046
    if (format != NULL && format[0] != '\0') {
2!
6047
        source = format;
×
6048
    }
6049

6050
    limit = dest_size - 1u;
2✔
6051
    if (limit == 0u) {
2!
6052
        dest[0] = '\0';
×
6053
        return;
×
6054
    }
6055

6056
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
2✔
6057
}
2✔
6058

6059

6060
static SIXELSTATUS
6061
clipboard_create_spool(sixel_allocator_t *allocator,
2✔
6062
                       char const *prefix,
6063
                       char **path_out,
6064
                       int *fd_out)
6065
{
6066
    SIXELSTATUS status;
6067
    char *template_path;
6068
    size_t template_capacity;
6069
    int open_flags;
6070
    int fd;
6071
    char *tmpname_result;
6072

6073
    status = SIXEL_FALSE;
2✔
6074
    template_path = NULL;
2✔
6075
    template_capacity = 0u;
2✔
6076
    open_flags = 0;
2✔
6077
    fd = (-1);
2✔
6078
    tmpname_result = NULL;
2✔
6079

6080
    template_path = create_temp_template_with_prefix(allocator,
4✔
6081
                                                     prefix,
2✔
6082
                                                     &template_capacity);
6083
    if (template_path == NULL) {
2!
6084
        sixel_helper_set_additional_message(
×
6085
            "clipboard: failed to allocate spool template.");
6086
        status = SIXEL_BAD_ALLOCATION;
×
6087
        goto end;
×
6088
    }
6089

6090
    if (sixel_compat_mktemp(template_path, template_capacity) != 0) {
2!
6091
        /* Fall back to tmpnam() when mktemp variants are unavailable. */
6092
        tmpname_result = tmpnam(template_path);
×
6093
        if (tmpname_result == NULL) {
×
6094
            sixel_helper_set_additional_message(
×
6095
                "clipboard: failed to reserve spool template.");
6096
            status = SIXEL_LIBC_ERROR;
×
6097
            goto end;
×
6098
        }
6099
        template_capacity = strlen(template_path) + 1u;
×
6100
    }
6101

6102
    open_flags = O_RDWR | O_CREAT | O_TRUNC;
2✔
6103
#if defined(O_EXCL)
6104
    open_flags |= O_EXCL;
2✔
6105
#endif
6106
    fd = sixel_compat_open(template_path, open_flags, S_IRUSR | S_IWUSR);
2✔
6107
    if (fd < 0) {
2!
6108
        sixel_helper_set_additional_message(
×
6109
            "clipboard: failed to open spool file.");
6110
        status = SIXEL_LIBC_ERROR;
×
6111
        goto end;
×
6112
    }
6113

6114
    *path_out = template_path;
2✔
6115
    if (fd_out != NULL) {
2!
6116
        *fd_out = fd;
1✔
6117
        fd = (-1);
1✔
6118
    }
1✔
6119

6120
    template_path = NULL;
2✔
6121
    status = SIXEL_OK;
2✔
6122

6123
end:
6124
    if (fd >= 0) {
2!
6125
        (void)sixel_compat_close(fd);
1✔
6126
    }
1✔
6127
    if (template_path != NULL) {
2!
6128
        sixel_allocator_free(allocator, template_path);
×
6129
    }
6130

6131
    return status;
2✔
6132
}
6133

6134

6135
static SIXELSTATUS
6136
clipboard_write_file(char const *path,
1✔
6137
                     unsigned char const *data,
6138
                     size_t size)
6139
{
6140
    FILE *stream;
6141
    size_t written;
6142

6143
    if (path == NULL) {
1!
6144
        sixel_helper_set_additional_message(
×
6145
            "clipboard: spool path is null.");
6146
        return SIXEL_BAD_ARGUMENT;
×
6147
    }
6148

6149
    stream = sixel_compat_fopen(path, "wb");
1✔
6150
    if (stream == NULL) {
1!
6151
        sixel_helper_set_additional_message(
×
6152
            "clipboard: failed to open spool file for write.");
6153
        return SIXEL_LIBC_ERROR;
×
6154
    }
6155

6156
    written = 0u;
1✔
6157
    if (size > 0u && data != NULL) {
1!
6158
        written = fwrite(data, 1u, size, stream);
1✔
6159
        if (written != size) {
1!
6160
            (void)fclose(stream);
×
6161
            sixel_helper_set_additional_message(
×
6162
                "clipboard: failed to write spool payload.");
6163
            return SIXEL_LIBC_ERROR;
×
6164
        }
6165
    }
1✔
6166

6167
    if (fclose(stream) != 0) {
1!
6168
        sixel_helper_set_additional_message(
×
6169
            "clipboard: failed to close spool file after write.");
6170
        return SIXEL_LIBC_ERROR;
×
6171
    }
6172

6173
    return SIXEL_OK;
1✔
6174
}
1✔
6175

6176

6177
static SIXELSTATUS
6178
clipboard_read_file(char const *path,
1✔
6179
                    unsigned char **data,
6180
                    size_t *size)
6181
{
6182
    FILE *stream;
6183
    long seek_result;
6184
    long file_size;
6185
    unsigned char *buffer;
6186
    size_t read_size;
6187

6188
    if (data == NULL || size == NULL) {
1!
6189
        sixel_helper_set_additional_message(
×
6190
            "clipboard: read buffer pointers are null.");
6191
        return SIXEL_BAD_ARGUMENT;
×
6192
    }
6193

6194
    *data = NULL;
1✔
6195
    *size = 0u;
1✔
6196

6197
    if (path == NULL) {
1!
6198
        sixel_helper_set_additional_message(
×
6199
            "clipboard: spool path is null.");
6200
        return SIXEL_BAD_ARGUMENT;
×
6201
    }
6202

6203
    stream = sixel_compat_fopen(path, "rb");
1✔
6204
    if (stream == NULL) {
1!
6205
        sixel_helper_set_additional_message(
×
6206
            "clipboard: failed to open spool file for read.");
6207
        return SIXEL_LIBC_ERROR;
×
6208
    }
6209

6210
    seek_result = fseek(stream, 0L, SEEK_END);
1✔
6211
    if (seek_result != 0) {
1!
6212
        (void)fclose(stream);
×
6213
        sixel_helper_set_additional_message(
×
6214
            "clipboard: failed to seek spool file.");
6215
        return SIXEL_LIBC_ERROR;
×
6216
    }
6217

6218
    file_size = ftell(stream);
1✔
6219
    if (file_size < 0) {
1!
6220
        (void)fclose(stream);
×
6221
        sixel_helper_set_additional_message(
×
6222
            "clipboard: failed to determine spool size.");
6223
        return SIXEL_LIBC_ERROR;
×
6224
    }
6225

6226
    seek_result = fseek(stream, 0L, SEEK_SET);
1✔
6227
    if (seek_result != 0) {
1!
6228
        (void)fclose(stream);
×
6229
        sixel_helper_set_additional_message(
×
6230
            "clipboard: failed to rewind spool file.");
6231
        return SIXEL_LIBC_ERROR;
×
6232
    }
6233

6234
    if (file_size == 0) {
1!
6235
        buffer = NULL;
×
6236
        read_size = 0u;
×
6237
    } else {
6238
        buffer = (unsigned char *)malloc((size_t)file_size);
1✔
6239
        if (buffer == NULL) {
1!
6240
            (void)fclose(stream);
×
6241
            sixel_helper_set_additional_message(
×
6242
                "clipboard: malloc() failed for spool payload.");
6243
            return SIXEL_BAD_ALLOCATION;
×
6244
        }
6245
        read_size = fread(buffer, 1u, (size_t)file_size, stream);
1✔
6246
        if (read_size != (size_t)file_size) {
1!
6247
            free(buffer);
×
6248
            (void)fclose(stream);
×
6249
            sixel_helper_set_additional_message(
×
6250
                "clipboard: failed to read spool payload.");
6251
            return SIXEL_LIBC_ERROR;
×
6252
        }
6253
    }
6254

6255
    if (fclose(stream) != 0) {
1!
6256
        if (buffer != NULL) {
×
6257
            free(buffer);
×
6258
        }
6259
        sixel_helper_set_additional_message(
×
6260
            "clipboard: failed to close spool file after read.");
6261
        return SIXEL_LIBC_ERROR;
×
6262
    }
6263

6264
    *data = buffer;
1✔
6265
    *size = read_size;
1✔
6266

6267
    return SIXEL_OK;
1✔
6268
}
1✔
6269

6270

6271
static SIXELSTATUS
6272
write_png_from_sixel(char const *sixel_path, char const *output_path)
9✔
6273
{
6274
    SIXELSTATUS status;
6275
    sixel_decoder_t *decoder;
6276

6277
    status = SIXEL_FALSE;
9✔
6278
    decoder = NULL;
9✔
6279

6280
    status = sixel_decoder_new(&decoder, NULL);
9✔
6281
    if (SIXEL_FAILED(status)) {
9!
6282
        goto end;
×
6283
    }
6284

6285
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_INPUT, sixel_path);
9✔
6286
    if (SIXEL_FAILED(status)) {
9!
6287
        goto end;
×
6288
    }
6289

6290
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_OUTPUT, output_path);
9✔
6291
    if (SIXEL_FAILED(status)) {
9!
6292
        goto end;
×
6293
    }
6294

6295
    status = sixel_decoder_decode(decoder);
9✔
6296

6297
end:
6✔
6298
    sixel_decoder_unref(decoder);
9✔
6299

6300
    return status;
9✔
6301
}
6302

6303

6304
/* load source data from specified file and encode it to SIXEL format
6305
 * output to encoder->outfd */
6306
SIXELAPI SIXELSTATUS
6307
sixel_encoder_encode(
436✔
6308
    sixel_encoder_t *encoder,   /* encoder object */
6309
    char const      *filename)  /* input filename */
6310
{
6311
    SIXELSTATUS status = SIXEL_FALSE;
436✔
6312
    SIXELSTATUS palette_status = SIXEL_OK;
436✔
6313
    int fuse_palette = 1;
436✔
6314
    sixel_loader_t *loader = NULL;
436✔
6315
    sixel_allocator_t *assessment_allocator = NULL;
436✔
6316
    sixel_frame_t *assessment_source_frame = NULL;
436✔
6317
    sixel_frame_t *assessment_target_frame = NULL;
436✔
6318
    sixel_frame_t *assessment_expanded_frame = NULL;
436✔
6319
    unsigned int assessment_section_mask =
436✔
6320
        encoder->assessment_sections & SIXEL_ASSESSMENT_SECTION_MASK;
436✔
6321
    int assessment_need_source_capture = 0;
436✔
6322
    int assessment_need_quantized_capture = 0;
436✔
6323
    int assessment_need_quality = 0;
436✔
6324
    int assessment_quality_quantized = 0;
436✔
6325
    assessment_json_sink_t assessment_sink;
6326
    FILE *assessment_json_file = NULL;
436✔
6327
    FILE *assessment_forward_stream = NULL;
436✔
6328
    int assessment_json_owned = 0;
436✔
6329
    char *assessment_temp_path = NULL;
436✔
6330
    size_t assessment_temp_capacity = 0u;
436✔
6331
    char *assessment_tmpnam_result = NULL;
436✔
6332
    sixel_assessment_spool_mode_t assessment_spool_mode
436✔
6333
        = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
6334
    char *assessment_forward_path = NULL;
436✔
6335
    size_t assessment_output_bytes;
6336
#if HAVE_SYS_STAT_H
6337
    struct stat assessment_stat;
6338
    int assessment_stat_result;
6339
    char const *assessment_size_path = NULL;
436✔
6340
#endif
6341
    char const *png_final_path = NULL;
436✔
6342
    char *png_temp_path = NULL;
436✔
6343
    size_t png_temp_capacity = 0u;
436✔
6344
    char *png_tmpnam_result = NULL;
436✔
6345
    int png_open_flags = 0;
436✔
6346
    int spool_required;
6347
    sixel_clipboard_spec_t clipboard_spec;
6348
    char clipboard_input_format[32];
6349
    char *clipboard_input_path;
6350
    unsigned char *clipboard_blob;
6351
    size_t clipboard_blob_size;
6352
    SIXELSTATUS clipboard_status;
6353
    char const *effective_filename;
6354

6355
    clipboard_input_format[0] = '\0';
436✔
6356
    clipboard_input_path = NULL;
436✔
6357
    clipboard_blob = NULL;
436✔
6358
    clipboard_blob_size = 0u;
436✔
6359
    clipboard_status = SIXEL_OK;
436✔
6360
    effective_filename = filename;
436✔
6361

6362
    clipboard_spec.is_clipboard = 0;
436✔
6363
    clipboard_spec.format[0] = '\0';
436✔
6364
    if (effective_filename != NULL
437!
6365
            && sixel_clipboard_parse_spec(effective_filename,
342!
6366
                                          &clipboard_spec)
6367
            && clipboard_spec.is_clipboard) {
99!
6368
        clipboard_select_format(clipboard_input_format,
2✔
6369
                                sizeof(clipboard_input_format),
6370
                                clipboard_spec.format,
1✔
6371
                                "sixel");
6372
        clipboard_status = sixel_clipboard_read(
1✔
6373
            clipboard_input_format,
1✔
6374
            &clipboard_blob,
6375
            &clipboard_blob_size,
6376
            encoder->allocator);
1✔
6377
        if (SIXEL_FAILED(clipboard_status)) {
1!
6378
            status = clipboard_status;
×
6379
            goto end;
×
6380
        }
6381
        clipboard_status = clipboard_create_spool(
1✔
6382
            encoder->allocator,
1✔
6383
            "clipboard-in",
6384
            &clipboard_input_path,
6385
            NULL);
6386
        if (SIXEL_FAILED(clipboard_status)) {
1!
6387
            status = clipboard_status;
×
6388
            goto end;
×
6389
        }
6390
        clipboard_status = clipboard_write_file(
1✔
6391
            clipboard_input_path,
1✔
6392
            clipboard_blob,
1✔
6393
            clipboard_blob_size);
1✔
6394
        if (SIXEL_FAILED(clipboard_status)) {
1!
6395
            status = clipboard_status;
×
6396
            goto end;
×
6397
        }
6398
        if (clipboard_blob != NULL) {
1!
6399
            free(clipboard_blob);
1✔
6400
            clipboard_blob = NULL;
1✔
6401
        }
1✔
6402
        effective_filename = clipboard_input_path;
1✔
6403
    }
1✔
6404

6405
    if (assessment_section_mask != SIXEL_ASSESSMENT_SECTION_NONE) {
436✔
6406
        status = sixel_allocator_new(&assessment_allocator,
3✔
6407
                                     malloc,
6408
                                     calloc,
6409
                                     realloc,
6410
                                     free);
6411
        if (SIXEL_FAILED(status) || assessment_allocator == NULL) {
3!
6412
            goto end;
×
6413
        }
6414
        status = sixel_assessment_new(&encoder->assessment_observer,
4✔
6415
                                       assessment_allocator);
1✔
6416
        if (SIXEL_FAILED(status) || encoder->assessment_observer == NULL) {
3!
6417
            goto end;
×
6418
        }
6419
        sixel_assessment_select_sections(encoder->assessment_observer,
4✔
6420
                                         encoder->assessment_sections);
1✔
6421
        sixel_assessment_attach_encoder(encoder->assessment_observer,
4✔
6422
                                        encoder);
1✔
6423
        assessment_need_quality =
3✔
6424
            (assessment_section_mask & SIXEL_ASSESSMENT_SECTION_QUALITY) != 0u;
3✔
6425
        assessment_quality_quantized =
3✔
6426
            (encoder->assessment_sections & SIXEL_ASSESSMENT_VIEW_QUANTIZED) != 0u;
3✔
6427
        assessment_need_quantized_capture =
3✔
6428
            ((assessment_section_mask &
3✔
6429
              (SIXEL_ASSESSMENT_SECTION_BASIC |
6430
               SIXEL_ASSESSMENT_SECTION_SIZE)) != 0u) ||
3!
6431
            assessment_quality_quantized;
6432
        assessment_need_source_capture =
3✔
6433
            (assessment_section_mask &
3✔
6434
             (SIXEL_ASSESSMENT_SECTION_BASIC |
6435
              SIXEL_ASSESSMENT_SECTION_PERFORMANCE |
6436
              SIXEL_ASSESSMENT_SECTION_SIZE |
6437
              SIXEL_ASSESSMENT_SECTION_QUALITY)) != 0u;
3✔
6438
        if (assessment_need_quality && !assessment_quality_quantized &&
3!
6439
                encoder->output_is_png) {
×
6440
            sixel_helper_set_additional_message(
×
6441
                "sixel_encoder_setopt: encoded quality assessment requires SIXEL output.");
6442
            status = SIXEL_BAD_ARGUMENT;
×
6443
            goto end;
×
6444
        }
6445
        status = sixel_encoder_enable_source_capture(encoder,
4✔
6446
                                                     assessment_need_source_capture);
1✔
6447
        if (SIXEL_FAILED(status)) {
3!
6448
            goto end;
×
6449
        }
6450
        status = sixel_encoder_setopt(encoder, SIXEL_OPTFLAG_STATIC, NULL);
3✔
6451
        if (SIXEL_FAILED(status)) {
3!
6452
            goto end;
×
6453
        }
6454
        if (assessment_need_quantized_capture) {
3!
6455
            status = sixel_encoder_enable_quantized_capture(encoder, 1);
3✔
6456
            if (SIXEL_FAILED(status)) {
3!
6457
                goto end;
×
6458
            }
6459
        }
1✔
6460
        assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
3✔
6461
        spool_required = 0;
3✔
6462
        if (assessment_need_quality && !assessment_quality_quantized) {
3!
6463
            if (encoder->sixel_output_path == NULL) {
×
6464
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT;
×
6465
                spool_required = 1;
×
6466
            } else if (strcmp(encoder->sixel_output_path, "-") == 0) {
×
6467
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT;
×
6468
                spool_required = 1;
×
6469
                free(encoder->sixel_output_path);
×
6470
                encoder->sixel_output_path = NULL;
×
6471
            } else if (is_dev_null_path(encoder->sixel_output_path)) {
×
6472
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_PATH;
×
6473
                spool_required = 1;
×
6474
                assessment_forward_path = encoder->sixel_output_path;
×
6475
                encoder->sixel_output_path = NULL;
×
6476
            }
6477
        }
6478
        if (spool_required) {
3!
6479
            assessment_temp_capacity = 0u;
×
6480
            assessment_tmpnam_result = NULL;
×
6481
            assessment_temp_path = create_temp_template(encoder->allocator,
×
6482
                                                        &assessment_temp_capacity);
6483
            if (assessment_temp_path == NULL) {
×
6484
                sixel_helper_set_additional_message(
×
6485
                    "sixel_encoder_encode: sixel_allocator_malloc() "
6486
                    "failed for assessment staging path.");
6487
                status = SIXEL_BAD_ALLOCATION;
×
6488
                goto end;
×
6489
            }
6490
            if (sixel_compat_mktemp(assessment_temp_path,
×
6491
                                    assessment_temp_capacity) != 0) {
6492
                /* Fall back to tmpnam() when mktemp variants are unavailable. */
6493
                assessment_tmpnam_result = tmpnam(assessment_temp_path);
×
6494
                if (assessment_tmpnam_result == NULL) {
×
6495
                    sixel_helper_set_additional_message(
×
6496
                        "sixel_encoder_encode: mktemp() failed for assessment staging file.");
6497
                    status = SIXEL_RUNTIME_ERROR;
×
6498
                    goto end;
×
6499
                }
6500
                assessment_temp_capacity = strlen(assessment_temp_path) + 1u;
×
6501
            }
6502
            status = sixel_encoder_setopt(encoder, SIXEL_OPTFLAG_OUTFILE,
×
6503
                                          assessment_temp_path);
6504
            if (SIXEL_FAILED(status)) {
×
6505
                goto end;
×
6506
            }
6507
            encoder->sixel_output_path = (char *)sixel_allocator_malloc(
×
6508
                encoder->allocator, strlen(assessment_temp_path) + 1);
×
6509
            if (encoder->sixel_output_path == NULL) {
×
6510
                sixel_helper_set_additional_message(
×
6511
                    "sixel_encoder_encode: malloc() failed for assessment staging name.");
6512
                status = SIXEL_BAD_ALLOCATION;
×
6513
                goto end;
×
6514
            }
6515
            (void)sixel_compat_strcpy(encoder->sixel_output_path,
×
6516
                                      strlen(assessment_temp_path) + 1,
×
6517
                                      assessment_temp_path);
6518
        }
6519

6520
    }
1✔
6521

6522
    if (encoder->output_is_png) {
436✔
6523
        png_temp_capacity = 0u;
9✔
6524
        png_tmpnam_result = NULL;
9✔
6525
        png_temp_path = create_temp_template(encoder->allocator,
9✔
6526
                                             &png_temp_capacity);
6527
        if (png_temp_path == NULL) {
9!
6528
            sixel_helper_set_additional_message(
×
6529
                "sixel_encoder_encode: malloc() failed for PNG staging path.");
6530
            status = SIXEL_BAD_ALLOCATION;
×
6531
            goto end;
×
6532
        }
6533
        if (sixel_compat_mktemp(png_temp_path, png_temp_capacity) != 0) {
9!
6534
            /* Fall back to tmpnam() when mktemp variants are unavailable. */
6535
            png_tmpnam_result = tmpnam(png_temp_path);
×
6536
            if (png_tmpnam_result == NULL) {
×
6537
                sixel_helper_set_additional_message(
×
6538
                    "sixel_encoder_encode: mktemp() failed for PNG staging file.");
6539
                status = SIXEL_RUNTIME_ERROR;
×
6540
                goto end;
×
6541
            }
6542
            png_temp_capacity = strlen(png_temp_path) + 1u;
×
6543
        }
6544
        if (encoder->outfd >= 0 && encoder->outfd != STDOUT_FILENO) {
9!
6545
            (void)sixel_compat_close(encoder->outfd);
9✔
6546
        }
3✔
6547
        png_open_flags = O_RDWR | O_CREAT | O_TRUNC;
9✔
6548
#if defined(O_EXCL)
6549
        png_open_flags |= O_EXCL;
9✔
6550
#endif
6551
        encoder->outfd = sixel_compat_open(png_temp_path,
12✔
6552
                                           png_open_flags,
3✔
6553
                                           S_IRUSR | S_IWUSR);
6554
        if (encoder->outfd < 0) {
9!
6555
            sixel_helper_set_additional_message(
×
6556
                "sixel_encoder_encode: failed to create the PNG target file.");
6557
            status = SIXEL_LIBC_ERROR;
×
6558
            goto end;
×
6559
        }
6560
    }
3✔
6561

6562
    if (encoder == NULL) {
436!
6563
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6564
#  pragma GCC diagnostic push
6565
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
6566
#endif
6567
        encoder = sixel_encoder_create();
×
6568
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6569
#  pragma GCC diagnostic pop
6570
#endif
6571
        if (encoder == NULL) {
×
6572
            sixel_helper_set_additional_message(
×
6573
                "sixel_encoder_encode: sixel_encoder_create() failed.");
6574
            status = SIXEL_BAD_ALLOCATION;
×
6575
            goto end;
×
6576
        }
6577
    } else {
6578
        sixel_encoder_ref(encoder);
436✔
6579
    }
6580

6581
    if (encoder->assessment_observer != NULL) {
436✔
6582
        sixel_assessment_stage_transition(
3✔
6583
            encoder->assessment_observer,
3✔
6584
            SIXEL_ASSESSMENT_STAGE_IMAGE_CHUNK);
6585
    }
1✔
6586
    encoder->last_loader_name[0] = '\0';
436✔
6587
    encoder->last_source_path[0] = '\0';
436✔
6588
    encoder->last_input_bytes = 0u;
436✔
6589

6590
    /* if required color is not set, set the max value */
6591
    if (encoder->reqcolors == (-1)) {
436✔
6592
        encoder->reqcolors = SIXEL_PALETTE_MAX;
418✔
6593
    }
140✔
6594

6595
    if (encoder->capture_source && encoder->capture_source_frame != NULL) {
436!
6596
        sixel_frame_unref(encoder->capture_source_frame);
×
6597
        encoder->capture_source_frame = NULL;
×
6598
    }
6599

6600
    /* if required color is less then 2, set the min value */
6601
    if (encoder->reqcolors < 2) {
436✔
6602
        encoder->reqcolors = SIXEL_PALETTE_MIN;
3✔
6603
    }
1✔
6604

6605
    /* if color space option is not set, choose RGB color space */
6606
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
436✔
6607
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
418✔
6608
    }
140✔
6609

6610
    /* if color option is not default value, prohibit to read
6611
       the file as a paletted image */
6612
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
436✔
6613
        fuse_palette = 0;
96✔
6614
    }
32✔
6615

6616
    /* if scaling options are set, prohibit to read the file as
6617
       a paletted image */
6618
    if (encoder->percentwidth > 0 ||
559✔
6619
        encoder->percentheight > 0 ||
424✔
6620
        encoder->pixelwidth > 0 ||
418✔
6621
        encoder->pixelheight > 0) {
400✔
6622
        fuse_palette = 0;
99✔
6623
    }
33✔
6624

6625
reload:
290✔
6626

6627
    sixel_helper_set_loader_trace(encoder->verbose);
436✔
6628
    sixel_helper_set_thumbnail_size_hint(
436✔
6629
        sixel_encoder_thumbnail_hint(encoder));
146✔
6630

6631
    status = sixel_loader_new(&loader, encoder->allocator);
436✔
6632
    if (SIXEL_FAILED(status)) {
436!
6633
        goto load_end;
×
6634
    }
6635

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

6643
    status = sixel_loader_setopt(loader,
436✔
6644
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
6645
                                 &fuse_palette);
6646
    if (SIXEL_FAILED(status)) {
436!
6647
        goto load_end;
×
6648
    }
6649

6650
    status = sixel_loader_setopt(loader,
582✔
6651
                                 SIXEL_LOADER_OPTION_REQCOLORS,
6652
                                 &encoder->reqcolors);
436✔
6653
    if (SIXEL_FAILED(status)) {
436!
6654
        goto load_end;
×
6655
    }
6656

6657
    status = sixel_loader_setopt(loader,
582✔
6658
                                 SIXEL_LOADER_OPTION_BGCOLOR,
6659
                                 encoder->bgcolor);
436✔
6660
    if (SIXEL_FAILED(status)) {
436!
6661
        goto load_end;
×
6662
    }
6663

6664
    status = sixel_loader_setopt(loader,
582✔
6665
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
6666
                                 &encoder->loop_mode);
436✔
6667
    if (SIXEL_FAILED(status)) {
436!
6668
        goto load_end;
×
6669
    }
6670

6671
    status = sixel_loader_setopt(loader,
582✔
6672
                                 SIXEL_LOADER_OPTION_INSECURE,
6673
                                 &encoder->finsecure);
436✔
6674
    if (SIXEL_FAILED(status)) {
436!
6675
        goto load_end;
×
6676
    }
6677

6678
    status = sixel_loader_setopt(loader,
582✔
6679
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
6680
                                 encoder->cancel_flag);
436✔
6681
    if (SIXEL_FAILED(status)) {
436!
6682
        goto load_end;
×
6683
    }
6684

6685
    status = sixel_loader_setopt(loader,
582✔
6686
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
6687
                                 encoder->loader_order);
436✔
6688
    if (SIXEL_FAILED(status)) {
436!
6689
        goto load_end;
×
6690
    }
6691

6692
    status = sixel_loader_setopt(loader,
582✔
6693
                                 SIXEL_LOADER_OPTION_CONTEXT,
6694
                                 encoder);
146✔
6695
    if (SIXEL_FAILED(status)) {
436!
6696
        goto load_end;
×
6697
    }
6698

6699
    /*
6700
     * Wire the optional assessment observer into the loader.
6701
     *
6702
     * The observer travels separately from the callback context so mapfile
6703
     * palette probes and other callbacks can keep using arbitrary structs.
6704
     */
6705
    status = sixel_loader_setopt(loader,
582✔
6706
                                 SIXEL_LOADER_OPTION_ASSESSMENT,
6707
                                 encoder->assessment_observer);
436✔
6708
    if (SIXEL_FAILED(status)) {
436!
6709
        goto load_end;
×
6710
    }
6711

6712
    status = sixel_loader_load_file(loader,
582✔
6713
                                    effective_filename,
146✔
6714
                                    load_image_callback);
6715
    if (status != SIXEL_OK) {
436✔
6716
        goto load_end;
15✔
6717
    }
6718
    encoder->last_input_bytes = sixel_loader_get_last_input_bytes(loader);
421✔
6719
    if (sixel_loader_get_last_success_name(loader) != NULL) {
421!
6720
        (void)snprintf(encoder->last_loader_name,
421✔
6721
                       sizeof(encoder->last_loader_name),
6722
                       "%s",
6723
                       sixel_loader_get_last_success_name(loader));
6724
    } else {
141✔
6725
        encoder->last_loader_name[0] = '\0';
×
6726
    }
6727
    if (sixel_loader_get_last_source_path(loader) != NULL) {
421✔
6728
        (void)snprintf(encoder->last_source_path,
283✔
6729
                       sizeof(encoder->last_source_path),
6730
                       "%s",
6731
                       sixel_loader_get_last_source_path(loader));
6732
    } else {
95✔
6733
        encoder->last_source_path[0] = '\0';
138✔
6734
    }
6735
    if (encoder->assessment_observer != NULL) {
422✔
6736
        sixel_assessment_record_loader(encoder->assessment_observer,
4✔
6737
                                       encoder->last_source_path,
3✔
6738
                                       encoder->last_loader_name,
3✔
6739
                                       encoder->last_input_bytes);
1✔
6740
    }
1✔
6741

6742
load_end:
278✔
6743
    sixel_loader_unref(loader);
436✔
6744
    loader = NULL;
436✔
6745

6746
    if (status != SIXEL_OK) {
436✔
6747
        goto end;
15✔
6748
    }
6749

6750
    palette_status = sixel_encoder_emit_palette_output(encoder);
421✔
6751
    if (SIXEL_FAILED(palette_status)) {
421!
6752
        status = palette_status;
×
6753
        goto end;
×
6754
    }
6755

6756
    if (encoder->pipe_mode) {
421!
6757
#if HAVE_CLEARERR
6758
        clearerr(stdin);
×
6759
#endif  /* HAVE_FSEEK */
6760
        while (encoder->cancel_flag && !*encoder->cancel_flag) {
×
6761
            status = sixel_tty_wait_stdin(1000000);
×
6762
            if (SIXEL_FAILED(status)) {
×
6763
                goto end;
×
6764
            }
6765
            if (status != SIXEL_OK) {
×
6766
                break;
×
6767
            }
6768
        }
6769
        if (!encoder->cancel_flag || !*encoder->cancel_flag) {
×
6770
            goto reload;
×
6771
        }
6772
    }
6773

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

6984
    if (encoder->output_is_png) {
421✔
6985
        png_final_path = encoder->output_png_to_stdout ? "-" : encoder->png_output_path;
9!
6986
        if (! encoder->output_png_to_stdout && png_final_path == NULL) {
9!
6987
            sixel_helper_set_additional_message(
×
6988
                "sixel_encoder_encode: missing PNG output path.");
6989
            status = SIXEL_RUNTIME_ERROR;
×
6990
            goto end;
×
6991
        }
6992
        status = write_png_from_sixel(png_temp_path, png_final_path);
9✔
6993
        if (SIXEL_FAILED(status)) {
9!
6994
            goto end;
×
6995
        }
6996
    }
3✔
6997

6998
    if (encoder->clipboard_output_active
421!
6999
            && encoder->clipboard_output_path != NULL) {
142!
7000
        unsigned char *clipboard_output_data;
7001
        size_t clipboard_output_size;
7002

7003
        clipboard_output_data = NULL;
1✔
7004
        clipboard_output_size = 0u;
1✔
7005

7006
        if (encoder->outfd
2!
7007
                && encoder->outfd != STDOUT_FILENO
1!
7008
                && encoder->outfd != STDERR_FILENO) {
1!
7009
            (void)sixel_compat_close(encoder->outfd);
1✔
7010
            encoder->outfd = STDOUT_FILENO;
1✔
7011
        }
1✔
7012

7013
        clipboard_status = clipboard_read_file(
1✔
7014
            encoder->clipboard_output_path,
1✔
7015
            &clipboard_output_data,
7016
            &clipboard_output_size);
7017
        if (SIXEL_SUCCEEDED(clipboard_status)) {
1!
7018
            clipboard_status = sixel_clipboard_write(
1✔
7019
                encoder->clipboard_output_format,
1✔
7020
                clipboard_output_data,
1✔
7021
                clipboard_output_size);
1✔
7022
        }
1✔
7023
        if (clipboard_output_data != NULL) {
1!
7024
            free(clipboard_output_data);
1✔
7025
        }
1✔
7026
        if (SIXEL_FAILED(clipboard_status)) {
1!
7027
            status = clipboard_status;
×
7028
            goto end;
×
7029
        }
7030
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
1✔
7031
        sixel_allocator_free(encoder->allocator,
2✔
7032
                             encoder->clipboard_output_path);
1✔
7033
        encoder->clipboard_output_path = NULL;
1✔
7034
        encoder->sixel_output_path = NULL;
1✔
7035
        encoder->clipboard_output_active = 0;
1✔
7036
        encoder->clipboard_output_format[0] = '\0';
1✔
7037
    }
1✔
7038

7039
    /* the status may not be SIXEL_OK */
7040

7041
end:
280✔
7042
    if (png_temp_path != NULL) {
436✔
7043
        (void)sixel_compat_unlink(png_temp_path);
9✔
7044
    }
3✔
7045
    sixel_allocator_free(encoder->allocator, png_temp_path);
436✔
7046
    if (clipboard_input_path != NULL) {
436!
7047
        (void)sixel_compat_unlink(clipboard_input_path);
1✔
7048
        sixel_allocator_free(encoder->allocator, clipboard_input_path);
1✔
7049
    }
1✔
7050
    if (clipboard_blob != NULL) {
436!
7051
        free(clipboard_blob);
×
7052
    }
7053
    if (encoder->clipboard_output_path != NULL) {
436!
7054
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
7055
        sixel_allocator_free(encoder->allocator,
×
7056
                             encoder->clipboard_output_path);
×
7057
        encoder->clipboard_output_path = NULL;
×
7058
        encoder->sixel_output_path = NULL;
×
7059
        encoder->clipboard_output_active = 0;
×
7060
        encoder->clipboard_output_format[0] = '\0';
×
7061
    }
7062
    sixel_allocator_free(encoder->allocator, encoder->png_output_path);
436✔
7063
    encoder->png_output_path = NULL;
436✔
7064
    if (assessment_forward_stream != NULL) {
436!
7065
        (void) fclose(assessment_forward_stream);
×
7066
    }
7067
    if (assessment_temp_path != NULL &&
436!
7068
            assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
7069
        (void)sixel_compat_unlink(assessment_temp_path);
×
7070
    }
7071
    sixel_allocator_free(encoder->allocator, assessment_temp_path);
436✔
7072
    sixel_allocator_free(encoder->allocator, assessment_forward_path);
436✔
7073
    if (assessment_json_owned && assessment_json_file != NULL) {
436!
7074
        (void) fclose(assessment_json_file);
3✔
7075
    }
1✔
7076
    if (assessment_target_frame != NULL) {
436!
7077
        sixel_frame_unref(assessment_target_frame);
×
7078
    }
7079
    if (assessment_expanded_frame != NULL) {
436!
7080
        sixel_frame_unref(assessment_expanded_frame);
×
7081
    }
7082
    if (assessment_source_frame != NULL) {
436✔
7083
        sixel_frame_unref(assessment_source_frame);
3✔
7084
    }
1✔
7085
    if (encoder->assessment_observer != NULL) {
436✔
7086
        sixel_assessment_unref(encoder->assessment_observer);
3✔
7087
        encoder->assessment_observer = NULL;
3✔
7088
    }
1✔
7089
    if (assessment_allocator != NULL) {
436✔
7090
        sixel_allocator_unref(assessment_allocator);
3✔
7091
    }
1✔
7092

7093
    sixel_encoder_unref(encoder);
436✔
7094

7095
    return status;
436✔
7096
}
7097

7098

7099
/* encode specified pixel data to SIXEL format
7100
 * output to encoder->outfd */
7101
SIXELAPI SIXELSTATUS
7102
sixel_encoder_encode_bytes(
×
7103
    sixel_encoder_t     /* in */    *encoder,
7104
    unsigned char       /* in */    *bytes,
7105
    int                 /* in */    width,
7106
    int                 /* in */    height,
7107
    int                 /* in */    pixelformat,
7108
    unsigned char       /* in */    *palette,
7109
    int                 /* in */    ncolors)
7110
{
7111
    SIXELSTATUS status = SIXEL_FALSE;
×
7112
    sixel_frame_t *frame = NULL;
×
7113

7114
    if (encoder == NULL || bytes == NULL) {
×
7115
        status = SIXEL_BAD_ARGUMENT;
×
7116
        goto end;
×
7117
    }
7118

7119
    status = sixel_frame_new(&frame, encoder->allocator);
×
7120
    if (SIXEL_FAILED(status)) {
×
7121
        goto end;
×
7122
    }
7123

7124
    status = sixel_frame_init(frame, bytes, width, height,
×
7125
                              pixelformat, palette, ncolors);
7126
    if (SIXEL_FAILED(status)) {
×
7127
        goto end;
×
7128
    }
7129

7130
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
×
7131
    if (SIXEL_FAILED(status)) {
×
7132
        goto end;
×
7133
    }
7134

7135
    status = SIXEL_OK;
×
7136

7137
end:
7138
    /* we need to free the frame before exiting, but we can't use the
7139
       sixel_frame_destroy function, because that will also attempt to
7140
       free the pixels and palette, which we don't own */
7141
    if (frame != NULL && encoder->allocator != NULL) {
×
7142
        sixel_allocator_free(encoder->allocator, frame);
×
7143
        sixel_allocator_unref(encoder->allocator);
×
7144
    }
7145
    return status;
×
7146
}
7147

7148

7149
/*
7150
 * Toggle source-frame capture for assessment consumers.
7151
 */
7152
SIXELAPI SIXELSTATUS
7153
sixel_encoder_enable_source_capture(
3✔
7154
    sixel_encoder_t *encoder,
7155
    int enable)
7156
{
7157
    if (encoder == NULL) {
3!
7158
        sixel_helper_set_additional_message(
×
7159
            "sixel_encoder_enable_source_capture: encoder is null.");
7160
        return SIXEL_BAD_ARGUMENT;
×
7161
    }
7162

7163
    encoder->capture_source = enable ? 1 : 0;
3✔
7164
    if (!encoder->capture_source && encoder->capture_source_frame != NULL) {
3!
7165
        sixel_frame_unref(encoder->capture_source_frame);
×
7166
        encoder->capture_source_frame = NULL;
×
7167
    }
7168

7169
    return SIXEL_OK;
3✔
7170
}
1✔
7171

7172

7173
/*
7174
 * Enable or disable the quantized-frame capture facility.
7175
 *
7176
 *     capture on --> encoder keeps the latest palette-quantized frame.
7177
 *     capture off --> encoder forgets previously stored frames.
7178
 */
7179
SIXELAPI SIXELSTATUS
7180
sixel_encoder_enable_quantized_capture(
3✔
7181
    sixel_encoder_t *encoder,
7182
    int enable)
7183
{
7184
    if (encoder == NULL) {
3!
7185
        sixel_helper_set_additional_message(
×
7186
            "sixel_encoder_enable_quantized_capture: encoder is null.");
7187
        return SIXEL_BAD_ARGUMENT;
×
7188
    }
7189

7190
    encoder->capture_quantized = enable ? 1 : 0;
3✔
7191
    if (!encoder->capture_quantized) {
3!
7192
        encoder->capture_valid = 0;
×
7193
    }
7194

7195
    return SIXEL_OK;
3✔
7196
}
1✔
7197

7198

7199
/*
7200
 * Materialize the captured quantized frame as a heap-allocated
7201
 * sixel_frame_t instance for assessment consumers.
7202
 */
7203
SIXELAPI SIXELSTATUS
7204
sixel_encoder_copy_quantized_frame(
×
7205
    sixel_encoder_t   *encoder,
7206
    sixel_allocator_t *allocator,
7207
    sixel_frame_t     **ppframe)
7208
{
7209
    SIXELSTATUS status = SIXEL_FALSE;
×
7210
    sixel_frame_t *frame;
7211
    unsigned char *pixels;
7212
    unsigned char *palette;
7213
    size_t palette_bytes;
7214

7215
    if (encoder == NULL || allocator == NULL || ppframe == NULL) {
×
7216
        sixel_helper_set_additional_message(
×
7217
            "sixel_encoder_copy_quantized_frame: invalid argument.");
7218
        return SIXEL_BAD_ARGUMENT;
×
7219
    }
7220

7221
    if (!encoder->capture_quantized || !encoder->capture_valid) {
×
7222
        sixel_helper_set_additional_message(
×
7223
            "sixel_encoder_copy_quantized_frame: no frame captured.");
7224
        return SIXEL_RUNTIME_ERROR;
×
7225
    }
7226

7227
    *ppframe = NULL;
×
7228
    frame = NULL;
×
7229
    pixels = NULL;
×
7230
    palette = NULL;
×
7231

7232
    status = sixel_frame_new(&frame, allocator);
×
7233
    if (SIXEL_FAILED(status)) {
×
7234
        return status;
×
7235
    }
7236

7237
    if (encoder->capture_pixel_bytes > 0) {
×
7238
        pixels = (unsigned char *)sixel_allocator_malloc(
×
7239
            allocator, encoder->capture_pixel_bytes);
7240
        if (pixels == NULL) {
×
7241
            sixel_helper_set_additional_message(
×
7242
                "sixel_encoder_copy_quantized_frame: "
7243
                "sixel_allocator_malloc() failed.");
7244
            status = SIXEL_BAD_ALLOCATION;
×
7245
            goto cleanup;
×
7246
        }
7247
        memcpy(pixels,
×
7248
               encoder->capture_pixels,
7249
               encoder->capture_pixel_bytes);
7250
    }
7251

7252
    palette_bytes = encoder->capture_palette_size;
×
7253
    if (palette_bytes > 0) {
×
7254
        palette = (unsigned char *)sixel_allocator_malloc(allocator,
×
7255
                                                          palette_bytes);
7256
        if (palette == NULL) {
×
7257
            sixel_helper_set_additional_message(
×
7258
                "sixel_encoder_copy_quantized_frame: "
7259
                "sixel_allocator_malloc() failed.");
7260
            status = SIXEL_BAD_ALLOCATION;
×
7261
            goto cleanup;
×
7262
        }
7263
        memcpy(palette,
×
7264
               encoder->capture_palette,
7265
               palette_bytes);
7266
    }
7267

7268
    status = sixel_frame_init(frame,
×
7269
                              pixels,
7270
                              encoder->capture_width,
7271
                              encoder->capture_height,
7272
                              encoder->capture_pixelformat,
7273
                              palette,
7274
                              encoder->capture_ncolors);
7275
    if (SIXEL_FAILED(status)) {
×
7276
        goto cleanup;
×
7277
    }
7278

7279
    pixels = NULL;
×
7280
    palette = NULL;
×
7281
    frame->colorspace = encoder->capture_colorspace;
×
7282
    *ppframe = frame;
×
7283
    return SIXEL_OK;
×
7284

7285
cleanup:
7286
    if (palette != NULL) {
×
7287
        sixel_allocator_free(allocator, palette);
×
7288
    }
7289
    if (pixels != NULL) {
×
7290
        sixel_allocator_free(allocator, pixels);
×
7291
    }
7292
    if (frame != NULL) {
×
7293
        sixel_frame_unref(frame);
×
7294
    }
7295
    return status;
×
7296
}
7297

7298

7299
/*
7300
 * Emit the captured palette in the requested format.
7301
 *
7302
 *   palette_output == NULL  -> skip
7303
 *   palette_output != NULL  -> materialize captured palette
7304
 */
7305
static SIXELSTATUS
7306
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder)
421✔
7307
{
7308
    SIXELSTATUS status;
7309
    sixel_frame_t *frame;
7310
    unsigned char const *palette;
7311
    int exported_colors;
7312
    FILE *stream;
7313
    int close_stream;
7314
    char const *path;
7315
    sixel_palette_format_t format_hint;
7316
    sixel_palette_format_t format_ext;
7317
    sixel_palette_format_t format_final;
7318
    char const *mode;
7319

7320
    status = SIXEL_OK;
421✔
7321
    frame = NULL;
421✔
7322
    palette = NULL;
421✔
7323
    exported_colors = 0;
421✔
7324
    stream = NULL;
421✔
7325
    close_stream = 0;
421✔
7326
    path = NULL;
421✔
7327
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
421✔
7328
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
421✔
7329
    format_final = SIXEL_PALETTE_FORMAT_NONE;
421✔
7330
    mode = "wb";
421✔
7331

7332
    if (encoder == NULL || encoder->palette_output == NULL) {
421!
7333
        return SIXEL_OK;
421✔
7334
    }
7335

7336
    status = sixel_encoder_copy_quantized_frame(encoder,
×
7337
                                                encoder->allocator,
7338
                                                &frame);
7339
    if (SIXEL_FAILED(status)) {
×
7340
        return status;
×
7341
    }
7342

7343
    palette = (unsigned char const *)sixel_frame_get_palette(frame);
×
7344
    exported_colors = sixel_frame_get_ncolors(frame);
×
7345
    if (palette == NULL || exported_colors <= 0) {
×
7346
        sixel_helper_set_additional_message(
×
7347
            "sixel_encoder_emit_palette_output: palette unavailable.");
7348
        status = SIXEL_BAD_INPUT;
×
7349
        goto cleanup;
×
7350
    }
7351
    if (exported_colors > 256) {
×
7352
        exported_colors = 256;
×
7353
    }
7354

7355
    path = sixel_palette_strip_prefix(encoder->palette_output, &format_hint);
×
7356
    if (path == NULL || *path == '\0') {
×
7357
        sixel_helper_set_additional_message(
×
7358
            "sixel_encoder_emit_palette_output: invalid path.");
7359
        status = SIXEL_BAD_ARGUMENT;
×
7360
        goto cleanup;
×
7361
    }
7362

7363
    format_ext = sixel_palette_format_from_extension(path);
×
7364
    format_final = format_hint;
×
7365
    if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
7366
        if (format_ext == SIXEL_PALETTE_FORMAT_NONE) {
×
7367
            if (strcmp(path, "-") == 0) {
×
7368
                sixel_helper_set_additional_message(
×
7369
                    "sixel_encoder_emit_palette_output: "
7370
                    "format required for '-'.");
7371
                status = SIXEL_BAD_ARGUMENT;
×
7372
                goto cleanup;
×
7373
            }
7374
            sixel_helper_set_additional_message(
×
7375
                "sixel_encoder_emit_palette_output: "
7376
                "unknown palette file extension.");
7377
            status = SIXEL_BAD_ARGUMENT;
×
7378
            goto cleanup;
×
7379
        }
7380
        format_final = format_ext;
×
7381
    }
7382
    if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
7383
        format_final = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
7384
    }
7385

7386
    if (strcmp(path, "-") == 0) {
×
7387
        stream = stdout;
×
7388
    } else {
7389
        if (format_final == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
7390
                format_final == SIXEL_PALETTE_FORMAT_GPL) {
7391
            mode = "w";
×
7392
        } else {
7393
            mode = "wb";
×
7394
        }
7395
        stream = fopen(path, mode);
×
7396
        if (stream == NULL) {
×
7397
            sixel_helper_set_additional_message(
×
7398
                "sixel_encoder_emit_palette_output: failed to open file.");
7399
            status = SIXEL_LIBC_ERROR;
×
7400
            goto cleanup;
×
7401
        }
7402
        close_stream = 1;
×
7403
    }
7404

7405
    switch (format_final) {
×
7406
    case SIXEL_PALETTE_FORMAT_ACT:
7407
        status = sixel_palette_write_act(stream, palette, exported_colors);
×
7408
        if (SIXEL_FAILED(status)) {
×
7409
            sixel_helper_set_additional_message(
×
7410
                "sixel_encoder_emit_palette_output: failed to write ACT.");
7411
        }
7412
        break;
×
7413
    case SIXEL_PALETTE_FORMAT_PAL_JASC:
7414
        status = sixel_palette_write_pal_jasc(stream,
×
7415
                                              palette,
7416
                                              exported_colors);
7417
        if (SIXEL_FAILED(status)) {
×
7418
            sixel_helper_set_additional_message(
×
7419
                "sixel_encoder_emit_palette_output: failed to write JASC.");
7420
        }
7421
        break;
×
7422
    case SIXEL_PALETTE_FORMAT_PAL_RIFF:
7423
        status = sixel_palette_write_pal_riff(stream,
×
7424
                                              palette,
7425
                                              exported_colors);
7426
        if (SIXEL_FAILED(status)) {
×
7427
            sixel_helper_set_additional_message(
×
7428
                "sixel_encoder_emit_palette_output: failed to write RIFF.");
7429
        }
7430
        break;
×
7431
    case SIXEL_PALETTE_FORMAT_GPL:
7432
        status = sixel_palette_write_gpl(stream,
×
7433
                                         palette,
7434
                                         exported_colors);
7435
        if (SIXEL_FAILED(status)) {
×
7436
            sixel_helper_set_additional_message(
×
7437
                "sixel_encoder_emit_palette_output: failed to write GPL.");
7438
        }
7439
        break;
×
7440
    default:
7441
        sixel_helper_set_additional_message(
×
7442
            "sixel_encoder_emit_palette_output: unsupported format.");
7443
        status = SIXEL_BAD_ARGUMENT;
×
7444
        break;
×
7445
    }
7446
    if (SIXEL_FAILED(status)) {
×
7447
        goto cleanup;
×
7448
    }
7449

7450
    if (close_stream) {
×
7451
        if (fclose(stream) != 0) {
×
7452
            sixel_helper_set_additional_message(
×
7453
                "sixel_encoder_emit_palette_output: fclose() failed.");
7454
            status = SIXEL_LIBC_ERROR;
×
7455
            stream = NULL;
×
7456
            goto cleanup;
×
7457
        }
7458
        stream = NULL;
×
7459
    } else {
7460
        if (fflush(stream) != 0) {
×
7461
            sixel_helper_set_additional_message(
×
7462
                "sixel_encoder_emit_palette_output: fflush() failed.");
7463
            status = SIXEL_LIBC_ERROR;
×
7464
            goto cleanup;
×
7465
        }
7466
    }
7467

7468
cleanup:
7469
    if (close_stream && stream != NULL) {
×
7470
        (void) fclose(stream);
×
7471
    }
7472
    if (frame != NULL) {
×
7473
        sixel_frame_unref(frame);
×
7474
    }
7475

7476
    return status;
×
7477
}
141✔
7478

7479

7480
/*
7481
 * Share the captured source frame with assessment consumers.
7482
 */
7483
SIXELAPI SIXELSTATUS
7484
sixel_encoder_copy_source_frame(
3✔
7485
    sixel_encoder_t *encoder,
7486
    sixel_frame_t  **ppframe)
7487
{
7488
    if (encoder == NULL || ppframe == NULL) {
3!
7489
        sixel_helper_set_additional_message(
×
7490
            "sixel_encoder_copy_source_frame: invalid argument.");
7491
        return SIXEL_BAD_ARGUMENT;
×
7492
    }
7493

7494
    if (!encoder->capture_source || encoder->capture_source_frame == NULL) {
3!
7495
        sixel_helper_set_additional_message(
×
7496
            "sixel_encoder_copy_source_frame: no frame captured.");
7497
        return SIXEL_RUNTIME_ERROR;
×
7498
    }
7499

7500
    sixel_frame_ref(encoder->capture_source_frame);
3✔
7501
    *ppframe = encoder->capture_source_frame;
3✔
7502

7503
    return SIXEL_OK;
3✔
7504
}
1✔
7505

7506

7507
#if HAVE_TESTS
7508
static int
7509
test1(void)
×
7510
{
7511
    int nret = EXIT_FAILURE;
×
7512
    sixel_encoder_t *encoder = NULL;
×
7513

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

7529
error:
7530
    sixel_encoder_unref(encoder);
×
7531
    return nret;
×
7532
}
7533

7534

7535
static int
7536
test2(void)
×
7537
{
7538
    int nret = EXIT_FAILURE;
×
7539
    SIXELSTATUS status;
7540
    sixel_encoder_t *encoder = NULL;
×
7541
    sixel_frame_t *frame = NULL;
×
7542
    unsigned char *buffer;
7543
    int height = 0;
×
7544
    int is_animation = 0;
×
7545

7546
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7547
#  pragma GCC diagnostic push
7548
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7549
#endif
7550
    encoder = sixel_encoder_create();
×
7551
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7552
#  pragma GCC diagnostic pop
7553
#endif
7554
    if (encoder == NULL) {
×
7555
        goto error;
×
7556
    }
7557

7558
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7559
#  pragma GCC diagnostic push
7560
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7561
#endif
7562
    frame = sixel_frame_create();
×
7563
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7564
#  pragma GCC diagnostic pop
7565
#endif
7566
    if (encoder == NULL) {
×
7567
        goto error;
×
7568
    }
7569

7570
    buffer = (unsigned char *)sixel_allocator_malloc(encoder->allocator, 3);
×
7571
    if (buffer == NULL) {
×
7572
        goto error;
×
7573
    }
7574
    status = sixel_frame_init(frame, buffer, 1, 1,
×
7575
                              SIXEL_PIXELFORMAT_RGB888,
7576
                              NULL, 0);
7577
    if (SIXEL_FAILED(status)) {
×
7578
        goto error;
×
7579
    }
7580

7581
    if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
×
7582
        is_animation = 1;
×
7583
    }
7584

7585
    height = sixel_frame_get_height(frame);
×
7586

7587
    status = sixel_tty_scroll(sixel_write_callback,
×
7588
                              &encoder->outfd,
×
7589
                              encoder->outfd,
7590
                              height,
7591
                              is_animation);
7592
    if (SIXEL_FAILED(status)) {
×
7593
        goto error;
×
7594
    }
7595

7596
    nret = EXIT_SUCCESS;
×
7597

7598
error:
7599
    sixel_encoder_unref(encoder);
×
7600
    sixel_frame_unref(frame);
×
7601
    return nret;
×
7602
}
7603

7604

7605
static int
7606
test3(void)
×
7607
{
7608
    int nret = EXIT_FAILURE;
×
7609
    int result;
7610

7611
    result = sixel_tty_wait_stdin(1000);
×
7612
    if (result != 0) {
×
7613
        goto error;
×
7614
    }
7615

7616
    nret = EXIT_SUCCESS;
×
7617

7618
error:
7619
    return nret;
×
7620
}
7621

7622

7623
static int
7624
test4(void)
×
7625
{
7626
    int nret = EXIT_FAILURE;
×
7627
    sixel_encoder_t *encoder = NULL;
×
7628
    SIXELSTATUS status;
7629

7630
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7631
# pragma GCC diagnostic push
7632
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7633
#endif
7634
    encoder = sixel_encoder_create();
×
7635
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7636
# pragma GCC diagnostic pop
7637
#endif
7638
    if (encoder == NULL) {
×
7639
        goto error;
×
7640
    }
7641

7642
    status = sixel_encoder_setopt(encoder,
×
7643
                                  SIXEL_OPTFLAG_LOOPMODE,
7644
                                  "force");
7645
    if (SIXEL_FAILED(status)) {
×
7646
        goto error;
×
7647
    }
7648

7649
    status = sixel_encoder_setopt(encoder,
×
7650
                                  SIXEL_OPTFLAG_PIPE_MODE,
7651
                                  "force");
7652
    if (SIXEL_FAILED(status)) {
×
7653
        goto error;
×
7654
    }
7655

7656
    nret = EXIT_SUCCESS;
×
7657

7658
error:
7659
    sixel_encoder_unref(encoder);
×
7660
    return nret;
×
7661
}
7662

7663

7664
static int
7665
test5(void)
×
7666
{
7667
    int nret = EXIT_FAILURE;
×
7668
    sixel_encoder_t *encoder = NULL;
×
7669
    sixel_allocator_t *allocator = NULL;
×
7670
    SIXELSTATUS status;
7671

7672
    status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
7673
    if (SIXEL_FAILED(status)) {
×
7674
        goto error;
×
7675
    }
7676

7677
    status = sixel_encoder_new(&encoder, allocator);
×
7678
    if (SIXEL_FAILED(status)) {
×
7679
        goto error;
×
7680
    }
7681

7682
    sixel_encoder_ref(encoder);
×
7683
    sixel_encoder_unref(encoder);
×
7684
    nret = EXIT_SUCCESS;
×
7685

7686
error:
7687
    sixel_encoder_unref(encoder);
×
7688
    return nret;
×
7689
}
7690

7691

7692
SIXELAPI int
7693
sixel_encoder_tests_main(void)
×
7694
{
7695
    int nret = EXIT_FAILURE;
×
7696
    size_t i;
7697
    typedef int (* testcase)(void);
7698

7699
    static testcase const testcases[] = {
7700
        test1,
7701
        test2,
7702
        test3,
7703
        test4,
7704
        test5
7705
    };
7706

7707
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
7708
        nret = testcases[i]();
×
7709
        if (nret != EXIT_SUCCESS) {
×
7710
            goto error;
×
7711
        }
7712
    }
7713

7714
    nret = EXIT_SUCCESS;
×
7715

7716
error:
7717
    return nret;
×
7718
}
7719
#endif  /* HAVE_TESTS */
7720

7721

7722
/* emacs Local Variables:      */
7723
/* emacs mode: c               */
7724
/* emacs tab-width: 4          */
7725
/* emacs indent-tabs-mode: nil */
7726
/* emacs c-basic-offset: 4     */
7727
/* emacs End:                  */
7728
/* vim: set expandtab ts=4 : */
7729
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc